Update 'last green build' method to ensure the branch is the same as the current branch the recipe is being run on

Bug: https://github.com/flutter/flutter/issues/120135
Change-Id: I1a9c9bd63d969254d74ac44f21c1bab5c206a05e
Reviewed-on: https://flutter-review.googlesource.com/c/recipes/+/39181
Reviewed-by: Godofredo Contreras <godofredoc@google.com>
Commit-Queue: Drew Roen <drewroen@google.com>
diff --git a/recipe_modules/gerrit_util/__init__.py b/recipe_modules/gerrit_util/__init__.py
new file mode 100644
index 0000000..0d19893
--- /dev/null
+++ b/recipe_modules/gerrit_util/__init__.py
@@ -0,0 +1,3 @@
+DEPS = [
+    'depot_tools/gerrit'
+]
diff --git a/recipe_modules/gerrit_util/api.py b/recipe_modules/gerrit_util/api.py
new file mode 100644
index 0000000..b7287ac
--- /dev/null
+++ b/recipe_modules/gerrit_util/api.py
@@ -0,0 +1,24 @@
+# Copyright 2020 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.
+
+from recipe_engine import recipe_api
+
+
+class GerritUtilApi(recipe_api.RecipeApi):
+  """Provides utilities to work with gerrit."""
+
+  def get_gerrit_cl_details(self, host, cl_number):
+    """Collects and returns details about a gerrit CL
+
+    Args:
+      host: The host url of the CL ('eg: flutter-review.googlesource.com')
+      cl_number: The CL number of the requested CL
+    """
+    cl_information = self.m.gerrit.call_raw_api(
+          'https://%s' % host,
+          '/changes/%s' % cl_number,
+          accept_statuses=[200],
+          name='get cl info %s' % cl_number)
+
+    return cl_information
diff --git a/recipe_modules/gerrit_util/examples/full.expected/basic.json b/recipe_modules/gerrit_util/examples/full.expected/basic.json
new file mode 100644
index 0000000..8b35a2f
--- /dev/null
+++ b/recipe_modules/gerrit_util/examples/full.expected/basic.json
@@ -0,0 +1,34 @@
+[
+  {
+    "cmd": [
+      "vpython3",
+      "RECIPE_REPO[depot_tools]/gerrit_client.py",
+      "rawapi",
+      "--host",
+      "https://flutter.googlesource.com",
+      "--path",
+      "/changes/12345",
+      "--json_file",
+      "/path/to/tmp/json",
+      "--accept_status",
+      "200"
+    ],
+    "env": {
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "gerrit get cl info 12345",
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@json.output@[@@@",
+      "@@@STEP_LOG_LINE@json.output@  [@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"branch\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"main\"@@@",
+      "@@@STEP_LOG_LINE@json.output@  ]@@@",
+      "@@@STEP_LOG_LINE@json.output@]@@@",
+      "@@@STEP_LOG_END@json.output@@@"
+    ]
+  },
+  {
+    "name": "$result"
+  }
+]
\ No newline at end of file
diff --git a/recipe_modules/gerrit_util/examples/full.py b/recipe_modules/gerrit_util/examples/full.py
new file mode 100644
index 0000000..7631117
--- /dev/null
+++ b/recipe_modules/gerrit_util/examples/full.py
@@ -0,0 +1,24 @@
+# Copyright 2020 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.
+
+DEPS = [
+    'flutter/gerrit_util',
+    'recipe_engine/json'
+]
+
+
+def RunSteps(api):
+    api.gerrit_util.get_gerrit_cl_details(
+        'flutter.googlesource.com', '12345'
+    )
+
+
+def GenTests(api):
+    yield api.test(
+        'basic',
+        api.step_data(
+            'gerrit get cl info 12345',
+            api.json.output([('branch', 'main')])
+        )
+    )
diff --git a/recipe_modules/recipe_testing/__init__.py b/recipe_modules/recipe_testing/__init__.py
index 4710706..7fe54ac 100644
--- a/recipe_modules/recipe_testing/__init__.py
+++ b/recipe_modules/recipe_testing/__init__.py
@@ -3,6 +3,7 @@
 # found in the LICENSE file.
 
 DEPS = [
+    "flutter/gerrit_util",
     "fuchsia/buildbucket_util",
     "fuchsia/commit_queue",
     "fuchsia/gerrit",
diff --git a/recipe_modules/recipe_testing/api.py b/recipe_modules/recipe_testing/api.py
index 81ececa..ab19523 100644
--- a/recipe_modules/recipe_testing/api.py
+++ b/recipe_modules/recipe_testing/api.py
@@ -19,6 +19,14 @@
 from recipe_engine import recipe_api
 from RECIPE_MODULES.fuchsia.swarming_retry import api as swarming_retry_api
 
+# The maximum amount of builds to go back through in buildbucket to find
+# a run that matches the current branch
+MAX_BUILD_RESULTS = 25
+
+# The default branch for a build if there is no branch set in a build found
+# from searching buildbucket
+DEFAULT_BRANCH = 'main'
+
 
 class Build(swarming_retry_api.LedTask):
     # This warning is spurious because LedTask defines _led_data.
@@ -168,23 +176,51 @@
             # in recipes CQ.
             return {r for r in affected_recipes if not r.startswith("contrib/")}
 
-    def _get_last_green_build(self, builder):
+    def _get_last_green_build(self, builder, cl_branch='main'):
         """Returns the build proto for a builder's most recent successful build.
 
         If no build younger than `self.max_build_age_seconds` is found, returns
-        None.
+        None. Also ensures that was returned build was run on the same branch
+        the current recipe is running on.
 
         Args:
           builder: builder protobuf object
         """
-        project, bucket, builder = builder.split("/")
-        # "infra" is not returned by default, so we have to specify it.
-        build = self.m.buildbucket_util.last_build(
-            project, bucket, builder, status=common_pb2.SUCCESS
-        )
-        if not build:
+        project, bucket, builder = builder.split('/')
+        predicate = builds_service_pb2.BuildPredicate()
+        predicate.builder.project = project
+        predicate.builder.bucket = bucket
+        predicate.builder.builder = builder
+        predicate.status = common_pb2.SUCCESS
+
+        builds = self.m.buildbucket.search(predicate, limit=MAX_BUILD_RESULTS)
+
+        def built_on_branch(build, branch):
+            """Return True if build was built on the provided branch."""
+            current_branch = \
+                'refs/heads/%s' % (branch or DEFAULT_BRANCH)
+            build_properties = \
+                build.input.properties
+            if 'exe_cipd_version' in build_properties.keys():
+                build_branch = build_properties['exe_cipd_version']
+            else:
+                build_branch = 'refs/heads/%s' % DEFAULT_BRANCH
+            # Some recipes do not specify the branch, so in the case where the
+            # branch is None, ensure a match can still be found.
+            return build_branch in [current_branch, None]
+
+        builds_with_current_branch = \
+            list(filter(
+                lambda build: built_on_branch(build, cl_branch), builds
+            ))
+
+        builds_with_current_branch.sort(
+            reverse=True, key=lambda build: build.start_time.seconds)
+
+        if not builds_with_current_branch:
             return None
 
+        build = builds_with_current_branch[0]
         age_seconds = self.m.time.time() - build.end_time.seconds
         if age_seconds > self.max_build_age_seconds:
             return None
@@ -366,10 +402,21 @@
         if not affected_recipes:
             return
 
+        cl_branch = self._get_current_merging_branch()
+
         if config.use_buildbucket:
-            self._run_buildbucket_tests(selftest_builder, builders, affected_recipes)
+            self._run_buildbucket_tests(
+                selftest_builder,
+                builders,
+                affected_recipes,
+                cl_branch)
         else:
-            self._run_led_tests(recipes_path, selftest_cl, builders, affected_recipes)
+            self._run_led_tests(
+                recipes_path,
+                selftest_cl,
+                builders,
+                affected_recipes,
+                cl_branch)
 
     def _is_build_affected(self, orig_build, affected_recipes, presentation):
         if not orig_build:
@@ -406,7 +453,12 @@
         )
         return {self.m.buildbucket_util.full_builder_name(b.builder) for b in builds}
 
-    def _run_buildbucket_tests(self, selftest_builder, builders, affected_recipes):
+    def _run_buildbucket_tests(
+            self,
+            selftest_builder,
+            builders,
+            affected_recipes,
+            cl_branch):
         affected_builders = []
         recipes_is_affected = False
 
@@ -415,7 +467,7 @@
             builders = [b for b in builders if b not in green_tryjobs]
             for builder in builders:
                 with self.m.step.nest(builder) as presentation:
-                    orig_build = self._get_last_green_build(builder)
+                    orig_build = self._get_last_green_build(builder, cl_branch)
                     if self._is_build_affected(
                         orig_build, affected_recipes, presentation
                     ):
@@ -455,14 +507,20 @@
                 raise_on_failure=True,
             )
 
