[tests] Simplify defining unit test components

This adda a new rule to generate simple unit test components,
where it will auto generate a cmx file that points at the
test binary. It also updates the following test packages to run
inside components:

* examples/rust_static_linking
* garnet/bin/debugserver
* garnet/public/lib/fidl/llcpp
* garnet/public/rust

Bug: CF-789 #comment

Change-Id: I0031c1669417e086a84b931f192587515f905c4c
diff --git a/build/test/test_package.gni b/build/test/test_package.gni
index 9db1a15..898efd8 100644
--- a/build/test/test_package.gni
+++ b/build/test/test_package.gni
@@ -60,6 +60,9 @@
 #       dest (optional)
 #         [path] Location the binary will be placed within `test/`.
 #
+#       manifest (optional)
+#         [path] Location of the component manifest file.
+#
 #       disabled (optional)
 #         [bool] Whether to disable the test on continuous integration
 #         jobs. This can be used when a test is temporarily broken, or if
@@ -110,9 +113,15 @@
     if (defined(test.dest)) {
       test_dest = test.dest
     }
+    if (defined(test.manifest)) {
+      test_manifest = test.manifest
+    } else {
+      test_manifest = "meta/${test_dest}.cmx"
+    }
+
     meta += [
       {
-        path = "meta/${test_dest}.cmx"
+        path = test_manifest
         dest = "${test_dest}.cmx"
       },
     ]
@@ -135,3 +144,157 @@
                            ])
   }
 }
+
+# Helper template to create unit test component which runtests can execute.
+#
+# This template assumes that all the test binaries in |tests| do not depend on
+# any component manifest facets, features, services and etc. If you wish to use
+# any of these features in your manifest, you must use `package` or
+# `test_package`.
+#
+# Parameters
+#
+#   meta (optional)
+#     [list of scopes] Defines the metadata entries in the package. A metadata
+#     entry is typically a source file and is placed in the `meta/` directory of
+#     the assembled package.
+#
+#     Entries in a scope in the meta list:
+#
+#       path (required)
+#         [path] Location of entry in source or build directory. If the
+#         resource is checked in, this will typically be specified as a
+#         path relative to the BUILD.gn file containing the `package()`
+#         target. If the resource is generated, this will typically be
+#         specified relative to `$target_gen_dir`.
+#
+#       dest (required)
+#         [path] Location the resource will be placed within `meta/`.
+#
+#   binaries (optional)
+#     [list of scopes] Defines the binaries in the package. A binary is
+#     typically produced by the build system and is placed in the `bin/`
+#     directory of the assembled package.
+#
+#     Entries in a scope in the binaries list:
+#
+#       name (required)
+#         [string] Name of the binary.
+#
+#       source (optional)
+#         [path] Location of the binary in the build directory if it is not
+#         at `$root_out_dir/$name`.
+#
+#       dest (optional)
+#         [path] Location the binary will be placed within `bin/`.
+#
+#
+#   tests (optional)
+#     [list of scopes] Defines the test binaries in the package. A test is
+#     typically produced by the build system and is placed in the `test/`
+#     directory of the assembled package.
+#
+#     Entries in a scope in the tests list:
+#
+#       name (required)
+#         [string] Name of the test.
+#
+#       dest (optional)
+#         [path] Location the binary will be placed within `test/`.
+#
+#       disabled (optional)
+#         [bool] Whether to disable the test on continuous integration
+#         jobs. This can be used when a test is temporarily broken, or if
+#         it is too flaky or slow for CI. The test will also be skipped by
+#         the `runtests` command.
+#
+#     For each tests entry this expects a corresponding cmx file in meta directory.
+#
+#   resources (optional)
+#     [list of scopes] Defines the resources in the package. A resource is a
+#     data file that may be produced by the build system, checked in to a
+#     source repository, or produced by another system that runs before the
+#     build. Resources are placed in the `data/` directory of the assembled
+#     package.
+#
+#     Entries in a scope in the resources list:
+#
+#       path (required)
+#         [path] Location of resource in source or build directory. If the
+#         resource is checked in, this will typically be specified as a
+#         path relative to the BUILD.gn file containing the `package()`
+#         target. If the resource is generated, this will typically be
+#         specified relative to `$target_gen_dir`.
+#
+#       dest (required)
+#         [path] Location the resource will be placed within `data/`.
+#
+#   extra (optional)
+#     [list of paths] Manifest files containing extra entries, which
+#     might be generated by the build.
+#
+#   deps (optional)
+#   public_deps (optional)
+#   data_deps (optional)
+#     Usual GN meanings.
+#
+template("unittest_package") {
+  package_tests = []
+  manifest_labels = []
+
+  foreach(test, invoker.tests) {
+    test_dest = test.name
+    if (defined(test.dest)) {
+      test_dest = test.dest
+    }
+    test_manifest = "${target_gen_dir}/meta/${test_dest}.cmx"
+
+    package_tests += [
+      {
+        forward_variables_from(test,
+                               [
+                                 "name",
+                                 "dest",
+                                 "disabled",
+                               ])
+        manifest = test_manifest
+      },
+    ]
+
+    manifest_label = "get_test_package_manifest_${test_dest}.cmx"
+    generated_file(manifest_label) {
+      outputs = [
+        test_manifest,
+      ]
+      contents = {
+        program = {
+          binary = "test/${test_dest}"
+        }
+      }
+      output_conversion = "json"
+    }
+    manifest_labels += [ ":$manifest_label" ]
+  }
+
+  test_package(target_name) {
+    forward_variables_from(invoker,
+                           [
+                             "binaries",
+                             "components",
+                             "data_deps",
+                             "extra",
+                             "public_deps",
+                             "resources",
+                             "drivers",
+                             "loadable_modules",
+                             "package_name",
+                             "meta",
+                           ])
+    tests = package_tests
+    deps = []
+    if (defined(invoker.deps)) {
+      deps = invoker.deps
+    }
+    deps += manifest_labels
+  }
+}
diff --git a/examples/rust_static_linking/BUILD.gn b/examples/rust_static_linking/BUILD.gn
index f8f4f2d..e9f95fe 100644
--- a/examples/rust_static_linking/BUILD.gn
+++ b/examples/rust_static_linking/BUILD.gn
@@ -2,17 +2,15 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
-import("//build/package.gni")
 import("//build/rust/rustc_library.gni")
