xref: /aosp_15_r20/external/cronet/testing/rust_gtest_interop/rust_gtest_interop.rs (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
1 // Copyright 2022 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 chromium::import! {
6     "//testing/rust_gtest_interop:gtest_attribute";
7 }
8 
9 use std::pin::Pin;
10 
11 /// Use `prelude:::*` to get access to all macros defined in this crate.
12 pub mod prelude {
13     // The #[extern_test_suite("cplusplus::Type") macro.
14     pub use gtest_attribute::extern_test_suite;
15     // The #[gtest(TestSuite, TestName)] macro.
16     pub use gtest_attribute::gtest;
17     // Gtest expectation macros, which should be used to verify test expectations.
18     // These replace the standard practice of using assert/panic in Rust tests
19     // which would crash the test binary.
20     pub use crate::expect_eq;
21     pub use crate::expect_false;
22     pub use crate::expect_ge;
23     pub use crate::expect_gt;
24     pub use crate::expect_le;
25     pub use crate::expect_lt;
26     pub use crate::expect_ne;
27     pub use crate::expect_true;
28 }
29 
30 // The gtest_attribute proc-macro crate makes use of small_ctor, with a path
31 // through this crate here to ensure it's available.
32 #[doc(hidden)]
33 pub extern crate small_ctor;
34 
35 /// A marker trait that promises the Rust type is an FFI wrapper around a C++
36 /// class which subclasses `testing::Test`. In particular, casting a
37 /// `testing::Test` pointer to the implementing class type is promised to be
38 /// valid.
39 ///
40 /// Implement this trait with the `#[extern_test_suite]` macro:
41 /// ```rs
42 /// #[extern_test_suite("cpp::type::wrapped::by::Foo")
43 /// unsafe impl TestSuite for Foo {}
44 /// ```
45 pub unsafe trait TestSuite {
46     /// Gives the Gtest factory function on the C++ side which constructs the
47     /// C++ class for which the implementing Rust type is an FFI wrapper.
48     #[doc(hidden)]
gtest_factory_fn_ptr() -> GtestFactoryFunction49     fn gtest_factory_fn_ptr() -> GtestFactoryFunction;
50 }
51 
52 /// Matches the C++ type `rust_gtest_interop::GtestFactoryFunction`, with the
53 /// `testing::Test` type erased to `OpaqueTestingTest`.
54 ///
55 /// We replace `testing::Test*` with `OpaqueTestingTest` because but we don't
56 /// know that C++ type in Rust, as we don't have a Rust generator giving access
57 /// to that type.
58 #[doc(hidden)]
59 pub type GtestFactoryFunction = unsafe extern "C" fn(
60     f: extern "C" fn(Pin<&mut OpaqueTestingTest>),
61 ) -> Pin<&'static mut OpaqueTestingTest>;
62 
63 /// Opaque replacement of a C++ `testing::Test` type, which can only be used as
64 /// a pointer, since its size is incorrect. Only appears in the
65 /// GtestFactoryFunction signature, which is a function pointer that passed to
66 /// C++, and never run from within Rust.
67 ///
68 /// See https://doc.rust-lang.org/nomicon/ffi.html#representing-opaque-structs
69 ///
70 /// TODO(danakj): If there was a way, without making references to it into wide
71 /// pointers, we should make this type be !Sized.
72 #[repr(C)]
73 #[doc(hidden)]
74 pub struct OpaqueTestingTest {
75     data: [u8; 0],
76     marker: std::marker::PhantomData<(*mut u8, std::marker::PhantomPinned)>,
77 }
78 
79 #[doc(hidden)]
80 pub trait TestResult {
into_error_message(self) -> Option<String>81     fn into_error_message(self) -> Option<String>;
82 }
83 impl TestResult for () {
into_error_message(self) -> Option<String>84     fn into_error_message(self) -> Option<String> {
85         None
86     }
87 }
88 // This impl requires an `Error` not just a `String` so that in the future we
89 // could print things like the backtrace too (though that field is currently
90 // unstable).
91 impl<E: Into<Box<dyn std::error::Error>>> TestResult for std::result::Result<(), E> {
into_error_message(self) -> Option<String>92     fn into_error_message(self) -> Option<String> {
93         match self {
94             Ok(_) => None,
95             Err(e) => Some(format!("Test returned error: {}", e.into())),
96         }
97     }
98 }
99 
100 // Internals used by code generated from the gtest-attriute proc-macro. Should
101 // not be used by human-written code.
102 #[doc(hidden)]
103 pub mod __private {
104     use super::{GtestFactoryFunction, OpaqueTestingTest, Pin};
105 
106     /// Rust wrapper around C++'s rust_gtest_add_failure().
107     ///
108     /// The wrapper converts the file name into a C++-friendly string,
109     /// and the line number into a C++-friendly signed int.
110     ///
111     /// TODO(crbug.com/1298175): We should be able to receive a C++-friendly
112     /// file path.
113     ///
114     /// TODO(danakj): We should be able to pass a `c_int` directly to C++:
115     /// https://github.com/dtolnay/cxx/issues/1015.
add_failure_at(file: &'static str, line: u32, message: &str)116     pub fn add_failure_at(file: &'static str, line: u32, message: &str) {
117         let null_term_file = std::ffi::CString::new(make_canonical_file_path(file)).unwrap();
118         let null_term_message = std::ffi::CString::new(message).unwrap();
119 
120         extern "C" {
121             fn rust_gtest_add_failure_at(
122                 file: *const std::ffi::c_char,
123                 line: i32,
124                 message: *const std::ffi::c_char,
125             );
126 
127         }
128         unsafe {
129             rust_gtest_add_failure_at(
130                 null_term_file.as_ptr(),
131                 line.try_into().unwrap_or(-1),
132                 null_term_message.as_ptr(),
133             )
134         }
135     }
136 
137     /// Turn a file!() string for a source file into a path from the root of the
138     /// source tree.
make_canonical_file_path(file: &str) -> String139     pub fn make_canonical_file_path(file: &str) -> String {
140         // The path of the file here is relative to and prefixed with the crate root's
141         // source file with the current directory being the build's output
142         // directory. So for a generated crate root at gen/foo/, the file path
143         // would look like `gen/foo/../../../../real/path.rs`. The last two `../
144         // ` move up from the build output directory to the source tree root. As such,
145         // we need to strip pairs of `something/../` until there are none left, and
146         // remove the remaining `../` path components up to the source tree
147         // root.
148         //
149         // Note that std::fs::canonicalize() does not work here since it requires the
150         // file to exist, but we're working with a relative path that is rooted
151         // in the build directory, not the current directory. We could try to
152         // get the path to the build directory.. but this is simple enough.
153         let (keep_rev, _) = std::path::Path::new(file).iter().rev().fold(
154             (Vec::new(), 0),
155             // Build the set of path components we want to keep, which we do by keeping a count of
156             // the `..` components and then dropping stuff that comes before them.
157             |(mut keep, dotdot_count), path_component| {
158                 if path_component == ".." {
159                     // The `..` component will skip the next downward component.
160                     (keep, dotdot_count + 1)
161                 } else if dotdot_count > 0 {
162                     // Skip the component as we drop it with `..` later in the path.
163                     (keep, dotdot_count - 1)
164                 } else {
165                     // Keep this component.
166                     keep.push(path_component);
167                     (keep, dotdot_count)
168                 }
169             },
170         );
171         // Reverse the path components, join them together, and write them into a
172         // string.
173         keep_rev
174             .into_iter()
175             .rev()
176             .fold(std::path::PathBuf::new(), |path, path_component| path.join(path_component))
177             .to_string_lossy()
178             .to_string()
179     }
180 
181     extern "C" {
182         /// extern for C++'s rust_gtest_default_factory().
183         /// TODO(danakj): We do this by hand because cxx doesn't support passing
184         /// raw function pointers: https://github.com/dtolnay/cxx/issues/1011.
rust_gtest_default_factory( f: extern "C" fn(Pin<&mut OpaqueTestingTest>), ) -> Pin<&'static mut OpaqueTestingTest>185         pub fn rust_gtest_default_factory(
186             f: extern "C" fn(Pin<&mut OpaqueTestingTest>),
187         ) -> Pin<&'static mut OpaqueTestingTest>;
188     }
189 
190     extern "C" {
191         /// extern for C++'s rust_gtest_add_test().
192         ///
193         /// Note that the `factory` parameter is actually a C++ function
194         /// pointer. TODO(danakj): We do this by hand because cxx
195         /// doesn't support passing raw function pointers nor passing `*const c_char`: https://github.com/dtolnay/cxx/issues/1011 and
196         /// https://github.com/dtolnay/cxx/issues/1015.
rust_gtest_add_test( factory: GtestFactoryFunction, run_test_fn: extern "C" fn(Pin<&mut OpaqueTestingTest>), test_suite_name: *const std::os::raw::c_char, test_name: *const std::os::raw::c_char, file: *const std::os::raw::c_char, line: i32, )197         pub fn rust_gtest_add_test(
198             factory: GtestFactoryFunction,
199             run_test_fn: extern "C" fn(Pin<&mut OpaqueTestingTest>),
200             test_suite_name: *const std::os::raw::c_char,
201             test_name: *const std::os::raw::c_char,
202             file: *const std::os::raw::c_char,
203             line: i32,
204         );
205     }
206 
207     /// Information used to register a function pointer as a test with the C++
208     /// Gtest framework.
209     pub struct TestRegistration {
210         pub func: extern "C" fn(suite: Pin<&mut OpaqueTestingTest>),
211         // TODO(danakj): These a C-String-Literals. Maybe we should expose that as a type
212         // somewhere.
213         pub test_suite_name: &'static [std::os::raw::c_char],
214         pub test_name: &'static [std::os::raw::c_char],
215         pub file: &'static [std::os::raw::c_char],
216         pub line: u32,
217         pub factory: GtestFactoryFunction,
218     }
219 
220     /// Register a given test function with the C++ Gtest framework.
221     ///
222     /// This function is called from static initializers. It may only be called
223     /// from the main thread, before main() is run. It may not panic, or
224     /// call anything that may panic.
register_test(r: TestRegistration)225     pub fn register_test(r: TestRegistration) {
226         let line = r.line.try_into().unwrap_or(-1);
227         // SAFETY: The `factory` parameter to rust_gtest_add_test() must be a C++
228         // function that returns a `testing::Test*` disguised as a
229         // `OpaqueTestingTest`. The #[gtest] macro will use
230         // `rust_gtest_interop::rust_gtest_default_factory()` by default.
231         unsafe {
232             rust_gtest_add_test(
233                 r.factory,
234                 r.func,
235                 r.test_suite_name.as_ptr(),
236                 r.test_name.as_ptr(),
237                 r.file.as_ptr(),
238                 line,
239             )
240         };
241     }
242 }
243 
244 mod expect_macros;
245