-    def _run_led_tests(self, recipes_path, selftest_cl, builders, affected_recipes):
+    def _run_led_tests(
+            self,
+            recipes_path,
+            selftest_cl,
+            builders,
+            affected_recipes,
+            cl_branch):
         builds = []
         with self.m.step.nest("get builders") as nest, self.m.context(
             cwd=recipes_path, infra_steps=True
         ):
             for builder in builders:
                 with self.m.step.nest(builder) as presentation:
-                    orig_build = self._get_last_green_build(builder)
+                    orig_build = self._get_last_green_build(builder, cl_branch)
                     if self._is_build_affected(
                         orig_build, affected_recipes, presentation
                     ):
@@ -490,3 +548,23 @@
                 props, preserving_proto_field_name=True
             )
         }
+
+    def _get_current_merging_branch(self):
+        """Returns the branch that the current CL is being merged into.
+
+        If the buildset is not available in the recipe, then DEFAULT_BRANCH is
+        used.
+        """
+        tags = self.m.buildbucket.build.tags
+        buildset_tag = list(
+            filter(lambda tag: tag.key=='buildset', tags)
+        )
+        buildset_property = self.m.properties.get('buildset')
+        if not buildset_tag and not buildset_property:
+            return DEFAULT_BRANCH
+        else:
+            buildset = buildset_tag[0].value if buildset_tag else buildset_property
+            host, cl_number = buildset.split('/')[2:4]
+            cl_information = \
+                self.m.gerrit_util.get_gerrit_cl_details(host, cl_number)
+            return cl_information.get('branch')
diff --git a/recipe_modules/recipe_testing/test_api.py b/recipe_modules/recipe_testing/test_api.py
index 79bfd72..6e72cf2 100644
--- a/recipe_modules/recipe_testing/test_api.py
+++ b/recipe_modules/recipe_testing/test_api.py
@@ -58,6 +58,7 @@
         # 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.
