blob: 49d8fc4d95d8bf266b63bdae688317fcaa474de8 [file] [log] [blame]
# Copyright 2021 The Fuchsia Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""A recipe for integration-testing the recipe_bootstrap tool.
The recipe executes the following steps:
1. Checks out the repo containing the recipe_bootstrap tool.
2. Builds recipe_bootstrap.
3. Invokes recipe_bootstrap locally, instructing it to run the child.py
recipe, which makes some assertions about the way that recipe_bootstrap
invoked it.
4. If the subbuild fails, exit with the same status (failure or infra failure).
5. In case of success, makes assertions about the resulting build.proto
emitted by recipe_bootstrap.
This differs a bit from how recipe_bootstrap will be run in production,
because here recipe_bootstrap is invoked by recipe engine code whereas in
production recipe_bootstrap is invoked as the top-level luciexe by bbagent
itself, and the two invoke luciexe executables slightly differently (most
notably, the recipe engine sets the --output flag).
"""
from PB.recipes.fuchsia.recipe_bootstrap_test.parent import InputProperties
from PB.go.chromium.org.luci.buildbucket.proto import common as common_pb2
from PB.go.chromium.org.luci.buildbucket.proto import build as build_pb2
DEPS = [
"fuchsia/buildbucket_util",
"fuchsia/git",
"fuchsia/go",
"recipe_engine/buildbucket",
"recipe_engine/context",
"recipe_engine/path",
"recipe_engine/properties",
"recipe_engine/proto",
"recipe_engine/python",
"recipe_engine/step",
]
PROPERTIES = InputProperties
def RunSteps(api, props):
checkout_dir = api.path["start_dir"].join("infra")
with api.step.nest("checkout"), api.context(infra_steps=True):
api.git.checkout_from_build_input(path=checkout_dir, repo=str(props.repo))
recipe_bootstrap = api.path.mkdtemp("build").join("recipe_bootstrap")
with api.context(cwd=checkout_dir):
api.go("build", "-o", recipe_bootstrap, props.go_package)
subbuild = build_pb2.Build(
builder=dict(
builder=props.child_builder,
bucket=api.buildbucket.build.builder.bucket,
project=api.buildbucket.build.builder.project,
)
)
subbuild.input.properties.update(
{"enable_property_versioning": True, "recipe_bootstrap_enabled": True}
)
# Log the input build.proto for debugging purposes.
step = api.step("construct child build.proto", None)
step.presentation.logs["child build.proto"] = api.proto.encode(
subbuild, "JSONPB", indent=2
).splitlines()
try:
subbuild = api.step.sub_build(
"recipe_bootstrap",
[recipe_bootstrap],
subbuild,
step_test_data=lambda: api.step.test_api.sub_build(
mock_output_build(output_properties={"output_property": "foo"})
),
).step.sub_build
except api.step.InfraFailure as e:
subbuild = api.step.active_result.step.sub_build
# If the child build completed with an infra failure status, then there
# was probably a true infra failure within the subbuild (e.g. checkout
# failure due to a Git server outage).
if subbuild and subbuild.status == common_pb2.INFRA_FAILURE:
summary = "child build had infra failure"
if subbuild.summary_markdown:
summary = "%s: %s" % (summary, subbuild.summary_markdown)
raise api.step.InfraFailure(summary)
# If no child build was retrieved, or if the child build *didn't* have
# an infra failure but processing the child build produced an infra
# failure, then the version of recipe_bootstrap that we're testing is
# probably buggy.
raise api.step.StepFailure(
"recipe_bootstrap failed to produce a valid output build.proto (retcode %d)"
% e.retcode
)
# Validate that recipe_bootstrap forwarded any output properties set by the
# child recipe.
if "output_property" not in subbuild.output.properties:
api.python.failing_step(
"checks failed",
"recipe_bootstrap didn't forward output properties from the subbuild",
)
def mock_output_build(status=common_pb2.SUCCESS, output_properties=None, **kwargs):
build = build_pb2.Build(status=status, **kwargs)
build.output.properties.update(output_properties or {})
return build
def GenTests(api):
def properties():
return api.properties(
repo="https://host.googlesource.com/infra",
child_builder="child-builder",
)
yield api.buildbucket_util.test("basic", tryjob=True) + properties()
yield (
api.buildbucket_util.test("subbuild_fails", tryjob=True, status="failure")
+ properties()
+ api.step_data(
"recipe_bootstrap",
api.step.sub_build(mock_output_build(status=common_pb2.FAILURE)),
)
)
yield (
api.buildbucket_util.test(
"subbuild_infra_fails", tryjob=True, status="infra_failure"
)
+ properties()
+ api.step_data(
"recipe_bootstrap",
api.step.sub_build(
mock_output_build(
status=common_pb2.INFRA_FAILURE, summary_markdown="checkout failed"
)
),
)
)
yield (
api.buildbucket_util.test(
"missing_output_property", tryjob=True, status="failure"
)
+ properties()
+ api.step_data(
"recipe_bootstrap",
api.step.sub_build(mock_output_build(output_properties={})),
)
)
yield (
api.buildbucket_util.test(
"recipe_bootstrap_broken", tryjob=True, status="failure"
)
+ properties()
# The retcode of a luciexe is ignored, what causes an exception is when
# the luciexe doesn't produce an output build (or the output build is
# malformed).
+ api.step_data("recipe_bootstrap", api.step.sub_build(None), retcode=2)
)