[rust] Add `rust_unit_tests_group(...)` gni template.

This CL adds a `rust_unit_tests_group(...)` gni template that can
generate a Chromium-bot-friendly script that can run _all_ Rust unit
tests inside the transitive `deps` of the group.

Manual testing:

1. Checking that the tests can be run locally:
    $ ninja -C out/rust build/rust/tests
    $ out/rust/bin/run_native_rust_unittests__from__build_rust_tests
    ... the tests execute successfully ...

2. Checking that the tests can be run on the bots in a follow-up CL:
   https://crrev.com/c/3322199/6

3. Checking that the runtime_deps are reasonable:
    $ gn clean out/rust
    $ gn desc out/rust \
        build/rust/tests:native_rust_unittests__from__build_rust_tests \
        runtime_deps
    bin/run_native_rust_unittests__from__build_rust_tests
    ../../testing/scripts/rust/main_program.py
    ../../testing/scripts/rust/test_filtering.py
    ../../testing/scripts/rust/test_results.py
    ../../testing/scripts/rust/exe_util.py
    ../../testing/scripts/rust/rust_main_program.py
    bin/run_test_mixed_component_rs_unittests
    ./test_mixed_component_rs_unittests
    bin/run_test_mixed_source_set_rs_unittests
    ./test_mixed_source_set_rs_unittests
    bin/run_test_rust_source_set_unittests
    ./test_rust_source_set_unittests
    bin/run_foo_tests
    ./foo_tests
    bin/run_test_rust_unittests
    ./test_rust_unittests
    bin/run_test_variable_source_set_rs_unittests
    ./test_variable_source_set_rs_unittests

Bug: 1260120
Change-Id: I19a15581ced0da85c6ec50a95016f2a547a43600
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3327603
Reviewed-by: Adrian Taylor <adetaylor@google.com>
Commit-Queue: Ɓukasz Anforowicz <lukasza@chromium.org>
Cr-Commit-Position: refs/heads/main@{#953019}
NOKEYCHECK=True
GitOrigin-RevId: 30500c5d5657387d3b4849d349037116c987e7ee
diff --git a/generate_bash_script.py b/generate_bash_script.py
index 86bd564..06414f5 100755
--- a/generate_bash_script.py
+++ b/generate_bash_script.py
@@ -32,19 +32,42 @@
                         metavar='PATH',
                         required=True)
 