@@ -74,6 +75,9 @@
         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)
         )
diff --git a/recipe_modules/recipe_testing/tests/full.expected/fuchsia_recipe_unaffected.json b/recipe_modules/recipe_testing/tests/full.expected/fuchsia_recipe_unaffected.json
index d6efcbb..5c17824 100644
--- a/recipe_modules/recipe_testing/tests/full.expected/fuchsia_recipe_unaffected.json
+++ b/recipe_modules/recipe_testing/tests/full.expected/fuchsia_recipe_unaffected.json
@@ -264,7 +264,7 @@
       "-json",
       "-nopage",
       "-n",
-      "1",
+      "25",
       "-fields",
       "builder,create_time,created_by,critical,end_time,id,infra,input,number,output,start_time,status,update_time",
       "-predicate",
@@ -311,7 +311,7 @@
       "-json",
       "-nopage",
       "-n",
-      "1",
+      "25",
       "-fields",
       "builder,create_time,created_by,critical,end_time,id,infra,input,number,output,start_time,status,update_time",
       "-predicate",
@@ -358,7 +358,7 @@
       "-json",
       "-nopage",
       "-n",
-      "1",
+      "25",
       "-fields",
       "builder,create_time,created_by,critical,end_time,id,infra,input,number,output,start_time,status,update_time",
       "-predicate",
