[affected_tests] look for paths of host tests

The ninja dry run output doesn't reliably contain stamp files of the
form that the script is looking for. The exact pattern of what to look
for depends on the specific GN templates used to define the test, but
all the examples I've looked at include the path to the test executable,
so search for this.

Bug: 66869
Change-Id: Ib084de8dc65efb672b392762da552a6a1b1b5855
Reviewed-on: https://fuchsia-review.googlesource.com/c/infra/recipes/+/471557
Fuchsia-Auto-Submit: Gary Miguel <garymm@google.com>
Reviewed-by: Shai Barack <shayba@google.com>
Reviewed-by: Ina Huh <ihuh@google.com>
Commit-Queue: Auto-Submit <auto-submit@fuchsia-infra.iam.gserviceaccount.com>
diff --git a/recipe_modules/build/resources/affected_tests.py b/recipe_modules/build/resources/affected_tests.py
index f702b4f..7eda277 100644
--- a/recipe_modules/build/resources/affected_tests.py
+++ b/recipe_modules/build/resources/affected_tests.py
@@ -48,7 +48,14 @@
     # Index tests.json
     tests = json.load(args.tests_json)
     stamp_to_test_names = collections.defaultdict(set)
+    path_to_test_name = {}
     for entry in tests:
+        # For host tests, we search for the executable path.
+        path = entry["test"].get("path")
+        if path:
+            path_to_test_name[path] = entry["test"]["name"]
+            continue
+        # For Fuchsia tests we derive the stamp path from the GN label.
         label = entry["test"]["label"]
         # Remove leading "//" and toolchain part
         # "//my/gn:path(//build/toolchain:host_x64)" -> "my/gn:path"
@@ -99,21 +106,34 @@
         with open(args.ninja_out, "wt") as outfile:
             outfile.write(ninja_output)
 
-    # Find all stale stamps
-    stale_stamps = [line.partition("] ")[2] for line in ninja_output.splitlines()]
+    # Example line:
+    # [4/5] some_executable arg1 arg2
+    # becomes action:
+    # some_executable arg1 arg2
+    actions = [line.partition("] ")[2] for line in ninja_output.splitlines()]
 
-    # Check stale stamps against test names
+    # Check stale actions against tests.
     affected_tests = set()
-    for stamp in stale_stamps:
-        if "touch " in stamp and "obj/" in stamp:
-            test_names = stamp_to_test_names[stamp[stamp.index("obj/") :]]
-            affected_tests = affected_tests.union(test_names)
+    for action in actions:
+        # looking for actions like "touch baz/obj/foo/bar.stamp"
+        if action.startswith("touch ") and "obj/" in action:
+            test_names = stamp_to_test_names[action[action.index("obj/") :]]
+            affected_tests |= test_names
+        # Looking for any action that includes the test path.
+        # Different types of host tests have different actions, but they
+        # all mention the final executable path.
+        else:
+            action_parts = [part.strip('"') for part in action.split()]
+            for action_part in action_parts:
+                test_name = path_to_test_name.get(action_part)
+                if test_name:
+                    affected_tests.add(test_name)
 
     # The following tests should never be considered affected
     never_affected = (
         # These tests use a system image as data, so they appear affected by a
         # broad range of changes, but they're almost never actually sensitive
-        # to said changes.
+        # to said changes. fxbug.dev/67305 tracks generating this list automatically.
         "overnet_serial_tests",
         "recovery_simulator_boot_test",
         "recovery_simulator_serial_test",