Add support for writing Gtest tests in Rust

Introduce a #[gtest(TestSuite, TestName)] macro, which is a Rust
equivalent to the C++ TEST(TestSuite, TestName) macro. The test's name
will be TestSuite.TestName, just as with the C++ macro.

The #[gtest] macro can be placed on any Rust fn, which will make it run
as a test inside Gtest. It should be used in binary test targets that
call RUN_ALL_TESTS() from C++ code, which is the Gtest main entrypoint.

Since Gtest tests all run on the main thread, Rust tests can not report
failures by causing panics. So we also introduce expect_eq!() and
friends as macros that print details about failures and report the
failure, along with the file path and line number, to Gtest.

Example output with a failure:

[==========] Running 6 tests from 1 test suite.
[----------] Global test environment set-up.
[----------] 6 tests from RustValues
[ RUN      ] RustValues.SetList
[       OK ] RustValues.SetList (0 ms)
[ RUN      ] RustValues.ReuseSlot
base/values_unittest.rs:91: Failure
Failed
Expected: 1 + 1 == 3
Actual: 2 vs 3
Stack trace:
#0 0x56325659ea43 rust_gtest_interop::rust_gtest_add_failure_at() [../../testing/rust_gtest_interop/rust_gtest_interop.cc:39:3]
#1 0x56325659f206 rust_gtest_interop_rs::__private::add_failure_at::h81802d5132f81496 [../../testing/rust_gtest_interop/rust_gtest_interop.rs:42:23]
#2 0x56325659dee0 run_test_testing_rust_gtest_interop_gtest_attribute_rs_RustValues_ReuseSlot_test_reuse_slot [../../base/values_unittest.rs:91:5]

[  FAILED  ] RustValues.ReuseSlot (24 ms)
[ RUN      ] RustValues.StartsNone
[       OK ] RustValues.StartsNone (0 ms)
[ RUN      ] RustValues.SetDict
[       OK ] RustValues.SetDict (0 ms)
[ RUN      ] RustValues.SetSimpleOptionalValues
[       OK ] RustValues.SetSimpleOptionalValues (0 ms)
[ RUN      ] RustValues.Dealloc
[       OK ] RustValues.Dealloc (0 ms)
[----------] 6 tests from RustValues (25 ms total)

[----------] Global test environment tear-down
[==========] 6 tests from 1 test suite ran. (25 ms total)

Tests can return either () or a Result<(), E>. If they return a
different type, they will fail.

A Result<i32> return type:

#[gtest(Test, WithNonVoidResultType)]
fn test() -> std::io::Result<i32> {
    expect_true!(true);
    Ok(1)
}

[ RUN      ] Test.WithNonVoidResultType
testing/rust_gtest_interop/rust_gtest_interop_unittest.rs:62: Failure
Failed
Invalid test return type. Must be `()` or `std::result::Result`. If you have a different Result type, try `.into()`.
[  FAILED  ] Test.WithNonVoidResultType (19 ms)

A bool return type:

#[gtest(Test, WithNonVoidType)]
fn test() -> bool {
    expect_true!(true);
    true
}

[ RUN      ] Test.WithNonVoidType
testing/rust_gtest_interop/rust_gtest_interop_unittest.rs:68: Failure
Failed
Invalid test return type. Must be `()` or `std::result::Result`. If you have a different Result type, try `.into()`.
[  FAILED  ] Test.WithNonVoidType (10 ms)

If the test returns a Result and it evaluates to Err, the test will also
fail:

#[gtest(Test, WithError)]
fn test() -> std::result::Result<(), Box<dyn std::error::Error>> {
    expect_true!(true);
    Err("uhoh".into())
}

[ RUN      ] Test.WithError
testing/rust_gtest_interop/rust_gtest_interop_unittest.rs:74: Failure
Failed
Test returned Err: uhoh
[  FAILED  ] Test.WithError (8 ms)

Bug: 1293979
Change-Id: I496626719d80c251c7fe2ad4755b5367f720772e
Cq-Include-Trybots: luci.chromium.try:linux-rust-x64-rel,android-rust-arm-rel
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3449134
Reviewed-by: John Chen <[email protected]>
Reviewed-by: Nico Weber <[email protected]>
Reviewed-by: Ɓukasz Anforowicz <[email protected]>
Commit-Queue: danakj <[email protected]>
Cr-Commit-Position: refs/heads/main@{#974627}
diff --git a/BUILD.gn b/BUILD.gn
index 20fc4f1..054be18 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -865,6 +865,9 @@
       "//mojo/public/rust:tests",
     ]
   }
+  if (enable_rust) {
+    deps += [ "//testing/rust_gtest_interop:rust_gtest_interop_unittests" ]
+  }
 }
 
 group("rust_build_tests") {