Introduce recipe for checking out luci-test CL

The luci-test recipe usese RunSteps to checkout the change during CQ
label process. GenTests is a test for the recipe and generates the
expect JSON file.

Next I will try to make it run the tests, and hook it up to the labels
in luci-test repo. After maybe similar experimenting with java/bazel,
I will do a similar recipe for `gerrit` repo and finally remove the
Jenkins labels/integrations and delete the luci-test repo.

Change-Id: I2d6501edac64b303d7b9a782e6c86278aca6b37f
diff --git a/infra/config/recipes.cfg b/infra/config/recipes.cfg
index 1ea176d..433c8d3 100644
--- a/infra/config/recipes.cfg
+++ b/infra/config/recipes.cfg
@@ -5,8 +5,13 @@
   "deps": {
     "recipe_engine": {
       "branch": "refs/heads/main",
-      "revision": "6a02d019c4b24e4412cc40003c4397320be41cae",
+      "revision": "a4e5d51c4351ab0674e264a8a360572286b04a6f",
       "url": "https://chromium.googlesource.com/infra/luci/recipes-py.git"
+    },
+    "depot_tools": {
+      "branch": "refs/heads/main",
+      "revision": "e1c8efebe0a3cce42ca46d6057b6d4bd909ad203",
+      "url": "https://chromium.googlesource.com/chromium/tools/depot_tools.git"
     }
   },
   "recipes_path": "recipes"
