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