| # 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. |
| |
| import datetime |
| |
| from google.protobuf import timestamp_pb2 |
| |
| from PB.go.chromium.org.luci.buildbucket.proto import ( |
| build as build_pb2, |
| builder_common as builder_common_pb2, |
| common as common_pb2, |
| ) |
| from PB.go.chromium.org.luci.led.job import job as job_pb2 |
| from PB.recipe_modules.flutter.recipe_testing import options as options_pb2 |
| from recipe_engine import recipe_test_api |
| |
| ONE_DAY = int(datetime.timedelta(days=1).total_seconds()) |
| MAX_BUILD_AGE_SECONDS = int(datetime.timedelta(days=28).total_seconds()) |
| |
| |
| class RecipeTestingTestApi(recipe_test_api.RecipeTestApi): |
| def project( |
| self, |
| name="flutter", |
| include_unrestricted=True, |
| include_restricted=False, |
| cq_config_name="commit-queue.cfg", |
| excluded_buckets=(), |
| ): |
| return options_pb2.Project( |
| name=name, |
| include_unrestricted=include_unrestricted, |
| include_restricted=include_restricted, |
| cq_config_name=cq_config_name, |
| excluded_buckets=excluded_buckets, |
| ) |
| |
| def options(self, projects=(), use_buildbucket=False, **kwargs): |
| if not projects: |
| projects = [self.project()] |
| return self.m.properties( |
| recipe_testing_options=options_pb2.Options( |
| projects=list(projects), use_buildbucket=use_buildbucket, **kwargs |
| ) |
| ) |
| |
| def build_data( |
| self, |
| name, |
| recipe, |
| age_seconds=ONE_DAY, |
| cl_cached=False, |
| skip=False, |
| num_log_entries=1, |
| project="flutter", |
| bucket="try", |
| # used for both buildbucket build id and swarming task id. |
| fake_id=100, |
| using_led=True, |
| exe_cipd_version=None |
| ): |
| # This time is taken from the time recipe_engine module. I see no way |
| # of getting it programmatically. |
| curr_time = 1337000000 |
| end_time = curr_time - age_seconds |
| |
| orig_build = build_pb2.Build(id=fake_id, status=common_pb2.SUCCESS) |
| orig_build.end_time.seconds = end_time |
| orig_build.builder.project = project |
| orig_build.builder.bucket = bucket |
| orig_build.builder.builder = name |
| orig_build.input.properties["recipe"] = recipe |
| cl = orig_build.input.gerrit_changes.add() |
| cl.host = "flutter-review.googlesource.com" |
| cl.project = project |
| |
| if exe_cipd_version: |
| orig_build.input.properties["exe_cipd_version"] = exe_cipd_version |
| |
| result = self.m.buildbucket.simulated_search_results( |
| [orig_build], "get builders.{}.buildbucket.search".format(name) |
| ) |
| |
| if skip or age_seconds > MAX_BUILD_AGE_SECONDS: |
| return result |
| |
| job = job_pb2.Definition() |
| build = self.m.buildbucket.ci_build_message( |
| priority=34500, project=project, bucket=bucket, builder=name |
| ) |
| build.input.properties["recipe"] = recipe |
| |
| # Don't inject test data for led steps when not using led, i.e. using |
| # the Buildbucket scheduling codepath. |
| if not using_led: |
| return result |
| |
| # It's unrealistic for the get-build response to have a task ID set, |
| # but the only way of mocking the task ID returned by `led launch` is |
| # to set the task ID on the input to `led launch`, which, for recipe |
| # testing, is the `led get-build` response. |
| build.infra.swarming.task_id = str(fake_id) |
| job.buildbucket.bbagent_args.build.CopyFrom(build) |
| result += self.m.led.mock_get_build( |
| job, |
| fake_id, |
| ) |
| |
| if recipe != "recipes" and not cl_cached: |
| result += self.m.gitiles.log( |
| "get builders.{}.log {}".format(name, cl.project), |
| "A", |
| n=num_log_entries, |
| ) |
| |
| return result |
| |
| def no_build(self, name): |
| return self.m.buildbucket.simulated_search_results( |
| [], "get builders.{}.buildbucket.search".format(name) |
| ) |
| |
| def affected_recipes_data( |
| self, |
| affected_recipes, |
| recipe_files=None, |
| changed_files=None, |
| error=None, |
| invalid_recipes=(), |
| step_name="get_affected_recipes.recipes-analyze", |
| ): |
| if not recipe_files: |
| recipe_files = ["foo", "flutter.py", "recipes.py", "sdk.expected"] |
| res = self.step_data( |
| "get_affected_recipes.ls-recipes", |
| stdout=self.m.raw_io.output_text( |
| "".join("{}\n".format(x) for x in recipe_files) |
| ), |
| ) |
| |
| if not changed_files: |
| changed_files = [ |
| "recipes/flutter.py", |
| "recipes/foo", |
| "recipes/non_expected_json_file.json", |
| "recipe_modules/foo/examples/full.expected/bar.json", |
| "recipe_modules/foo/examples/full.py", |
| "recipe_modules/foo/test_api.py", |
| ] |
| res += self.m.git.get_changed_files( |
| "get_affected_recipes.git diff-tree", |
| changed_files, |
| ) |
| |
| output = { |
| "recipes": list(affected_recipes), |
| "error": error or "", |
| "invalidRecipes": list(invalid_recipes), |
| } |
| retcode = -1 if error else 0 |
| res += self.step_data(step_name, self.m.json.output(output), retcode=retcode) |
| |
| return res |
| |
| def task_result(self, task_id, name, failed=False): |
| return self.m.swarming.task_result( |
| id=task_id, |
| name="recipes-cq:%s" % name, |
| state=None if not name else self.m.swarming.TaskState.COMPLETED, |
| failure=failed, |
| ) |
| |
| def existing_green_tryjobs(self, tryjobs): |
| search_results = [] |
| for builder_name in tryjobs: |
| project, bucket, builder = builder_name.split("/") |
| search_results.append( |
| build_pb2.Build( |
| builder=builder_common_pb2.BuilderID( |
| project=project, |
| bucket=bucket, |
| builder=builder, |
| ), |
| create_time=timestamp_pb2.Timestamp(seconds=1527292217), |
| ) |
| ) |
| return self.m.buildbucket.simulated_search_results( |
| search_results, |
| step_name="get builders.get green tryjobs", |
| ) |