diff --git a/recipes/README.recipes.md b/recipes/README.recipes.md
index 40c7a41..4351976 100644
--- a/recipes/README.recipes.md
+++ b/recipes/README.recipes.md
@@ -4,6 +4,7 @@
 
 **[Recipes](#Recipes)**
   * [hello_world](#recipes-hello_world)
+  * [luci-test](#recipes-luci-test) (Python3 ✅)
 ## Recipes
 
 ### *recipes* / [hello\_world](/recipes/recipes/hello_world.py)
@@ -13,5 +14,17 @@
 PYTHON_VERSION_COMPATIBILITY: PY2
 
 — **def [RunSteps](/recipes/recipes/hello_world.py#11)(api):**
+### *recipes* / [luci-test](/recipes/recipes/luci-test.py)
 
-[recipe_engine/recipe_modules/step]: https://chromium.googlesource.com/infra/luci/recipes-py.git/+/6a02d019c4b24e4412cc40003c4397320be41cae/README.recipes.md#recipe_modules-step
+[DEPS](/recipes/recipes/luci-test.py#3): [depot\_tools/bot\_update][depot_tools/recipe_modules/bot_update], [depot\_tools/gclient][depot_tools/recipe_modules/gclient], [recipe\_engine/buildbucket][recipe_engine/recipe_modules/buildbucket], [recipe\_engine/context][recipe_engine/recipe_modules/context], [recipe\_engine/path][recipe_engine/recipe_modules/path]
+
+PYTHON_VERSION_COMPATIBILITY: PY3
+
+— **def [RunSteps](/recipes/recipes/luci-test.py#13)(api):**
+
+[depot_tools/recipe_modules/bot_update]: https://chromium.googlesource.com/chromium/tools/depot_tools.git/+/e1c8efebe0a3cce42ca46d6057b6d4bd909ad203/recipes/README.recipes.md#recipe_modules-bot_update
+[depot_tools/recipe_modules/gclient]: https://chromium.googlesource.com/chromium/tools/depot_tools.git/+/e1c8efebe0a3cce42ca46d6057b6d4bd909ad203/recipes/README.recipes.md#recipe_modules-gclient
+[recipe_engine/recipe_modules/buildbucket]: https://chromium.googlesource.com/infra/luci/recipes-py.git/+/a4e5d51c4351ab0674e264a8a360572286b04a6f/README.recipes.md#recipe_modules-buildbucket
+[recipe_engine/recipe_modules/context]: https://chromium.googlesource.com/infra/luci/recipes-py.git/+/a4e5d51c4351ab0674e264a8a360572286b04a6f/README.recipes.md#recipe_modules-context
+[recipe_engine/recipe_modules/path]: https://chromium.googlesource.com/infra/luci/recipes-py.git/+/a4e5d51c4351ab0674e264a8a360572286b04a6f/README.recipes.md#recipe_modules-path
+[recipe_engine/recipe_modules/step]: https://chromium.googlesource.com/infra/luci/recipes-py.git/+/a4e5d51c4351ab0674e264a8a360572286b04a6f/README.recipes.md#recipe_modules-step
diff --git a/recipes/recipes/luci-test.expected/basic.json b/recipes/recipes/luci-test.expected/basic.json
new file mode 100644
index 0000000..10ab1c7
--- /dev/null
+++ b/recipes/recipes/luci-test.expected/basic.json
@@ -0,0 +1,150 @@
+[
+  {
+    "cmd": [
+      "vpython3",
+      "RECIPE_REPO[depot_tools]/gerrit_client.py",
+      "changes",
+      "--host",
+      "https://gerrit-review.googlesource.com",
+      "--json_file",
+      "/path/to/tmp/json",
+      "--limit",
+      "1",
+      "-p",
+      "change=123456",
+      "-o",
+      "ALL_REVISIONS",
+      "-o",
+      "DOWNLOAD_COMMANDS"
+    ],
+    "cwd": "[CACHE]/builder",
+    "env": {
+      "PATH": "<PATH>:RECIPE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "gerrit:try"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "gerrit fetch current CL info",
+    "timeout": 60,
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@json.output@[@@@",
+      "@@@STEP_LOG_LINE@json.output@  {@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"branch\": \"main\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"owner\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@      \"name\": \"John Doe\"@@@",
+      "@@@STEP_LOG_LINE@json.output@    }, @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"revisions\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@      \"184ebe53805e102605d11f6b143486d15c23a09c\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@        \"_number\": \"7\", @@@",
+      "@@@STEP_LOG_LINE@json.output@        \"ref\": \"refs/changes/56/123456/7\"@@@",
+      "@@@STEP_LOG_LINE@json.output@      }@@@",
+      "@@@STEP_LOG_LINE@json.output@    }@@@",
+      "@@@STEP_LOG_LINE@json.output@  }@@@",
+      "@@@STEP_LOG_LINE@json.output@]@@@",
+      "@@@STEP_LOG_END@json.output@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython3",
+      "-u",
+      "RECIPE_MODULE[depot_tools::bot_update]/resources/bot_update.py",
+      "--spec-path",
+      "cache_dir = '[CACHE]/git'\nsolutions = [{'deps_file': '.DEPS.git', 'managed': True, 'name': 'src', 'url': 'https://gerrit.googlesource.com/luci-test'}]",
+      "--patch_root",
+      "src",
+      "--revision_mapping_file",
+      "{\"got_revision\": \"src\"}",
+      "--git-cache-dir",
+      "[CACHE]/git",
+      "--cleanup-dir",
+      "[CLEANUP]/bot_update",
+      "--output_json",
+      "/path/to/tmp/json",
+      "--patch_ref",
+      "https://gerrit.googlesource.com/luci-test@refs/heads/main:refs/changes/56/123456/7",
+      "--revision",
+      "src@refs/heads/main",
+      "--refs",
+      "refs/heads/main"
+    ],
+    "cwd": "[CACHE]/builder",
+    "env": {
+      "DEPOT_TOOLS_REPORT_BUILD": "gerrit/try/builder/8945511751514863184",
+      "GIT_HTTP_LOW_SPEED_LIMIT": "102400",
+      "GIT_HTTP_LOW_SPEED_TIME": "1800"
+    },
+    "env_suffixes": {
+      "DEPOT_TOOLS_UPDATE": [
+        "0"
+      ],
+      "PATH": [
+        "RECIPE_REPO[depot_tools]"
+      ]
+    },
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "gerrit:try"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "bot_update",
+    "~followup_annotations": [
+      "@@@STEP_TEXT@Some step text@@@",
+      "@@@STEP_LOG_LINE@json.output@{@@@",
+      "@@@STEP_LOG_LINE@json.output@  \"did_run\": true, @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"fixed_revisions\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"src\": \"HEAD\"@@@",
+      "@@@STEP_LOG_LINE@json.output@  }, @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"manifest\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"src\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@      \"repository\": \"https://fake.org/src.git\", @@@",
+      "@@@STEP_LOG_LINE@json.output@      \"revision\": \"f27fede2220bcd326aee3e86ddfd4ebd0fe58cb9\"@@@",
+      "@@@STEP_LOG_LINE@json.output@    }@@@",
+      "@@@STEP_LOG_LINE@json.output@  }, @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"patch_failure\": false, @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"patch_root\": \"src\", @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"properties\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"got_revision\": \"f27fede2220bcd326aee3e86ddfd4ebd0fe58cb9\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"got_revision_cp\": \"refs/heads/main@{#170242}\"@@@",
+      "@@@STEP_LOG_LINE@json.output@  }, @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"root\": \"src\", @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"source_manifest\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"directories\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@      \"src\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@        \"git_checkout\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@          \"repo_url\": \"https://fake.org/src.git\", @@@",
+      "@@@STEP_LOG_LINE@json.output@          \"revision\": \"f27fede2220bcd326aee3e86ddfd4ebd0fe58cb9\"@@@",
+      "@@@STEP_LOG_LINE@json.output@        }@@@",
+      "@@@STEP_LOG_LINE@json.output@      }@@@",
+      "@@@STEP_LOG_LINE@json.output@    }, @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"version\": 0@@@",
+      "@@@STEP_LOG_LINE@json.output@  }, @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"step_text\": \"Some step text\"@@@",
+      "@@@STEP_LOG_LINE@json.output@}@@@",
+      "@@@STEP_LOG_END@json.output@@@",
+      "@@@SET_BUILD_PROPERTY@got_revision@\"f27fede2220bcd326aee3e86ddfd4ebd0fe58cb9\"@@@",
+      "@@@SET_BUILD_PROPERTY@got_revision_cp@\"refs/heads/main@{#170242}\"@@@"
+    ]
+  },
+  {
+    "name": "$result"
+  }
+]
\ No newline at end of file
diff --git a/recipes/recipes/luci-test.py b/recipes/recipes/luci-test.py
new file mode 100644
index 0000000..73a6c09
--- /dev/null
+++ b/recipes/recipes/luci-test.py
@@ -0,0 +1,57 @@
+PYTHON_VERSION_COMPATIBILITY = "PY3"
+
+DEPS = [
+  'depot_tools/bot_update',
+  'depot_tools/gclient',
+  'recipe_engine/buildbucket',
+  'recipe_engine/context',
+  'recipe_engine/path',
+]
+
+# Check out the change and run the tests to verify the change as part of Change
+# Verification (ie. CQ label).
+def RunSteps(api):
+  cl = api.buildbucket.build.input.gerrit_changes[0]
+
+  gs_suffix = '-review.googlesource.com'
+  host = cl.host
+  if host.endswith(gs_suffix):
+    host = '%s.googlesource.com' % host[:-len(gs_suffix)]
+
+  gclient_config = api.gclient.make_config()
+  s = gclient_config.solutions.add()
+  s.url = 'https://%s/%s' % (host, cl.project)
+  # name is the subfolder under api.path['cache'].join('builder') the
+  # repo will be checked out at. For simplicity, I've picked `src`.
+  #
+  # Note that gclient (and by extension, bot_update), allow for complicated
+  # multi-repo layouts and dependency inclusion (via "DEPS" files). There are
+  # other repo management tools and techniques available, but this one is what
+  # chrome currently uses and is tuned to have reasonable performance by default
+  # via caches.
+  s.name = 'src'
+  gclient_config.got_revision_mapping[s.name] = 'got_revision'
+
+  with api.context(cwd=api.path['cache'].join('builder')):
+    update_result = api.bot_update.ensure_checkout(
+        gclient_config=gclient_config)
+
+  # At this point the code for the Gerrit CL is checked out at
+  # `api.path['cache'].join('builder')`, which by default is preserved locally
+  # on the bot machine and re-used between different builds for the same
+  # builder.
+
+  # TODO(frankborden): perform $ npm run test
+
+# Test the recipe and generate the expect json file.
+def GenTests(api):
+  yield api.test(
+    'basic',
+    # These are just to make the JSON expectation file data look closer to
+    # reality. Project and git_repo will be filled in "for real" by the LUCI
+    # Change Verifier service when it creates your build.
+    api.buildbucket.try_build(
+      project="gerrit",
+      git_repo="https://gerrit.googlesource.com/luci-test",
+    ),
+  )