Support --tast-var in cros_run_tests

This CL supports specifying runtime variables to invoke Tast tests on
DUTs. An immediate use case is to support running Lacros tast tests
against a freshly built lacros-chrome by Chromium CI instead of always
downloading a stale one from gcs bucket.

BUG=chromium:1158590
TEST=unit tests.

Change-Id: I580434535915c4966949e09b39134ef6f2430f88
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/chromite/+/2605229
Tested-by: Yuke Liao <liaoyuke@chromium.org>
Commit-Queue: Yuke Liao <liaoyuke@chromium.org>
Reviewed-by: Ben Pastene <bpastene@chromium.org>
diff --git a/lib/cros_test.py b/lib/cros_test.py
index 1c13328..5b17ae2 100644
--- a/lib/cros_test.py
+++ b/lib/cros_test.py
@@ -55,6 +55,7 @@
 
     self.autotest = opts.autotest
     self.tast = opts.tast
+    self.tast_vars = opts.tast_vars
     self.results_dir = opts.results_dir
     self.test_that_args = opts.test_that_args
     self.test_timeout = opts.test_timeout
@@ -367,6 +368,8 @@
       if need_chroot:
         results_dir = path_util.ToChrootPath(self.results_dir)
       cmd += ['-resultsdir', results_dir]
+    if self.tast_vars:
+      cmd += ['-var=%s' % v for v in self.tast_vars]
     if self._device.ssh_port:
       cmd += ['%s:%d' % (self._device.device, self._device.ssh_port)]
     else:
@@ -545,6 +548,9 @@
   parser.add_argument('--tast', nargs='+',
                       help='Tast test pattern to run, passed to tast. '
                       'See go/tast-running for patterns.')
+  parser.add_argument('--tast-var', dest='tast_vars', action='append',
+                      help='Runtime variables for Tast tests, and the format '
+                      'are expected to be "key=value" pairs.')
   parser.add_argument('--chrome-test', action='store_true', default=False,
                       help='Run chrome test on device. The first arg in the '
                       'remote command should be the test binary name, such as '
@@ -624,6 +630,9 @@
     if not os.path.isdir(opts.build_dir):
       parser.error('%s is not a directory.' % opts.build_dir)
 
+  if opts.tast_vars and not opts.tast:
+    parser.error('--tast-var is only applicable to Tast tests.')
+
   if opts.results_src:
     for src in opts.results_src:
       if not os.path.isabs(src):
diff --git a/lib/cros_test_unittest.py b/lib/cros_test_unittest.py
index 93b305b..6fb24bc 100644
--- a/lib/cros_test_unittest.py
+++ b/lib/cros_test_unittest.py
@@ -375,6 +375,17 @@
         '(("dep:chrome" || "dep:android") && !flaky && !disabled)'
     ])
 
+  def testTastTestWithVars(self):
+    """Verify running tast tests with vars specified."""
+    self._tester.tast = ['ui.ChromeLogin']
+    self._tester.tast_vars = ['key=value']
+    self._tester.Run()
+    self.assertCommandContains([
+        'tast', 'run', '-build=false', '-waituntilready',
+        '-extrauseflags=tast_vm', '-var=key=value', 'localhost:9222',
+        'ui.ChromeLogin'
+    ])
+
   @mock.patch('chromite.lib.cros_build_lib.IsInsideChroot')
   def testTastTestWithOtherArgs(self, check_inside_chroot_mock):
     """Verify running a single tast test with various arguments."""
@@ -628,3 +639,10 @@
 
     # Parser error when a non-existent file is passed to --files.
     self.CheckParserError(['--files', 'fake/file'], 'does not exist')
+
+  def testParserVars(self):
+    """Verify we get parser errors when specifying vars with non-tast tests."""
+    self.CheckParserError([
+        '--tast-var', 'key=value', '--chrome-test', '--',
+        './out_amd64-generic/Release/base_unittests'
+    ], '--tast-var is only applicable to Tast tests.')