blob: 3039f3c044b74f93ca565da0fd59d5780e70a5e5 [file]
# Copyright 2022 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.
"""Orchestrator recipe.
Trigger the compilator to perform all check-out-related tasks and streams
its steps. Afterwards perform testing based on compilator properties.
The orchestrator is only used in a trybot setting.
"""
import json
from PB.go.chromium.org.luci.buildbucket.proto import build as build_pb2
from PB.go.chromium.org.luci.buildbucket.proto import common as common_pb
from PB.recipe_engine import result as result_pb2
from PB.recipe_modules.recipe_engine.led import properties as led_properties_pb
from recipe_engine.post_process import (
DoesNotRun,
DropExpectation,
MustRun,
SummaryMarkdown,
)
from recipe_engine.recipe_api import Property
from google.protobuf import json_format
from google.protobuf import struct_pb2
from dataclasses import dataclass
from recipe_engine.recipe_api import RecipeScriptApi
from recipe_engine.recipe_test_api import RecipeTestApi
from RECIPE_MODULES.build import isolate, v8_orchestrator, v8_tests
from RECIPE_MODULES.depot_tools import gitiles, tryserver
from RECIPE_MODULES.recipe_engine import (
buildbucket,
cipd,
file,
json as json_module,
led,
path,
properties,
runtime,
step,
swarming,
)
@dataclass
class DEPS(RecipeScriptApi):
buildbucket: buildbucket.API
cipd: cipd.API
file: file.API
gitiles: gitiles.API
isolate: isolate.API
json: json_module.API
led: led.API
path: path.API
properties: properties.API
runtime: runtime.API
step: step.API
swarming: swarming.API
tryserver: tryserver.API
v8_orchestrator: v8_orchestrator.API
v8_tests: v8_tests.API
@dataclass
class TEST_DEPS(RecipeTestApi):
buildbucket: buildbucket.TEST_API
json: json_module.TEST_API
properties: properties.TEST_API
step: step.TEST_API
swarming: swarming.TEST_API
v8_tests: v8_tests.TEST_API
PROPERTIES = {
# Name of the compilator trybot to use.
'compilator_name': Property(kind=str),
}
BUILD_CANCELED_SUMMARY = 'Build was canceled.'
BUILD_WRONGLY_CANCELED_SUMMARY = (
'Compilator was canceled before the parent orchestrator was canceled.'
)
EXONERATE_FLAKES_MAX = 3
def orchestrator_steps(api: DEPS, compilator_name):
v8 = api.v8_tests
def initialize_v8_testing():
# Initialize V8 testing.
v8.set_config('v8')
v8.read_cl_footer_flags()
v8.load_static_test_configs()
api.swarming.ensure_client()
v8.set_up_swarming()
comp_props, maybe_result = api.v8_orchestrator.orchestrated_compilation(
compilator_name, initialize_v8_testing
)
if maybe_result:
return maybe_result
# Initialize the test specs the compilator retrieved from the checkout.
tests = v8.extra_tests_from_properties(
{'parent_test_spec': dict(comp_props['parent_test_spec'])}
)
if not tests:
# Running a testing trybot that doesn't specify tests might point to
# a configuration error.
return result_pb2.RawResult(
status=common_pb.FAILURE, summary_markdown='No tests specified'
)
# Run tests and initialize other configs needed for testing. GN args
# from compilation to show on test failures and CAS digests of isolated
# build artifacts to set up swarming tasks.
v8.gn_args = list(comp_props['gn_args'])
v8.isolated_tests = dict(comp_props['swarm_hashes'])
test_results = v8.runtests(None, tests)
status = common_pb.SUCCESS
summary_markdown = None
if test_results.has_failures:
status = common_pb.FAILURE
summary_markdown = 'Failures in tryjob.'
elif len(test_results.flakes) > EXONERATE_FLAKES_MAX:
status = common_pb.FAILURE
summary_markdown = 'Too many flakes in tryjob.'
return result_pb2.RawResult(status=status, summary_markdown=summary_markdown)
def RunSteps(api: DEPS, compilator_name):
try:
return orchestrator_steps(api, compilator_name)
finally:
if api.runtime.in_global_shutdown:
# pylint: disable=lost-exception
# Cancellation can cause all sorts of spurious exceptions.
return result_pb2.RawResult(
status=common_pb.CANCELED, summary_markdown=BUILD_CANCELED_SUMMARY
)
def GenTests(api: TEST_DEPS):
def subbuild_data(output_properties, summary='', status=common_pb.SUCCESS):
output_properties = output_properties or {}
sub_build = build_pb2.Build(
id=54321,
status=status,
summary_markdown=summary,
output=dict(
properties=json_format.Parse(
api.json.dumps(output_properties), struct_pb2.Struct()
)
),
)
return api.step_data('compilator steps', api.step.sub_build(sub_build))
# Minimal v8-side test spec for simulating most recipe features.
test_spec = json.dumps(
{
'swarming_task_attrs': {
'priority': 25,
'expiration': 1200,
'hard_timeout': 3600,
},
"tests": [
{"name": "v8testing"},
{"name": "test262", "test_args": ["--extra-flags=--flag"]},
],
},
indent=2,
)
parent_test_spec = api.v8_tests.example_parent_test_spec_properties(
'v8_foobar_rel', test_spec
)
# Dummy CAS digest hashes.
buider_spec = parent_test_spec.get('parent_test_spec', {})
swarm_hashes = api.v8_tests._make_dummy_swarm_hashes(
test[0] for test in buider_spec.get('tests', [])
)
# Dummy GN args from compilation.
gn_args = ['use_foo = true', 'also_interesting = "absolutely"']
# Set up compilator build output.
output_properties = {
"compilator_properties": {
"swarm_hashes": swarm_hashes,
"gn_args": gn_args,
**parent_test_spec,
},
}
def test(name, *args, **kwargs):
return api.test(
name,
api.buildbucket.try_build(builder='v8_foobar_rel'),
api.properties(compilator_name='v8_foobar_compile_rel'),
*args,
**kwargs,
)
yield test(
'basic',
subbuild_data(output_properties),
api.step_data('Check', api.v8_tests.one_failure()),
api.post_process(MustRun, 'Check'),
api.post_process(MustRun, 'Test262'),
api.post_process(SummaryMarkdown, 'Failures in tryjob.'),
status='FAILURE',
)
yield test(
'too_many_flakes',
subbuild_data(output_properties),
api.step_data('Check', api.v8_tests.flakes(count=4)),
api.post_process(MustRun, 'Check'),
api.post_process(MustRun, 'Test262'),
api.post_process(SummaryMarkdown, 'Too many flakes in tryjob.'),
api.post_process(DropExpectation),
status='FAILURE',
)
yield test(
'missing_properties',
subbuild_data({}, 'Compile failed', common_pb.FAILURE),
api.post_process(DoesNotRun, 'Check'),
api.post_process(DoesNotRun, 'Test262'),
api.post_process(SummaryMarkdown, 'Compile failed'),
api.post_process(DropExpectation),
status='FAILURE',
)
yield test(
'no_subbuild',
api.post_process(SummaryMarkdown, 'sub_build missing from step'),
api.post_process(DropExpectation),
status='INFRA_FAILURE',
)
yield test(
'no_tests',
subbuild_data({'compilator_properties': {'parent_test_spec': {}}}),
api.post_process(DoesNotRun, 'Check'),
api.post_process(DoesNotRun, 'Test262'),
api.post_process(SummaryMarkdown, 'No tests specified'),
api.post_process(DropExpectation),
status='FAILURE',
)