diff --git a/recipe_modules/recipe_testing/tests/full.expected/no_build_old_build_ignored_build.json b/recipe_modules/recipe_testing/tests/full.expected/no_build_old_build_ignored_build.json
index e809b71..07d8471 100644
--- a/recipe_modules/recipe_testing/tests/full.expected/no_build_old_build_ignored_build.json
+++ b/recipe_modules/recipe_testing/tests/full.expected/no_build_old_build_ignored_build.json
@@ -264,7 +264,7 @@
       "-json",
       "-nopage",
       "-n",
-      "1",
+      "25",
       "-fields",
       "builder,create_time,created_by,critical,end_time,id,infra,input,number,output,start_time,status,update_time",
       "-predicate",
@@ -309,7 +309,7 @@
       "-json",
       "-nopage",
       "-n",
-      "1",
+      "25",
       "-fields",
       "builder,create_time,created_by,critical,end_time,id,infra,input,number,output,start_time,status,update_time",
       "-predicate",
@@ -352,7 +352,7 @@
       "-json",
       "-nopage",
       "-n",
-      "1",
+      "25",
       "-fields",
       "builder,create_time,created_by,critical,end_time,id,infra,input,number,output,start_time,status,update_time",
       "-predicate",
diff --git a/recipe_modules/recipe_testing/tests/full.expected/no_latest_cl.json b/recipe_modules/recipe_testing/tests/full.expected/no_latest_cl.json
index e200938..7d08918 100644
--- a/recipe_modules/recipe_testing/tests/full.expected/no_latest_cl.json
+++ b/recipe_modules/recipe_testing/tests/full.expected/no_latest_cl.json
@@ -262,7 +262,7 @@
       "-json",
       "-nopage",
       "-n",
-      "1",
+      "25",
       "-fields",
       "builder,create_time,created_by,critical,end_time,id,infra,input,number,output,start_time,status,update_time",
       "-predicate",
@@ -307,7 +307,7 @@
       "-json",
       "-nopage",
       "-n",
-      "1",
+      "25",
       "-fields",
       "builder,create_time,created_by,critical,end_time,id,infra,input,number,output,start_time,status,update_time",
       "-predicate",
@@ -910,7 +910,7 @@
       "-json",
       "-nopage",
       "-n",
-      "1",
+      "25",
       "-fields",
       "builder,create_time,created_by,critical,end_time,id,infra,input,number,output,start_time,status,update_time",
       "-predicate",
diff --git a/recipe_modules/recipe_testing/tests/full.expected/recipe_proto.json b/recipe_modules/recipe_testing/tests/full.expected/recipe_proto.json
index 82a1dc8..9741de0 100644
--- a/recipe_modules/recipe_testing/tests/full.expected/recipe_proto.json
+++ b/recipe_modules/recipe_testing/tests/full.expected/recipe_proto.json
@@ -258,7 +258,7 @@
       "-json",
       "-nopage",
       "-n",
-      "1",
+      "25",
       "-fields",
       "builder,create_time,created_by,critical,end_time,id,infra,input,number,output,start_time,status,update_time",
       "-predicate",
@@ -301,7 +301,7 @@
       "-json",
       "-nopage",
       "-n",
-      "1",
+      "25",
       "-fields",
       "builder,create_time,created_by,critical,end_time,id,infra,input,number,output,start_time,status,update_time",
       "-predicate",
@@ -344,7 +344,7 @@
       "-json",
       "-nopage",
       "-n",
-      "1",
+      "25",
       "-fields",
       "builder,create_time,created_by,critical,end_time,id,infra,input,number,output,start_time,status,update_time",
       "-predicate",
diff --git a/recipe_modules/recipe_testing/tests/full.expected/recipes.json b/recipe_modules/recipe_testing/tests/full.expected/recipes.json
index 0d8696f..1b8fdd5 100644
--- a/recipe_modules/recipe_testing/tests/full.expected/recipes.json
+++ b/recipe_modules/recipe_testing/tests/full.expected/recipes.json
@@ -262,7 +262,7 @@
       "-json",
       "-nopage",
       "-n",
-      "1",
+      "25",
       "-fields",
       "builder,create_time,created_by,critical,end_time,id,infra,input,number,output,start_time,status,update_time",
       "-predicate",
