Add ignorabletest test harness.

This is a custom test harness which allows tests to be ignored at
runtime based on arbitrary conditions.

Bug: 260692911
Test: Ran apkdmverity and devicemapper tests with this harness
Change-Id: Ibafef988b8114ac54db569435bd854ea5b78ed2b
diff --git a/libs/ignorabletest/Android.bp b/libs/ignorabletest/Android.bp
new file mode 100644
index 0000000..0947243
--- /dev/null
+++ b/libs/ignorabletest/Android.bp
@@ -0,0 +1,26 @@
+rust_library {
+    name: "libignorabletest",
+    host_supported: true,
+    crate_name: "ignorabletest",
+    cargo_env_compat: true,
+    cargo_pkg_version: "0.1.0",
+    srcs: ["src/lib.rs"],
+    edition: "2021",
+    rustlibs: [
+        "liblibtest_mimic",
+    ],
+    proc_macros: ["libpaste"],
+    apex_available: [
+        "//apex_available:platform",
+        "//apex_available:anyapex",
+    ],
+}
+
+rust_defaults {
+    name: "ignorabletest.defaults",
+    test_harness: false,
+    cfgs: ["test"],
+    rustlibs: [
+        "libignorabletest",
+    ],
+}
diff --git a/libs/ignorabletest/README.md b/libs/ignorabletest/README.md
new file mode 100644
index 0000000..e03864f
--- /dev/null
+++ b/libs/ignorabletest/README.md
@@ -0,0 +1,78 @@
+# ignorabletest
+
+This is a custom Rust test harness which allows tests to be ignored at runtime based on arbitrary
+criteria. The built-in Rust test harness only allows tests to be ignored at compile time, but this
+is often not enough on Android, where we want to ignore tests based on system properties or other
+characteristics of the device on which the test is being run, which are not known at build time.
+
+## Usage
+
+Unfortunately without the built-in support that rustc provides to the standard test harness, this
+one is slightly more cumbersome to use. Firstly, add it to the `rust_test` build rule in your
+`Android.bp` by adding the defaults provided:
+
+```soong
+rust_test {
+    name: "mycrate.test",
+    defaults: ["ignorabletest.defaults"],
+    // ...
+}
+```
+
+If you are testing a binary that has a `main` function, you'll need to remove it from the test
+build:
+
+```rust
+#[cfg(not(test))]
+fn main() {
+    // ...
+}
+```
+
+(If you're testing a library or anything else which doesn't have a `main` function, you don't need
+to worry about this.)
+
+Each test case should be marked with the `ignorabletest::test!` macro, rather than the standard
+`#[test]` attribute:
+
+```rust
+use ignorabletest::test;
+
+test!(one_plus_one);
+fn one_plus_one {
+    assert_eq!(1 + 1, 2);
+}
+```
+
+To ignore a test, you can add an `ignore_if` clause with a boolean expression:
+
+```rust
+use ignorabletest::test;
+
+test!(clap_hands, ignore_if: !feeling_happy());
+fn clap_hands {
+    assert!(HANDS.clap().is_ok());
+}
+```
+
+Somewhere in your main module, you need to use the `test_main` macro to generate an entry point for
+the test harness. This needs to be provided with a list of all tests, which can be generated by the `list_tests!` macro:
+
+```rust
+#[cfg(test)]
+ignorabletest::test_main!(tests::all_tests());
+
+#[cfg(test)]
+mod tests {
+    use ignorabletest::{list_tests, test};
+
+    list_tests! {all_tests: [
+        one_plus_one,
+        clap_hands,
+    ]}
+
+    // ...
+}
+```
+
+You can then run your tests as usual with `atest`.
diff --git a/libs/ignorabletest/src/lib.rs b/libs/ignorabletest/src/lib.rs
new file mode 100644
index 0000000..746c0dd
--- /dev/null
+++ b/libs/ignorabletest/src/lib.rs
@@ -0,0 +1,80 @@
+//! Test harness which supports ignoring tests at runtime.
+
+pub mod runner;
+
+#[doc(hidden)]
+pub use libtest_mimic as _libtest_mimic;
+#[doc(hidden)]
+pub use paste as _paste;
+
+/// Macro to generate the main function for the test harness.
+#[macro_export]
+macro_rules! test_main {
+    ($tests:expr) => {
+        #[cfg(test)]
+        fn main() {
+            ignorabletest::runner::main($tests)
+        }
+    };
+}
+
+/// Macro to generate a function which returns a list of tests to be run.
+///
+/// # Usage
+/// ```
+/// list_tests!{all_tests: [test_this, test_that]};
+///
+/// test!(test_this);
+/// fn test_this() {
+///   // ...
+/// }
+///
+/// test!(test_that);
+/// fn test_that() {
+///   // ...
+/// }
+/// ```
+#[macro_export]
+macro_rules! list_tests {
+    {$function_name:ident: [$( $test_name:ident ),* $(,)? ]} => {
+        pub fn $function_name() -> ::std::vec::Vec<$crate::_libtest_mimic::Trial> {
+            vec![
+                $( $crate::_paste::paste!([<__test_ $test_name>]()) ),*
+            ]
+        }
+    };
+}
+
+/// Macro to generate a wrapper function for a single test.
+///
+/// # Usage
+///
+/// ```
+/// test!(test_string_equality);
+/// fn test_string_equality() {
+///   assert_eq!("", "");
+/// }
+/// ```
+#[macro_export]
+macro_rules! test {
+    ($test_name:ident) => {
+        $crate::_paste::paste!(
+            fn [< __test_ $test_name >]() -> $crate::_libtest_mimic::Trial {
+                $crate::_libtest_mimic::Trial::test(
+                    ::std::stringify!($test_name),
+                    move || ignorabletest::runner::run($test_name),
+                )
+            }
+        );
+    };
+    ($test_name:ident, ignore_if: $ignore_expr:expr) => {
+        $crate::_paste::paste!(
+            fn [< __test_ $test_name >]() -> $crate::_libtest_mimic::Trial {
+                $crate::_libtest_mimic::Trial::test(
+                    ::std::stringify!($test_name),
+                    move || ignorabletest::runner::run($test_name),
+                ).with_ignored_flag($ignore_expr)
+            }
+        );
+    };
+}
diff --git a/libs/ignorabletest/src/runner.rs b/libs/ignorabletest/src/runner.rs
new file mode 100644
index 0000000..e1b14e0
--- /dev/null
+++ b/libs/ignorabletest/src/runner.rs
@@ -0,0 +1,20 @@
+//! Test runner.
+
+use core::ops::{Deref, FnOnce};
+use libtest_mimic::{Arguments, Failed, Trial};
+use std::env;
+
+/// Command-line arguments to ignore, because they are not supported by libtest-mimic.
+const IGNORED_ARGS: [&str; 2] = ["-Zunstable-options", "--report-time"];
+
+/// Runs all tests.
+pub fn main(tests: Vec<Trial>) {
+    let args = Arguments::from_iter(env::args().filter(|arg| !IGNORED_ARGS.contains(&arg.deref())));
+    libtest_mimic::run(&args, tests).exit();
+}
+
+/// Runs the given test.
+pub fn run(test: impl FnOnce()) -> Result<(), Failed> {
+    test();
+    Ok(())
+}