| # Copyright 2015 The Chromium OS Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| """Unittests for config.""" |
| |
| from __future__ import print_function |
| |
| import copy |
| import cPickle |
| |
| from chromite.cbuildbot import cbuildbot_config |
| from chromite.cbuildbot import config_lib |
| from chromite.lib import cros_test_lib |
| |
| |
| def MockBuildConfig(): |
| """Create a BuildConfig object for convenient testing pleasure.""" |
| site_config = MockSiteConfig() |
| return site_config['x86-generic-paladin'] |
| |
| |
| def MockSiteConfig(): |
| """Create a SiteConfig object for convenient testing pleasure.""" |
| return config_lib.CreateConfigFromString( |
| ''' |
| { |
| "_default": { |
| "active_waterfall": null, |
| "afdo_generate": false, |
| "afdo_generate_min": false, |
| "afdo_update_ebuild": false, |
| "afdo_use": false, |
| "archive": true, |
| "archive_build_debug": false, |
| "binhost_base_url": null, |
| "binhost_bucket": null, |
| "binhost_key": null, |
| "binhost_test": false, |
| "board_replace": false, |
| "boards": null, |
| "branch": false, |
| "build_before_patching": false, |
| "build_packages_in_background": false, |
| "build_tests": true, |
| "build_type": "pfq", |
| "buildbot_waterfall_name": null, |
| "builder_class_name": null, |
| "child_configs": [], |
| "chrome_binhost_only": false, |
| "chrome_rev": null, |
| "chrome_sdk": false, |
| "chrome_sdk_build_chrome": true, |
| "chrome_sdk_goma": false, |
| "chromeos_official": false, |
| "chroot_replace": false, |
| "compilecheck": false, |
| "cpe_export": true, |
| "create_delta_sysroot": false, |
| "critical_for_chrome": false, |
| "debug_symbols": true, |
| "description": null, |
| "dev_installer_prebuilts": false, |
| "dev_manifest": "default.xml", |
| "disk_layout": null, |
| "do_not_apply_cq_patches": false, |
| "doc": null, |
| "factory": true, |
| "factory_install_netboot": true, |
| "factory_toolkit": true, |
| "gcc_githash": null, |
| "git_sync": false, |
| "grouped": false, |
| "gs_path": "default", |
| "health_alert_recipients": [], |
| "health_threshold": 0, |
| "hw_tests": [], |
| "hwqual": false, |
| "image_test": false, |
| "images": [ |
| "test" |
| ], |
| "important": false, |
| "internal": false, |
| "latest_toolchain": false, |
| "lkgm_manifest": "LKGM/lkgm.xml", |
| "manifest": "default.xml", |
| "manifest_repo_url": "mock_manifest_repo_url", |
| "manifest_version": false, |
| "master": false, |
| "name": null, |
| "overlays": "public", |
| "packages": [], |
| "paygen": false, |
| "paygen_skip_delta_payloads": false, |
| "paygen_skip_testing": false, |
| "payload_image": null, |
| "postsync_patch": true, |
| "postsync_reexec": true, |
| "pre_cq": false, |
| "prebuilts": false, |
| "profile": null, |
| "push_image": false, |
| "push_overlays": null, |
| "quick_unit": false, |
| "rootfs_verification": true, |
| "sanity_check_slaves": null, |
| "separate_debug_symbols": true, |
| "shared_user_password": null, |
| "signer_tests": false, |
| "sync_chrome": null, |
| "tests_supported": true, |
| "trybot_list": false, |
| "unittest_blacklist": [], |
| "unittests": true, |
| "upload_hw_test_artifacts": true, |
| "upload_standalone_images": true, |
| "upload_symbols": false, |
| "uprev": true, |
| "use_chrome_lkgm": false, |
| "use_lkgm": false, |
| "use_sdk": true, |
| "useflags": [], |
| "usepkg_build_packages": true, |
| "usepkg_toolchain": true, |
| "vm_test_runs": 1, |
| "vm_tests": [ |
| "smoke_suite", |
| "pfq_suite" |
| ] |
| }, |
| "x86-generic-paladin": { |
| "active_waterfall": "chromiumos", |
| "boards": [ |
| "x86-generic" |
| ], |
| "build_type": "paladin", |
| "chrome_sdk": true, |
| "chrome_sdk_build_chrome": false, |
| "description": "Commit Queue", |
| "doc": "http://mock_url/", |
| "image_test": true, |
| "images": [ |
| "base", |
| "test" |
| ], |
| "important": true, |
| "manifest_version": true, |
| "name": "x86-generic-paladin", |
| "prebuilts": "public", |
| "trybot_list": true, |
| "upload_standalone_images": false, |
| "vm_tests": [ |
| "smoke_suite" |
| ] |
| } |
| } |
| ''') |
| |
| |
| class _CustomObject(object): |
| """Simple object. For testing deepcopy.""" |
| |
| def __init__(self, x): |
| self.x = x |
| |
| def __eq__(self, other): |
| return self.x == other.x |
| |
| |
| class _CustomObjectWithSlots(object): |
| """Simple object with slots. For testing deepcopy.""" |
| |
| __slots__ = ['x'] |
| |
| def __init__(self, x): |
| self.x = x |
| |
| def __eq__(self, other): |
| return self.x == other.x |
| |
| |
| class BuildConfigClassTest(cros_test_lib.TestCase): |
| """BuildConfig tests.""" |
| |
| def setUp(self): |
| self.fooConfig = config_lib.BuildConfig(name='foo', value=1) |
| self.barConfig = config_lib.BuildConfig(name='bar', value=2) |
| self.deepConfig = config_lib.BuildConfig( |
| name='deep', nested=[1, 2, 3], value=3, |
| child_configs=[self.fooConfig, self.barConfig]) |
| |
| self.config = { |
| 'foo': self.fooConfig, |
| 'bar': self.barConfig, |
| 'deep': self.deepConfig, |
| } |
| |
| |
| def testMockSiteConfig(self): |
| """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): |
| self.assertEqual(self.fooConfig.name, 'foo') |
| self.assertEqual(self.fooConfig.name, self.fooConfig['name']) |
| |
| self.assertRaises(AttributeError, getattr, self.fooConfig, 'foobar') |
| |
| # pylint: disable=protected-access |
| def testDeleteKey(self): |
| base_config = config_lib.BuildConfig(foo='bar') |
| inherited_config = base_config.derive( |
| foo=config_lib.BuildConfig.delete_key()) |
| self.assertTrue('foo' in base_config) |
| self.assertFalse('foo' in inherited_config) |
| |
| def testDeleteKeys(self): |
| base_config = config_lib.BuildConfig(foo='bar', baz='bak') |
| inherited_config_1 = base_config.derive(qzr='flp') |
| inherited_config_2 = inherited_config_1.derive( |
| config_lib.BuildConfig.delete_keys(base_config)) |
| self.assertEqual(inherited_config_2, {'qzr': 'flp'}) |
| |
| def testCallableOverrides(self): |
| append_foo = lambda x: x + 'foo' if x else 'foo' |
| base_config = config_lib.BuildConfig() |
| inherited_config_1 = base_config.derive(foo=append_foo) |
| inherited_config_2 = inherited_config_1.derive(foo=append_foo) |
| self.assertEqual(inherited_config_1, {'foo': 'foo'}) |
| self.assertEqual(inherited_config_2, {'foo': 'foofoo'}) |
| |
| def AssertDeepCopy(self, obj1, obj2, obj3): |
| """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. |
| 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): |
| """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): |
| """Test that we test deep copy correctly.""" |
| test1 = ['foo', 'bar', ['hey']] |
| tests = [test1, |
| set([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, cPickle.loads(cPickle.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)) |
| |
| class ConfigClassTest(cros_test_lib.TestCase): |
| """Config tests.""" |
| |
| def testSaveLoadEmpty(self): |
| config = config_lib.SiteConfig() |
| |
| config_str = config.SaveConfigToString() |
| self.assertEqual(config_str, """{ |
| "_default": {} |
| }""") |
| |
| loaded = config_lib.CreateConfigFromString(config_str) |
| loaded_str = loaded.SaveConfigToString() |
| |
| self.assertEqual(config, loaded) |
| self.assertEqual(config_str, loaded_str) |
| |
| def testSaveLoadComplex(self): |
| |
| # pylint: disable=line-too-long |
| src_str = """{ |
| "_default": { |
| "bar": true, |
| "child_configs": [], |
| "foo": false, |
| "hw_tests": [] |
| }, |
| "diff_build": { |
| "bar": false, |
| "foo": true |
| }, |
| "match_build": {}, |
| "parent_build": { |
| "child_configs": [ |
| {}, |
| { |
| "bar": false, |
| "hw_tests": [ |
| "{\\n \\"async\\": true,\\n \\"blocking\\": false,\\n \\"critical\\": false,\\n \\"file_bugs\\": true,\\n \\"max_retries\\": null,\\n \\"minimum_duts\\": 4,\\n \\"num\\": 2,\\n \\"pool\\": \\"bvt\\",\\n \\"priority\\": \\"PostBuild\\",\\n \\"retry\\": false,\\n \\"suite\\": \\"bvt-perbuild\\",\\n \\"suite_min_duts\\": 1,\\n \\"timeout\\": 13200,\\n \\"warn_only\\": false\\n}" |
| ] |
| } |
| ] |
| } |
| }""" |
| |
| config = config_lib.CreateConfigFromString(src_str) |
| config_str = config.SaveConfigToString() |
| |
| # Verify that the dumped string matches the source string. |
| self.assertEqual(src_str, config_str) |
| |
| # Verify assorted stuff in the loaded config to make sure it matches |
| # expectations. |
| self.assertFalse(config['match_build'].foo) |
| self.assertTrue(config['match_build'].bar) |
| self.assertTrue(config['diff_build'].foo) |
| self.assertFalse(config['diff_build'].bar) |
| self.assertTrue(config['parent_build'].bar) |
| self.assertTrue(config['parent_build'].child_configs[0].bar) |
| self.assertFalse(config['parent_build'].child_configs[1].bar) |
| self.assertEqual( |
| config['parent_build'].child_configs[1].hw_tests[0], |
| config_lib.HWTestConfig( |
| suite='bvt-perbuild', |
| async=True, file_bugs=True, max_retries=None, |
| minimum_duts=4, num=2, priority='PostBuild', |
| retry=False, suite_min_duts=1)) |
| |
| # Load an save again, just to make sure there are no changes. |
| loaded = config_lib.CreateConfigFromString(config_str) |
| loaded_str = loaded.SaveConfigToString() |
| |
| self.assertEqual(config, loaded) |
| self.assertEqual(config_str, loaded_str) |
| |
| |
| class FindConfigsForBoardTest(cros_test_lib.TestCase): |
| """Test locating of official build for a board.""" |
| |
| def setUp(self): |
| self.config = cbuildbot_config.GetConfig() |
| |
| def _CheckFullConfig( |
| self, board, external_expected=None, internal_expected=None): |
| """Check FindFullConfigsForBoard has expected results. |
| |
| Args: |
| board: Argument to pass to FindFullConfigsForBoard. |
| external_expected: Expected config name (singular) to be found. |
| internal_expected: Expected config name (singular) to be found. |
| """ |
| |
| def check_expected(l, expected): |
| if expected is not None: |
| self.assertTrue(expected in [v['name'] for v in l]) |
| |
| external, internal = self.config.FindFullConfigsForBoard(board) |
| self.assertFalse(external_expected is None and internal_expected is None) |
| check_expected(external, external_expected) |
| check_expected(internal, internal_expected) |
| |
| def _CheckCanonicalConfig(self, board, ending): |
| self.assertEquals( |
| '-'.join((board, ending)), |
| self.config.FindCanonicalConfigForBoard(board)['name']) |
| |
| def testExternal(self): |
| """Test finding of a full builder.""" |
| self._CheckFullConfig( |
| 'amd64-generic', external_expected='amd64-generic-full') |
| |
| def testInternal(self): |
| """Test finding of a release builder.""" |
| self._CheckFullConfig('lumpy', internal_expected='lumpy-release') |
| |
| def testBoth(self): |
| """Both an external and internal config exist for board.""" |
| self._CheckFullConfig( |
| 'daisy', external_expected='daisy-full', |
| internal_expected='daisy-release') |
| |
| def testExternalCanonicalResolution(self): |
| """Test an external canonical config.""" |
| self._CheckCanonicalConfig('x86-generic', 'full') |
| |
| def testInternalCanonicalResolution(self): |
| """Test prefer internal over external when both exist.""" |
| self._CheckCanonicalConfig('daisy', 'release') |
| |
| def testAFDOCanonicalResolution(self): |
| """Test prefer non-AFDO over AFDO builder.""" |
| self._CheckCanonicalConfig('lumpy', 'release') |
| |
| def testOneFullConfigPerBoard(self): |
| """There is at most one 'full' config for a board.""" |
| # Verifies that there is one external 'full' and one internal 'release' |
| # build per board. This is to ensure that we fail any new configs that |
| # wrongly have names like *-bla-release or *-bla-full. This case can also |
| # be caught if the new suffix was added to |
| # config_lib.CONFIG_TYPE_DUMP_ORDER |
| # (see testNonOverlappingConfigTypes), but that's not guaranteed to happen. |
| def AtMostOneConfig(board, label, configs): |
| if len(configs) > 1: |
| self.fail( |
| 'Found more than one %s config for %s: %r' |
| % (label, board, [c['name'] for c in configs])) |
| |
| boards = set() |
| for build_config in self.config.itervalues(): |
| boards.update(build_config['boards']) |
| |
| # Sanity check of the boards. |
| self.assertTrue(boards) |
| |
| for b in boards: |
| # TODO(akeshet): Figure out why we have both panther_embedded-minimal |
| # release and panther_embedded-release, and eliminate one of them. |
| if b == 'panther_embedded': |
| continue |
| external, internal = self.config.FindFullConfigsForBoard(b) |
| AtMostOneConfig(b, 'external', external) |
| AtMostOneConfig(b, 'internal', internal) |