diff --git a/recipe_modules/recipe_testing/tests/full.expected/recipes_with_buildbucket.json b/recipe_modules/recipe_testing/tests/full.expected/recipes_with_buildbucket.json
index 6fab6a3..31028b1 100644
--- a/recipe_modules/recipe_testing/tests/full.expected/recipes_with_buildbucket.json
+++ b/recipe_modules/recipe_testing/tests/full.expected/recipes_with_buildbucket.json
@@ -293,7 +293,7 @@
       "-json",
       "-nopage",
       "-n",
-      "1",
+      "25",
       "-fields",
       "builder,create_time,created_by,critical,end_time,id,infra,input,number,output,start_time,status,update_time",
       "-predicate",
diff --git a/recipe_modules/recipe_testing/tests/full.expected/two_pass_one_skip.json b/recipe_modules/recipe_testing/tests/full.expected/two_pass_one_skip.json
index 7aed25d..eb42aaa 100644
--- a/recipe_modules/recipe_testing/tests/full.expected/two_pass_one_skip.json
+++ b/recipe_modules/recipe_testing/tests/full.expected/two_pass_one_skip.json
@@ -264,7 +264,7 @@
       "-json",
       "-nopage",
       "-n",
-      "1",
+      "25",
       "-fields",
       "builder,create_time,created_by,critical,end_time,id,infra,input,number,output,start_time,status,update_time",
       "-predicate",
@@ -311,7 +311,7 @@
       "-json",
       "-nopage",
       "-n",
-      "1",
+      "25",
       "-fields",
       "builder,create_time,created_by,critical,end_time,id,infra,input,number,output,start_time,status,update_time",
       "-predicate",
@@ -1125,7 +1125,7 @@
       "-json",
       "-nopage",
       "-n",
-      "1",
+      "25",
       "-fields",
       "builder,create_time,created_by,critical,end_time,id,infra,input,number,output,start_time,status,update_time",
       "-predicate",
diff --git a/recipe_modules/recipe_testing/tests/full.expected/with_buildbucket.json b/recipe_modules/recipe_testing/tests/full.expected/with_buildbucket.json
index 11c49d9..05d58d1 100644
--- a/recipe_modules/recipe_testing/tests/full.expected/with_buildbucket.json
+++ b/recipe_modules/recipe_testing/tests/full.expected/with_buildbucket.json
@@ -239,6 +239,42 @@
     ]
   },
   {
+    "cmd": [
+      "vpython3",
+      "RECIPE_REPO[depot_tools]/gerrit_client.py",
+      "rawapi",
+      "--host",
+      "https://flutter-review.googlesource.com",
+      "--path",
+      "/changes/12345",
+      "--json_file",
+      "/path/to/tmp/json",
+      "--accept_status",
+      "200"
+    ],
+    "env": {
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "fuchsia:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "gerrit get cl info 12345",
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@json.output@{}@@@",
+      "@@@STEP_LOG_END@json.output@@@"
+    ]
+  },
+  {
     "cmd": [],
     "name": "get builders"
   },
@@ -297,7 +333,7 @@
       "-json",
       "-nopage",
       "-n",
-      "1",
+      "25",
       "-fields",
       "builder,create_time,created_by,critical,end_time,id,infra,input,number,output,start_time,status,update_time",
       "-predicate",
@@ -319,7 +355,7 @@
     "name": "get builders.fuchsia/try/cobalt-x64-linux.buildbucket.search",
     "~followup_annotations": [
       "@@@STEP_NEST_LEVEL@2@@@",
-      "@@@STEP_LOG_LINE@raw_io.output_text@{\"builder\": {\"bucket\": \"try\", \"builder\": \"fuchsia/try/cobalt-x64-linux\", \"project\": \"flutter\"}, \"endTime\": \"2012-05-13T12:53:20Z\", \"id\": \"100\", \"input\": {\"gerritChanges\": [{\"host\": \"flutter-review.googlesource.com\", \"project\": \"flutter\"}], \"properties\": {\"recipe\": \"cobalt\"}}, \"status\": \"SUCCESS\"}@@@",
+      "@@@STEP_LOG_LINE@raw_io.output_text@{\"builder\": {\"bucket\": \"try\", \"builder\": \"fuchsia/try/cobalt-x64-linux\", \"project\": \"flutter\"}, \"endTime\": \"2012-05-13T12:53:20Z\", \"id\": \"100\", \"input\": {\"gerritChanges\": [{\"host\": \"flutter-review.googlesource.com\", \"project\": \"flutter\"}], \"properties\": {\"exe_cipd_version\": \"refs/heads/main\", \"recipe\": \"cobalt\"}}, \"status\": \"SUCCESS\"}@@@",
       "@@@STEP_LOG_END@raw_io.output_text@@@",
       "@@@STEP_LINK@100@https://cr-buildbucket.appspot.com/build/100@@@"
     ]
