3 # Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
4 # Use of this source code is governed by a BSD-style license that can be
5 # found in the LICENSE file.
7 """Unittests for config. Needs to be run inside of chroot for mox."""
14 sys.path.insert(0, constants.SOURCE_ROOT)
15 from chromite.buildbot import cbuildbot_config
16 from chromite.lib import cros_test_lib
17 from chromite.lib import git
18 from chromite.lib import parallel
20 CHROMIUM_WATCHING_URL = ('http://src.chromium.org/chrome/trunk/tools/build/'
21 'masters/master.chromium.chromiumos/master_chromiumos_cros_cfg.py')
24 # pylint: disable=W0212,R0904
25 class ConfigPickleTest(cros_test_lib.TestCase):
26 """Test that a config object is pickleable."""
29 bc1 = cbuildbot_config.config['x86-mario-paladin']
30 bc2 = cPickle.loads(cPickle.dumps(bc1))
32 self.assertEquals(bc1.boards, bc2.boards)
33 self.assertEquals(bc1.name, bc2.name)
36 class ConfigClassTest(cros_test_lib.TestCase):
37 """Tests of the config class itself."""
39 def testValueAccess(self):
40 cfg = cbuildbot_config.config['x86-mario-paladin']
42 self.assertTrue(cfg.name)
43 self.assertEqual(cfg.name, cfg['name'])
45 self.assertRaises(AttributeError, getattr, cfg, 'foobar')
48 class CBuildBotTest(cros_test_lib.MoxTestCase):
49 """General tests of cbuildbot_config with respect to cbuildbot."""
51 def testConfigsKeysMismatch(self):
52 """Verify that all configs contain exactly the default keys.
54 This checks for mispelled keys, or keys that are somehow removed.
56 expected_keys = set(cbuildbot_config._default.keys())
57 for build_name, config in cbuildbot_config.config.iteritems():
58 config_keys = set(config.keys())
60 extra_keys = config_keys.difference(expected_keys)
61 self.assertFalse(extra_keys, ('Config %s has extra values %s' %
62 (build_name, list(extra_keys))))
64 missing_keys = expected_keys.difference(config_keys)
65 self.assertFalse(missing_keys, ('Config %s is missing values %s' %
66 (build_name, list(missing_keys))))
68 def testConfigsHaveName(self):
69 """Configs must have names set."""
70 for build_name, config in cbuildbot_config.config.iteritems():
71 self.assertTrue(build_name == config['name'])
73 def testConfigUseflags(self):
74 """Useflags must be lists.
76 Strings are interpreted as arrays of characters for this, which is not
79 for build_name, config in cbuildbot_config.config.iteritems():
80 useflags = config.get('useflags')
81 if not useflags is None:
83 isinstance(useflags, list),
84 'Config %s: useflags should be a list.' % build_name)
87 """Verify 'boards' is explicitly set for every config."""
88 for build_name, config in cbuildbot_config.config.iteritems():
89 self.assertTrue(isinstance(config['boards'], (tuple, list)),
90 "Config %s doesn't have a list of boards." % build_name)
91 self.assertEqual(len(set(config['boards'])), len(config['boards']),
92 'Config %s has duplicate boards.' % build_name)
93 self.assertTrue(config['boards'] is not None,
94 'Config %s defines a list of boards.' % build_name)
96 def testOverlaySettings(self):
97 """Verify overlays and push_overlays have legal values."""
98 for build_name, config in cbuildbot_config.config.iteritems():
99 overlays = config['overlays']
100 push_overlays = config['push_overlays']
102 self.assertTrue(overlays in [None, 'public', 'private', 'both'],
103 'Config %s: has unexpected overlays value.' % build_name)
105 push_overlays in [None, 'public', 'private', 'both'],
106 'Config %s: has unexpected push_overlays value.' % build_name)
110 elif overlays == 'public':
111 subset = [None, 'public']
112 elif overlays == 'private':
113 subset = [None, 'private']
114 elif overlays == 'both':
115 subset = [None, 'public', 'private', 'both']
118 push_overlays in subset,
119 'Config %s: push_overlays should be a subset of overlays.' %
122 def testOverlayMaster(self):
123 """Verify that only one master is pushing uprevs for each overlay."""
125 for build_name, config in cbuildbot_config.config.iteritems():
126 overlays = config['overlays']
127 push_overlays = config['push_overlays']
128 if (overlays and push_overlays and config['uprev'] and config['master']
129 and not config['branch']):
130 other_master = masters.get(push_overlays)
131 err_msg = 'Found two masters for push_overlays=%s: %s and %s'
132 self.assertFalse(other_master,
133 err_msg % (push_overlays, build_name, other_master))
134 masters[push_overlays] = build_name
136 if 'both' in masters:
137 self.assertEquals(len(masters), 1, 'Found too many masters.')
139 def testChromeRev(self):
140 """Verify chrome_rev has an expected value"""
141 for build_name, config in cbuildbot_config.config.iteritems():
143 config['chrome_rev'] in constants.VALID_CHROME_REVISIONS + [None],
144 'Config %s: has unexpected chrome_rev value.' % build_name)
146 config['chrome_rev'] == constants.CHROME_REV_LOCAL,
147 'Config %s: has unexpected chrome_rev_local value.' % build_name)
148 if config['chrome_rev']:
149 self.assertTrue(cbuildbot_config.IsPFQType(config['build_type']),
150 'Config %s: has chrome_rev but is not a PFQ.' % build_name)
152 def testValidVMTestType(self):
153 """Verify vm_tests has an expected value"""
154 for build_name, config in cbuildbot_config.config.iteritems():
155 for test_type in config['vm_tests']:
157 test_type in constants.VALID_VM_TEST_TYPES,
158 'Config %s: has unexpected vm test type value.' % build_name)
160 def testBuildType(self):
161 """Verifies that all configs use valid build types."""
162 for build_name, config in cbuildbot_config.config.iteritems():
164 config['build_type'] in constants.VALID_BUILD_TYPES,
165 'Config %s: has unexpected build_type value.' % build_name)
167 def testGCCGitHash(self):
168 """Verifies that gcc_githash is not set without setting latest_toolchain."""
169 for build_name, config in cbuildbot_config.config.iteritems():
170 if config['gcc_githash']:
172 config['latest_toolchain'],
173 'Config %s: has gcc_githash but not latest_toolchain.' % build_name)
175 def testBuildToRun(self):
176 """Verify we don't try to run tests without building them."""
177 for build_name, config in cbuildbot_config.config.iteritems():
179 isinstance(config['useflags'], list) and
180 '-build_tests' in config['useflags'] and config['vm_tests'],
181 'Config %s: has vm_tests and use -build_tests.' % build_name)
183 def testSyncToChromeSdk(self):
184 """Verify none of the configs build chrome sdk but don't sync chrome."""
185 for build_name, config in cbuildbot_config.config.iteritems():
186 if config['sync_chrome'] is not None and not config['sync_chrome']:
188 config['chrome_sdk'],
189 'Config %s: has chrome_sdk but not sync_chrome.' % build_name)
191 def testARMNoVMTest(self):
192 """Verify ARM builds don't get VMTests turned on by accident."""
193 for build_name, config in cbuildbot_config.config.iteritems():
194 if build_name.startswith('arm-') or config['arm']:
195 self.assertTrue(not config['vm_tests'],
196 "ARM builder %s can't run vm tests!" % build_name)
198 def testHWTestsIFFArchivingHWTestArtifacts(self):
199 """Make sure all configs upload artifacts that need them for hw testing."""
200 for build_name, config in cbuildbot_config.config.iteritems():
201 if config['hw_tests']:
203 config['upload_hw_test_artifacts'],
204 "%s is trying to run hw tests without uploading payloads." %
207 def testHWTestTimeout(self):
208 """Verify that hw test timeout is in a reasonable range."""
209 # The parallel library will kill the process if it's silent for longer
210 # than the silent timeout.
211 max_timeout = parallel._BackgroundTask.SILENT_TIMEOUT
212 for build_name, config in cbuildbot_config.config.iteritems():
213 for test_config in config['hw_tests']:
214 self.assertTrue(test_config.timeout < max_timeout,
215 '%s has a hw_tests_timeout of %s that is too large. Expected %s' %
216 (build_name, test_config.timeout, max_timeout))
218 def testValidUnifiedMasterConfig(self):
219 """Make sure any unified master configurations are valid."""
220 for build_name, config in cbuildbot_config.config.iteritems():
221 error = 'Unified config for %s has invalid values' % build_name
222 # Unified masters must be internal and must rev both overlays.
225 config['internal'] and config['manifest_version'], error)
226 elif not config['master'] and config['manifest_version']:
227 # Unified slaves can rev either public or both depending on whether
228 # they are internal or not.
229 if not config['internal']:
230 self.assertEqual(config['overlays'], constants.PUBLIC_OVERLAYS, error)
231 elif cbuildbot_config.IsCQType(config['build_type']):
232 self.assertEqual(config['overlays'], constants.BOTH_OVERLAYS, error)
234 def testGetSlaves(self):
235 """Make sure every master has a sane list of slaves"""
236 for build_name, config in cbuildbot_config.config.iteritems():
238 configs = cbuildbot_config.GetSlavesForMaster(config)
240 len(map(repr, configs)), len(set(map(repr, configs))),
241 'Duplicate board in slaves of %s will cause upload prebuilts'
242 ' failures' % build_name)
244 def testFactoryFirmwareValidity(self):
245 """Ensures that firmware/factory branches have at least 1 valid name."""
246 tracking_branch = git.GetChromiteTrackingBranch()
247 for branch in ['firmware', 'factory']:
248 if tracking_branch.startswith(branch):
249 saw_config_for_branch = False
250 for build_name in cbuildbot_config.config:
251 if build_name.endswith('-%s' % branch):
252 self.assertFalse('release' in build_name,
253 'Factory|Firmware release builders should not '
254 'contain release in their name.')
255 saw_config_for_branch = True
258 saw_config_for_branch, 'No config found for %s branch. '
259 'As this is the %s branch, all release configs that are being used '
260 'must end in %s.' % (branch, tracking_branch, branch))
262 def testBuildTests(self):
263 """Verify that we don't try to use tests without building them."""
265 for build_name, config in cbuildbot_config.config.iteritems():
266 if not config['build_tests']:
267 for flag in ('factory_toolkit', 'vm_tests', 'hw_tests'):
268 self.assertFalse(config[flag],
269 'Config %s set %s without build_tests.' % (build_name, flag))
271 def testPGOInBackground(self):
272 """Verify that we don't try to build or use PGO data in the background."""
273 for build_name, config in cbuildbot_config.config.iteritems():
274 if config.build_packages_in_background:
275 # It is unsupported to use the build_packages_in_background flags with
276 # the pgo_generate or pgo_use config options.
277 msg = 'Config %s uses build_packages_in_background with pgo_%s'
278 self.assertFalse(config.pgo_generate, msg % (build_name, 'generate'))
279 self.assertFalse(config.pgo_use, msg % (build_name, 'use'))
281 def testReleaseGroupInBackground(self):
282 """Verify build_packages_in_background settings for release groups.
284 For each release group, the first builder should be set to run in the
285 foreground (to build binary packages), and the remainder of the builders
286 should be set to run in parallel (to install the binary packages.)
288 for build_name, config in cbuildbot_config.config.iteritems():
289 if build_name.endswith('-release-group'):
290 msg = 'Config %s should not build_packages_in_background'
291 self.assertFalse(config.build_packages_in_background, msg % build_name)
293 self.assertTrue(config.child_configs,
294 'Config %s should have child configs' % build_name)
295 first_config = config.child_configs[0]
296 msg = 'Primary config for %s should not build_packages_in_background'
297 self.assertFalse(first_config.build_packages_in_background,
300 msg = 'Child config %s for %s should build_packages_in_background'
301 for child_config in config.child_configs[1:]:
302 self.assertTrue(child_config.build_packages_in_background,
303 msg % (child_config.name, build_name))
305 def testNoGrandChildConfigs(self):
306 """Verify that no child configs have a child config."""
307 for build_name, config in cbuildbot_config.config.iteritems():
308 for child_config in config.child_configs:
309 for grandchild_config in child_config.child_configs:
310 self.fail('Config %s has grandchild %s' % (build_name,
311 grandchild_config.name))
313 def testUseChromeLKGMImpliesInternal(self):
314 """Currently use_chrome_lkgm refers only to internal manifests."""
315 for build_name, config in cbuildbot_config.config.iteritems():
316 if config['use_chrome_lkgm']:
317 self.assertTrue(config['internal'],
318 'Chrome lkgm currently only works with an internal manifest: %s' % (
321 def testChromePFQsNeedChromeOSPFQs(self):
322 """Make sure every Chrome PFQ has a matching ChromeOS PFQ for prebuilts.
324 See http://crosbug.com/31695 for details on why.
326 Note: This check isn't perfect (as we can't check to see if we have
327 any ChromeOS PFQs actually running), but it at least makes sure people
328 realize we need to have the configs in sync.
331 # Figure out all the boards our Chrome and ChromeOS PFQs are using.
334 for config in cbuildbot_config.config.itervalues():
335 if config['build_type'] == constants.CHROME_PFQ_TYPE:
336 cr_pfqs.update(config['boards'])
337 elif config['build_type'] == constants.PALADIN_TYPE:
338 cros_pfqs.update(config['boards'])
340 # Then make sure the ChromeOS PFQ set is a superset of the Chrome PFQ set.
341 missing_pfqs = cr_pfqs.difference(cros_pfqs)
343 self.fail('Chrome PFQs are using boards that are missing ChromeOS PFQs:'
344 '\n\t' + ' '.join(missing_pfqs))
346 def testNonOverlappingConfigTypes(self):
347 """Test that a config can only match one build suffix."""
348 for config_type in cbuildbot_config.CONFIG_TYPE_DUMP_ORDER:
349 # A longer config_type should never end with a shorter suffix.
350 my_list = list(cbuildbot_config.CONFIG_TYPE_DUMP_ORDER)
351 my_list.remove(config_type)
353 cbuildbot_config.GetDisplayPosition(
354 config_type, type_order=my_list),
357 def testCorrectConfigTypeIndex(self):
358 """Test that the correct build suffix index is returned."""
364 for index, config_type in enumerate(type_order):
365 config = '-'.join(['pre-fix', config_type])
367 cbuildbot_config.GetDisplayPosition(
368 config, type_order=type_order),
371 # Verify suffix needs to match up to a '-'.
373 cbuildbot_config.GetDisplayPosition(
374 'pre-fix-sometype1', type_order=type_order),
377 def testConfigTypesComplete(self):
378 """Verify CONFIG_TYPE_DUMP_ORDER contains all valid config types."""
379 for config_name in cbuildbot_config.config:
381 cbuildbot_config.GetDisplayPosition(config_name),
382 len(cbuildbot_config.CONFIG_TYPE_DUMP_ORDER),
383 '%s did not match any types in %s' %
384 (config_name, 'cbuildbot_config.CONFIG_TYPE_DUMP_ORDER'))
386 def testCantBeBothTypesOfLKGM(self):
387 """Using lkgm and chrome_lkgm doesn't make sense."""
388 for config in cbuildbot_config.config.values():
389 self.assertFalse(config['use_lkgm'] and config['use_chrome_lkgm'])
391 def testNoDuplicateSlavePrebuilts(self):
392 """Test that no two same-board paladin slaves upload prebuilts."""
393 for cfg in cbuildbot_config.config.values():
394 if (cfg['build_type'] == constants.PALADIN_TYPE and cfg['master']):
395 slaves = cbuildbot_config.GetSlavesForMaster(cfg)
396 prebuilt_slaves = [s for s in slaves if s['prebuilts']]
397 # Dictionary from board name to builder name that uploads prebuilt
398 prebuilt_slave_boards = {}
399 for slave in prebuilt_slaves:
400 for board in slave['boards']:
401 self.assertFalse(prebuilt_slave_boards.has_key(board),
402 'Configs %s and %s both upload prebuilts for '
403 'board %s.' % (prebuilt_slave_boards.get(board),
406 prebuilt_slave_boards[board] = slave['name']
408 def testCantBeBothTypesOfPGO(self):
409 """Using pgo_generate and pgo_use together doesn't work."""
410 for config in cbuildbot_config.config.values():
411 self.assertFalse(config['pgo_use'] and config['pgo_generate'])
413 def testValidPrebuilts(self):
414 """Verify all builders have valid prebuilt values."""
415 for build_name, config in cbuildbot_config.config.iteritems():
416 msg = 'Config %s: has unexpected prebuilts value.' % build_name
417 valid_values = (False, constants.PRIVATE, constants.PUBLIC)
418 self.assertTrue(config['prebuilts'] in valid_values, msg)
420 def testInternalPrebuilts(self):
421 for build_name, config in cbuildbot_config.config.iteritems():
422 if (config['internal'] and
423 config['build_type'] != constants.CHROME_PFQ_TYPE):
424 msg = 'Config %s is internal but has public prebuilts.' % build_name
425 self.assertNotEqual(config['prebuilts'], constants.PUBLIC, msg)
427 def testValidHWTestPriority(self):
428 """Verify that hw test priority is valid."""
429 for build_name, config in cbuildbot_config.config.iteritems():
430 for test_config in config['hw_tests']:
432 test_config.priority in constants.HWTEST_VALID_PRIORITIES,
433 '%s has an invalid hwtest priority.' % build_name)
435 def testPushImageSignerResultsPaygenDependancies(self):
436 """Paygen requires SignerResults which requires PushImage."""
437 for build_name, config in cbuildbot_config.config.iteritems():
439 # signer_results can't complete without push_image.
440 if config['signer_results']:
441 self.assertTrue(config['push_image'],
442 '%s has signer_results without push_image' % build_name)
444 # paygen can't complete without signer_results, except for payloads
445 # where --channel arguments meet the requirements.
447 self.assertTrue(config['signer_results'] or
448 config['build_type'] == constants.PAYLOADS_TYPE,
449 '%s has paygen without signer_results' % build_name)
452 class FindFullTest(cros_test_lib.TestCase):
453 """Test locating of official build for a board."""
455 def _RunTest(self, board, external_expected=None, internal_expected=None):
456 def check_expected(l, expected):
457 if expected is not None:
458 self.assertTrue(expected in [v['name'] for v in l])
460 external, internal = cbuildbot_config.FindFullConfigsForBoard(board)
462 all(v is None for v in [external_expected, internal_expected]))
463 check_expected(external, external_expected)
464 check_expected(internal, internal_expected)
466 def _CheckCanonicalConfig(self, board, ending):
468 '-'.join((board, ending)),
469 cbuildbot_config.FindCanonicalConfigForBoard(board)['name'])
471 def testExternal(self):
472 """Test finding of a full builder."""
473 self._RunTest('amd64-generic', external_expected='amd64-generic-full')
475 def testInternal(self):
476 """Test finding of a release builder."""
477 self._RunTest('lumpy', internal_expected='lumpy-release')
480 """Both an external and internal config exist for board."""
481 self._RunTest('daisy', external_expected='daisy-full',
482 internal_expected='daisy-release')
484 def testExternalCanonicalResolution(self):
485 """Test an external canonical config."""
486 self._CheckCanonicalConfig('x86-generic', 'full')
488 def testInternalCanonicalResolution(self):
489 """Test prefer internal over external when both exist."""
490 self._CheckCanonicalConfig('daisy', 'release')
492 def testPGOCanonicalResolution(self):
493 """Test prefer non-PGO over PGO builder."""
494 self._CheckCanonicalConfig('lumpy', 'release')
496 def testOneFullConfigPerBoard(self):
497 """There is at most one 'full' config for a board."""
498 # Verifies that there is one external 'full' and one internal 'release'
499 # build per board. This is to ensure that we fail any new configs that
500 # wrongly have names like *-bla-release or *-bla-full. This case can also
501 # be caught if the new suffix was added to
502 # cbuildbot_config.CONFIG_TYPE_DUMP_ORDER
503 # (see testNonOverlappingConfigTypes), but that's not guaranteed to happen.
504 def AtMostOneConfig(board, label, configs):
507 'Found more than one %s config for %s: %r'
508 % (label, board, [c['name'] for c in configs]))
511 for config in cbuildbot_config.config.itervalues():
512 boards.update(config['boards'])
513 # Sanity check of the boards.
517 external, internal = cbuildbot_config.FindFullConfigsForBoard(b)
518 AtMostOneConfig(b, 'external', external)
519 AtMostOneConfig(b, 'internal', internal)
522 class OverrideForTrybotTest(cros_test_lib.TestCase):
523 """Test config override functionality."""
525 def _testWithOptions(self, **kwargs):
526 mock_options = mock.Mock()
527 for k, v in kwargs.iteritems():
528 mock_options.setattr(k, v)
530 for config in cbuildbot_config.config.itervalues():
531 cbuildbot_config.OverrideConfigForTrybot(config, mock_options)
533 def testLocalTrybot(self):
534 """Override each config for local trybot."""
535 self._testWithOptions(remote_trybot=False, hw_test=False)
537 def testRemoteTrybot(self):
538 """Override each config for remote trybot."""
539 self._testWithOptions(remote_trybot=True, hw_test=False)
541 def testRemoteHWTest(self):
542 """Override each config for remote trybot + hwtests."""
543 self._testWithOptions(remote_trybot=True, hw_test=True)
545 def testChromeInternalOverride(self):
546 """Verify that we are not using official Chrome for local trybots."""
547 mock_options = mock.Mock()
548 mock_options.remote_trybot = False
549 mock_options.hw_test = False
550 old = cbuildbot_config.config['x86-mario-paladin']
551 new = cbuildbot_config.OverrideConfigForTrybot(old, mock_options)
552 self.assertTrue(constants.USE_CHROME_INTERNAL in old['useflags'])
553 self.assertTrue(constants.USE_CHROME_PDF in old['useflags'])
554 self.assertFalse(constants.USE_CHROME_INTERNAL in new['useflags'])
555 self.assertFalse(constants.USE_CHROME_PDF in new['useflags'])
557 def testVmTestOverride(self):
558 """Verify that vm_tests override for trybots pay heed to original config."""
559 mock_options = mock.Mock()
560 old = cbuildbot_config.config['x86-mario-paladin']
561 new = cbuildbot_config.OverrideConfigForTrybot(old, mock_options)
562 self.assertEquals(new['vm_tests'], [constants.SIMPLE_AU_TEST_TYPE,
563 constants.CROS_VM_TEST_TYPE])
564 old['vm_tests'] = None
565 new = cbuildbot_config.OverrideConfigForTrybot(old, mock_options)
566 self.assertIsNone(new['vm_tests'])
569 if __name__ == '__main__':