blob: 4ba5b7222184df77da781e2142edab71b98529dd [file] [log] [blame]
# Copyright 2015 The ChromiumOS Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Unittests for config."""
import copy
import json
import pickle
from typing import Any
from chromite.lib import config_lib
from chromite.lib import cros_test_lib
# pylint: disable=protected-access
def MockBuildConfig():
"""Create a BuildConfig object for convenient testing pleasure."""
site_config = MockSiteConfig()
return site_config["amd64-generic-release"]
def MockSiteConfig():
"""Create a SiteConfig object for convenient testing pleasure.
Shared amoung a number of unittest files, so be careful if changing it.
"""
result = config_lib.SiteConfig()
# Add a single, simple build config.
result.Add(
"amd64-generic-release",
boards=["amd64-generic"],
display_label="MockLabel",
build_type="canary",
description="LegacyRelease",
doc="http://mock_url/",
images=["base", "test"],
important=True,
manifest_version=True,
prebuilts="public",
upload_standalone_images=False,
)
return result
def AssertSiteIndependentParameters(site_config):
"""Helper function to test that SiteConfigs contain site-independent values.
Args:
site_config: A SiteConfig object.
Returns:
A boolean. True if the config contained all site-independent values.
False otherwise.
"""
# Enumerate the necessary site independent parameter keys.
# All keys must be documented.
# TODO (msartori): Fill in this list.
site_independent_params = []
site_params = site_config.params
return all(x in site_params for x in site_independent_params)
class _CustomObject:
"""Simple object. For testing deepcopy."""
def __init__(self, x) -> None:
self.x = x
def __eq__(self, other: Any) -> bool:
return self.x == other.x
class _CustomObjectWithSlots:
"""Simple object with slots. For testing deepcopy."""
__slots__ = ["x"]
def __init__(self, x) -> None:
self.x = x
def __eq__(self, other: Any) -> bool:
return self.x == other.x
class BuildConfigClassTest(cros_test_lib.TestCase):
"""BuildConfig tests."""
def setUp(self) -> None:
self.fooConfig = config_lib.BuildConfig(name="foo", foo=1)
self.barConfig = config_lib.BuildConfig(name="bar", bar=2)
self.deepConfig = config_lib.BuildConfig(
name="deep",
nested=[1, 2, 3],
deep=3,
)
def testAppendUseflags(self) -> None:
base_config = config_lib.BuildConfig(useflags=[])
inherited_config_1 = base_config.derive(
useflags=config_lib.append_useflags(["foo", "bar", "-baz"])
)
inherited_config_2 = inherited_config_1.derive(
useflags=config_lib.append_useflags(["-bar", "baz"])
)
self.assertEqual(base_config.useflags, [])
self.assertEqual(inherited_config_1.useflags, ["-baz", "bar", "foo"])
self.assertEqual(inherited_config_2.useflags, ["-bar", "baz", "foo"])
def testMockSiteConfig(self) -> None:
"""Make sure Mock generator fucntion doesn't crash."""
site_config = MockSiteConfig()
self.assertIsNotNone(site_config)
build_config = MockBuildConfig()
self.assertIsNotNone(build_config)
def testValueAccess(self) -> None:
self.assertEqual(self.fooConfig.name, "foo")
self.assertEqual(self.fooConfig.name, self.fooConfig["name"])
self.assertRaises(AttributeError, getattr, self.fooConfig, "foobar")
def testApplyEmpty(self) -> None:
orig = self.fooConfig.deepcopy()
# Do nothing.
self.fooConfig.apply()
self.assertEqual(self.fooConfig, orig)
def testApplyValues(self) -> None:
# Apply simple values..
self.fooConfig.apply(a=1, b=2)
self.assertEqual(self.fooConfig, dict(name="foo", foo=1, a=1, b=2))
def testApplyBuildConfig(self) -> None:
# Apply a BuildConfig.
self.fooConfig.apply(self.barConfig)
self.assertEqual(self.fooConfig, dict(name="bar", foo=1, bar=2))
def testApplyMixed(self) -> None:
# Apply simple values..
config = config_lib.BuildConfig()
config.apply(self.fooConfig, self.barConfig, a=1, b=2, bar=3)
self.assertEqual(config, dict(name="bar", foo=1, bar=3, a=1, b=2))
def testDeriveMixed(self) -> None:
config = config_lib.BuildConfig()
result = config.derive(self.fooConfig, self.barConfig, a=1, b=2, bar=3)
self.assertIsNot(config, result)
self.assertEqual(config, {})
self.assertEqual(result, dict(name="bar", foo=1, bar=3, a=1, b=2))
def testApplyCallable(self) -> None:
# Callable that adds a configurable amount.
def append(x):
return lambda base: base + " " + x
site_config = config_lib.SiteConfig()
site_config.AddTemplate("add1", foo=append("one"))
site_config.AddTemplate("add2", foo=append("two"))
site_config.AddTemplate("fixed", foo="fixed")
site_config.AddTemplate("derived", site_config.templates.add1)
site_config.AddTemplate(
"stacked", site_config.templates.add1, site_config.templates.add2
)
site_config.AddTemplate(
"stackedDeep",
site_config.templates.fixed,
site_config.templates.add1,
site_config.templates.add1,
site_config.templates.add1,
foo=append("deep"),
)
site_config.AddTemplate(
"stackedDeeper",
site_config.templates.stacked,
site_config.templates.stackedDeep,
foo=append("deeper"),
)
base = config_lib.BuildConfig(foo="base")
# Basic apply.
result = base.derive(site_config.templates.add1)
self.assertEqual(result.foo, "base one")
# Callable template + local callable.
result = base.derive(site_config.templates.add1, foo=append("local"))
self.assertEqual(result.foo, "base one local")
# Callable template + local fixed.
result = base.derive(site_config.templates.add1, foo="local")
self.assertEqual(result.foo, "local")
# Derived template.
result = base.derive(site_config.templates.derived)
self.assertEqual(result.foo, "base one")
# Template with fixed override after stacking template (all callable
# magic should disappear).
result = base.derive(site_config.templates.fixed)
self.assertEqual(result.foo, "fixed")
# Template with stacked.
result = base.derive(site_config.templates.stacked)
self.assertEqual(result.foo, "base one two")
# Callables on top of fixed from template.
result = base.derive(site_config.templates.stackedDeep)
self.assertEqual(result.foo, "fixed one one one deep")
# We must go deeper.
result = base.derive(site_config.templates.stackedDeeper)
self.assertEqual(result.foo, "fixed one one one deep deeper")
# Ensure objects derived from weren't modified.
self.assertEqual(base.foo, "base")
self.assertEqual(site_config.templates.fixed.foo, "fixed")
def AssertDeepCopy(self, obj1, obj2, obj3) -> None:
"""Assert that |obj3| is a deep copy of |obj1|.
Args:
obj1: Object that was copied.
obj2: A true deep copy of obj1 (produced using copy.deepcopy).
obj3: The purported deep copy of obj1.
"""
# Check whether the item was copied by deepcopy. If so, then it
# must have been copied by our algorithm as well.
if obj1 is not obj2:
self.assertIsNot(obj1, obj3)
# Assert the three items are all equal.
self.assertEqual(obj1, obj2)
self.assertEqual(obj1, obj3)
if isinstance(obj1, (tuple, list)):
# Copy tuples and lists item by item.
# pylint: disable=consider-using-enumerate
for i in range(len(obj1)):
self.AssertDeepCopy(obj1[i], obj2[i], obj3[i])
elif isinstance(obj1, set):
# Compare sorted versions of the set.
self.AssertDeepCopy(
list(sorted(obj1)), list(sorted(obj2)), list(sorted(obj3))
)
elif isinstance(obj1, dict):
# Copy dicts item by item.
for k in obj1:
self.AssertDeepCopy(obj1[k], obj2[k], obj3[k])
elif hasattr(obj1, "__dict__"):
# Make sure the dicts are copied.
self.AssertDeepCopy(obj1.__dict__, obj2.__dict__, obj3.__dict__)
elif hasattr(obj1, "__slots__"):
# Make sure the slots are copied.
for attr in obj1.__slots__:
self.AssertDeepCopy(
getattr(obj1, attr),
getattr(obj2, attr),
getattr(obj3, attr),
)
else:
# This should be an object that copy.deepcopy didn't copy (probably
# an immutable object.) If not, the test needs to be updated to
# handle this kind of object.
self.assertIs(obj1, obj2)
def testDeepCopy(self) -> None:
"""Test that we deep copy correctly."""
for cfg in [self.fooConfig, self.barConfig, self.deepConfig]:
self.AssertDeepCopy(cfg, copy.deepcopy(cfg), cfg.deepcopy())
def testAssertDeepCopy(self) -> None:
"""Test that we test deep copy correctly."""
test1 = ["foo", "bar", ["hey"]]
tests = [
test1,
{tuple(x) for x in test1},
dict(zip([tuple(x) for x in test1], test1)),
_CustomObject(test1),
_CustomObjectWithSlots(test1),
]
for x in tests + [[tests]]:
copy_x = copy.deepcopy(x)
self.AssertDeepCopy(x, copy_x, copy.deepcopy(x))
self.AssertDeepCopy(x, copy_x, pickle.loads(pickle.dumps(x, -1)))
self.assertRaises(AssertionError, self.AssertDeepCopy, x, copy_x, x)
if not isinstance(x, set):
self.assertRaises(
AssertionError, self.AssertDeepCopy, x, copy_x, copy.copy(x)
)
def testPickle(self) -> None:
bc1 = MockBuildConfig()
bc2 = pickle.loads(pickle.dumps(bc1))
self.assertEqual(bc1.boards, bc2.boards)
self.assertEqual(bc1.name, bc2.name)
class GetSiteParamsTest(cros_test_lib.TestCase):
"""Tests for the return value from config_lib.GetSiteParams()."""
def testAttributeAccess(self) -> None:
"""Test that dot-accessor works correctly."""
site_params = config_lib.GetSiteParams()
# Ensure our test key is not in site_params.
self.assertNotIn("foo", site_params)
# Test that we raise when accessing a non-existent value.
# pylint: disable=pointless-statement
with self.assertRaises(AttributeError):
site_params.foo
# Test the dot-accessor.
site_params.update({"foo": "bar"})
self.assertEqual("bar", site_params.foo)
class SiteConfigTest(cros_test_lib.TestCase):
"""Config tests."""
@staticmethod
def _callable(x):
return x + " extended"
def setUp(self) -> None:
self.complex_defaults = {
"value": "default",
}
# Construct our test config.
site_config = config_lib.SiteConfig(defaults=self.complex_defaults)
site_config.AddTemplate("match", value="default")
site_config.AddTemplate("template", value="template")
site_config.AddTemplate("mixin", value="mixin")
site_config.AddTemplate("unused", value="unused")
site_config.AddTemplate("callable", value=self._callable)
site_config.Add("default")
site_config.Add("default_with_override", value="override")
site_config.AddWithoutTemplate(
"default_with_mixin", site_config.templates.mixin
)
site_config.AddWithoutTemplate(
"mixin_with_override", site_config.templates.mixin, value="override"
)
site_config.Add("default_with_template", site_config.templates.template)
site_config.Add(
"template_with_override",
site_config.templates.template,
value="override",
)
site_config.Add(
"template_with_mixin",
site_config.templates.template,
site_config.templates.mixin,
)
site_config.Add(
"template_with_mixin_override",
site_config.templates.template,
site_config.templates.mixin,
value="override",
)
site_config.Add("match", value="default")
site_config.Add(
"template_back_to_default",
site_config.templates.template,
value="default",
)
site_config.Add("calling", site_config.templates.callable)
self.site_config = site_config
def testAddedContents(self) -> None:
"""Verify our complex config looks like we expect, before saving."""
expected = {
"default": {
"_template": None,
"name": "default",
"value": "default",
},
"default_with_override": {
"_template": None,
"name": "default_with_override",
"value": "override",
},
"default_with_mixin": {
"_template": None,
"name": "default_with_mixin",
"value": "mixin",
},
"mixin_with_override": {
"_template": None,
"name": "mixin_with_override",
"value": "override",
},
"default_with_template": {
"_template": "template",
"name": "default_with_template",
"value": "template",
},
"template_with_override": {
"_template": "template",
"name": "template_with_override",
"value": "override",
},
"template_with_mixin": {
"_template": "template",
"name": "template_with_mixin",
"value": "mixin",
},
"template_with_mixin_override": {
"_template": "template",
"name": "template_with_mixin_override",
"value": "override",
},
"calling": {
"_template": "callable",
"name": "calling",
"value": "default extended",
},
"match": {
"_template": None,
"name": "match",
"value": "default",
},
"template_back_to_default": {
"_template": "template",
"name": "template_back_to_default",
"value": "default",
},
}
# Make sure our expected build configs exist.
self.assertCountEqual(list(self.site_config), list(expected))
# Make sure each one contains
self.longMessage = True
# TODO(b/236161656): Fix.
# pylint: disable-next=consider-using-dict-items
for name in expected:
self.assertGreaterEqual(
self.site_config[name].items(), expected[name].items(), name
)
def testAddErrors(self) -> None:
"""Test the SiteConfig.Add behavior."""
self.site_config.Add("foo")
# Test we can't add the 'foo' config again.
with self.assertRaises(AssertionError):
self.site_config.Add("foo")
# Create a template without using AddTemplate so the site config doesn't
# know about it.
fake_template = config_lib.BuildConfig(
name="fake_template", _template="fake_template"
)
with self.assertRaises(AssertionError):
self.site_config.Add("bar", fake_template)
def testTemplateAttr(self) -> None:
"""Test the SiteConfig.templates.name behavior."""
template1 = self.site_config.AddTemplate("template1", value="template")
template2 = self.site_config.AddTemplate("template2", value="template")
self.assertIs(template1, self.site_config.templates.template1)
self.assertIs(template2, self.site_config.templates.template2)
# Try to fetch a non-existent template.
with self.assertRaises(AttributeError):
# pylint: disable=pointless-statement
self.site_config.templates.no_such_template
# pylint: enable=pointless-statement
def testAddForBoards(self) -> None:
per_board = {
"foo": config_lib.BuildConfig(value="foo"),
"bar": config_lib.BuildConfig(value="bar"),
"multiboard": config_lib.BuildConfig(boards=["foo", "bar"]),
}
# Test the minimal invocation.
self.site_config.AddForBoards(
"minimal",
["foo", "bar"],
)
self.assertIn("foo-minimal", self.site_config)
self.assertEqual(self.site_config["foo-minimal"].boards, ["foo"])
self.assertEqual(self.site_config["bar-minimal"].boards, ["bar"])
# Test a partial set of per-board values specified.
self.site_config.AddForBoards(
"partial_per_board",
["foo", "no_per"],
per_board,
)
self.assertIn("foo-partial_per_board", self.site_config)
self.assertIn("no_per-partial_per_board", self.site_config)
self.assertEqual(self.site_config["foo-partial_per_board"].value, "foo")
# Test all boards with per_board values specified, and test we can
# override board listing in per_board values.
self.site_config.AddForBoards(
"per_board",
["foo", "bar", "multiboard"],
per_board,
)
self.assertEqual(self.site_config["foo-per_board"].value, "foo")
self.assertEqual(self.site_config["bar-per_board"].value, "bar")
self.assertEqual(
self.site_config["multiboard-per_board"].boards, ["foo", "bar"]
)
# Test using a template
self.site_config.AddForBoards(
"template",
["foo", "bar"],
None,
self.site_config.templates.template,
)
self.assertEqual(self.site_config["foo-template"].value, "template")
# Test a template, and a mixin.
self.site_config.AddForBoards(
"mixin",
["foo", "bar"],
None,
self.site_config.templates.template,
self.site_config.templates.mixin,
)
self.assertEqual(self.site_config["foo-mixin"].value, "mixin")
# Test a template, and a mixin, and a per-board.
self.site_config.AddForBoards(
"mixin_per_board",
["foo", "bar"],
per_board,
self.site_config.templates.template,
self.site_config.templates.mixin,
)
self.assertEqual(self.site_config["foo-mixin_per_board"].value, "foo")
# Test a template, and a mixin, and a per-board, and an override.
self.site_config.AddForBoards(
"override",
["foo", "bar"],
per_board,
self.site_config.templates.template,
self.site_config.templates.mixin,
value="override",
)
self.assertEqual(self.site_config["foo-override"].value, "override")
def _verifyLoadSave(self, site_config):
"""Make sure that we can save and re-load a site."""
config_str = site_config.SaveConfigToString()
loaded = config_lib.LoadConfigFromString(config_str)
#
# BUG ALERT ON TEST FAILURE
#
# assertDictEqual can correctly compare these structs for equivalence,
# but has a bug when displaying differences on failure. The embedded
# HWTestConfig values are correctly compared, but ALWAYS display as
# different, if something else triggers a failure.
#
# This for loop is to make differences easier to find/read.
self.longMessage = True
for name in site_config:
self.assertDictEqual(loaded[name], site_config[name], name)
# This includes templates and the default build config.
self.assertEqual(site_config, loaded)
loaded_str = loaded.SaveConfigToString()
self.assertEqual(config_str, loaded_str)
# Cycle through save load again, just for completeness.
loaded2 = config_lib.LoadConfigFromString(loaded_str)
loaded2_str = loaded2.SaveConfigToString()
self.assertEqual(loaded_str, loaded2_str)
# Make sure we can dump long content without crashing.
self.assertNotEqual(site_config.DumpExpandedConfigToString(), "")
self.assertNotEqual(loaded.DumpExpandedConfigToString(), "")
self.assertNotEqual(loaded.DumpConfigCsv(), "")
return loaded
def testSaveLoadEmpty(self) -> None:
"""Create, save, and reload an empty config."""
site_config = config_lib.SiteConfig()
loaded = self._verifyLoadSave(site_config)
self.assertEqual(list(loaded), [])
self.assertEqual(list(loaded._templates), [])
self.assertDictEqual(loaded.GetDefault(), config_lib.DefaultSettings())
def testSaveLoadComplex(self) -> None:
"""Create, save, and reload an complex config."""
# Verify it.
loaded = self._verifyLoadSave(self.site_config)
# Verify default build config
expected_defaults = config_lib.DefaultSettings()
expected_defaults.update(self.complex_defaults)
self.assertDictEqual(loaded.GetDefault(), expected_defaults)
# Ensure that expected templates are present.
self.assertCountEqual(list(loaded.templates), ["template", "callable"])
def testTemplatesToSave(self) -> None:
def _invert(x):
return not x
config = config_lib.SiteConfig()
config.AddTemplate("base", foo=True, important=False)
config.AddTemplate("callable", important=_invert)
config.AddTemplate("unused", bar=True)
config.Add("build1", config.templates.base, var=1)
config.Add("build2", var=2)
config.Add("build3", config.templates.base, var=3)
config.Add("build4", config.templates.callable, var=4)
self.assertCountEqual(
config.templates,
{
"base": {"_template": "base", "foo": True, "important": False},
"callable": {"_template": "callable", "important": _invert},
"unused": {"_template": "unused", "bar": True},
},
)
results = config._MarshalTemplates()
self.assertCountEqual(
results,
{
"base": {"_template": "base", "foo": True},
"callable": {"_template": "callable", "important": True},
},
)
class SiteConfigFindTests(cros_test_lib.TestCase):
"""Tests related to Find helpers on SiteConfig."""
def testGetBoardsMockConfig(self) -> None:
site_config = MockSiteConfig()
self.assertEqual(site_config.GetBoards(), set(["amd64-generic"]))
def testGetBoardsComplexConfig(self) -> None:
site_config = MockSiteConfig()
site_config.Add("build_a", boards=["foo_board"])
site_config.Add("build_b", boards=["bar_board"])
site_config.Add("build_c", boards=["foo_board", "car_board"])
self.assertEqual(
site_config.GetBoards(),
set(["amd64-generic", "foo_board", "bar_board", "car_board"]),
)
def testFindCanonicalConfig(self) -> None:
site_config = MockSiteConfig()
amd64_full = site_config.Add(
"amd64-generic-full", boards=["amd64-generic"]
)
self.assertEqual(
site_config.FindCanonicalConfigForBoard("amd64-generic"), amd64_full
)
site_config = MockSiteConfig()
amd64_full = site_config.Add(
"amd64-generic-full", boards=["amd64-generic"]
)
self.assertEqual(
site_config.FindCanonicalConfigForBoard("amd64-generic"), amd64_full
)
self.assertEqual(
site_config.FindCanonicalConfigForBoard(None), amd64_full
)
def testGetSlaveConfigMapForMasterAll(self) -> None:
"""Test GetSlaveConfigMapForMaster, GetSlavesForMaster all slaves."""
site_config = MockSiteConfig()
master = site_config.Add(
"master",
master=True,
manifest_version=True,
slave_configs=["slave_a", "slave_b"],
)
slave_a = site_config.Add("slave_a", important=True)
slave_b = site_config.Add("slave_b", important=False)
site_config.Add("other")
results_map = site_config.GetSlaveConfigMapForMaster(
master, important_only=False
)
results_slaves = site_config.GetSlavesForMaster(
master, important_only=False
)
self.assertEqual(results_map, {"slave_a": slave_a, "slave_b": slave_b})
self.assertCountEqual(results_slaves, [slave_a, slave_b])
def testGetSlaveConfigMapForMasterImportant(self) -> None:
"""Test GetSlaveConfigMapForMaster/GetSlavesForMaster important only."""
site_config = MockSiteConfig()
master = site_config.Add(
"master",
master=True,
manifest_version=True,
slave_configs=["slave_a", "slave_b"],
)
slave_a = site_config.Add("slave_a", important=True)
site_config.Add("slave_b", important=False)
site_config.Add("other")
results_map = site_config.GetSlaveConfigMapForMaster(master)
results_slaves = site_config.GetSlavesForMaster(master)
self.assertEqual(results_map, {"slave_a": slave_a})
self.assertCountEqual(results_slaves, [slave_a])
class GetConfigTests(cros_test_lib.TestCase):
"""Tests related to SiteConfig.GetConfig()."""
def testGetConfigCaching(self) -> None:
"""Test that config_lib.GetConfig() caches it's results correctly."""
config_a = config_lib.GetConfig()
config_b = config_lib.GetConfig()
# Ensure that we get a SiteConfig, and that the result is cached.
self.assertIsInstance(config_a, config_lib.SiteConfig)
self.assertIs(config_a, config_b)
class GEBuildConfigTests(cros_test_lib.TestCase):
"""Test GE build config related methods."""
def setUp(self) -> None:
self._fake_ge_build_config_json = """
{
"metadata_version": "1.0",
"reference_board_unified_builds": [
{
"name": "reef",
"reference_board_name": "reef",
"builder": "RELEASE",
"experimental": true,
"arch": "X86_INTERNAL",
"models" : [
{
"board_name": "reef"
},
{
"board_name": "pyro"
}
]
}
],
"boards": [
{
"name": "reef",
"configs": [
{
"builder": "RELEASE",
"experimental": false,
"leader_board": true,
"board_group": "reef",
"arch": "X86_INTERNAL"
}
]
}
]
}
"""
self._fake_ge_build_config = json.loads(self._fake_ge_build_config_json)
def testGetArchBoardDict(self) -> None:
"""Test GetArchBoardDict."""
ge_build_config = config_lib.LoadGEBuildConfigFromFile()
arch_board_dict = config_lib.GetArchBoardDict(ge_build_config)
self.assertIsNotNone(arch_board_dict)
def testGetArchBoardDictUnifiedBuilds(self) -> None:
"""Test GetArchBoardDict."""
arch_board_dict = config_lib.GetArchBoardDict(
self._fake_ge_build_config
)
self.assertIsNotNone(arch_board_dict)
self.assertIs(1, len(arch_board_dict[config_lib.CONFIG_X86_INTERNAL]))
def testGetUnifiedBuildConfigAllBuilds(self) -> None:
uni_builds = config_lib.GetUnifiedBuildConfigAllBuilds(
self._fake_ge_build_config
)
self.assertEqual(1, len(uni_builds))
def testGetUnifiedBuildConfigAllBuildsWithNoBuilds(self) -> None:
uni_builds = config_lib.GetUnifiedBuildConfigAllBuilds({})
self.assertEqual(0, len(uni_builds))