| # 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) |
| ) |