[fidldev] Add support for fidldoc

This adds support for `fidldev regen fidldoc`, and also automatically
regens fidldoc if either the FIDL files or fidldoc itself changes.

It also runs the fidldoc tests (without the REGENERATE_GOLDENS_FOLDER
var) when fidldoc or the fidlgen goldens change.

Test: python3 $FIDLMISC_DIR/fidldev/fidldev_test.py -b
Change-Id: I20c22979c7656508c39f0481b014e8a274abc870
Reviewed-on: https://fuchsia-review.googlesource.com/c/fidl-misc/+/405262
Reviewed-by: Mitchell Kember <mkember@google.com>
diff --git a/fidldev/fidldev_test.py b/fidldev/fidldev_test.py
index d8946dd..8a339c7 100644
--- a/fidldev/fidldev_test.py
+++ b/fidldev/fidldev_test.py
@@ -46,6 +46,7 @@
             util.BUILD_FIDLC,
             regen.path_to_regen_command(regen.FIDLC_REGEN), util.BUILD_FIDLGEN,
             regen.path_to_regen_command(regen.FIDLGEN_REGEN),
+            regen.FIDLDOC_REGEN,
             util.BUILD_FIDLGEN_DART,
             regen.path_to_regen_command(regen.FIDLGEN_DART_REGEN)
         ]
@@ -95,6 +96,7 @@
             regen.path_to_regen_command(regen.FIDLC_REGEN),
             util.BUILD_FIDLGEN,
             regen.path_to_regen_command(regen.FIDLGEN_REGEN),
+            regen.FIDLDOC_REGEN,
             util.BUILD_FIDLGEN_DART,
             regen.path_to_regen_command(regen.FIDLGEN_DART_REGEN),
             regen.path_to_regen_command(regen.GO_BINDINGS_REGEN),
@@ -123,6 +125,7 @@
             regen.path_to_regen_command(regen.FIDLC_REGEN),
             util.BUILD_FIDLGEN,
             regen.path_to_regen_command(regen.FIDLGEN_REGEN),
+            regen.FIDLDOC_REGEN,
             util.BUILD_FIDLGEN_DART,
             regen.path_to_regen_command(regen.FIDLGEN_DART_REGEN),
         ]
@@ -154,6 +157,16 @@
         ]
         self.assertListEqual(get_commands(mocks, command), expected)
 
+    def test_fidldoc_changed(self):
+        mocks = {
+            'get_changed_files':
+                itertools.repeat(
+                    ['tools/fidl/fidldoc/src/fidljson.rs'])
+        }
+        command = ['regen']
+        expected = [regen.FIDLDOC_REGEN]
+        self.assertListEqual(get_commands(mocks, command), expected)
+
     def test_fidlgen_dart_changed(self):
         mocks = {
             'get_changed_files':
@@ -173,6 +186,7 @@
             regen.path_to_regen_command(regen.FIDLC_REGEN),
             util.BUILD_FIDLGEN,
             regen.path_to_regen_command(regen.FIDLGEN_REGEN),
+            regen.FIDLDOC_REGEN,
             util.BUILD_FIDLGEN_DART,
             regen.path_to_regen_command(regen.FIDLGEN_DART_REGEN),
             regen.path_to_regen_command(regen.GO_BINDINGS_REGEN),
@@ -184,6 +198,7 @@
         expected = [
             regen.path_to_regen_command(regen.FIDLC_REGEN),
             regen.path_to_regen_command(regen.FIDLGEN_REGEN),
+            regen.FIDLDOC_REGEN,
             regen.path_to_regen_command(regen.FIDLGEN_DART_REGEN),
             regen.path_to_regen_command(regen.GO_BINDINGS_REGEN),
         ]
@@ -231,7 +246,7 @@
         }
         command = ['test', '--no-regen']
         actual = get_commands(mocks, command)
-        expected = set(util.FIDLGEN_TEST_TARGETS)
+        expected = set(util.FIDLGEN_TEST_TARGETS) | {util.FIDLDOC_DIR}
         self.assertEqual(len(actual), 1)
         self.assertTestsRun(actual[0], expected)
 
@@ -272,7 +287,7 @@
         }
         command = ['test', '--no-regen']
         actual = get_commands(mocks, command)
-        expected = set(util.FIDLGEN_TEST_TARGETS) | {util.HLCPP_TEST_TARGET}
+        expected = set(util.FIDLGEN_TEST_TARGETS) | {util.HLCPP_TEST_TARGET, util.FIDLDOC_DIR}
         self.assertEqual(len(actual), 1)
         self.assertTestsRun(actual[0], expected)
 
diff --git a/fidldev/regen.py b/fidldev/regen.py
index 41fcd2b..785ce2e 100644
--- a/fidldev/regen.py
+++ b/fidldev/regen.py
@@ -4,6 +4,7 @@
 
 FIDLC_REGEN = 'zircon/tools/fidl/testdata/regen.sh'
 FIDLGEN_REGEN = 'garnet/go/src/fidl/compiler/backend/typestest/regen.sh'
+FIDLDOC_REGEN = 'REGENERATE_GOLDENS_FOLDER=$FUCHSIA_DIR/tools/fidl/fidldoc/src/templates/markdown/testdata fx test host_x64/fidldoc_bin_test -- golden_test'
 FIDLGEN_DART_REGEN = 'topaz/bin/fidlgen_dart/regen.sh'
 GO_BINDINGS_REGEN = 'third_party/go/regen-fidl'
 