@@ -343,7 +379,7 @@
       "-json",
       "-nopage",
       "-n",
-      "1",
+      "25",
       "-fields",
       "builder,create_time,created_by,critical,end_time,id,infra,input,number,output,start_time,status,update_time",
       "-predicate",
@@ -365,7 +401,7 @@
     "name": "get builders.fuchsia/try/core.arm64-debug.buildbucket.search",
     "~followup_annotations": [
       "@@@STEP_NEST_LEVEL@2@@@",
-      "@@@STEP_LOG_LINE@raw_io.output_text@{\"builder\": {\"bucket\": \"try\", \"builder\": \"fuchsia/try/core.arm64-debug\", \"project\": \"flutter\"}, \"endTime\": \"2012-05-13T12:53:20Z\", \"id\": \"200\", \"input\": {\"gerritChanges\": [{\"host\": \"flutter-review.googlesource.com\", \"project\": \"flutter\"}], \"properties\": {\"recipe\": \"fuchsia\"}}, \"status\": \"SUCCESS\"}@@@",
+      "@@@STEP_LOG_LINE@raw_io.output_text@{\"builder\": {\"bucket\": \"try\", \"builder\": \"fuchsia/try/core.arm64-debug\", \"project\": \"flutter\"}, \"endTime\": \"2012-05-13T12:53:20Z\", \"id\": \"200\", \"input\": {\"gerritChanges\": [{\"host\": \"flutter-review.googlesource.com\", \"project\": \"flutter\"}], \"properties\": {\"exe_cipd_version\": \"refs/heads/main\", \"recipe\": \"fuchsia\"}}, \"status\": \"SUCCESS\"}@@@",
       "@@@STEP_LOG_END@raw_io.output_text@@@",
       "@@@STEP_LINK@200@https://cr-buildbucket.appspot.com/build/200@@@"
     ]
@@ -389,7 +425,7 @@
       "-json",
       "-nopage",
       "-n",
-      "1",
+      "25",
       "-fields",
       "builder,create_time,created_by,critical,end_time,id,infra,input,number,output,start_time,status,update_time",
       "-predicate",
@@ -411,7 +447,7 @@
     "name": "get builders.fuchsia/try/core.x64-debug.buildbucket.search",
     "~followup_annotations": [
       "@@@STEP_NEST_LEVEL@2@@@",
-      "@@@STEP_LOG_LINE@raw_io.output_text@{\"builder\": {\"bucket\": \"try\", \"builder\": \"fuchsia/try/core.x64-debug\", \"project\": \"flutter\"}, \"endTime\": \"2012-05-13T12:53:20Z\", \"id\": \"100\", \"input\": {\"gerritChanges\": [{\"host\": \"flutter-review.googlesource.com\", \"project\": \"flutter\"}], \"properties\": {\"recipe\": \"fuchsia\"}}, \"status\": \"SUCCESS\"}@@@",
+      "@@@STEP_LOG_LINE@raw_io.output_text@{\"builder\": {\"bucket\": \"try\", \"builder\": \"fuchsia/try/core.x64-debug\", \"project\": \"flutter\"}, \"endTime\": \"2012-05-13T12:53:20Z\", \"id\": \"100\", \"input\": {\"gerritChanges\": [{\"host\": \"flutter-review.googlesource.com\", \"project\": \"flutter\"}], \"properties\": {\"exe_cipd_version\": \"refs/heads/main\", \"recipe\": \"fuchsia\"}}, \"status\": \"SUCCESS\"}@@@",
       "@@@STEP_LOG_END@raw_io.output_text@@@",
       "@@@STEP_LINK@100@https://cr-buildbucket.appspot.com/build/100@@@"
     ]
