blob: 35b0515fbe573f119956b68e5ad67941cb7f5c6c [file] [log] [blame]
Avi Drissmandfd880852022-09-15 20:11:091// Copyright 2022 The Chromium Authors
danakj71dabfa2022-02-24 14:32:142// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
danakj62e4aae2023-12-15 22:31:225chromium::import! {
6 "//testing/rust_gtest_interop:gtest_attribute";
7}
8
danakj2f9c6272022-05-12 20:53:069use std::pin::Pin;
10
danakj71dabfa2022-02-24 14:32:1411/// Use `prelude:::*` to get access to all macros defined in this crate.
12pub mod prelude {
danakj2f9c6272022-05-12 20:53:0613 // The #[extern_test_suite("cplusplus::Type") macro.
14 pub use gtest_attribute::extern_test_suite;
danakj71dabfa2022-02-24 14:32:1415 // The #[gtest(TestSuite, TestName)] macro.
16 pub use gtest_attribute::gtest;
Collin Bakerdbf27b42022-11-29 20:24:5717 // 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.
danakj71dabfa2022-02-24 14:32:1420 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
[email protected]720cb5f2023-07-28 21:49:4330// The gtest_attribute proc-macro crate makes use of small_ctor, with a path
Collin Bakerdbf27b42022-11-29 20:24:5731// through this crate here to ensure it's available.
danakj71dabfa2022-02-24 14:32:1432#[doc(hidden)]
[email protected]720cb5f2023-07-28 21:49:4333pub extern crate small_ctor;
danakj71dabfa2022-02-24 14:32:1434
Collin Bakerdbf27b42022-11-29 20:24:5735/// 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.
danakj2f9c6272022-05-12 20:53:0639///
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/// ```
45pub unsafe trait TestSuite {
Collin Bakerdbf27b42022-11-29 20:24:5746 /// 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.
danakj2f9c6272022-05-12 20:53:0648 #[doc(hidden)]
49 fn gtest_factory_fn_ptr() -> GtestFactoryFunction;
50}
51
Collin Bakerdbf27b42022-11-29 20:24:5752/// Matches the C++ type `rust_gtest_interop::GtestFactoryFunction`, with the
53/// `testing::Test` type erased to `OpaqueTestingTest`.
danakj2f9c6272022-05-12 20:53:0654///
Collin Bakerdbf27b42022-11-29 20:24:5755/// 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.
danakj2f9c6272022-05-12 20:53:0658#[doc(hidden)]
59pub type GtestFactoryFunction = unsafe extern "C" fn(
60 f: extern "C" fn(Pin<&mut OpaqueTestingTest>),
61) -> Pin<&'static mut OpaqueTestingTest>;
62
Collin Bakerdbf27b42022-11-29 20:24:5763/// 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.
danakj2f9c6272022-05-12 20:53:0667///
Collin Baker153146f32023-03-09 20:08:4168/// See https://doc.rust-lang.org/nomicon/ffi.html#representing-opaque-structs
69///
Collin Bakerdbf27b42022-11-29 20:24:5770/// TODO(danakj): If there was a way, without making references to it into wide
71/// pointers, we should make this type be !Sized.
danakj2f9c6272022-05-12 20:53:0672#[repr(C)]
73#[doc(hidden)]
Collin Baker153146f32023-03-09 20:08:4174pub struct OpaqueTestingTest {
75 data: [u8; 0],
76 marker: std::marker::PhantomData<(*mut u8, std::marker::PhantomPinned)>,
77}
danakj2f9c6272022-05-12 20:53:0678
danakj2f9c6272022-05-12 20:53:0679#[doc(hidden)]
danakj71dabfa2022-02-24 14:32:1480pub trait TestResult {
81 fn into_error_message(self) -> Option<String>;
82}
83impl TestResult for () {
84 fn into_error_message(self) -> Option<String> {
85 None
86 }
87}
Collin Bakerdbf27b42022-11-29 20:24:5788// 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).
danakj71dabfa2022-02-24 14:32:1491impl<E: Into<Box<dyn std::error::Error>>> TestResult for std::result::Result<(), E> {
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
Collin Bakerdbf27b42022-11-29 20:24:57100// Internals used by code generated from the gtest-attriute proc-macro. Should
101// not be used by human-written code.
danakj71dabfa2022-02-24 14:32:14102#[doc(hidden)]
103pub mod __private {
danakj2f9c6272022-05-12 20:53:06104 use super::{GtestFactoryFunction, OpaqueTestingTest, Pin};
danakj8029f802022-04-01 14:43:43105
Daniel Cheng79bdb0c2023-10-17 00:55:30106 /// Rust wrapper around C++'s rust_gtest_add_failure().
danakj71dabfa2022-02-24 14:32:14107 ///
Daniel Cheng79bdb0c2023-10-17 00:55:30108 /// The wrapper converts the file name into a C++-friendly string,
Collin Bakerdbf27b42022-11-29 20:24:57109 /// and the line number into a C++-friendly signed int.
danakj71dabfa2022-02-24 14:32:14110 ///
Alison Gale71bd8f152024-04-26 22:38:20111 /// TODO(crbug.com/40215436): We should be able to receive a C++-friendly
Collin Bakerdbf27b42022-11-29 20:24:57112 /// file path.
danakj71dabfa2022-02-24 14:32:14113 ///
114 /// TODO(danakj): We should be able to pass a `c_int` directly to C++:
115 /// https://github.com/dtolnay/cxx/issues/1015.
116 pub fn add_failure_at(file: &'static str, line: u32, message: &str) {
danakj8295221f2022-09-30 14:05:10117 let null_term_file = std::ffi::CString::new(make_canonical_file_path(file)).unwrap();
danakj7b4ff3b2022-11-04 20:27:43118 let null_term_message = std::ffi::CString::new(message).unwrap();
119
120 extern "C" {
Daniel Cheng79bdb0c2023-10-17 00:55:30121 fn rust_gtest_add_failure_at(
danakj662d5272022-11-09 23:10:37122 file: *const std::ffi::c_char,
danakj7b4ff3b2022-11-04 20:27:43123 line: i32,
danakj662d5272022-11-09 23:10:37124 message: *const std::ffi::c_char,
danakj7b4ff3b2022-11-04 20:27:43125 );
126
127 }
danakj71dabfa2022-02-24 14:32:14128 unsafe {
Daniel Cheng79bdb0c2023-10-17 00:55:30129 rust_gtest_add_failure_at(
danakj662d5272022-11-09 23:10:37130 null_term_file.as_ptr(),
danakj71dabfa2022-02-24 14:32:14131 line.try_into().unwrap_or(-1),
danakj662d5272022-11-09 23:10:37132 null_term_message.as_ptr(),
danakj71dabfa2022-02-24 14:32:14133 )
134 }
135 }
136
Collin Bakerdbf27b42022-11-29 20:24:57137 /// Turn a file!() string for a source file into a path from the root of the
138 /// source tree.
danakj8295221f2022-09-30 14:05:10139 pub fn make_canonical_file_path(file: &str) -> String {
Collin Bakerdbf27b42022-11-29 20:24:57140 // 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.
danakj8295221f2022-09-30 14:05:10148 //
Collin Bakerdbf27b42022-11-29 20:24:57149 // 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.
danakj8295221f2022-09-30 14:05:10153 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 );
Collin Bakerdbf27b42022-11-29 20:24:57171 // Reverse the path components, join them together, and write them into a
172 // string.
danakj8295221f2022-09-30 14:05:10173 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
Daniel Cheng79bdb0c2023-10-17 00:55:30181 extern "C" {
Daniel Cheng79bdb0c2023-10-17 00:55:30182 /// 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.
185 pub fn rust_gtest_default_factory(
186 f: extern "C" fn(Pin<&mut OpaqueTestingTest>),
187 ) -> Pin<&'static mut OpaqueTestingTest>;
danakj2f9c6272022-05-12 20:53:06188 }
189
Daniel Cheng79bdb0c2023-10-17 00:55:30190 extern "C" {
Daniel Cheng79bdb0c2023-10-17 00:55:30191 /// 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.
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 );
danakj71dabfa2022-02-24 14:32:14205 }
206
Collin Bakerdbf27b42022-11-29 20:24:57207 /// Information used to register a function pointer as a test with the C++
208 /// Gtest framework.
danakj71dabfa2022-02-24 14:32:14209 pub struct TestRegistration {
danakj2f9c6272022-05-12 20:53:06210 pub func: extern "C" fn(suite: Pin<&mut OpaqueTestingTest>),
Collin Bakerdbf27b42022-11-29 20:24:57211 // TODO(danakj): These a C-String-Literals. Maybe we should expose that as a type
212 // somewhere.
danakj71dabfa2022-02-24 14:32:14213 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,
danakj8029f802022-04-01 14:43:43217 pub factory: GtestFactoryFunction,
danakj71dabfa2022-02-24 14:32:14218 }
219
220 /// Register a given test function with the C++ Gtest framework.
221 ///
Collin Bakerdbf27b42022-11-29 20:24:57222 /// 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.
danakj71dabfa2022-02-24 14:32:14225 pub fn register_test(r: TestRegistration) {
226 let line = r.line.try_into().unwrap_or(-1);
Collin Bakerdbf27b42022-11-29 20:24:57227 // 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
danakj8029f802022-04-01 14:43:43230 // `rust_gtest_interop::rust_gtest_default_factory()` by default.
danakj84ba2112022-03-15 18:40:39231 unsafe {
danakj84ba2112022-03-15 18:40:39232 rust_gtest_add_test(
danakj8029f802022-04-01 14:43:43233 r.factory,
danakj84ba2112022-03-15 18:40:39234 r.func,
235 r.test_suite_name.as_ptr(),
236 r.test_name.as_ptr(),
237 r.file.as_ptr(),
238 line,
239 )
240 };
danakj71dabfa2022-02-24 14:32:14241 }
242}
243
244mod expect_macros;