blob: 84d95c2f0e5d7073edaee051c46cb82a65d94ff8 [file] [log] [blame]
# Copyright 2022 The Pigweed Authors
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may not
# use this file except in compliance with the License. You may obtain a copy of
# the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations under
# the License.
"""Recipe for launching sequences of builders."""
from __future__ import annotations
import re
from typing import TYPE_CHECKING
from PB.go.chromium.org.luci.buildbucket.proto import common as common_pb2
import PB.recipes.pigweed.pipeline as pipeline_pb2
from PB.recipe_engine import result
from recipe_engine import post_process
if TYPE_CHECKING: # pragma: no cover
from typing import Any, Generator
from recipe_engine import config_types, recipe_api, recipe_test_api
DEPS = [
'fuchsia/subbuild',
'recipe_engine/properties',
'recipe_engine/step',
]
PROPERTIES = pipeline_pb2.InputProperties
def RunSteps(
api: recipe_api.RecipeScriptApi,
props: pipeline_pb2.InputProperties,
):
"""Launch sequences of builders."""
build_ids: list[int] = []
for i, round_property in enumerate(props.rounds):
with api.step.nest(f'round {i}') as pres:
builders: list[str] = []
for builder_property in round_property.builders:
# TODO: b/245788264 - Support non-current project.
assert not builder_property.project
# TODO: b/245788264 - Support non-current bucket.
assert not builder_property.bucket
builders.append(builder_property.builder)
extra_props: dict[str, dict[str, Any]] = {
'$pigweed/pipeline': {
'inside_a_pipeline': True,
'round': i,
'builds_from_previous_iteration': [
str(x) for x in build_ids
],
},
}
launched_builds: list[int] = api.subbuild.launch(
builders, pres, extra_properties=extra_props
)
for builder, build in launched_builds.items():
pres.links[builder] = build.url
build_ids: list[int] = [
x.build_id for x in launched_builds.values()
]
collected_builds: dict[str, api.subbuild.SubbuildResult] = (
api.subbuild.collect(build_ids)
)
failures: list[str] = []
for build in collected_builds.values():
with api.step.nest(build.builder) as sub_pres:
if build.build_proto.status != common_pb2.SUCCESS:
sub_pres.status = 'FAILURE'
failures.append(build.builder)
if failures:
return result.RawResult(
summary_markdown=f'Failed builds: {", ".join(failures)}',
status=common_pb2.FAILURE,
)
def GenTests(api) -> Generator[recipe_test_api.TestData, None, None]:
"""Create tests."""
def builder(name):
match = re.match(
r'(?:(?:(?P<project>[^/]+)/)?(?P<bucket>[^/]+)/)?(?P<name>[^/]+)$',
name,
)
assert match
return pipeline_pb2.Builder(
project=match.group('project') or '',
bucket=match.group('bucket') or '',
builder=match.group('name'),
)
def round(*builders, timeout_sec=0):
return pipeline_pb2.Round(
builders=list(builders),
timeout_sec=timeout_sec,
)
def properties(*rounds):
return api.properties(pipeline_pb2.InputProperties(rounds=list(rounds)))
def result_ci(name, id, status='SUCCESS', **kwargs):
return api.subbuild.ci_build_message(
builder=name,
build_id=id,
status=status,
**kwargs,
)
def assert_step_success(round, name):
return api.post_process(
post_process.StepSuccess, f'round {round}.{name}'
)
def assert_step_failure(round, name):
return api.post_process(
post_process.StepFailure, f'round {round}.{name}'
)
def assert_num_rounds(round):
return api.post_process(
post_process.MustRun, f'round {round-1}'
) + api.post_process(post_process.DoesNotRun, f'round {round}')
def drop():
return api.post_process(post_process.DropExpectation)
yield api.test(
'one_builder',
properties(round(builder('a'))),
api.subbuild.child_build_steps(
[result_ci('a', 1000)],
launch_step='round 0',
collect_step='round 0',
),
assert_step_success(0, 'a'),
assert_num_rounds(1),
)
yield api.test(
'two_builders',
properties(
round(builder('a'), builder('b')),
),
api.subbuild.child_build_steps(
[result_ci('a', 1000), result_ci('b', 1001)],
launch_step='round 0',
collect_step='round 0',
),
assert_step_success(0, 'a'),
assert_step_success(0, 'b'),
assert_num_rounds(1),
drop(),
)
yield api.test(
'two_rounds',
properties(
round(builder('a')),
round(builder('b')),
),
api.subbuild.child_build_steps(
[result_ci('a', 1000)],
launch_step='round 0',
collect_step='round 0',
),
api.subbuild.child_build_steps(
[result_ci('b', 2000)],
launch_step='round 1',
collect_step='round 1',
),
assert_step_success(0, 'a'),
assert_step_success(1, 'b'),
assert_num_rounds(2),
drop(),
)
yield api.test(
'first_round_failure',
properties(
round(builder('a'), builder('b')),
round(builder('c')),
),
api.subbuild.child_build_steps(
[result_ci('a', 1000), result_ci('b', 1001, status='FAILURE')],
launch_step='round 0',
collect_step='round 0',
),
assert_step_success(0, 'a'),
assert_step_failure(0, 'b'),
assert_num_rounds(1),
drop(),
status='FAILURE',
)
yield api.test(
'second_round_failure',
properties(
round(builder('a'), builder('b')),
round(builder('c')),
round(builder('d')),
),
api.subbuild.child_build_steps(
[result_ci('a', 1000), result_ci('b', 1001)],
launch_step='round 0',
collect_step='round 0',
),
api.subbuild.child_build_steps(
[result_ci('c', 2000, 'FAILURE')],
launch_step='round 1',
collect_step='round 1',
),
assert_step_success(0, 'a'),
assert_step_success(0, 'b'),
assert_step_failure(1, 'c'),
assert_num_rounds(2),
drop(),
status='FAILURE',
)