diff --git a/recipe_modules/recipe_testing/tests/full.py b/recipe_modules/recipe_testing/tests/full.py
index ad2fcbe..29d85a1 100644
--- a/recipe_modules/recipe_testing/tests/full.py
+++ b/recipe_modules/recipe_testing/tests/full.py
@@ -13,6 +13,7 @@
     "fuchsia/commit_queue",
     "flutter/recipe_testing",
     "fuchsia/swarming_retry",
+    "recipe_engine/json",
     "recipe_engine/path",
     "recipe_engine/properties",
 ]
@@ -153,7 +154,11 @@
         + api.commit_queue.test_data(project)
         + test.affected_recipes_data(["fuchsia"])
         + test.build_data(
-            "fuchsia/try/cobalt-x64-linux", "cobalt", skip=True, using_led=False
+            "fuchsia/try/cobalt-x64-linux",
+            "cobalt",
+            skip=True,
+            using_led=False,
+            exe_cipd_version="refs/heads/main"
         )
         + test.build_data(
             "fuchsia/try/core.x64-debug",
@@ -161,12 +166,14 @@
             cl_cached=True,
             fake_id=100,
             using_led=False,
+            exe_cipd_version="refs/heads/main"
         )
         + test.build_data(
             "fuchsia/try/core.arm64-debug",
             "fuchsia",
             fake_id=200,
             using_led=False,
+            exe_cipd_version="refs/heads/main"
         )
         + test.existing_green_tryjobs(["fuchsia/try/core.arm64-release"])
         # This line only affects coverage. It's sufficiently tested in other
@@ -175,6 +182,13 @@
             use_buildbucket=True,
             projects=(api.recipe_testing.project(),),
         )
+        + api.step_data(
+            "gerrit get cl info 12345",
+            api.json.output({})
+        )
+        + api.properties(
+            buildset='patch/gerrit/flutter-review.googlesource.com/12345/1'
+        )
     )
 
     yield (
diff --git a/recipes/recipes.expected/ci.json b/recipes/recipes.expected/ci.json
index 08df3e1..aff0907 100644
--- a/recipes/recipes.expected/ci.json
+++ b/recipes/recipes.expected/ci.json
@@ -1679,7 +1679,7 @@
       "-json",
       "-nopage",
       "-n",
-      "1",
+      "25",
       "-fields",
       "builder,create_time,created_by,critical,end_time,id,infra,input,number,output,start_time,status,update_time",
       "-predicate",
@@ -1726,7 +1726,7 @@
       "-json",
       "-nopage",
       "-n",
-      "1",
+      "25",
       "-fields",
       "builder,create_time,created_by,critical,end_time,id,infra,input,number,output,start_time,status,update_time",
       "-predicate",
@@ -1773,7 +1773,7 @@
       "-json",
       "-nopage",
       "-n",
-      "1",
+      "25",
       "-fields",
       "builder,create_time,created_by,critical,end_time,id,infra,input,number,output,start_time,status,update_time",
       "-predicate",
diff --git a/recipes/recipes.expected/cq_try.json b/recipes/recipes.expected/cq_try.json
index 08df3e1..aff0907 100644
--- a/recipes/recipes.expected/cq_try.json
+++ b/recipes/recipes.expected/cq_try.json
@@ -1679,7 +1679,7 @@
       "-json",
       "-nopage",
       "-n",
-      "1",
+      "25",
       "-fields",
       "builder,create_time,created_by,critical,end_time,id,infra,input,number,output,start_time,status,update_time",
       "-predicate",
@@ -1726,7 +1726,7 @@
       "-json",
       "-nopage",
       "-n",
-      "1",
+      "25",
       "-fields",
       "builder,create_time,created_by,critical,end_time,id,infra,input,number,output,start_time,status,update_time",
       "-predicate",
@@ -1773,7 +1773,7 @@
       "-json",
       "-nopage",
       "-n",
-      "1",
+      "25",
       "-fields",
       "builder,create_time,created_by,critical,end_time,id,infra,input,number,output,start_time,status,update_time",
       "-predicate",