+import("//build/test/test_package.gni")
 
 rustc_library("static_linking") {
   name = "static_linking"
   with_unit_tests = true
   version = "0.1.0"
   edition = "2018"
-  non_rust_deps = [
-    ":static",
-  ]
+  non_rust_deps = [ ":static" ]
 }
 
 static_library("static") {
@@ -21,8 +19,7 @@
   ]
 }
 
-package("static_linking_tests") {
-  testonly = true
+unittest_package("static_linking_tests") {
   deps = [
     ":static_linking",
   ]
diff --git a/garnet/bin/debugserver/BUILD.gn b/garnet/bin/debugserver/BUILD.gn
index 62a3f64..b7e540b 100644
--- a/garnet/bin/debugserver/BUILD.gn
+++ b/garnet/bin/debugserver/BUILD.gn
@@ -3,6 +3,7 @@
 # found in the LICENSE file.
 
 import("//build/package.gni")
+import("//build/test/test_package.gni")
 import("//build/testing/environments.gni")
 
 executable("bin") {
@@ -57,8 +58,7 @@
   testonly = true
 
   public_deps = [
-    ":debugserver-unittests",
-    "test_apps",
+    ":debugserver_tests",
   ]
 }
 
@@ -100,11 +100,10 @@
   ]
 }
 
-package("debugserver_tests") {
-  testonly = true
-
+unittest_package("debugserver_tests") {
   deps = [
-    ":tests",
+    ":debugserver-unittests",
+    "test_apps",
   ]
 
   tests = [
diff --git a/garnet/packages/tests/BUILD.gn b/garnet/packages/tests/BUILD.gn
index a96dcd2..447fcd5 100644
--- a/garnet/packages/tests/BUILD.gn
+++ b/garnet/packages/tests/BUILD.gn
@@ -264,7 +264,7 @@
 group("debugserver") {
   testonly = true
   public_deps = [
-    "//garnet/bin/debugserver:debugserver_tests",
+    "//garnet/bin/debugserver:tests",
     "//garnet/lib/debugger_utils:debugger_utils_tests",
     "//garnet/lib/inferior_control:inferior_control_tests",
     "//garnet/packages/prod:debugserver",
diff --git a/garnet/public/lib/fidl/llcpp/BUILD.gn b/garnet/public/lib/fidl/llcpp/BUILD.gn
index e7052dc..53d0c7f 100644
--- a/garnet/public/lib/fidl/llcpp/BUILD.gn
+++ b/garnet/public/lib/fidl/llcpp/BUILD.gn
@@ -3,7 +3,8 @@
 # found in the LICENSE file.
 
 import("//build/fidl/fidl.gni")
-import("//build/package.gni")
+import("//build/test/test_package.gni")
+import("//build/testing/environments.gni")
 
 executable("fidl_llcpp_conformance_test_bin") {
   testonly = true
@@ -23,9 +24,7 @@
   ]
 }
 
-package("fidl_llcpp_conformance_test") {
-  testonly = true
-
+unittest_package("fidl_llcpp_conformance_test") {
   deps = [
     ":fidl_llcpp_conformance_test_bin",
   ]
@@ -61,11 +60,9 @@
   ]
 }
 
-package("fidl_llcpp_types_test") {
-  testonly = true
-
+unittest_package("fidl_llcpp_types_test") {
   deps = [
-    ":fidl_llcpp_types_test_bin"
+    ":fidl_llcpp_types_test_bin",
   ]
 
   tests = [
@@ -74,4 +71,4 @@
       environments = basic_envs
     },
   ]
-}
\ No newline at end of file
+}
diff --git a/garnet/public/rust/BUILD.gn b/garnet/public/rust/BUILD.gn
index 2bfca60..96d72e4 100644
--- a/garnet/public/rust/BUILD.gn
+++ b/garnet/public/rust/BUILD.gn
@@ -2,12 +2,10 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
-import("//build/package.gni")
+import("//build/test/test_package.gni")
 import("//build/testing/environments.gni")
 
-package("rust-crates-tests") {
-  testonly = true
-
+unittest_package("rust-crates-tests") {
   deps = [
     "fdio",
     "fuchsia-archive",