pw_build: Require "tests" and "docs" targets in all modules

- Allow empty pw_test_group targets.
- Create empty pw_test_group targets for modules that need them.
- Create stub docs pages for modules that are missing them.
- Require odules to provide "tests" and "docs" targets by assuming
  they're present in the module list generation code.
- Update the generated modules list.

Change-Id: I5a67741afa0a8a5d2e2ba508435027f750a471c4
Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/103487
Commit-Queue: Auto-Submit <auto-submit@pigweed.google.com.iam.gserviceaccount.com>
Pigweed-Auto-Submit: Wyatt Hepler <hepler@google.com>
Reviewed-by: Armando Montanez <amontanez@google.com>
diff --git a/docker/BUILD.gn b/docker/BUILD.gn
index dd021e8..a4d5ebe 100644
--- a/docker/BUILD.gn
+++ b/docker/BUILD.gn
@@ -15,7 +15,11 @@
 import("//build_overrides/pigweed.gni")
 
 import("$dir_pw_docgen/docs.gni")
+import("$dir_pw_unit_test/test.gni")
 
 pw_doc_group("docs") {
   sources = [ "docs.rst" ]
 }
+
+pw_test_group("tests") {
+}
diff --git a/docs/module_structure.rst b/docs/module_structure.rst
index 18f9de1..3a41a6c 100644
--- a/docs/module_structure.rst
+++ b/docs/module_structure.rst
@@ -442,6 +442,8 @@
    - Declare tests in ``pw_test_group("tests")``
    - Declare docs in ``pw_docs_group("docs")``
 
+   Both ``tests`` and ``docs`` are required, even if the module is empty!
+
 6. Add Bazel build support in ``{pw_module_dir}/BUILD.bazel``
 
 7. Add CMake build support in ``{pw_module_dir}/CMakeLists.txt``
diff --git a/pw_android_toolchain/BUILD.gn b/pw_android_toolchain/BUILD.gn
index 8cd2719..cf6b65b 100644
--- a/pw_android_toolchain/BUILD.gn
+++ b/pw_android_toolchain/BUILD.gn
@@ -15,6 +15,7 @@
 import("//build_overrides/pigweed.gni")
 
 import("$dir_pw_docgen/docs.gni")
