| Index: scripts/slave/recipes/chromium_trybot.py
|
| diff --git a/scripts/slave/recipes/chromium_trybot.py b/scripts/slave/recipes/chromium_trybot.py
|
| index fd1147b414f2bfa23ca6122f3e8f650af8404a7f..243ee98ddcd5a0d54ef956041fb237f4d4fbc8c1 100644
|
| --- a/scripts/slave/recipes/chromium_trybot.py
|
| +++ b/scripts/slave/recipes/chromium_trybot.py
|
| @@ -16,6 +16,7 @@ DEPS = [
|
| 'raw_io',
|
| 'step',
|
| 'step_history',
|
| + 'swarming',
|
| 'test_utils',
|
| 'tryserver',
|
| ]
|
| @@ -362,6 +363,21 @@ BUILDERS = {
|
| }
|
|
|
|
|
| +def add_swarming_builder(original, swarming, server='tryserver.chromium'):
|
| + """Duplicates builder config on |server|, adding 'enable_swarming: True'."""
|
| + assert server in BUILDERS
|
| + assert original in BUILDERS[server]['builders']
|
| + assert swarming not in BUILDERS[server]['builders']
|
| + conf = BUILDERS[server]['builders'][original].copy()
|
| + conf['enable_swarming'] = True
|
| + BUILDERS[server]['builders'][swarming] = conf
|
| +
|
| +
|
| +add_swarming_builder('linux_chromium_rel', 'linux_chromium_rel_swarming')
|
| +add_swarming_builder('win_chromium_rel', 'win_chromium_rel_swarming')
|
| +add_swarming_builder('mac_chromium_rel', 'mac_chromium_rel_swarming')
|
| +
|
| +
|
| def GenSteps(api):
|
| class CheckdepsTest(api.test_utils.Test): # pylint: disable=W0232
|
| name = 'checkdeps'
|
| @@ -506,12 +522,119 @@ def GenSteps(api):
|
| global_tags = gtest_results.raw.get('global_tags', [])
|
| return 'UNRELIABLE_RESULTS' not in global_tags
|
|
|
| -
|
| def failures(self, suffix):
|
| step_name = self._step_name(suffix)
|
| return api.step_history[step_name].json.gtest_results.failures
|
|
|
|
|
| + class SwarmingGTestTest(api.test_utils.Test):
|
| + def __init__(self, name, args=None):
|
| + api.test_utils.Test.__init__(self)
|
| + self._name = name
|
| + self._args = args or []
|
| + self._tasks = {}
|
| + self._results = {}
|
| +
|
| + @property
|
| + def name(self):
|
| + return self._name
|
| +
|
| + def compile_targets(self):
|
| + # <X>_run target depends on <X>, and then isolates it invoking isolate.py.
|
| + # It is a convention, not a hard coded rule.
|
| + return [self._name + '_run']
|
| +
|
| + def pre_run(self, suffix):
|
| + """Launches the test on Swarming."""
|
| + assert suffix not in self._tasks, (
|
| + 'Test %s was already triggered' % self._step_name(suffix))
|
| +
|
| + # *.isolated may be missing if *_run target is misconfigured. It's a error
|
| + # in gyp, not a recipe failure. So carry on with recipe execution.
|
| + isolated_hash = api.isolate.isolated_tests.get(self._name)
|
| + if not isolated_hash:
|
| + return api.python.inline(
|
| + '[error] %s' % self._step_name(suffix),
|
| + r"""
|
| + import sys
|
| + print '*.isolated file for target %s is missing' % sys.argv[1]
|
| + sys.exit(1)
|
| + """,
|
| + args=[self._name],
|
| + always_run=True)
|
| +
|
| + # If rerunning without a patch, run only tests that failed.
|
| + args = self._args[:]
|
| + if suffix == 'without patch':
|
| + failed_tests = sorted(self.failures('with patch'))
|
| + args.append('--gtest_filter=%s' % ':'.join(failed_tests))
|
| +
|
| + # Trigger the test on swarming.
|
| + self._tasks[suffix] = api.swarming.gtest_task(
|
| + title=self._step_name(suffix),
|
| + isolated_hash=isolated_hash,
|
| + test_launcher_summary_output=api.json.gtest_results(
|
| + add_json_log=False),
|
| + extra_args=args)
|
| + return api.swarming.trigger([self._tasks[suffix]], always_run=True)
|
| +
|
| + def run(self, suffix): # pylint: disable=R0201
|
| + """Not used. All logic in pre_run, post_run."""
|
| + return []
|
| +
|
| + def post_run(self, suffix):
|
| + """Waits for launched test to finish and collect the results."""
|
| + assert suffix not in self._results, (
|
| + 'Results of %s were already collected' % self._step_name(suffix))
|
| +
|
| + # Emit error if test wasn't triggered. This happens if *.isolated is not
|
| + # found. (The build is already red by this moment anyway).
|
| + if suffix not in self._tasks:
|
| + return api.python.inline(
|
| + '[collect error] %s' % self._step_name(suffix),
|
| + r"""
|
| + import sys
|
| + print '%s wasn\'t triggered' % sys.argv[1]
|
| + sys.exit(1)
|
| + """,
|
| + args=[self._name],
|
| + always_run=True)
|
| +
|
| + # Update step presentation, store step results in self._results.
|
| + def followup_fn(step_result):
|
| + r = step_result.json.gtest_results
|
| + p = step_result.presentation
|
| + if r.valid:
|
| + p.step_text += api.test_utils.format_step_text([
|
| + ['failures:', r.failures]
|
| + ])
|
| + self._results[suffix] = r
|
| +
|
| + # Wait for test on swarming to finish. If swarming infrastructure is
|
| + # having issues, this step produces no valid *.json test summary, and
|
| + # 'has_valid_results' returns False.
|
| + return api.swarming.collect(
|
| + [self._tasks[suffix]],
|
| + always_run=True,
|
| + can_fail_build=False,
|
| + followup_fn=followup_fn)
|
| +
|
| + def has_valid_results(self, suffix):
|
| + # Test wasn't triggered or wasn't collected.
|
| + if suffix not in self._tasks or not suffix in self._results:
|
| + return False
|
| + # Test ran, but failed to produce valid *.json.
|
| + gtest_results = self._results[suffix]
|
| + if not gtest_results.valid: # pragma: no cover
|
| + return False
|
| + global_tags = gtest_results.raw.get('global_tags', [])
|
| + return 'UNRELIABLE_RESULTS' not in global_tags
|
| +
|
| + def failures(self, suffix):
|
| + assert self.has_valid_results(suffix)
|
| + return self._results[suffix].failures
|
| +
|
| +
|
| class NaclIntegrationTest(api.test_utils.Test): # pylint: disable=W0232
|
| name = 'nacl_integration'
|
|
|
| @@ -541,6 +664,71 @@ def GenSteps(api):
|
| failures = api.step_history[self._step_name(suffix)].json.output
|
| return [f['raw_name'] for f in failures]
|
|
|
| +
|
| + def parse_test_spec(test_spec, enable_swarming, should_use_test):
|
| + """Returns a list of tests to run and additional targets to compile.
|
| +
|
| + Uses 'should_use_test' callback to figure out what tests should be skipped.
|
| +
|
| + Returns triple (compile_targets, gtest_tests, swarming_tests) where
|
| + gtest_tests is a list of GTestTest
|
| + swarming_tests is a list of SwarmingGTestTest.
|
| + """
|
| + compile_targets = []
|
| + gtest_tests_spec = []
|
| + if isinstance(test_spec, dict):
|
| + compile_targets = test_spec.get('compile_targets', [])
|
| + gtest_tests_spec = test_spec.get('gtest_tests', [])
|
| + else:
|
| + # TODO(nodir): Remove this after
|
| + # https://codereview.chromium.org/297303012/#ps50001
|
| + # lands.
|
| + gtest_tests_spec = test_spec
|
| +
|
| + gtest_tests = []
|
| + swarming_tests = []
|
| + for test in gtest_tests_spec:
|
| + test_name = None
|
| + test_dict = None
|
| +
|
| + # Read test_dict for the test, it defines where test can run.
|
| + if isinstance(test, unicode):
|
| + test_name = test.encode('utf-8')
|
| + test_dict = {}
|
| + elif isinstance(test, dict):
|
| + if 'test' not in test: # pragma: no cover
|
| + raise ValueError('Invalid entry in test spec: %r' % test)
|
| + test_name = test['test'].encode('utf-8')
|
| + test_dict = test
|
| + else: # pragma: no cover
|
| + raise ValueError('Unrecognized entry in test spec: %r' % test)
|
| +
|
| + # Should skip it completely?
|
| + if not test_name or not should_use_test(test_dict):
|
| + continue
|
| +
|
| + # If test can run on swarming, test_dict has a section that defines when
|
| + # swarming should be used, in same format as main test dict.
|
| + use_swarming = False
|
| + if enable_swarming:
|
| + swarming_spec = test_dict.get('swarming') or {}
|
| + if not isinstance(swarming_spec, dict): # pragma: no cover
|
| + raise ValueError('\'swarming\' entry in test spec should be a dict')
|
| + if swarming_spec.get('can_use_on_swarming_builders'):
|
| + use_swarming = should_use_test(swarming_spec)
|
| +
|
| + test_args = test_dict.get('args')
|
| + if isinstance(test_args, basestring):
|
| + test_args = [test_args]
|
| +
|
| + if use_swarming:
|
| + swarming_tests.append(SwarmingGTestTest(test_name, test_args))
|
| + else:
|
| + gtest_tests.append(GTestTest(test_name, test_args))
|
| +
|
| + return compile_targets, gtest_tests, swarming_tests
|
| +
|
| +
|
| mastername = api.properties.get('mastername')
|
| buildername = api.properties.get('buildername')
|
| master_dict = BUILDERS.get(mastername, {})
|
| @@ -557,8 +745,6 @@ def GenSteps(api):
|
| # Settings GYP_DEFINES explicitly because chromium config constructor does
|
| # not support that.
|
| api.chromium.c.gyp_env.GYP_DEFINES.update(bot_config.get('GYP_DEFINES', {}))
|
| - if bot_config.get('use_isolate'):
|
| - api.isolate.set_isolate_environment(api.chromium.c)
|
| api.chromium.apply_config('trybot_flavor')
|
| api.gclient.set_config('chromium')
|
| api.step.auto_resolve_conflicts = True
|
| @@ -609,6 +795,31 @@ def GenSteps(api):
|
| followup_fn=test_spec_followup_fn,
|
| )
|
|
|
| + def should_use_test(test):
|
| + """Given a test dict from test spec returns True or False."""
|
| + if 'platforms' in test:
|
| + if api.platform.name not in test['platforms']:
|
| + return False
|
| + if 'chromium_configs' in test:
|
| + if bot_config['chromium_config'] not in test['chromium_configs']:
|
| + return False
|
| + if 'exclude_builders' in test:
|
| + if '%s:%s' % (mastername, buildername) in test['exclude_builders']:
|
| + return False
|
| + return True
|
| +
|
| + # Parse test spec file into list of Test instances.
|
| + compile_targets, gtest_tests, swarming_tests = parse_test_spec(
|
| + api.step_history['read test spec'].json.output,
|
| + bot_config.get('enable_swarming'),
|
| + should_use_test)
|
| +
|
| + # Swarming uses Isolate to transfer files to swarming bots.
|
| + # set_isolate_environment modifies GYP_DEFINES to enable test isolation.
|
| + use_isolate = swarming_tests or bot_config.get('use_isolate')
|
| + if use_isolate:
|
| + api.isolate.set_isolate_environment(api.chromium.c)
|
| +
|
| runhooks_env = bot_config.get('runhooks_env', {})
|
|
|
| yield api.chromium.runhooks(env=runhooks_env, abort_on_failure=False,
|
| @@ -639,51 +850,10 @@ def GenSteps(api):
|
| api.chromium.runhooks(env=runhooks_env)
|
| )
|
|
|
| - gtest_tests = []
|
| - compile_targets = []
|
| - test_spec = api.step_history['read test spec'].json.output
|
| -
|
| - if isinstance(test_spec, dict):
|
| - compile_targets = test_spec.get('compile_targets', [])
|
| - gtest_tests_spec = test_spec.get('gtest_tests', [])
|
| - else:
|
| - # TODO (nodir): Remove this after
|
| - # https://codereview.chromium.org/297303012/#ps50001
|
| - # lands.
|
| - gtest_tests_spec = test_spec
|
| -
|
| - for test in gtest_tests_spec:
|
| - test_name = None
|
| - test_args = None
|
| -
|
| - if isinstance(test, unicode):
|
| - test_name = test.encode('utf-8')
|
| - elif isinstance(test, dict):
|
| - if 'platforms' in test:
|
| - if api.platform.name not in test['platforms']:
|
| - continue
|
| -
|
| - if 'chromium_configs' in test:
|
| - if bot_config['chromium_config'] not in test['chromium_configs']:
|
| - continue
|
| -
|
| - if 'exclude_builders' in test:
|
| - if '%s:%s' % (mastername, buildername) in test['exclude_builders']:
|
| - continue
|
| -
|
| - test_args = test.get('args')
|
| - if isinstance(test_args, basestring):
|
| - test_args = [test_args]
|
| -
|
| - if 'test' not in test: # pragma: no cover
|
| - raise ValueError('Invalid entry in test spec: %r' % test)
|
| -
|
| - test_name = test['test'].encode('utf-8')
|
| - else: # pragma: no cover
|
| - raise ValueError('Unrecognized entry in test spec: %r' % test)
|
| -
|
| - if test_name:
|
| - gtest_tests.append(GTestTest(test_name, test_args))
|
| + # If going to use swarming_client (pinned in src/DEPS), ensure it is
|
| + # compatible with what recipes expect.
|
| + if swarming_tests:
|
| + yield api.swarming.check_client_version()
|
|
|
| tests = []
|
| tests.append(CheckdepsTest())
|
| @@ -693,8 +863,8 @@ def GenSteps(api):
|
| ChecklicensesTest(),
|
| ])
|
| tests.append(Deps2GitTest())
|
| - for test in gtest_tests:
|
| - tests.append(test)
|
| + tests.extend(gtest_tests)
|
| + tests.extend(swarming_tests)
|
| tests.append(NaclIntegrationTest())
|
|
|
| compile_targets.extend(bot_config.get('compile_targets', []))
|
| @@ -748,7 +918,9 @@ def GenSteps(api):
|
| if api.step_history.failed:
|
| return
|
|
|
| - if bot_config.get('use_isolate'):
|
| + # Collect *.isolated hashes for all isolated targets, used when triggering
|
| + # tests on swarming.
|
| + if use_isolate:
|
| yield api.isolate.find_isolated_tests(api.chromium.output_dir)
|
|
|
| if bot_config['compile_only']:
|
| @@ -757,6 +929,7 @@ def GenSteps(api):
|
| if bot_config['chromium_config'] not in ['chromium_chromeos',
|
| 'chromium_chromeos_clang']:
|
| # TODO(phajdan.jr): Make it possible to retry telemetry tests (add JSON).
|
| + # TODO(vadimsh): Trigger swarming tests before telemetry tests.
|
| yield (
|
| api.chromium.run_telemetry_unittests(),
|
| api.chromium.run_telemetry_perf_unittests(),
|
| @@ -791,6 +964,9 @@ def GenSteps(api):
|
| name='compile (without patch, clobber)',
|
| force_clobber=True,
|
| always_run=True)
|
| + if use_isolate:
|
| + yield api.isolate.find_isolated_tests(api.chromium.output_dir,
|
| + always_run=True)
|
|
|
| yield api.test_utils.determine_new_failures(tests, deapply_patch_fn)
|
|
|
| @@ -1023,3 +1199,81 @@ def GenTests(api):
|
| },
|
| ]))
|
| )
|
| +
|
| + # Successfully compiling, isolating and running two targets on swarming.
|
| + yield (
|
| + api.test('swarming_basic') +
|
| + props(buildername='linux_chromium_rel_swarming') +
|
| + api.platform.name('linux') +
|
| + api.override_step_data('read test spec', api.json.output({
|
| + 'gtest_tests': [
|
| + {
|
| + 'test': 'base_unittests',
|
| + 'swarming': {'can_use_on_swarming_builders': True},
|
| + },
|
| + {
|
| + 'test': 'browser_tests',
|
| + 'swarming': {
|
| + 'can_use_on_swarming_builders': True,
|
| + 'platforms': ['linux'],
|
| + },
|
| + },
|
| + ],
|
| + })
|
| + ) +
|
| + api.override_step_data(
|
| + 'find isolated tests',
|
| + api.isolate.output_json(['base_unittests', 'browser_tests']))
|
| + )
|
| +
|
| + # One target (browser_tests) failed to produce *.isolated file.
|
| + yield (
|
| + api.test('swarming_missing_isolated') +
|
| + props(buildername='linux_chromium_rel_swarming') +
|
| + api.platform.name('linux') +
|
| + api.override_step_data('read test spec', api.json.output({
|
| + 'gtest_tests': [
|
| + {
|
| + 'test': 'base_unittests',
|
| + 'swarming': {'can_use_on_swarming_builders': True},
|
| + },
|
| + {
|
| + 'test': 'browser_tests',
|
| + 'swarming': {'can_use_on_swarming_builders': True},
|
| + },
|
| + ],
|
| + })
|
| + ) +
|
| + api.override_step_data(
|
| + 'find isolated tests',
|
| + api.isolate.output_json(['base_unittests']))
|
| + )
|
| +
|
| + # One test (base_unittest) failed on swarming. It is retried with
|
| + # deapplied patch.
|
| + yield (
|
| + api.test('swarming_deapply_patch') +
|
| + props(buildername='linux_chromium_rel_swarming') +
|
| + api.platform.name('linux') +
|
| + api.override_step_data('read test spec', api.json.output({
|
| + 'gtest_tests': [
|
| + {
|
| + 'test': 'base_unittests',
|
| + 'swarming': {'can_use_on_swarming_builders': True},
|
| + },
|
| + {
|
| + 'test': 'browser_tests',
|
| + 'swarming': {'can_use_on_swarming_builders': True},
|
| + },
|
| + ],
|
| + })
|
| + ) +
|
| + api.override_step_data(
|
| + 'find isolated tests',
|
| + api.isolate.output_json(['base_unittests', 'browser_tests'])) +
|
| + api.override_step_data('[swarming] base_unittests (with patch)',
|
| + canned_test(passing=False)) +
|
| + api.override_step_data(
|
| + 'find isolated tests (2)',
|
| + api.isolate.output_json(['base_unittests']))
|
| + )
|
|
|