blob: ae89bd01eef1e0b828e4e5541521b8d9c881ffac [file] [log] [blame]
# Copyright 2019 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
from collections import defaultdict
import json
from recipe_engine.config import Dict
from recipe_engine.config import List
from recipe_engine.config import Single
from recipe_engine.post_process import (DoesNotRun, DropExpectation, MustRun,
StepCommandContains, StatusFailure,
StatusSuccess, ResultReason)
from recipe_engine.recipe_api import Property
from PB.recipes.build.findit.chromium.single_revision import InputProperties
from PB.go.chromium.org.luci.buildbucket.proto import common as common_pb
from RECIPE_MODULES.build import chromium
from RECIPE_MODULES.build.chromium_tests import bot_db, bot_spec, steps
DEPS = [
'chromium',
'chromium_swarming',
'chromium_tests',
'depot_tools/tryserver',
'filter',
'findit',
'goma',
'isolate',
'recipe_engine/buildbucket',
'recipe_engine/json',
'recipe_engine/properties',
'test_utils',
]
PROPERTIES = InputProperties
def RunSteps(api, properties):
if properties.isolate_targets:
for target in properties.isolate_targets:
api.isolate.isolate_server = target.server
args = [
'--isolated-script-test-repeat=%d' % properties.test_repeat_count
] if properties.test_repeat_count else []
api.isolate.run_isolated("Run isolated test '%s'" % target.hash,
target.hash, args)
return
# 0. Validate properties.
assert (
properties.target_builder and properties.target_builder.master and
properties.target_builder.builder), 'Target builder property is required'
# 1. Configure the builder.
builder_id, bot_config, compile_kwargs = _configure_builder(
api, properties.target_builder)
# 2. Check out the code.
bot_update_step, build_config = api.chromium_tests.prepare_checkout(
bot_config)
# 3. Configure swarming
api.chromium_swarming.configure_swarming('chromium', precommit=False)
# 4. Determine what to build and what to test.
compile_targets, test_objects = _compute_targets_and_tests(
api, bot_config, build_config, builder_id, properties.tests,
properties.compile_targets, properties.skip_analyze)
# 5. Build what's needed.
# Since these builders run on different platforms, and require different Goma
# settings depending on the platform, set the Goma flags using recipe configs.
api.goma.set_client_flags('goma.chromium.org', '?prod')
if compile_targets:
compile_result = api.chromium_tests.compile_specific_targets(
bot_config,
bot_update_step,
build_config,
compile_targets,
tests_including_triggered=test_objects,
**compile_kwargs)
if compile_result.status != common_pb.SUCCESS:
return compile_result
# 6. Run the tests.
_run_tests(api, bot_config, test_objects, properties.tests,
properties.test_repeat_count)
def _configure_builder(api, target_tester):
bot_mirror = api.findit.get_bot_mirror_for_tester(
chromium.BuilderId.create_for_master(target_tester.master,
target_tester.builder))
bot_config = api.chromium_tests.create_bot_config_object([bot_mirror])
api.chromium_tests.configure_build(
bot_config, override_bot_type='builder_tester')
# If there is a problem with goma, rather than default to compiling locally
# only, fail. This is important because findit relies on fast compile for
# timely production of actionable changes, and local compilation alone is
# unlikely to help findit find a culprit in time for automatic revert.
# Better to fail the analysis and let the sheriffs try to find a culprit
# manually.
api.chromium.apply_config('goma_failfast')
if bot_mirror.tester_id:
tester_spec = api.chromium_tests.builders[bot_mirror.tester_id]
for key, value in tester_spec.swarming_dimensions.iteritems():
# Coercing str as json.loads creates unicode strings. This only matters
# for testing.
api.chromium_swarming.set_default_dimension(str(key), str(value))
compile_kwargs = {
'builder_id': bot_mirror.builder_id,
'override_bot_type': 'builder_tester',
}
return bot_mirror.builder_id, bot_config, compile_kwargs
def _compute_targets_and_tests(api, bot_config, build_config, builder_id,
requested_tests, compile_targets, skip_analyze):
if requested_tests:
# Figure out which test steps to run.
requested_tests_to_run = [
test for test in build_config.all_tests()
if test.canonical_name in requested_tests
]
# Figure out the test targets to be compiled.
requested_test_targets = []
for test in requested_tests_to_run:
requested_test_targets.extend(test.compile_targets())
requested_test_targets = sorted(set(requested_test_targets))
if skip_analyze:
# All test targets requested
return requested_test_targets, requested_tests_to_run
# '' is src/ relative to src/
changed_files = api.tryserver.get_files_affected_by_patch('')
affected_test_targets, actual_compile_targets = (
api.filter.analyze(
changed_files,
test_targets=tuple(requested_test_targets),
additional_compile_targets=tuple(compile_targets),
config_file_name='trybot_analyze_config.json',
builder_id=builder_id))
actual_tests_to_run = []
for test in requested_tests_to_run:
targets = test.compile_targets()
if targets and not any(t in affected_test_targets for t in targets):
# Skip tests whose targets are not affected by the change.
# NB: Non-compiled tests i.e. checkperms are not filtered out.
continue
actual_tests_to_run.append(test)
# Targets filtered by analyze
return actual_compile_targets, actual_tests_to_run
# No tests, only compile targets
default_compile_targets = build_config.get_compile_targets(
build_config.all_tests())
# If no targets were specifically requested, compile every target in the spec.
if not compile_targets:
return default_compile_targets, []
# Filter out targets that do not exist in this revision. i.e. By calling
# `ninja query`.
existing_targets = api.findit.existing_targets(
compile_targets, builder_id=builder_id)
return existing_targets, []
def _run_tests(api, bot_config, test_objects, requested_tests,
test_repeat_count):
# Default to 20 repeats.
test_repeat_count = test_repeat_count or 20
# test_objects are instances of the classes under chromium_tests/steps
# whereas requested_tests is the dictionary passed as a property mapping a
# test step to the test names to run.
for test_obj in test_objects:
test_filter = tuple(requested_tests[test_obj.canonical_name].names)
# ScriptTests do not support test_options property
if not isinstance(test_obj, steps.ScriptTest):
test_obj.test_options = steps.TestOptions(
test_filter=test_filter,
repeat_count=test_repeat_count,
retry_limit=0 if test_repeat_count else None,
run_disabled=bool(test_repeat_count))
# Run the tests.
with api.chromium_tests.wrap_chromium_tests(bot_config, test_objects):
return api.test_utils.run_tests(api.chromium_tests.m, test_objects, '')
def GenTests(api):
def _StepCommandNotContains(check, step_odict, step, arg):
check('Step %s does not contain %s' % (step, arg),
arg not in step_odict[step].cmd)
def _common(api, test_repeat_count=20, target_master='chromium.linux',
target_builder='Linux Builder', tests=None, compile_targets=None,
test_override_builders=False, spec=None, skip_analyze=False):
"""Create test properties and other data for tests."""
tests = tests or {}
compile_targets = compile_targets or []
_default_spec = 'chromium.linux', {
'Linux Builder': {
'isolated_scripts': [{
'isolate_name': 'blink_web_tests',
'name': 'blink_web_tests',
'swarming': {
'can_use_on_swarming_builders': True,
'shards': 1,
},
}],
'gtest_tests': [{
'test': 'base_unittests',
'swarming': {
'can_use_on_swarming_builders': True
},
}],
}
}
_default_builders = bot_db.BotDatabase.create({
'chromium.linux': {
'Linux Tests':
bot_spec.BotSpec.create(
parent_buildername='Linux Builder',
swarming_dimensions={'pool': 'luci.dummy.pool'},
chromium_config='chromium',
chromium_apply_config=['mb'],
gclient_config='chromium',
chromium_config_kwargs={
'BUILD_CONFIG': 'Release',
'TARGET_BITS': 64,
},
bot_type='tester',
simulation_platform='linux',
),
'Linux Builder':
bot_spec.BotSpec.create(
swarming_dimensions={'pool': 'luci.dummy.pool'},
chromium_config='chromium',
chromium_apply_config=['mb'],
gclient_config='chromium',
chromium_config_kwargs={
'BUILD_CONFIG': 'Release',
'TARGET_BITS': 64,
},
bot_type='builder',
simulation_platform='linux',
),
},
})
props_proto = InputProperties()
props_proto.skip_analyze = skip_analyze
props_proto.test_repeat_count = test_repeat_count
props_proto.target_builder.master = target_master
props_proto.target_builder.builder = target_builder
for t in compile_targets:
props_proto.compile_targets.append(t)
for k, v in tests.iteritems():
new_message = props_proto.tests[k]
for t in v:
new_message.names.append(t)
t = sum([
api.chromium.ci_build(
mastername=target_master,
builder=target_builder,
),
api.properties(props_proto),
api.chromium_tests.read_source_side_spec(*(spec or _default_spec)),
], api.empty_test_data())
if test_override_builders:
t += api.chromium_tests.builders(_default_builders)
return t
yield api.test(
'all_targets',
_common(api),
api.post_process(MustRun, 'compile'),
api.post_process(StepCommandContains, 'compile', ['blink_web_tests']),
api.post_process(StepCommandContains, 'compile', ['base_unittests']),
api.post_process(StatusSuccess),
api.post_process(DropExpectation),
)
yield api.test(
'specific_target',
_common(api, compile_targets=['base_unittests', 'missing_target']),
api.override_step_data('check_targets',
api.json.output({
'found': ['base_unittests']
})),
api.post_process(MustRun, 'compile'),
api.post_process(StepCommandContains, 'compile', ['base_unittests']),
api.post_process(_StepCommandNotContains, 'compile', 'missing_target'),
api.post_process(StatusSuccess),
api.post_process(DropExpectation),
)
yield api.test(
'compile_failure',
_common(api),
api.step_data('compile', retcode=1),
api.post_process(StatusFailure),
api.post_process(DropExpectation),
)
yield api.test(
'with_tests',
_common(
api,
skip_analyze=True,
tests={
'blink_web_tests': [
'fast/Test/One.html', 'fast/Test/Two.html', 'dummy/Three.js'
],
'base_unittests': []
}),
api.post_process(MustRun, 'compile'),
api.post_process(MustRun, 'test_pre_run.[trigger] blink_web_tests'),
api.post_process(
StepCommandContains, 'test_pre_run.[trigger] blink_web_tests', [
'--gtest_filter='
'fast/Test/One.html:fast/Test/Two.html:dummy/Three.js'
]),
api.post_process(MustRun, 'test_pre_run.[trigger] base_unittests'),
api.post_process(DoesNotRun, 'analyze'),
api.post_process(StatusSuccess),
api.post_process(DropExpectation),
)
yield api.test(
'with_tests_and_analyze',
_common(
api,
test_override_builders=True,
target_builder='Linux Tests',
tests={
'blink_web_tests': [
'fast/Test/One.html', 'fast/Test/Two.html', 'dummy/Three.js'
],
'base_unittests': [],
}),
api.override_step_data(
'analyze',
api.json.output({
'status': 'Found dependency',
'compile_targets': ['base_unittests'],
'test_targets': ['base_unittests'],
})),
api.post_process(MustRun, 'compile'),
api.post_process(DoesNotRun, 'test_pre_run.[trigger] blink_web_tests'),
api.post_process(MustRun, 'test_pre_run.[trigger] base_unittests'),
api.post_process(MustRun, 'analyze'),
api.post_process(StatusSuccess),
api.post_process(DropExpectation),
)
yield api.test(
'compile_skipped',
_common(
api,
tests={'checkperms': []},
spec=('chromium.linux', {
'Linux Builder': {
'scripts': [{
'name': 'checkperms',
'script': 'checkperms.py'
}]
}
}),
),
api.post_process(DoesNotRun, 'compile'),
api.post_process(MustRun, 'checkperms'),
api.post_process(StatusSuccess),
api.post_process(DropExpectation),
)
isolate_properties = InputProperties()
isolate_properties.isolate_targets.add(server='server1', hash='hash1')
isolate_properties.isolate_targets.add(server='server2', hash='hash2')
yield api.test('isolate_targets', api.properties(isolate_properties))