-    parser.add_argument('--rust-test-executable',
-                        action='append',
+    parser.add_argument('--rust-test-executables',
                         dest='rust_test_executables',
-                        default=[],
-                        help='One or more executables to wrap. ' \
-                             '(basename - no extension or directory)',
-                        metavar='BASENAME',
+                        help='File listing one or more executables to wrap. ' \
+                             '(basenames - no .exe extension or directory)',
+                        metavar='FILEPATH',
                         required=True)
 
     return parser.parse_args(args=args)
 
 
-def _generate_script(args):
+def _find_test_executables(args):
+    exes = set()
+    input_filepath = args.rust_test_executables
+    with open(input_filepath) as f:
+        for line in f:
+            exe_name = line.strip()
+            # TODO(https://crbug.com/1271215): Append ".exe" extension when
+            # *targeting* Windows.  (The "targeting" part means that we can't
+            # just detect whether the build is *hosted* on Windows.)
+            if exe_name in exes:
+                raise ValueError("Duplicate entry ('{}') in {}".format(
+                    exe_name, input_filepath))
+            exes.add(exe_name)
+    if not exes:
+        raise ValueError("Unexpectedly empty file: {}".format(input_filepath))
+    exes = sorted(exes)  # For stable results in unit tests.
+    return exes
+
+
+def _validate_if_test_executables_exist(exes):
+    for exe in exes:
+        if not os.path.isfile(exe):
+            raise ValueError("File not found: {}".format(exe_path))
+
+
+def _generate_script(args, should_validate_if_exes_exist=True):
     res = '#!/bin/bash\n'
 
     script_dir = os.path.dirname(args.script_path)
@@ -59,8 +82,12 @@
     lib_dir = os.path.normpath(lib_dir)
     res += 'LIB_DIR="$SCRIPT_DIR/{}"\n'.format(lib_dir)
 
+    exes = _find_test_executables(args)
+    if should_validate_if_exes_exist:
+        _validate_if_test_executables_exist(exes)
+
     res += 'env vpython3 "$LIB_DIR/rust_main_program.py" \\\n'
-    for exe in args.rust_test_executables:
+    for exe in exes:
         res += '    "--rust-test-executable=$EXE_DIR/{}" \\\n'.format(exe)
     res += '    "$@"'
 
diff --git a/generate_bash_script_unittests.py b/generate_bash_script_unittests.py
index 89b8119..a81a9fc 100755
--- a/generate_bash_script_unittests.py
+++ b/generate_bash_script_unittests.py
@@ -5,22 +5,24 @@
 # found in the LICENSE file.
 
 import os
+from pyfakefs import fake_filesystem_unittest
+import tempfile
 import unittest
 
 from generate_bash_script import _parse_args
 from generate_bash_script import _generate_script
 
 
-class Tests(unittest.TestCase):
+class Tests(fake_filesystem_unittest.TestCase):
     def test_parse_args(self):
         raw_args = [
             '--script-path=./bin/run_foobar', '--exe-dir=.',
-            '--rust-test-executable=foo', '--rust-test-executable=bar'
+            '--rust-test-executables=metadata.json'
         ]
         parsed_args = _parse_args(raw_args)
         self.assertEqual('./bin/run_foobar', parsed_args.script_path)
         self.assertEqual('.', parsed_args.exe_dir)
-        self.assertEqual(['foo', 'bar'], parsed_args.rust_test_executables)
+        self.assertEqual('metadata.json', parsed_args.rust_test_executables)
 
     def test_generate_script(self):
         lib_dir = os.path.dirname(__file__)
@@ -28,17 +30,29 @@
         args = type('', (), {})()
         args.script_path = os.path.join(out_dir, 'bin/run_foo_bar')
         args.exe_dir = out_dir
-        args.rust_test_executables = ['foo', 'bar']
 
-        actual = _generate_script(args)
+        # pylint: disable=unexpected-keyword-arg
+        with tempfile.NamedTemporaryFile(delete=False,
+                                         mode='w',
+                                         encoding='utf-8') as f:
+            filepath = f.name
+            f.write("foo\n")
+            f.write("bar\n")
+        try:
+            args.rust_test_executables = filepath
+            actual = _generate_script(args,
+                                      should_validate_if_exes_exist=False)
+        finally:
+            os.remove(filepath)
+
         expected = '''
 #!/bin/bash
 SCRIPT_DIR=`dirname $0`
 EXE_DIR="$SCRIPT_DIR/.."
 LIB_DIR="$SCRIPT_DIR/../../../testing/scripts/rust"
 env vpython3 "$LIB_DIR/rust_main_program.py" \\
-    "--rust-test-executable=$EXE_DIR/foo" \\
     "--rust-test-executable=$EXE_DIR/bar" \\
+    "--rust-test-executable=$EXE_DIR/foo" \\
     "$@"
 '''.strip()
 
diff --git a/rust_main_program.py b/rust_main_program.py
index ba57b9d..556d797 100644
--- a/rust_main_program.py
+++ b/rust_main_program.py
@@ -124,6 +124,11 @@
         Returns:
             A list of test_results.TestResult objects.
         """
+        list_of_tests_to_run = _get_exe_specific_tests(
+            self._name_of_test_executable, list_of_tests_to_run)
+        if not list_of_tests_to_run:
+            return []
+
         # TODO(lukasza): Avoid passing all test names on the cmdline (might
         # require adding support to Rust test executables for reading cmdline
         # args from a file).
@@ -134,10 +139,14 @@
             self._path_to_test_executable, '--test', '--format=pretty',
             '--exact'
         ]
-        list_of_tests_to_run = _get_exe_specific_tests(
-            self._name_of_test_executable, list_of_tests_to_run)
         args.extend(list_of_tests_to_run)
+
+        print("Running tests from {}...".format(self._name_of_test_executable))
         output = exe_util.run_and_tee_output(args)
+        print("Running tests from {}... DONE.".format(
+            self._name_of_test_executable))
+        print()
+
         return _scrape_test_results(output, self._name_of_test_executable,
                                     list_of_tests_to_run)
 
@@ -152,7 +161,7 @@
                         action='append',
                         dest='rust_test_executables',
                         default=[],
-                        help='Paths to one or more executables to wrap.',
+                        help=argparse.SUPPRESS,
                         metavar='FILEPATH',
                         required=True)
 
diff --git a/test_filtering.py b/test_filtering.py
index 2e10c21..a386dd0 100644
--- a/test_filtering.py
+++ b/test_filtering.py
@@ -212,7 +212,8 @@
                   'a prefix plus a "*" on the end (to form a glob). If the ' \
                   'string has a "-" at the front, the test (or glob of ' \
                   'tests) will be skipped, not run.'
-    argparse_parser.add_argument('--isolated-script-test-filter',
+    argparse_parser.add_argument('--test-filter',
+                                 '--isolated-script-test-filter',
                                  action='append',
                                  default=[],
                                  dest='test_filters',
@@ -221,7 +222,8 @@
     file_help = 'Path to a file with test filters in Chromium Test List ' \
                 'Format. See also //testing/buildbot/filters/README.md and ' \
                 'bit.ly/chromium-test-list-format'
-    argparse_parser.add_argument('--isolated-script-test-filter-file',
+    argparse_parser.add_argument('--test-filter-file',
+                                 '--isolated-script-test-filter-file',
                                  action='append',
                                  default=[],
                                  dest='test_filter_files',