@@ -21,6 +22,10 @@
         path_to_regen_command(FIDLGEN_REGEN), dry_run, exit_on_failure=True)
 
 
+def regen_fidldoc_goldens(build_first, dry_run):
+    util.run(FIDLDOC_REGEN, dry_run, exit_on_failure=True)
+
+
 def regen_fidlgendart_goldens(build_first, dry_run):
     if build_first:
         util.run(util.BUILD_FIDLGEN_DART, dry_run, exit_on_failure=True)
@@ -50,7 +55,7 @@
 def is_go_bindings_changed():
     for path in util.get_changed_files():
         if path.startswith(
-                util.FIDLGEN_DIR) and path.endswith('.test.json.go.golden'):
+                util.FIDLGEN_GOLDENS_DIR) and path.endswith('.test.json.go.golden'):
             return True
     return False
 
@@ -58,6 +63,7 @@
 REGEN_TARGETS = [
     ('fidlc', regen_fidlc_goldens),
     ('fidlgen', regen_fidlgen_goldens),
+    ('fidldoc', regen_fidldoc_goldens),
     ('fidlgen_dart', regen_fidlgendart_goldens),
     ('go', regen_go_bindings),
 ]
@@ -72,6 +78,7 @@
 def regen_changed(changed_files, build_first, dry_run):
     regen_fidlc = False
     regen_fidlgen = False
+    regen_fidldoc = False
     regen_fidlgendart = False
     regen_go = False
     for file_ in changed_files:
@@ -79,6 +86,8 @@
             regen_fidlc = True
         if is_in_fidlgen_backend(file_):
             regen_fidlgen = True
+        if file_.startswith(util.FIDLDOC_DIR):
+            regen_fidldoc = True
         if file_.startswith(util.FIDLGEN_DART_DIR):
             regen_fidlgendart = True
         if file_.startswith(util.FIDLGEN_GO_DIR):
@@ -88,6 +97,7 @@
         regen_fidlc_goldens(build_first, dry_run)
         if dry_run or is_ir_changed():
             regen_fidlgen = True
+            regen_fidldoc = True
             regen_fidlgendart = True
 
     if regen_fidlgen:
@@ -95,6 +105,9 @@
         if dry_run or is_go_bindings_changed():
             regen_go = True
 
+    if regen_fidldoc:
+        regen_fidldoc_goldens(build_first, dry_run)
+
     if regen_fidlgendart:
         regen_fidlgendart_goldens(build_first, dry_run)
 
diff --git a/fidldev/test_.py b/fidldev/test_.py
index ad3c36c..7cbbcfb 100644
--- a/fidldev/test_.py
+++ b/fidldev/test_.py
@@ -21,7 +21,9 @@
 #   - PREDICATES is a list of predicates P such that P(file) returns true if the
 #     test group should run when |file| is changed
 #   - TARGETS is a list of test names as supported by `fx test`, e.g.
-#     fully-formed Fuchsia Package URLs, package names, or directories
+#     fully-formed Fuchsia Package URLs, package names, or directories. Standalone
+#     or non fx test commands can be provided, but they must be specified as manual
+#     (see is_manual_test)
 #   - BUILD COMMAND is any command that should be run prior to running the test
 #     group. It can be None if no build step is required, and is skipped if the
 #     --no-build flag is passed in. It currently needs to be a List - run_tests
@@ -40,6 +42,10 @@
             [startswith(p) for p in util.FIDLGEN_BACKEND_DIRS],
             util.FIDLGEN_TEST_TARGETS, None)),
     (
+        'fidldoc', (
+            [startswith(util.FIDLDOC_DIR), startswith(util.FIDLGEN_GOLDENS_DIR)],
+            [util.FIDLDOC_DIR], None)),
+    (
         'fidlgen_dart', (
             [startswith(util.FIDLGEN_DART_DIR)],
             [util.FIDLGEN_DART_TEST_TARGET], util.BUILD_FIDLGEN_DART)),
diff --git a/fidldev/util.py b/fidldev/util.py
index 27be481..a07e250 100644
--- a/fidldev/util.py
+++ b/fidldev/util.py
@@ -8,6 +8,8 @@
 
 FIDLC_DIR = 'zircon/tools/fidl'
 FIDLGEN_DIR = 'garnet/go/src/fidl'
+FIDLGEN_GOLDENS_DIR = 'garnet/go/src/fidl/compiler/backend/goldens'
+FIDLDOC_DIR = 'tools/fidl/fidldoc'
 FIDLGEN_DART_DIR = 'topaz/bin/fidlgen_dart'
 FIDLGEN_GO_DIR = 'tools/fidl/fidlgen_go'
 FIDLGEN_BACKEND_DIRS = [
@@ -55,11 +57,12 @@
     Run the given command, returning True if it completed successfuly. If
     dry_run is true, just prints rather than running. If exit_on_failure is
     true, exits instead of returning False.
+    WARNING: passing a str command will use shell=True
     """
     if dry_run:
         print('would run: {}'.format(command))
         return True
-    retcode = subprocess.call(command)
+    retcode = subprocess.call(command, shell=isinstance(command, str))
     success = retcode == 0
     if exit_on_failure and not success:
         print_err(