[fidldev] Add --gtest_filter command for fidlc tests

This allows forwarding arguments to the fidl-compiler executable
like so:

  fidldev test fidlc --gtest_filter 'EnumsTests.*'

Also does some refactoring to simplify code

Change-Id: If32c96a0413722b2242676f481f60d99085e2545
Reviewed-on: https://fuchsia-review.googlesource.com/c/fidl-misc/+/442578
Reviewed-by: Mitchell Kember <mkember@google.com>
Reviewed-by: Pascal Perez <pascallouis@google.com>
diff --git a/fidldev/fidldev.py b/fidldev/fidldev.py
index 45c46d2..be3b659 100755
--- a/fidldev/fidldev.py
+++ b/fidldev/fidldev.py
@@ -45,7 +45,12 @@
 
 Pass flags to invocations of fx test:
 
-    fidldev test --fx-test-args "-v -o --dry"
+    fidldev test --fx-test-args="-v -o --dry"
+
+
+Pass a gtest_filter to fidlc test:
+
+    fidldev test --gtest_filter 'EnumsTests.*'
 
 """
 
@@ -65,19 +70,19 @@
                 'Error: unknown test targets {}'.format(unknown_args))
             print('use `fidldev test --help` to print list of valid targets')
             sys.exit(1)
-        success = test_.test_explicit(args.targets, not args.no_build,
-                                      args.dry_run, args.interactive,
-                                      args.fx_test_args)
+        tests = test_.test_explicit(args.targets)
     else:
         changed_files = util.get_changed_files()
         if not args.no_regen:
             regen.regen_changed(changed_files, not args.no_build, args.dry_run)
             changed_files = util.get_changed_files()
-        success = test_.test_changed(changed_files, not args.no_build,
-                                     args.dry_run, args.interactive,
-                                     args.fx_test_args)
+        tests = test_.test_changed(changed_files)
         if args.dry_run:
             print_dryrun_warning()
+
+    success = test_.run_tests(tests, not args.no_build, args.dry_run,
+                              args.interactive, args.fx_test_args,
+                              args.gtest_filter)
     if not success:
         sys.exit(1)
 
@@ -150,6 +155,10 @@
     "Extra flags and arguments to pass to any invocations of fx test. The flag value is passed verbatim. By default, only '-v' is used.",
     default='-v',
 )
+test_parser.add_argument(
+    "--gtest_filter",
+    help="Pass a gtest filter to fidlc tests"
+)
 
 regen_parser = subparsers.add_parser("regen", help="Run regen commands")
 regen_parser.set_defaults(func=regen_cmd)
diff --git a/fidldev/fidldev_test.py b/fidldev/fidldev_test.py
index de6bf0c..a1067fc 100644
--- a/fidldev/fidldev_test.py
+++ b/fidldev/fidldev_test.py
@@ -410,6 +410,30 @@
         self.assertEqual(len(actual), 1)
         self.assertTestsRun(actual[0], expected)
 
+    def test_extra_flags(self):
+        mocks = {
+            'get_changed_files': [[
+                'zircon/tools/fidl/lib/parser.cc',
+                'topaz/public/dart/fidl/lib/src/types.dart',
+            ]]
+        }
+        command = [
+            'test', '--no-build', '--no-regen', '--fx-test-args=-ov',
+            '--gtest_filter=\'EnumsTests.*\''
+        ]
+        actual = get_commands(mocks, command)
+
+        self.assertEqual(len(actual), 2)
+        self.assertEqual(
+            actual[0],
+            'fuchsia_dir/out/default/host_x64/fidl-compiler --gtest_filter=\'EnumsTests.*\''
+        )
+        self.assertEqual(actual[1][0], 'fx')
+        self.assertEqual(actual[1][1], 'test')
+        self.assertEqual(actual[1][2], '-ov')
+        self.assertEqual(actual[1][3], '--no-build')
+        self.assertEqual(actual[1][4], 'fidl_bindings_test')
+
     def assertTestsRun(self, raw_command, expected):
         self.assertEqual(raw_command[0], 'fx')
         self.assertEqual(raw_command[1], 'test')
diff --git a/fidldev/test_.py b/fidldev/test_.py
index dd0207d..8fe7995 100644
--- a/fidldev/test_.py
+++ b/fidldev/test_.py
@@ -117,31 +117,31 @@
 ]
 
 
-def test_explicit(targets, build_first, dry_run, interactive, fx_test_args):
-    """ Test an explicit set of test groups """
+def test_explicit(targets):
+    """ Return an explicit set of test groups """
     tests = []
     for name, test in TEST_GROUPS:
         if name in targets or 'all' in targets:
             tests.append(test)
-    return run_tests(tests, build_first, dry_run, interactive, fx_test_args)
+    return tests
 
 
-def test_changed(
-        changed_files, build_first, dry_run, interactive, fx_test_args):
-    """ Test relevant test groups given a set of changed files """
+def test_changed(changed_files):
+    """ Return relevant test groups given a set of changed files """
     tests = []
     for _, test in TEST_GROUPS:
         (predicates, _, _) = test
         for file_ in changed_files:
             if any(p(file_) for p in predicates):
                 tests.append(test)
-    return run_tests(tests, build_first, dry_run, interactive, fx_test_args)
+    return tests
 
 
-def run_tests(tests, build_first, dry_run, interactive, fx_test_args):
+def run_tests(tests, build_first, dry_run, interactive, fx_test_args,
+              gtest_filter):
     already_built = set()
     test_targets = set()
-    manual_tests = set()
+    test_fidlc = False
     for name, targets, build in tests:
         if build_first and build is not None and tuple(
                 build) not in already_built:
@@ -149,21 +149,25 @@
             util.run(build, dry_run, exit_on_failure=True)
 
         for target in targets:
-            if is_manual_test(target):
-                manual_tests.add(target)
+            if target == util.TEST_FIDLC:
+                test_fidlc = True
             else:
                 test_targets.add(target)
 
-    manual_tests = list(manual_tests)
     test_targets = list(test_targets)
     if interactive:
         print('all tests: ')
         pprint.pprint(manual_tests + test_targets)
-        manual_tests = interactive_filter(manual_tests)
+        if test_fidlc:
+            if input('run {}? (Y/n)'.format(util.TEST_FIDLC)) == 'n':
+                test_fidlc = False
         test_targets = interactive_filter(test_targets)
 
     success = True
-    for cmd in manual_tests:
+    if test_fidlc:
+        cmd = util.TEST_FIDLC
+        if gtest_filter:
+            cmd += f' --gtest_filter={gtest_filter}'
         success = success and util.run(cmd, dry_run)
         # print test line that can be copied into a commit message
         # the absolute FUCHSIA_DIR paths are stripped for readability and
@@ -192,15 +196,3 @@
             continue
         filtered.append(test)
     return filtered
-
-
-def is_manual_test(test):
-    """
-    Return whether this is meant to be called with fx test or used as a
-    standalone test command.
-    """
-    # currently fidlc is the only test that doesn't use fx test, since it
-    # uses some fidlc/fidl-compiler-test binary that is not built with the
-    # usual build commands (like fx build zircon/tools, fx ninja -C out/default
-    # host_x64/fidlc)
-    return test == util.TEST_FIDLC