[autotest] add ability for test_that to run jobs in lab

This CL adds the ability for test_that to run jobs in the lab. It
accomplishes this thusly:

1) A new optional |suite_args| argument to afe.create_suite_job, whose
value gets injected into the suite control file in much the same way as
job args are injected into job control files.

2) Plumbing to thread #1 through from run_suite.py.

3) A new suite control file, control.test_that_wrapper, that is
responsible for kicking of test_that-initiated suites. It expects to see
a suite_args string giving the command line that test_that was run with,
and re-uses test_that's argument parser to figure out which tests (and
therefore which suite predicates) are needed.

4) A small refactor to dynamic_suite, so that it uses suite's
create_from_predicates rather than create_from_name.

5) A new optional |predicate| argument to dynamic_suite's
reimage_and_run, which if specified causes dynamic suite to use this
job-matching predicate rather than its default suite-name predicate.

6) Code path in test_that to pass :lab: runs on to run_suite.py.

So, when a developer runs, at their desk:
$ test_that -b board -i image :lab: dummy_Pass
Then test_that will, kick off a test_that_wrapper suite in the lab, using
run_suite, passing along test_that's args into the suite control file so
that it can figure out which tests to run:
$ run_suite -b board -i image -s test_that_wrapper --pool try-bot \
--suite_args="-b board -i image :lab: dummy_Pass"

This CL also fixes a bug in dynamic_suite/tools.py in which strings that
contained quotes were being incorrectly injected into control files in
inject_var.

CQ-DEPEND=CL:I4b1483b977d9032c7adf8117cf32e83c65eed007
BUG=chromium:262984
TEST=Unit tests pass.
Ran some test_that jobs against my local autotest instance, using
a trybot image that was build with this CL (so that
control.test_that_wrapper would be available).
$ test_that -b lumpy -i ${image} :lab: dummy_Pass
$ test_that -b lumpy -i ${image} :lab: e:login_.*
$ test_that -b lumpy -i ${image} :lab: suite:smoke dummy_Fail
DEPLOY=apache

Change-Id: Ifa73d7de7aac9c6efebd5f559708623804ad3691
Reviewed-on: https://chromium-review.googlesource.com/63378
Reviewed-by: Aviv Keshet <akeshet@chromium.org>
Tested-by: Aviv Keshet <akeshet@chromium.org>
Commit-Queue: Aviv Keshet <akeshet@chromium.org>
diff --git a/frontend/afe/site_rpc_interface.py b/frontend/afe/site_rpc_interface.py
index 84b191a..f536c75 100644
--- a/frontend/afe/site_rpc_interface.py
+++ b/frontend/afe/site_rpc_interface.py
@@ -81,7 +81,8 @@
 
 def create_suite_job(suite_name, board, build, pool, check_hosts=True,
                      num=None, file_bugs=False, timeout=24,
-                     priority=priorities.Priority.DEFAULT):
+                     priority=priorities.Priority.DEFAULT,
+                     suite_args=None):
     """
     Create a job to run a test suite on the given device with the given image.
 
@@ -99,6 +100,9 @@
     @param file_bugs: File a bug on each test failure in this suite.
     @param timeout: The max lifetime of this suite, in hours.
     @param priority: Integer denoting priority. Higher is more important.
+    @param suite_args: Optional arguments which will be parsed by the suite
+                       control file. Used by control.test_that_wrapper to
+                       determine which tests to run.
 
     @raises ControlFileNotFound: if a unique suite control file doesn't exist.
     @raises NoControlFileList: if we can't list the control files at all.
@@ -144,7 +148,10 @@
                    'file_bugs': file_bugs,
                    'timeout': timeout,
                    'devserver_url': ds.url(),
-                   'priority': priority}
+                   'priority': priority,
+                   'suite_args' : suite_args
+                   }
+
     control_file = tools.inject_vars(inject_dict, control_file_in)
 
     return _rpc_utils().create_job_common('%s-%s' % (build, suite_name),