+import("$dir_pw_unit_test/test.gni")
 
 config("static_libstdc") {
   # Compile against the static libstdc++ and libc++, since we don't have an
@@ -25,3 +26,6 @@
 pw_doc_group("docs") {
   sources = [ "docs.rst" ]
 }
+
+pw_test_group("tests") {
+}
diff --git a/pw_arduino_build/BUILD.gn b/pw_arduino_build/BUILD.gn
index bbe2003..e853386 100644
--- a/pw_arduino_build/BUILD.gn
+++ b/pw_arduino_build/BUILD.gn
@@ -18,6 +18,7 @@
 import("$dir_pw_build/facade.gni")
 import("$dir_pw_build/target_types.gni")
 import("$dir_pw_docgen/docs.gni")
+import("$dir_pw_unit_test/test.gni")
 
 declare_args() {
   # Backend for the pw_arduino_init module.
@@ -51,3 +52,6 @@
 pw_doc_group("docs") {
   sources = [ "docs.rst" ]
 }
+
+pw_test_group("tests") {
+}
diff --git a/pw_assert_basic/BUILD.gn b/pw_assert_basic/BUILD.gn
index f31ab13..4c52443 100644
--- a/pw_assert_basic/BUILD.gn
+++ b/pw_assert_basic/BUILD.gn
@@ -17,6 +17,7 @@
 import("$dir_pw_build/facade.gni")
 import("$dir_pw_build/target_types.gni")
 import("$dir_pw_docgen/docs.gni")
+import("$dir_pw_unit_test/test.gni")
 import("backend.gni")
 
 config("public_include_path") {
@@ -89,3 +90,6 @@
 pw_doc_group("docs") {
   sources = [ "docs.rst" ]
 }
+
+pw_test_group("tests") {
+}
diff --git a/pw_assert_log/BUILD.gn b/pw_assert_log/BUILD.gn
index 28ac04d..b623873 100644
--- a/pw_assert_log/BUILD.gn
+++ b/pw_assert_log/BUILD.gn
@@ -16,6 +16,7 @@
 
 import("$dir_pw_build/target_types.gni")
 import("$dir_pw_docgen/docs.gni")
+import("$dir_pw_unit_test/test.gni")
 
 config("public_include_path") {
   include_dirs = [ "public" ]
@@ -90,3 +91,6 @@
 pw_doc_group("docs") {
   sources = [ "docs.rst" ]
 }
+
+pw_test_group("tests") {
+}
diff --git a/pw_assert_tokenized/BUILD.gn b/pw_assert_tokenized/BUILD.gn
index fc64ffe..bcc9eec 100644
--- a/pw_assert_tokenized/BUILD.gn
+++ b/pw_assert_tokenized/BUILD.gn
@@ -16,6 +16,7 @@
 
 import("$dir_pw_build/target_types.gni")
 import("$dir_pw_docgen/docs.gni")
+import("$dir_pw_unit_test/test.gni")
 
 declare_args() {
   pw_assert_tokenized_HANDLER_BACKEND = "$dir_pw_assert_tokenized:log_handler"
@@ -111,3 +112,6 @@
 pw_doc_group("docs") {
   sources = [ "docs.rst" ]
 }
+
+pw_test_group("tests") {
+}
diff --git a/pw_assert_zephyr/BUILD.gn b/pw_assert_zephyr/BUILD.gn
index 9a6699a..1b11f77 100644
--- a/pw_assert_zephyr/BUILD.gn
+++ b/pw_assert_zephyr/BUILD.gn
@@ -15,7 +15,11 @@
 import("//build_overrides/pigweed.gni")
 
 import("$dir_pw_docgen/docs.gni")
+import("$dir_pw_unit_test/test.gni")
 
 pw_doc_group("docs") {
   sources = [ "docs.rst" ]
 }
+
+pw_test_group("tests") {
+}
diff --git a/pw_bloat/BUILD.gn b/pw_bloat/BUILD.gn
index 17d0767..929fd1d 100644
--- a/pw_bloat/BUILD.gn
+++ b/pw_bloat/BUILD.gn
@@ -16,6 +16,7 @@
 
 import("$dir_pw_build/target_types.gni")
 import("$dir_pw_docgen/docs.gni")
+import("$dir_pw_unit_test/test.gni")
 import("bloat.gni")
 
 config("default_config") {
@@ -51,3 +52,6 @@
     "examples:simple_bloat_loop",
   ]
 }
+
+pw_test_group("tests") {
+}
diff --git a/pw_boot/BUILD.gn b/pw_boot/BUILD.gn
index 0082ad7..77b5c95 100644
--- a/pw_boot/BUILD.gn
+++ b/pw_boot/BUILD.gn
@@ -18,6 +18,7 @@
 import("$dir_pw_build/facade.gni")
 import("$dir_pw_docgen/docs.gni")
 import("$dir_pw_unit_test/test.gni")
+import("$dir_pw_unit_test/test.gni")
 
 config("default_config") {
   include_dirs = [ "public" ]
@@ -32,3 +33,6 @@
 pw_doc_group("docs") {
   sources = [ "docs.rst" ]
 }
+
+pw_test_group("tests") {
+}
diff --git a/pw_boot_cortex_m/BUILD.gn b/pw_boot_cortex_m/BUILD.gn
index d28da4f..8647766 100644
--- a/pw_boot_cortex_m/BUILD.gn
+++ b/pw_boot_cortex_m/BUILD.gn
@@ -18,6 +18,7 @@
 import("$dir_pw_build/linker_script.gni")
 import("$dir_pw_build/target_types.gni")
 import("$dir_pw_docgen/docs.gni")
+import("$dir_pw_unit_test/test.gni")
 
 declare_args() {
   # This list should contain the necessary defines for setting pw_boot linker
@@ -73,3 +74,6 @@
 pw_doc_group("docs") {
   sources = [ "docs.rst" ]
 }
+
+pw_test_group("tests") {
+}
diff --git a/pw_build/generated_pigweed_modules_lists.gni b/pw_build/generated_pigweed_modules_lists.gni
index b211c2e..aaf3c68 100644
--- a/pw_build/generated_pigweed_modules_lists.gni
+++ b/pw_build/generated_pigweed_modules_lists.gni
@@ -290,40 +290,75 @@
 
   # A list with all Pigweed module test groups. DO NOT SET THIS BUILD ARGUMENT!
   pw_module_tests = [
+    "$dir_docker:tests",
     "$dir_pw_allocator:tests",
     "$dir_pw_analog:tests",
+    "$dir_pw_android_toolchain:tests",
+    "$dir_pw_arduino_build:tests",
     "$dir_pw_assert:tests",
+    "$dir_pw_assert_basic:tests",
+    "$dir_pw_assert_log:tests",
+    "$dir_pw_assert_tokenized:tests",
+    "$dir_pw_assert_zephyr:tests",
     "$dir_pw_base64:tests",
+    "$dir_pw_bloat:tests",
     "$dir_pw_blob_store:tests",
     "$dir_pw_bluetooth:tests",
     "$dir_pw_bluetooth_hci:tests",
+    "$dir_pw_boot:tests",
+    "$dir_pw_boot_cortex_m:tests",
     "$dir_pw_build:tests",
+    "$dir_pw_build_info:tests",
+    "$dir_pw_build_mcuxpresso:tests",
     "$dir_pw_bytes:tests",
     "$dir_pw_checksum:tests",
     "$dir_pw_chrono:tests",
+    "$dir_pw_chrono_embos:tests",
+    "$dir_pw_chrono_freertos:tests",
+    "$dir_pw_chrono_stl:tests",
+    "$dir_pw_chrono_threadx:tests",
+    "$dir_pw_chrono_zephyr:tests",
+    "$dir_pw_cli:tests",
+    "$dir_pw_console:tests",
     "$dir_pw_containers:tests",
+    "$dir_pw_cpu_exception:tests",
     "$dir_pw_cpu_exception_cortex_m:tests",
     "$dir_pw_crypto:tests",
     "$dir_pw_digital_io:tests",
+    "$dir_pw_docgen:tests",
+    "$dir_pw_doctor:tests",
+    "$dir_pw_env_setup:tests",
     "$dir_pw_file:tests",
     "$dir_pw_function:tests",
     "$dir_pw_fuzzer:tests",
     "$dir_pw_hdlc:tests",
     "$dir_pw_hex_dump:tests",
     "$dir_pw_i2c:tests",
+    "$dir_pw_i2c_mcuxpresso:tests",
+    "$dir_pw_interrupt:tests",
+    "$dir_pw_interrupt_cortex_m:tests",
+    "$dir_pw_interrupt_zephyr:tests",
     "$dir_pw_kvs:tests",
     "$dir_pw_libc:tests",
     "$dir_pw_log:tests",
+    "$dir_pw_log_android:tests",
+    "$dir_pw_log_basic:tests",
     "$dir_pw_log_null:tests",
     "$dir_pw_log_rpc:tests",
+    "$dir_pw_log_string:tests",
     "$dir_pw_log_tokenized:tests",
+    "$dir_pw_log_zephyr:tests",
+    "$dir_pw_malloc:tests",
     "$dir_pw_malloc_freelist:tests",
     "$dir_pw_metric:tests",
     "$dir_pw_minimal_cpp_stdlib:tests",
+    "$dir_pw_module:tests",
     "$dir_pw_multisink:tests",
+    "$dir_pw_package:tests",
     "$dir_pw_persistent_ram:tests",
     "$dir_pw_polyfill:tests",
     "$dir_pw_preprocessor:tests",
+    "$dir_pw_presubmit:tests",
     "$dir_pw_protobuf:tests",
     "$dir_pw_protobuf_compiler:tests",
     "$dir_pw_random:tests",
@@ -336,10 +371,28 @@
     "$dir_pw_span:tests",
     "$dir_pw_spi:tests",
     "$dir_pw_status:tests",
+    "$dir_pw_stm32cube_build:tests",
     "$dir_pw_stream:tests",
     "$dir_pw_string:tests",
+    "$dir_pw_symbolizer:tests",
     "$dir_pw_sync:tests",
+    "$dir_pw_sync_baremetal:tests",
+    "$dir_pw_sync_embos:tests",
     "$dir_pw_sync_freertos:tests",
+    "$dir_pw_sync_stl:tests",
+    "$dir_pw_sync_threadx:tests",
+    "$dir_pw_sync_zephyr:tests",
+    "$dir_pw_sys_io:tests",
+    "$dir_pw_sys_io_arduino:tests",
+    "$dir_pw_sys_io_baremetal_lm3s6965evb:tests",
+    "$dir_pw_sys_io_baremetal_stm32f429:tests",
+    "$dir_pw_sys_io_emcraft_sf2:tests",
+    "$dir_pw_sys_io_mcuxpresso:tests",
+    "$dir_pw_sys_io_stdio:tests",
+    "$dir_pw_sys_io_stm32cube:tests",
+    "$dir_pw_sys_io_zephyr:tests",
+    "$dir_pw_system:tests",
+    "$dir_pw_target_runner:tests",
     "$dir_pw_thread:tests",
     "$dir_pw_thread_embos:tests",
     "$dir_pw_thread_freertos:tests",
@@ -349,11 +402,15 @@
     "$dir_pw_tls_client_boringssl:tests",
     "$dir_pw_tls_client_mbedtls:tests",
     "$dir_pw_tokenizer:tests",
+    "$dir_pw_tool:tests",
+    "$dir_pw_toolchain:tests",
     "$dir_pw_trace:tests",
     "$dir_pw_trace_tokenized:tests",
     "$dir_pw_transfer:tests",
     "$dir_pw_unit_test:tests",
     "$dir_pw_varint:tests",
+    "$dir_pw_watch:tests",
+    "$dir_pw_web_ui:tests",
     "$dir_pw_work_queue:tests",
   ]
 
@@ -410,11 +467,13 @@
     "$dir_pw_kvs:docs",
     "$dir_pw_libc:docs",
     "$dir_pw_log:docs",
+    "$dir_pw_log_android:docs",
     "$dir_pw_log_basic:docs",
     "$dir_pw_log_null:docs",
     "$dir_pw_log_rpc:docs",
     "$dir_pw_log_string:docs",
     "$dir_pw_log_tokenized:docs",
+    "$dir_pw_log_zephyr:docs",
     "$dir_pw_malloc:docs",
     "$dir_pw_malloc_freelist:docs",
     "$dir_pw_metric:docs",
@@ -451,6 +510,7 @@
     "$dir_pw_sync_zephyr:docs",
     "$dir_pw_sys_io:docs",
     "$dir_pw_sys_io_arduino:docs",
+    "$dir_pw_sys_io_baremetal_lm3s6965evb:docs",
     "$dir_pw_sys_io_baremetal_stm32f429:docs",
     "$dir_pw_sys_io_emcraft_sf2:docs",
     "$dir_pw_sys_io_mcuxpresso:docs",
@@ -468,6 +528,7 @@
     "$dir_pw_tls_client_boringssl:docs",
     "$dir_pw_tls_client_mbedtls:docs",
     "$dir_pw_tokenizer:docs",
+    "$dir_pw_tool:docs",
     "$dir_pw_toolchain:docs",
     "$dir_pw_trace:docs",
     "$dir_pw_trace_tokenized:docs",
diff --git a/pw_build/py/pw_build/generate_modules_lists.py b/pw_build/py/pw_build/generate_modules_lists.py
index 38a0b38..bf731b7 100644
--- a/pw_build/py/pw_build/generate_modules_lists.py
+++ b/pw_build/py/pw_build/generate_modules_lists.py
@@ -28,7 +28,7 @@
 from pathlib import Path
 import sys
 import subprocess
-from typing import Iterator, List, Optional, Sequence, Tuple
+from typing import Iterator, Optional, Sequence
 
 _COPYRIGHT_NOTICE = '''\
 # Copyright 2022 The Pigweed Authors
@@ -98,24 +98,6 @@
         yield ''
 
 
-# TODO(hepler): Add tests and docs targets to all modules.
-def _find_tests_and_docs(
-        root: Path, modules: Sequence[str]) -> Tuple[List[str], List[str]]:
-    """Lists "tests" and "docs" targets for modules that declare them."""
-    tests = []
-    docs = []
-
-    for module in modules:
-        build_gn_contents = root.joinpath(module, 'BUILD.gn').read_bytes()
-        if b'group("tests")' in build_gn_contents:
-            tests.append(f'"$dir_{module}:tests",')
-
-        if b'group("docs")' in build_gn_contents:
-            docs.append(f'"$dir_{module}:docs",')
-
-    return tests, docs
-
-
 def _generate_modules_gni(root: Path, prefix: Path,
                           modules: Sequence[str]) -> Iterator[str]:
     """Generates a .gni file with variables and lists for Pigweed modules."""
@@ -157,16 +139,20 @@
     yield ']'
     yield ''
 
-    tests, docs = _find_tests_and_docs(root, modules)
-
     yield f'# A list with all Pigweed module test groups. {_DO_NOT_SET}'
     yield 'pw_module_tests = ['
-    yield from tests
+
+    for module in modules:
+        yield f'"$dir_{module}:tests",'
+
     yield ']'
     yield ''
     yield f'# A list with all Pigweed modules docs groups. {_DO_NOT_SET}'
     yield 'pw_module_docs = ['
-    yield from docs
+
+    for module in modules:
+        yield f'"$dir_{module}:docs",'
+
     yield ']'
     yield ''
     yield '}'
diff --git a/pw_build_info/BUILD.gn b/pw_build_info/BUILD.gn
index a6b7d39..f3758af 100644
--- a/pw_build_info/BUILD.gn
+++ b/pw_build_info/BUILD.gn
@@ -16,6 +16,7 @@
 
 import("$dir_pw_build/target_types.gni")
 import("$dir_pw_docgen/docs.gni")
+import("$dir_pw_unit_test/test.gni")
 
 config("linker_script") {
   inputs = [ "build_id_linker_snippet.ld" ]
@@ -73,3 +74,6 @@
   sources = [ "docs.rst" ]
   inputs = [ "build_id_linker_snippet.ld" ]
 }
+
+pw_test_group("tests") {
+}
diff --git a/pw_build_mcuxpresso/BUILD.gn b/pw_build_mcuxpresso/BUILD.gn
index 9a6699a..1b11f77 100644
--- a/pw_build_mcuxpresso/BUILD.gn
+++ b/pw_build_mcuxpresso/BUILD.gn
@@ -15,7 +15,11 @@
 import("//build_overrides/pigweed.gni")
 
 import("$dir_pw_docgen/docs.gni")
+import("$dir_pw_unit_test/test.gni")
 
 pw_doc_group("docs") {
   sources = [ "docs.rst" ]
 }
+
+pw_test_group("tests") {
+}
diff --git a/pw_chrono_embos/BUILD.gn b/pw_chrono_embos/BUILD.gn
index b4ad97c..5b7f366 100644
--- a/pw_chrono_embos/BUILD.gn
+++ b/pw_chrono_embos/BUILD.gn
@@ -17,6 +17,7 @@
 import("$dir_pw_build/module_config.gni")
 import("$dir_pw_build/target_types.gni")
 import("$dir_pw_docgen/docs.gni")
+import("$dir_pw_unit_test/test.gni")
 
 declare_args() {
   # The build target that overrides the default configuration options for this
@@ -98,3 +99,6 @@
 pw_doc_group("docs") {
   sources = [ "docs.rst" ]
 }
+
+pw_test_group("tests") {
+}
diff --git a/pw_chrono_freertos/BUILD.gn b/pw_chrono_freertos/BUILD.gn
index afe8afe..4174d47 100644
--- a/pw_chrono_freertos/BUILD.gn
+++ b/pw_chrono_freertos/BUILD.gn
@@ -17,6 +17,7 @@
 import("$dir_pw_build/module_config.gni")
 import("$dir_pw_build/target_types.gni")
 import("$dir_pw_docgen/docs.gni")
+import("$dir_pw_unit_test/test.gni")
 
 declare_args() {
   # The build target that overrides the default configuration options for this
@@ -97,3 +98,6 @@
 pw_doc_group("docs") {
   sources = [ "docs.rst" ]
 }
+
+pw_test_group("tests") {
+}
diff --git a/pw_chrono_stl/BUILD.gn b/pw_chrono_stl/BUILD.gn
index 9ecbd85..5acf446 100644
--- a/pw_chrono_stl/BUILD.gn
+++ b/pw_chrono_stl/BUILD.gn
@@ -16,6 +16,7 @@
 
 import("$dir_pw_build/target_types.gni")
 import("$dir_pw_docgen/docs.gni")
+import("$dir_pw_unit_test/test.gni")
 
 config("public_include_path") {
   include_dirs = [ "public" ]
@@ -69,3 +70,6 @@
 pw_doc_group("docs") {
   sources = [ "docs.rst" ]
 }
+
+pw_test_group("tests") {
+}
diff --git a/pw_chrono_threadx/BUILD.gn b/pw_chrono_threadx/BUILD.gn
index 3cd0434..4bc4fc4 100644
--- a/pw_chrono_threadx/BUILD.gn
+++ b/pw_chrono_threadx/BUILD.gn
@@ -17,6 +17,7 @@
 import("$dir_pw_build/module_config.gni")
 import("$dir_pw_build/target_types.gni")
 import("$dir_pw_docgen/docs.gni")
+import("$dir_pw_unit_test/test.gni")
 
 declare_args() {
   # The build target that overrides the default configuration options for this
@@ -71,3 +72,6 @@
 pw_doc_group("docs") {
   sources = [ "docs.rst" ]
 }
+
+pw_test_group("tests") {
+}
diff --git a/pw_chrono_zephyr/BUILD.gn b/pw_chrono_zephyr/BUILD.gn
index 9a6699a..1b11f77 100644
--- a/pw_chrono_zephyr/BUILD.gn
+++ b/pw_chrono_zephyr/BUILD.gn
@@ -15,7 +15,11 @@
 import("//build_overrides/pigweed.gni")
 
 import("$dir_pw_docgen/docs.gni")
+import("$dir_pw_unit_test/test.gni")
 
 pw_doc_group("docs") {
   sources = [ "docs.rst" ]
 }
+
+pw_test_group("tests") {
+}
diff --git a/pw_cli/BUILD.gn b/pw_cli/BUILD.gn
index 79f575b..17500d5 100644
--- a/pw_cli/BUILD.gn
+++ b/pw_cli/BUILD.gn
@@ -15,8 +15,12 @@
 import("//build_overrides/pigweed.gni")
 
 import("$dir_pw_docgen/docs.gni")
+import("$dir_pw_unit_test/test.gni")
 
 pw_doc_group("docs") {
   sources = [ "docs.rst" ]
   other_deps = [ "py" ]
 }
+
+pw_test_group("tests") {
+}
diff --git a/pw_console/BUILD.gn b/pw_console/BUILD.gn
index fcfec09..ec3b198 100644
--- a/pw_console/BUILD.gn
+++ b/pw_console/BUILD.gn
@@ -15,6 +15,7 @@
 import("//build_overrides/pigweed.gni")
 
 import("$dir_pw_docgen/docs.gni")
+import("$dir_pw_unit_test/test.gni")
 
 pw_doc_group("docs") {
   inputs = [
@@ -39,3 +40,6 @@
     "testing.rst",
   ]
 }
+
+pw_test_group("tests") {
+}
diff --git a/pw_cpu_exception/BUILD.gn b/pw_cpu_exception/BUILD.gn
index 2e773a3..4183dd6 100644
--- a/pw_cpu_exception/BUILD.gn
+++ b/pw_cpu_exception/BUILD.gn
@@ -16,6 +16,7 @@
 
 import("$dir_pw_build/facade.gni")
 import("$dir_pw_docgen/docs.gni")
+import("$dir_pw_unit_test/test.gni")
 import("backend.gni")
 
 config("public_include_path") {
@@ -118,3 +119,6 @@
 pw_doc_group("docs") {
   sources = [ "docs.rst" ]
 }
+
+pw_test_group("tests") {
+}
diff --git a/pw_docgen/BUILD.gn b/pw_docgen/BUILD.gn
index 0aaa8cd..e9226cc 100644
--- a/pw_docgen/BUILD.gn
+++ b/pw_docgen/BUILD.gn
@@ -14,8 +14,12 @@
 
 import("//build_overrides/pigweed.gni")
 
+import("$dir_pw_unit_test/test.gni")
 import("docs.gni")
 
 pw_doc_group("docs") {
   sources = [ "docs.rst" ]
 }
+
+pw_test_group("tests") {
+}
diff --git a/pw_doctor/BUILD.gn b/pw_doctor/BUILD.gn
index 0969672..45e4c14 100644
--- a/pw_doctor/BUILD.gn
+++ b/pw_doctor/BUILD.gn
@@ -15,7 +15,11 @@
 import("//build_overrides/pigweed.gni")
 
 import("$dir_pw_docgen/docs.gni")
+import("$dir_pw_unit_test/test.gni")
 
 pw_doc_group("docs") {
   sources = [ "docs.rst" ]
 }
+
+pw_test_group("tests") {
+}
diff --git a/pw_env_setup/BUILD.gn b/pw_env_setup/BUILD.gn
index 0415df2..ca5195a 100644
--- a/pw_env_setup/BUILD.gn
+++ b/pw_env_setup/BUILD.gn
@@ -19,6 +19,7 @@
 import("$dir_pw_build/python_gn_args.gni")
 import("$dir_pw_build/python_venv.gni")
 import("$dir_pw_docgen/docs.gni")
+import("$dir_pw_unit_test/test.gni")
 
 pw_doc_group("docs") {
   inputs = [ "doc_resources/pw_env_setup_output.png" ]
@@ -187,3 +188,6 @@
     packages = [ ":generate_pigweed_python_package" ]
   }
 }
+
+pw_test_group("tests") {
+}
diff --git a/pw_i2c_mcuxpresso/BUILD.gn b/pw_i2c_mcuxpresso/BUILD.gn
index b3b62b4..3ff44c6 100644
--- a/pw_i2c_mcuxpresso/BUILD.gn
+++ b/pw_i2c_mcuxpresso/BUILD.gn
@@ -16,6 +16,7 @@
 import("$dir_pw_build/target_types.gni")
 import("$dir_pw_docgen/docs.gni")
 import("$dir_pw_third_party/mcuxpresso/mcuxpresso.gni")
+import("$dir_pw_unit_test/test.gni")
 
 config("default_config") {
   include_dirs = [ "public" ]
@@ -42,3 +43,6 @@
 pw_doc_group("docs") {
   sources = [ "docs.rst" ]
 }
+
+pw_test_group("tests") {
+}
diff --git a/pw_interrupt/BUILD.gn b/pw_interrupt/BUILD.gn
index 37604bb..1114687 100644
--- a/pw_interrupt/BUILD.gn
+++ b/pw_interrupt/BUILD.gn
@@ -17,6 +17,7 @@
 import("$dir_pw_build/facade.gni")
 import("$dir_pw_docgen/docs.gni")
 import("$dir_pw_unit_test/test.gni")
+import("$dir_pw_unit_test/test.gni")
 import("backend.gni")
 
 config("public_include_path") {
@@ -33,3 +34,6 @@
 pw_doc_group("docs") {
   sources = [ "docs.rst" ]
 }
+
+pw_test_group("tests") {
+}
diff --git a/pw_interrupt_cortex_m/BUILD.gn b/pw_interrupt_cortex_m/BUILD.gn
index fed8d29..9548079 100644
--- a/pw_interrupt_cortex_m/BUILD.gn
+++ b/pw_interrupt_cortex_m/BUILD.gn
@@ -16,6 +16,7 @@
 
 import("$dir_pw_build/target_types.gni")
 import("$dir_pw_docgen/docs.gni")
+import("$dir_pw_unit_test/test.gni")
 
 config("public_include_path") {
   include_dirs = [ "public" ]
@@ -56,3 +57,6 @@
 pw_doc_group("docs") {
   sources = [ "docs.rst" ]
 }
+
+pw_test_group("tests") {
+}
diff --git a/pw_interrupt_zephyr/BUILD.gn b/pw_interrupt_zephyr/BUILD.gn
index 9a6699a..1b11f77 100644
--- a/pw_interrupt_zephyr/BUILD.gn
+++ b/pw_interrupt_zephyr/BUILD.gn
@@ -15,7 +15,11 @@
 import("//build_overrides/pigweed.gni")
 
 import("$dir_pw_docgen/docs.gni")
+import("$dir_pw_unit_test/test.gni")
 
 pw_doc_group("docs") {
   sources = [ "docs.rst" ]
 }
+
+pw_test_group("tests") {
+}
diff --git a/pw_log_android/BUILD.gn b/pw_log_android/BUILD.gn
index 8316fd8..4292b75 100644
--- a/pw_log_android/BUILD.gn
+++ b/pw_log_android/BUILD.gn
@@ -12,4 +12,15 @@
 # License for the specific language governing permissions and limitations under
 # the License.
 
+import("//build_overrides/pigweed.gni")
+
+import("$dir_pw_docgen/docs.gni")
+import("$dir_pw_unit_test/test.gni")
+
 # Android only uses Soong Blueprints, so this file is empty.
+pw_test_group("tests") {
+}
+
+pw_doc_group("docs") {
+  sources = [ "docs.rst" ]
+}
diff --git a/pw_log_android/docs.rst b/pw_log_android/docs.rst
new file mode 100644
index 0000000..84c5069
--- /dev/null
+++ b/pw_log_android/docs.rst
@@ -0,0 +1,9 @@
+.. _module-pw_log_android:
+
+==============
+pw_log_android
+==============
+
+.. warning::
+
+  This documentation is under construction.
diff --git a/pw_log_basic/BUILD.gn b/pw_log_basic/BUILD.gn
index a478ea2..e8dd52a 100644
--- a/pw_log_basic/BUILD.gn
+++ b/pw_log_basic/BUILD.gn
@@ -17,6 +17,7 @@
 import("$dir_pw_build/module_config.gni")
 import("$dir_pw_build/target_types.gni")
 import("$dir_pw_docgen/docs.gni")
+import("$dir_pw_unit_test/test.gni")
 
 declare_args() {
   # The build target that overrides the default configuration options for this
@@ -75,3 +76,6 @@
 pw_doc_group("docs") {
   sources = [ "docs.rst" ]
 }
+
+pw_test_group("tests") {
+}
diff --git a/pw_log_string/BUILD.gn b/pw_log_string/BUILD.gn
index 0909a2a..130550c 100644
--- a/pw_log_string/BUILD.gn
+++ b/pw_log_string/BUILD.gn
@@ -18,6 +18,7 @@
 import("$dir_pw_build/facade.gni")
 import("$dir_pw_build/target_types.gni")
 import("$dir_pw_docgen/docs.gni")
+import("$dir_pw_unit_test/test.gni")
 import("backend.gni")
 
 config("public_include_path") {
@@ -92,3 +93,6 @@
 pw_doc_group("docs") {
   sources = [ "docs.rst" ]
 }
+
+pw_test_group("tests") {
+}
diff --git a/pw_log_zephyr/BUILD.gn b/pw_log_zephyr/BUILD.gn
index 68ffb75..7eccfeb 100644
--- a/pw_log_zephyr/BUILD.gn
+++ b/pw_log_zephyr/BUILD.gn
@@ -12,4 +12,15 @@
 # License for the specific language governing permissions and limitations under
 # the License.
 
+import("//build_overrides/pigweed.gni")
+
+import("$dir_pw_docgen/docs.gni")
+import("$dir_pw_unit_test/test.gni")
+
 # Zephyr only uses CMake, so this file is empty.
+pw_test_group("tests") {
+}
+
+pw_doc_group("docs") {
+  sources = [ "docs.rst" ]
+}
diff --git a/pw_malloc/BUILD.gn b/pw_malloc/BUILD.gn
index 5a700a8..5af660e 100644
--- a/pw_malloc/BUILD.gn
+++ b/pw_malloc/BUILD.gn
@@ -19,6 +19,7 @@
 import("$dir_pw_docgen/docs.gni")
 import("$dir_pw_malloc/backend.gni")
 import("$dir_pw_unit_test/test.gni")
+import("$dir_pw_unit_test/test.gni")
 
 config("default_config") {
   include_dirs = [ "public" ]
@@ -52,3 +53,6 @@
 pw_doc_group("docs") {
   sources = [ "docs.rst" ]
 }
+
+pw_test_group("tests") {
+}
diff --git a/pw_module/BUILD.gn b/pw_module/BUILD.gn
index 0969672..45e4c14 100644
--- a/pw_module/BUILD.gn
+++ b/pw_module/BUILD.gn
@@ -15,7 +15,11 @@
 import("//build_overrides/pigweed.gni")
 
 import("$dir_pw_docgen/docs.gni")
+import("$dir_pw_unit_test/test.gni")
 
 pw_doc_group("docs") {
   sources = [ "docs.rst" ]
 }
+
+pw_test_group("tests") {
+}
diff --git a/pw_package/BUILD.gn b/pw_package/BUILD.gn
index dd021e8..a4d5ebe 100644
--- a/pw_package/BUILD.gn
+++ b/pw_package/BUILD.gn
@@ -15,7 +15,11 @@
 import("//build_overrides/pigweed.gni")
 
 import("$dir_pw_docgen/docs.gni")
+import("$dir_pw_unit_test/test.gni")
 
 pw_doc_group("docs") {
   sources = [ "docs.rst" ]
 }
+
+pw_test_group("tests") {
+}
diff --git a/pw_presubmit/BUILD.gn b/pw_presubmit/BUILD.gn
index 35fa9ff..8bda35a 100644
--- a/pw_presubmit/BUILD.gn
+++ b/pw_presubmit/BUILD.gn
@@ -15,6 +15,7 @@
 import("//build_overrides/pigweed.gni")
 
 import("$dir_pw_docgen/docs.gni")
+import("$dir_pw_unit_test/test.gni")
 
 pw_doc_group("docs") {
   sources = [ "docs.rst" ]
@@ -24,3 +25,6 @@
     "py/pw_presubmit/presubmit.py",
   ]
 }
+
+pw_test_group("tests") {
+}
diff --git a/pw_stm32cube_build/BUILD.gn b/pw_stm32cube_build/BUILD.gn
index 9a6699a..1b11f77 100644
--- a/pw_stm32cube_build/BUILD.gn
+++ b/pw_stm32cube_build/BUILD.gn
@@ -15,7 +15,11 @@
 import("//build_overrides/pigweed.gni")
 
 import("$dir_pw_docgen/docs.gni")
+import("$dir_pw_unit_test/test.gni")
 
 pw_doc_group("docs") {
   sources = [ "docs.rst" ]
 }
+
+pw_test_group("tests") {
+}
diff --git a/pw_symbolizer/BUILD.gn b/pw_symbolizer/BUILD.gn
index 9a6699a..1b11f77 100644
--- a/pw_symbolizer/BUILD.gn
+++ b/pw_symbolizer/BUILD.gn
@@ -15,7 +15,11 @@
 import("//build_overrides/pigweed.gni")
 
 import("$dir_pw_docgen/docs.gni")
+import("$dir_pw_unit_test/test.gni")
 
 pw_doc_group("docs") {
   sources = [ "docs.rst" ]
 }
+
+pw_test_group("tests") {
+}
diff --git a/pw_sync_baremetal/BUILD.gn b/pw_sync_baremetal/BUILD.gn
index 092f793..a857c7a 100644
--- a/pw_sync_baremetal/BUILD.gn
+++ b/pw_sync_baremetal/BUILD.gn
@@ -17,6 +17,7 @@
 import("$dir_pw_build/target_types.gni")
 import("$dir_pw_chrono/backend.gni")
 import("$dir_pw_docgen/docs.gni")
+import("$dir_pw_unit_test/test.gni")
 
 config("public_include_path") {
   include_dirs = [ "public" ]
@@ -99,3 +100,6 @@
 pw_doc_group("docs") {
   sources = [ "docs.rst" ]
 }
+
+pw_test_group("tests") {
+}
diff --git a/pw_sync_embos/BUILD.gn b/pw_sync_embos/BUILD.gn
index c399748..7f43766 100644
--- a/pw_sync_embos/BUILD.gn
+++ b/pw_sync_embos/BUILD.gn
@@ -18,6 +18,7 @@
 import("$dir_pw_build/target_types.gni")
 import("$dir_pw_chrono/backend.gni")
 import("$dir_pw_docgen/docs.gni")
+import("$dir_pw_unit_test/test.gni")
 
 config("public_include_path") {
   include_dirs = [ "public" ]
@@ -158,3 +159,6 @@
 pw_doc_group("docs") {
   sources = [ "docs.rst" ]
 }
+
+pw_test_group("tests") {
+}
diff --git a/pw_sync_stl/BUILD.gn b/pw_sync_stl/BUILD.gn
index 7b5cde9..984f734 100644
--- a/pw_sync_stl/BUILD.gn
+++ b/pw_sync_stl/BUILD.gn
@@ -18,6 +18,7 @@
 import("$dir_pw_build/target_types.gni")
 import("$dir_pw_chrono/backend.gni")
 import("$dir_pw_docgen/docs.gni")
+import("$dir_pw_unit_test/test.gni")
 
 config("public_include_path") {
   include_dirs = [ "public" ]
@@ -152,3 +153,6 @@
 pw_doc_group("docs") {
   sources = [ "docs.rst" ]
 }
+
+pw_test_group("tests") {
+}
diff --git a/pw_sync_threadx/BUILD.gn b/pw_sync_threadx/BUILD.gn
index 524c478..6932c84 100644
--- a/pw_sync_threadx/BUILD.gn
+++ b/pw_sync_threadx/BUILD.gn
@@ -19,6 +19,7 @@
 import("$dir_pw_chrono/backend.gni")
 import("$dir_pw_docgen/docs.gni")
 import("$dir_pw_sync/backend.gni")
+import("$dir_pw_unit_test/test.gni")
 
 config("public_include_path") {
   include_dirs = [ "public" ]
@@ -162,3 +163,6 @@
 pw_doc_group("docs") {
   sources = [ "docs.rst" ]
 }
+
+pw_test_group("tests") {
+}
diff --git a/pw_sync_zephyr/BUILD.gn b/pw_sync_zephyr/BUILD.gn
index 9a6699a..1b11f77 100644
--- a/pw_sync_zephyr/BUILD.gn
+++ b/pw_sync_zephyr/BUILD.gn
@@ -15,7 +15,11 @@
 import("//build_overrides/pigweed.gni")
 
 import("$dir_pw_docgen/docs.gni")
+import("$dir_pw_unit_test/test.gni")
 
 pw_doc_group("docs") {
   sources = [ "docs.rst" ]
 }
+
+pw_test_group("tests") {
+}
diff --git a/pw_sys_io/BUILD.gn b/pw_sys_io/BUILD.gn
index ee2e8f0..0fabc26 100644
--- a/pw_sys_io/BUILD.gn
+++ b/pw_sys_io/BUILD.gn
@@ -17,6 +17,7 @@
 import("$dir_pw_build/facade.gni")
 import("$dir_pw_build/target_types.gni")
 import("$dir_pw_docgen/docs.gni")
+import("$dir_pw_unit_test/test.gni")
 import("backend.gni")
 
 config("public_include_path") {
@@ -41,3 +42,6 @@
 pw_doc_group("docs") {
   sources = [ "docs.rst" ]
 }
+
+pw_test_group("tests") {
+}
diff --git a/pw_sys_io_arduino/BUILD.gn b/pw_sys_io_arduino/BUILD.gn
index eb4b51c..e29de26 100644
--- a/pw_sys_io_arduino/BUILD.gn
+++ b/pw_sys_io_arduino/BUILD.gn
@@ -17,6 +17,7 @@
 import("$dir_pw_arduino_build/arduino.gni")
 import("$dir_pw_build/target_types.gni")
 import("$dir_pw_docgen/docs.gni")
+import("$dir_pw_unit_test/test.gni")
 
 config("default_config") {
   include_dirs = [ "public" ]
@@ -40,3 +41,6 @@
 pw_doc_group("docs") {
   sources = [ "docs.rst" ]
 }
+
+pw_test_group("tests") {
+}
diff --git a/pw_sys_io_baremetal_lm3s6965evb/BUILD.gn b/pw_sys_io_baremetal_lm3s6965evb/BUILD.gn
index 1fa0ecf..3e14bb4 100644
--- a/pw_sys_io_baremetal_lm3s6965evb/BUILD.gn
+++ b/pw_sys_io_baremetal_lm3s6965evb/BUILD.gn
@@ -16,6 +16,7 @@
 
 import("$dir_pw_build/target_types.gni")
 import("$dir_pw_docgen/docs.gni")
+import("$dir_pw_unit_test/test.gni")
 
 config("default_config") {
   include_dirs = [ "public" ]
@@ -34,3 +35,10 @@
   ]
   sources = [ "sys_io_baremetal.cc" ]
 }
+
+pw_test_group("tests") {
+}
+
+pw_doc_group("docs") {
+  sources = [ "docs.rst" ]
+}
diff --git a/pw_sys_io_baremetal_lm3s6965evb/docs.rst b/pw_sys_io_baremetal_lm3s6965evb/docs.rst
new file mode 100644
index 0000000..a865373
--- /dev/null
+++ b/pw_sys_io_baremetal_lm3s6965evb/docs.rst
@@ -0,0 +1,9 @@
+.. _module-pw_sys_io_baremetal_lm3s6965evb:
+
+===============================
+pw_sys_io_baremetal_lm3s6965evb
+===============================
+
+.. warning::
+
+  This documentation is under construction.
diff --git a/pw_sys_io_baremetal_stm32f429/BUILD.gn b/pw_sys_io_baremetal_stm32f429/BUILD.gn
index c9eb3b2..4385089 100644
--- a/pw_sys_io_baremetal_stm32f429/BUILD.gn
+++ b/pw_sys_io_baremetal_stm32f429/BUILD.gn
@@ -16,6 +16,7 @@
 
 import("$dir_pw_build/target_types.gni")
 import("$dir_pw_docgen/docs.gni")
+import("$dir_pw_unit_test/test.gni")
 
 config("default_config") {
   include_dirs = [ "public" ]
@@ -38,3 +39,6 @@
 pw_doc_group("docs") {
   sources = [ "docs.rst" ]
 }
+
+pw_test_group("tests") {
+}
diff --git a/pw_sys_io_emcraft_sf2/BUILD.gn b/pw_sys_io_emcraft_sf2/BUILD.gn
index 236722f..a9b29b0 100644
--- a/pw_sys_io_emcraft_sf2/BUILD.gn
+++ b/pw_sys_io_emcraft_sf2/BUILD.gn
@@ -18,6 +18,7 @@
 import("$dir_pw_build/target_types.gni")
 import("$dir_pw_docgen/docs.gni")
 import("$dir_pw_third_party/smartfusion_mss/mss.gni")
+import("$dir_pw_unit_test/test.gni")
 
 declare_args() {
   # The build target that overrides the default configuration options for this
@@ -58,3 +59,6 @@
 pw_doc_group("docs") {
   sources = [ "docs.rst" ]
 }
+
+pw_test_group("tests") {
+}
diff --git a/pw_sys_io_mcuxpresso/BUILD.gn b/pw_sys_io_mcuxpresso/BUILD.gn
index 8c06717..7e8ccff 100644
--- a/pw_sys_io_mcuxpresso/BUILD.gn
+++ b/pw_sys_io_mcuxpresso/BUILD.gn
@@ -17,6 +17,7 @@
 import("$dir_pw_build/target_types.gni")
 import("$dir_pw_docgen/docs.gni")
 import("$dir_pw_third_party/mcuxpresso/mcuxpresso.gni")
+import("$dir_pw_unit_test/test.gni")
 
 config("default_config") {
   include_dirs = [ "public" ]
@@ -42,3 +43,6 @@
 pw_doc_group("docs") {
   sources = [ "docs.rst" ]
 }
+
+pw_test_group("tests") {
+}
diff --git a/pw_sys_io_stdio/BUILD.gn b/pw_sys_io_stdio/BUILD.gn
index 2a7c578..8de56e3 100644
--- a/pw_sys_io_stdio/BUILD.gn
+++ b/pw_sys_io_stdio/BUILD.gn
@@ -16,6 +16,7 @@
 
 import("$dir_pw_build/target_types.gni")
 import("$dir_pw_docgen/docs.gni")
+import("$dir_pw_unit_test/test.gni")
 
 pw_source_set("pw_sys_io_stdio") {
   deps = [
@@ -28,3 +29,6 @@
 pw_doc_group("docs") {
   sources = [ "docs.rst" ]
 }
+
+pw_test_group("tests") {
+}
diff --git a/pw_sys_io_stm32cube/BUILD.gn b/pw_sys_io_stm32cube/BUILD.gn
index edf540a..a24d83b 100644
--- a/pw_sys_io_stm32cube/BUILD.gn
+++ b/pw_sys_io_stm32cube/BUILD.gn
@@ -18,6 +18,7 @@
 import("$dir_pw_build/target_types.gni")
 import("$dir_pw_docgen/docs.gni")
 import("$dir_pw_third_party/stm32cube/stm32cube.gni")
+import("$dir_pw_unit_test/test.gni")
 
 declare_args() {
   # The build target that overrides the default configuration options for this
@@ -57,3 +58,6 @@
 pw_doc_group("docs") {
   sources = [ "docs.rst" ]
 }
+
+pw_test_group("tests") {
+}
diff --git a/pw_sys_io_zephyr/BUILD.gn b/pw_sys_io_zephyr/BUILD.gn
index 9a6699a..1b11f77 100644
--- a/pw_sys_io_zephyr/BUILD.gn
+++ b/pw_sys_io_zephyr/BUILD.gn
@@ -15,7 +15,11 @@
 import("//build_overrides/pigweed.gni")
 
 import("$dir_pw_docgen/docs.gni")
+import("$dir_pw_unit_test/test.gni")
 
 pw_doc_group("docs") {
   sources = [ "docs.rst" ]
 }
+
+pw_test_group("tests") {
+}
diff --git a/pw_system/BUILD.gn b/pw_system/BUILD.gn
index de21e9c..21b2444 100644
--- a/pw_system/BUILD.gn
+++ b/pw_system/BUILD.gn
@@ -23,6 +23,7 @@
 import("$dir_pw_build/module_config.gni")
 import("$dir_pw_build/target_types.gni")
 import("$dir_pw_docgen/docs.gni")
+import("$dir_pw_unit_test/test.gni")
 import("backend.gni")
 
 declare_args() {
@@ -248,3 +249,6 @@
 pw_doc_group("docs") {
   sources = [ "docs.rst" ]
 }
+
+pw_test_group("tests") {
+}
diff --git a/pw_target_runner/BUILD.gn b/pw_target_runner/BUILD.gn
index 9db1016..6bc6ea2 100644
--- a/pw_target_runner/BUILD.gn
+++ b/pw_target_runner/BUILD.gn
@@ -16,6 +16,7 @@
 
 import("$dir_pw_docgen/docs.gni")
 import("$dir_pw_protobuf_compiler/proto.gni")
+import("$dir_pw_unit_test/test.gni")
 
 pw_doc_group("docs") {
   sources = [ "docs.rst" ]
@@ -29,3 +30,6 @@
 pw_proto_library("exec_server_config_proto") {
   sources = [ "pw_target_runner_server_protos/exec_server_config.proto" ]
 }
+
+pw_test_group("tests") {
+}
diff --git a/pw_tool/BUILD.gn b/pw_tool/BUILD.gn
index 0bee3f3..04df09b 100644
--- a/pw_tool/BUILD.gn
+++ b/pw_tool/BUILD.gn
@@ -14,6 +14,8 @@
 
 import("//build_overrides/pigweed.gni")
 import("$dir_pw_build/target_types.gni")
+import("$dir_pw_docgen/docs.gni")
+import("$dir_pw_unit_test/test.gni")
 
 pw_executable("pw_tool") {
   output_name = "pw_tool"
@@ -24,3 +26,10 @@
   ]
   sources = [ "main.cc" ]
 }
+
+pw_test_group("tests") {
+}
+
+pw_doc_group("docs") {
+  sources = [ "docs.rst" ]
+}
diff --git a/pw_tool/docs.rst b/pw_tool/docs.rst
new file mode 100644
index 0000000..0bc0b34
--- /dev/null
+++ b/pw_tool/docs.rst
@@ -0,0 +1,9 @@
+.. _module-pw_tool:
+
+=======
+pw_tool
+=======
+
+.. warning::
+
+  This documentation is under construction.
diff --git a/pw_toolchain/BUILD.gn b/pw_toolchain/BUILD.gn
index 0ac53a8..10d4014 100644
--- a/pw_toolchain/BUILD.gn
+++ b/pw_toolchain/BUILD.gn
@@ -15,6 +15,7 @@
 import("//build_overrides/pigweed.gni")
 
 import("$dir_pw_docgen/docs.gni")
+import("$dir_pw_unit_test/test.gni")
 import("arm_gcc/toolchains.gni")
 import("generate_toolchain.gni")
 import("host_clang/toolchains.gni")
@@ -43,3 +44,6 @@
 pw_doc_group("docs") {
   sources = [ "docs.rst" ]
 }
+
+pw_test_group("tests") {
+}
diff --git a/pw_unit_test/test.gni b/pw_unit_test/test.gni
index 7b1d3d2..e75d638 100644
--- a/pw_unit_test/test.gni
+++ b/pw_unit_test/test.gni
@@ -283,6 +283,11 @@
     _deps = []
   }
 
+  # Allow empty pw_test_groups with no tests or group_deps.
+  if (!defined(invoker.tests) && !defined(invoker.group_deps)) {
+    not_needed("*")
+  }
+
   _group_is_enabled = !defined(invoker.enable_if) || invoker.enable_if
 
   if (_group_is_enabled) {
diff --git a/pw_watch/BUILD.gn b/pw_watch/BUILD.gn
index c0c9988..8ef52a0 100644
--- a/pw_watch/BUILD.gn
+++ b/pw_watch/BUILD.gn
@@ -15,8 +15,12 @@
 import("//build_overrides/pigweed.gni")
 
 import("$dir_pw_docgen/docs.gni")
+import("$dir_pw_unit_test/test.gni")
 
 pw_doc_group("docs") {
   inputs = [ "doc_resources/pw_watch_on_device_demo.gif" ]
   sources = [ "docs.rst" ]
 }
+
+pw_test_group("tests") {
+}
diff --git a/pw_web_ui/BUILD.gn b/pw_web_ui/BUILD.gn
index dd021e8..a4d5ebe 100644
--- a/pw_web_ui/BUILD.gn
+++ b/pw_web_ui/BUILD.gn
@@ -15,7 +15,11 @@
 import("//build_overrides/pigweed.gni")
 
 import("$dir_pw_docgen/docs.gni")
+import("$dir_pw_unit_test/test.gni")
 
 pw_doc_group("docs") {
   sources = [ "docs.rst" ]
 }
+
+pw_test_group("tests") {
+}