Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(331)

Unified Diff: scripts/slave/recipe_modules/chromium_tests/api.py

Issue 1574433004: Allow a single trybot to mirror multiple waterfall bots. (Closed) Base URL: https://chromium.googlesource.com/chromium/tools/build.git@bot-config-and-test-db
Patch Set: Created 4 years, 11 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: scripts/slave/recipe_modules/chromium_tests/api.py
diff --git a/scripts/slave/recipe_modules/chromium_tests/api.py b/scripts/slave/recipe_modules/chromium_tests/api.py
index 25292663aeffa3ee9c0fba4d56c06ae1ccdb9c03..588b5b4c892421fd03b16f177456b6c86894d9e0 100644
--- a/scripts/slave/recipe_modules/chromium_tests/api.py
+++ b/scripts/slave/recipe_modules/chromium_tests/api.py
@@ -18,7 +18,6 @@ from . import builders
from . import steps
from . import trybots
-
Sergey Berezin 2016/01/12 01:44:23 nit: please keep 2 blank lines: we separate most t
Ken Russell (switch to Gerrit) 2016/01/12 05:35:51 Sorry, that was an accident.
MB_CONFIG_FILENAME = ['tools', 'mb', 'mb_config.pyl']
@@ -69,62 +68,131 @@ class ChromiumTestsApi(recipe_api.RecipeApi):
"""Adds builders to our builder map"""
self._builders.update(builders)
- def _get_bot_config(self, mastername, buildername):
+ def _get_mutable_bot_config(self, mastername, buildername):
master_dict = self.builders.get(mastername, {})
- return freeze(master_dict.get('builders', {}).get(buildername))
+ return master_dict.get('builders', {}).get(buildername)
- def configure_build(self, mastername, buildername, override_bot_type=None):
- master_dict = self.builders.get(mastername, {})
- bot_config = master_dict.get('builders', {}).get(buildername)
+ def _get_bot_config(self, mastername, buildername):
+ return freeze(self._get_mutable_bot_config(mastername, buildername))
+
+ def _get_bot_configs(self, bot_descs):
+ bot_configs = []
+ for bot_desc in bot_descs:
+ bot_configs.append(self._get_mutable_bot_config(bot_desc['mastername'],
+ bot_desc['buildername']))
+ return bot_configs
+
+ def normalize_bot_descs(self, bot_descs):
Paweł Hajdan Jr. 2016/01/11 18:41:11 Why do we need this? It's not a very strong prefer
Ken Russell (switch to Gerrit) 2016/01/11 22:54:01 chromium_trybot.py fetches the entry for a tryserv
Sergey Berezin 2016/01/12 01:44:23 This is per my request in the original CL to keep
+ """Gets the bot_descs into a uniform format. It should be an array of
+ dictionary-like objects containing the properties 'mastername' and
+ 'buildername'.
+ """
+ if not isinstance(bot_descs, (list, tuple)):
+ return [bot_descs]
+ return bot_descs
+
+ def _verify_property_is_same_across_bot_configs(self, bot_configs, property):
+ if len(bot_configs) < 2:
+ return
+ # This code path is only taken for trybots that mirror more than
+ # one waterfall bot. Verify that all of the other builders this
+ # trybot is supposed to mirror all use the same key properties
+ # like chromium_config and gclient_config. Otherwise, unexpected
+ # results are likely.
+ value = bot_configs[0].get(property)
+ for bot_config in bot_configs[1:]:
+ if value != bot_config.get(property):
+ # TODO(kbr): add a test configuration which triggers this error.
+ # Unclear how difficult it will be.
+ raise ValueError(
+ '%s was different between two bots a trybot is mirroring (%s vs %s)' %
+ (property, value, bot_config.get(value))) # pragma: no cover
+
+ def create_bot_desc(self, mastername, buildername):
+ """Creates a valid "bot_desc" argument for the various APIs below from
+ the given mastername and buildername. A dictionary containing the
+ properties "mastername" and "buildername" is a suitable argument.
+ An array of such dictionaries is also suitable, in which case tests
+ will be pulled from multiple testers.
Sergey Berezin 2016/01/12 01:44:23 The docstring doesnt' make sense to me. It appears
Ken Russell (switch to Gerrit) 2016/01/12 05:35:51 That's true. I should have documented the "bot_des
+ """
+ return {'mastername': mastername, 'buildername': buildername}
+
+ def configure_build(self, bot_desc, override_bot_type=None):
+ bot_desc = self.normalize_bot_descs(bot_desc)
+ bot_configs = self._get_bot_configs(bot_desc)
# Get the buildspec version. It can be supplied as a build property or as
# a recipe config value.
buildspec_version = (self.m.properties.get('buildspec_version') or
- bot_config.get('buildspec_version'))
+ bot_configs[0].get('buildspec_version'))
Paweł Hajdan Jr. 2016/01/11 18:41:11 Shouldn't we check that 'buildspec_version' is con
Ken Russell (switch to Gerrit) 2016/01/11 22:54:01 Probably, or at least possibly. I didn't know what
+
+ self._verify_property_is_same_across_bot_configs(bot_configs,
+ 'chromium_config')
self.m.chromium.set_config(
- bot_config.get('chromium_config'),
- **bot_config.get('chromium_config_kwargs', {}))
+ bot_configs[0].get('chromium_config'),
+ **bot_configs[0].get('chromium_config_kwargs', {}))
# Set GYP_DEFINES explicitly because chromium config constructor does
# not support that.
- self.m.chromium.c.gyp_env.GYP_DEFINES.update(
- bot_config.get('GYP_DEFINES', {}))
- if bot_config.get('use_isolate'):
- self.m.isolate.set_isolate_environment(self.m.chromium.c)
+ for bot_config in bot_configs:
+ self.m.chromium.c.gyp_env.GYP_DEFINES.update(
+ bot_config.get('GYP_DEFINES', {}))
+ if bot_config.get('use_isolate'):
+ self.m.isolate.set_isolate_environment(self.m.chromium.c)
+
+ self._verify_property_is_same_across_bot_configs(bot_configs,
+ 'gclient_config')
self.m.gclient.set_config(
- bot_config.get('gclient_config'),
+ bot_configs[0].get('gclient_config'),
PATCH_PROJECT=self.m.properties.get('patch_project'),
BUILDSPEC_VERSION=buildspec_version,
- **bot_config.get('gclient_config_kwargs', {}))
+ **bot_configs[0].get('gclient_config_kwargs', {}))
Sergey Berezin 2016/01/12 01:44:23 Should *_kwargs properties also be checked for bei
Ken Russell (switch to Gerrit) 2016/01/12 05:35:50 Good point. Yes, I think so. (Is this property eve
- if 'android_config' in bot_config:
+ if 'android_config' in bot_configs[0]:
Sergey Berezin 2016/01/12 01:44:23 nit: perhaps, more generally: if any('android_c
Ken Russell (switch to Gerrit) 2016/01/12 05:35:50 Good point.
self.m.chromium_android.configure_from_properties(
- bot_config['android_config'],
- **bot_config.get('chromium_config_kwargs', {}))
-
- if 'amp_config' in bot_config:
- self.m.amp.set_config(bot_config['amp_config'])
-
- for c in bot_config.get('chromium_apply_config', []):
- self.m.chromium.apply_config(c)
-
- for c in bot_config.get('gclient_apply_config', []):
- self.m.gclient.apply_config(c)
+ bot_configs[0]['android_config'],
+ **bot_configs[0].get('chromium_config_kwargs', {}))
+ # This code path hasn't been generalized yet for a trybot that
Paweł Hajdan Jr. 2016/01/11 18:41:11 Why not a TODO? Also applies to other places which
Ken Russell (switch to Gerrit) 2016/01/11 22:54:01 Yes, these should have TODOs.
+ # mirrors multiple bots.
+ assert len(bot_configs) == 1
+
+ if 'amp_config' in bot_configs[0]:
Sergey Berezin 2016/01/12 01:44:23 nit: same generalization as above.
Ken Russell (switch to Gerrit) 2016/01/12 05:35:51 Agree.
+ self.m.amp.set_config(bot_configs[0]['amp_config'])
+ # This code path hasn't been generalized yet for a trybot that
+ # mirrors multiple bots.
+ assert len(bot_configs) == 1
+
+ chromium_applied_configs = set()
+ gclient_applied_configs = set()
+ for bot_config in bot_configs:
+ for c in bot_config.get('chromium_apply_config', []):
+ if not c in chromium_applied_configs:
+ self.m.chromium.apply_config(c)
+ chromium_applied_configs.add(c)
+ for c in bot_config.get('gclient_apply_config', []):
+ if not c in gclient_applied_configs:
+ self.m.gclient.apply_config(c)
+ gclient_applied_configs.add(c)
# WARNING: src-side runtest.py is only tested with chromium CQ builders.
# Usage not covered by chromium CQ is not supported and can break
# without notice.
+ master_dict = self.builders.get(bot_desc[0]['mastername'], {})
Paweł Hajdan Jr. 2016/01/11 18:41:11 Why don't we also ensure consistency here?
Ken Russell (switch to Gerrit) 2016/01/11 22:54:01 No good reason not to. This query would be a littl
if master_dict.get('settings', {}).get('src_side_runtest_py'):
self.m.chromium.c.runtest_py.src_side = True
- if bot_config.get('goma_canary'):
- self.m.goma.update_goma_canary(buildername)
+ if bot_configs[0].get('goma_canary'):
Sergey Berezin 2016/01/12 01:44:23 nit: same generalization as above.
Ken Russell (switch to Gerrit) 2016/01/12 05:35:51 Acknowledged.
+ self.m.goma.update_goma_canary(bot_desc[0]['buildername'])
+ # This code path hasn't been generalized yet for a trybot that
+ # mirrors multiple bots.
+ assert len(bot_configs) == 1
- bot_type = override_bot_type or bot_config.get('bot_type', 'builder_tester')
+ bot_type = override_bot_type or bot_configs[0].get('bot_type',
Sergey Berezin 2016/01/12 01:44:23 nit: move inside the 'if'. This var is only used i
Ken Russell (switch to Gerrit) 2016/01/12 05:35:51 Good point.
+ 'builder_tester')
- if bot_config.get('set_component_rev'):
+ if bot_configs[0].get('set_component_rev'):
Sergey Berezin 2016/01/12 01:44:23 nit: same generalization as above.
Ken Russell (switch to Gerrit) 2016/01/12 05:35:51 Acknowledged.
# If this is a component build and the main revision is e.g. blink,
# webrtc, or v8, the custom deps revision of this component must be
# dynamically set to either:
@@ -137,8 +205,11 @@ class ChromiumTestsApi(recipe_api.RecipeApi):
if bot_type == 'tester':
component_rev = self.m.properties.get(
'parent_got_revision', component_rev)
- dep = bot_config.get('set_component_rev')
+ dep = bot_configs[0].get('set_component_rev')
self.m.gclient.c.revisions[dep['name']] = dep['rev_str'] % component_rev
+ # This code path hasn't been generalized yet for a trybot that
+ # mirrors multiple bots.
+ assert len(bot_configs) == 1
def ensure_checkout(self, mastername, buildername,
root_solution_revision=None):
@@ -179,7 +250,8 @@ class ChromiumTestsApi(recipe_api.RecipeApi):
else:
self.m.chromium.runhooks()
- def get_test_spec(self, mastername, buildername):
+ def get_test_spec(self, mastername, buildername,
+ annotate_master_name=False):
bot_config = self._get_bot_config(mastername, buildername)
test_spec_file = bot_config.get('testing', {}).get(
@@ -188,7 +260,9 @@ class ChromiumTestsApi(recipe_api.RecipeApi):
# TODO(phajdan.jr): Bots should have no generators instead.
if bot_config.get('disable_tests'):
return {}
- return self.read_test_spec(self.m, test_spec_file)
+ return self.read_test_spec(self.m, test_spec_file,
+ annotate_master_name=annotate_master_name,
+ master_name_for_annotation=mastername)
def _get_master_dict_with_dynamic_tests(
self, mastername, buildername, test_spec, scripts_compile_targets):
@@ -217,43 +291,63 @@ class ChromiumTestsApi(recipe_api.RecipeApi):
bot_db._add_master_dict_and_test_spec(mastername, master_dict, {})
return bot_db
- def prepare_checkout(self, mastername, buildername,
- root_solution_revision=None):
- bot_config = self._get_bot_config(mastername, buildername)
+ def prepare_checkout(self, bot_desc, root_solution_revision=None):
+ bot_desc = self.normalize_bot_descs(bot_desc)
+ bot_configs = self._get_bot_configs(bot_desc)
+
+ # Use the first bot's mastername and buildername for several of
+ # the setup steps in this routine.
+ mastername = bot_desc[0]['mastername']
+ buildername = bot_desc[0]['buildername']
- update_step = self.ensure_checkout(mastername, buildername,
+ update_step = self.ensure_checkout(mastername,
+ buildername,
root_solution_revision)
# TODO(robertocn): Remove this hack by the end of Q1/2016.
if (mastername == 'tryserver.chromium.perf'
- and bot_config.get('bot_type') == 'builder'
+ and bot_configs[0].get('bot_type') == 'builder'
Sergey Berezin 2016/01/12 01:44:23 nit: perhaps, also generalize as above?
Ken Russell (switch to Gerrit) 2016/01/12 05:35:50 Maybe. Since this is a hack it might be best to to
and buildername.endswith('builder')):
force_legacy_compile = self.should_force_legacy_compiling(
mastername, buildername)
if force_legacy_compile:
self.m.chromium.c.project_generator.tool = 'gyp'
+ # This code path hasn't been generalized yet for a trybot that
+ # mirrors multiple bots.
+ assert len(bot_configs) == 1
self.set_up_swarming(mastername, buildername)
self.runhooks(update_step)
- test_spec = self.get_test_spec(mastername, buildername)
+ bot_db = bdb_module.BotConfigAndTestDB()
- # TODO(phajdan.jr): Bots should have no generators instead.
- if bot_config.get('disable_tests'):
- scripts_compile_targets = {}
- else:
- scripts_compile_targets = \
- self.get_compile_targets_for_scripts().json.output
+ scripts_compile_targets = {}
+ first_iteration = True
+
+ for bot in bot_desc:
+ test_spec = self.get_test_spec(
+ bot['mastername'], bot['buildername'],
+ annotate_master_name=not first_iteration)
+
+ # This is done this way to reduce test expectation changes.
+ if first_iteration:
+ # TODO(phajdan.jr): Bots should have no generators instead.
+ if bot_configs[0].get('disable_tests'):
+ scripts_compile_targets = {}
+ else:
+ scripts_compile_targets = \
+ self.get_compile_targets_for_scripts().json.output
+ first_iteration = False
- master_dict = self._get_master_dict_with_dynamic_tests(
- mastername, buildername, test_spec, scripts_compile_targets)
+ master_dict = self._get_master_dict_with_dynamic_tests(
+ bot['mastername'], bot['buildername'], test_spec,
+ scripts_compile_targets)
+ bot_db._add_master_dict_and_test_spec(
+ bot['mastername'], master_dict, test_spec)
if self.m.chromium.c.lto and \
not self.m.chromium.c.env.LLVM_FORCE_HEAD_REVISION:
self.m.chromium.download_lto_plugin()
- bot_db = bdb_module.BotConfigAndTestDB()
- bot_db._add_master_dict_and_test_spec(mastername, master_dict, test_spec)
-
return update_step, bot_db
def generate_tests_from_test_spec(self, api, test_spec, builder_dict,
@@ -269,11 +363,15 @@ class ChromiumTestsApi(recipe_api.RecipeApi):
tests)
return tests
- def read_test_spec(self, api, test_spec_file):
+ def read_test_spec(self, api, test_spec_file, annotate_master_name=False,
+ master_name_for_annotation=None):
test_spec_path = api.path['checkout'].join('testing', 'buildbot',
test_spec_file)
+ step_name = 'read test spec'
+ if annotate_master_name:
+ step_name += ' (%s)' % master_name_for_annotation
test_spec_result = api.json.read(
- 'read test spec',
+ step_name,
test_spec_path,
step_test_data=lambda: api.json.test_api.output({}))
test_spec_result.presentation.step_text = 'path: %s' % test_spec_path
@@ -332,6 +430,33 @@ class ChromiumTestsApi(recipe_api.RecipeApi):
return test_runner
def get_compile_targets_and_tests(
+ self, bot_desc, bot_db, override_bot_type=None, override_tests=None):
+ bot_desc = self.normalize_bot_descs(bot_desc)
+ """Returns a tuple: list of compile targets and list of tests.
+
+ The list of tests includes ones on the triggered testers."""
+ all_compile_targets = set()
+ all_tests = []
+ for bot in bot_desc:
+ compile_targets, tests = self._get_single_bot_compile_targets_and_tests(
+ bot['mastername'], bot['buildername'], bot_db,
+ override_bot_type=override_bot_type, override_tests=override_tests)
+ all_compile_targets.update(compile_targets)
+ # It doesn't seem feasible to de-duplicate tests in the case where
+ # a tryserver mirrors multiple waterfall bots. (This is the only
+ # case where there's more than one entry in bot_configs.) Just add
+ # them all in the order returned.
+ all_tests.extend(tests)
+ # The incoming 'override_tests' must be reset during the second
Paweł Hajdan Jr. 2016/01/11 18:41:11 Why don't we override tests at the end? The curre
Ken Russell (switch to Gerrit) 2016/01/11 22:54:01 Good point; that's probably the correct thing to d
+ # and subsequent loop iterations, or trybots which mirror multiple
+ # bots will add the same set of tests multiple times.
+ # TODO(kbr): it isn't clear to me what the correct behavior is here.
+ # What should happen with the second bot's 'tests' property?
+ override_tests = None
+
+ return sorted(all_compile_targets), all_tests
+
+ def _get_single_bot_compile_targets_and_tests(
self, mastername, buildername, bot_db,
override_bot_type=None, override_tests=None):
"""Returns a tuple: list of compile targets and list of tests.
@@ -391,19 +516,19 @@ class ChromiumTestsApi(recipe_api.RecipeApi):
command(lambda name: name)
- def compile(self, mastername, buildername, update_step, bot_db,
+ def compile(self, bot_desc, update_step, bot_db,
mb_mastername=None, mb_buildername=None):
- """Runs compile and related steps for given builder."""
+ """Runs compile and related steps for builder(s) described in bot_desc."""
+ bot_desc = self.normalize_bot_descs(bot_desc)
assert isinstance(bot_db, bdb_module.BotConfigAndTestDB), \
"bot_db argument %r was not a BotConfigAndTestDB" % (bot_db)
compile_targets, tests_including_triggered = \
self.get_compile_targets_and_tests(
- mastername,
- buildername,
+ bot_desc,
bot_db)
self.compile_specific_targets(
- mastername, buildername, update_step, bot_db,
- compile_targets, tests_including_triggered,
+ bot_desc[0]['mastername'], bot_desc[0]['buildername'],
Sergey Berezin 2016/01/12 01:44:23 Shouldn't we compile for all bot_descs? E.g. this
Ken Russell (switch to Gerrit) 2016/01/12 05:35:51 At first glance it didn't seem necessary to genera
+ update_step, bot_db, compile_targets, tests_including_triggered,
mb_mastername=mb_mastername, mb_buildername=mb_buildername)
def compile_specific_targets(

Powered by Google App Engine
This is Rietveld 408576698