Avi Drissman | dfd88085 | 2022-09-15 20:11:09 | [diff] [blame] | 1 | // Copyright 2022 The Chromium Authors |
danakj | 71dabfa | 2022-02-24 14:32:14 | [diff] [blame] | 2 | // Use of this source code is governed by a BSD-style license that can be |
| 3 | // found in the LICENSE file. |
| 4 | |
danakj | 62e4aae | 2023-12-15 22:31:22 | [diff] [blame] | 5 | chromium::import! { |
| 6 | "//testing/rust_gtest_interop:gtest_attribute"; |
| 7 | } |
| 8 | |
danakj | 2f9c627 | 2022-05-12 20:53:06 | [diff] [blame] | 9 | use std::pin::Pin; |
| 10 | |
danakj | 71dabfa | 2022-02-24 14:32:14 | [diff] [blame] | 11 | /// Use `prelude:::*` to get access to all macros defined in this crate. |
| 12 | pub mod prelude { |
danakj | 2f9c627 | 2022-05-12 20:53:06 | [diff] [blame] | 13 | // The #[extern_test_suite("cplusplus::Type") macro. |
| 14 | pub use gtest_attribute::extern_test_suite; |
danakj | 71dabfa | 2022-02-24 14:32:14 | [diff] [blame] | 15 | // The #[gtest(TestSuite, TestName)] macro. |
| 16 | pub use gtest_attribute::gtest; |
Collin Baker | dbf27b4 | 2022-11-29 20:24:57 | [diff] [blame] | 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. |
danakj | 71dabfa | 2022-02-24 14:32:14 | [diff] [blame] | 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 | |
[email protected] | 720cb5f | 2023-07-28 21:49:43 | [diff] [blame] | 30 | // The gtest_attribute proc-macro crate makes use of small_ctor, with a path |
Collin Baker | dbf27b4 | 2022-11-29 20:24:57 | [diff] [blame] | 31 | // through this crate here to ensure it's available. |
danakj | 71dabfa | 2022-02-24 14:32:14 | [diff] [blame] | 32 | #[doc(hidden)] |
[email protected] | 720cb5f | 2023-07-28 21:49:43 | [diff] [blame] | 33 | pub extern crate small_ctor; |
danakj | 71dabfa | 2022-02-24 14:32:14 | [diff] [blame] | 34 | |
Collin Baker | dbf27b4 | 2022-11-29 20:24:57 | [diff] [blame] | 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. |
danakj | 2f9c627 | 2022-05-12 20:53:06 | [diff] [blame] | 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 { |
Collin Baker | dbf27b4 | 2022-11-29 20:24:57 | [diff] [blame] | 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. |
danakj | 2f9c627 | 2022-05-12 20:53:06 | [diff] [blame] | 48 | #[doc(hidden)] |
| 49 | fn gtest_factory_fn_ptr() -> GtestFactoryFunction; |
| 50 | } |
| 51 | |
Collin Baker | dbf27b4 | 2022-11-29 20:24:57 | [diff] [blame] | 52 | /// Matches the C++ type `rust_gtest_interop::GtestFactoryFunction`, with the |
| 53 | /// `testing::Test` type erased to `OpaqueTestingTest`. |
danakj | 2f9c627 | 2022-05-12 20:53:06 | [diff] [blame] | 54 | /// |
Collin Baker | dbf27b4 | 2022-11-29 20:24:57 | [diff] [blame] | 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. |
danakj | 2f9c627 | 2022-05-12 20:53:06 | [diff] [blame] | 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 | |
Collin Baker | dbf27b4 | 2022-11-29 20:24:57 | [diff] [blame] | 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. |
danakj | 2f9c627 | 2022-05-12 20:53:06 | [diff] [blame] | 67 | /// |
Collin Baker | 153146f3 | 2023-03-09 20:08:41 | [diff] [blame] | 68 | /// See https://doc.rust-lang.org/nomicon/ffi.html#representing-opaque-structs |
| 69 | /// |
Collin Baker | dbf27b4 | 2022-11-29 20:24:57 | [diff] [blame] | 70 | /// TODO(danakj): If there was a way, without making references to it into wide |
| 71 | /// pointers, we should make this type be !Sized. |
danakj | 2f9c627 | 2022-05-12 20:53:06 | [diff] [blame] | 72 | #[repr(C)] |
| 73 | #[doc(hidden)] |
Collin Baker | 153146f3 | 2023-03-09 20:08:41 | [diff] [blame] | 74 | pub struct OpaqueTestingTest { |
| 75 | data: [u8; 0], |
| 76 | marker: std::marker::PhantomData<(*mut u8, std::marker::PhantomPinned)>, |
| 77 | } |
danakj | 2f9c627 | 2022-05-12 20:53:06 | [diff] [blame] | 78 | |
danakj | 2f9c627 | 2022-05-12 20:53:06 | [diff] [blame] | 79 | #[doc(hidden)] |
danakj | 71dabfa | 2022-02-24 14:32:14 | [diff] [blame] | 80 | pub trait TestResult { |
| 81 | fn into_error_message(self) -> Option<String>; |
| 82 | } |
| 83 | impl TestResult for () { |
| 84 | fn into_error_message(self) -> Option<String> { |
| 85 | None |
| 86 | } |
| 87 | } |
Collin Baker | dbf27b4 | 2022-11-29 20:24:57 | [diff] [blame] | 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). |
danakj | 71dabfa | 2022-02-24 14:32:14 | [diff] [blame] | 91 | impl<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 Baker | dbf27b4 | 2022-11-29 20:24:57 | [diff] [blame] | 100 | // Internals used by code generated from the gtest-attriute proc-macro. Should |
| 101 | // not be used by human-written code. |
danakj | 71dabfa | 2022-02-24 14:32:14 | [diff] [blame] | 102 | #[doc(hidden)] |
| 103 | pub mod __private { |
danakj | 2f9c627 | 2022-05-12 20:53:06 | [diff] [blame] | 104 | use super::{GtestFactoryFunction, OpaqueTestingTest, Pin}; |
danakj | 8029f80 | 2022-04-01 14:43:43 | [diff] [blame] | 105 | |
Daniel Cheng | 79bdb0c | 2023-10-17 00:55:30 | [diff] [blame] | 106 | /// Rust wrapper around C++'s rust_gtest_add_failure(). |
danakj | 71dabfa | 2022-02-24 14:32:14 | [diff] [blame] | 107 | /// |
Daniel Cheng | 79bdb0c | 2023-10-17 00:55:30 | [diff] [blame] | 108 | /// The wrapper converts the file name into a C++-friendly string, |
Collin Baker | dbf27b4 | 2022-11-29 20:24:57 | [diff] [blame] | 109 | /// and the line number into a C++-friendly signed int. |
danakj | 71dabfa | 2022-02-24 14:32:14 | [diff] [blame] | 110 | /// |
Alison Gale | 71bd8f15 | 2024-04-26 22:38:20 | [diff] [blame] | 111 | /// TODO(crbug.com/40215436): We should be able to receive a C++-friendly |
Collin Baker | dbf27b4 | 2022-11-29 20:24:57 | [diff] [blame] | 112 | /// file path. |
danakj | 71dabfa | 2022-02-24 14:32:14 | [diff] [blame] | 113 | /// |
| 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) { |
danakj | 8295221f | 2022-09-30 14:05:10 | [diff] [blame] | 117 | let null_term_file = std::ffi::CString::new(make_canonical_file_path(file)).unwrap(); |
danakj | 7b4ff3b | 2022-11-04 20:27:43 | [diff] [blame] | 118 | let null_term_message = std::ffi::CString::new(message).unwrap(); |
| 119 | |
| 120 | extern "C" { |
Daniel Cheng | 79bdb0c | 2023-10-17 00:55:30 | [diff] [blame] | 121 | fn rust_gtest_add_failure_at( |
danakj | 662d527 | 2022-11-09 23:10:37 | [diff] [blame] | 122 | file: *const std::ffi::c_char, |
danakj | 7b4ff3b | 2022-11-04 20:27:43 | [diff] [blame] | 123 | line: i32, |
danakj | 662d527 | 2022-11-09 23:10:37 | [diff] [blame] | 124 | message: *const std::ffi::c_char, |
danakj | 7b4ff3b | 2022-11-04 20:27:43 | [diff] [blame] | 125 | ); |
| 126 | |
| 127 | } |
danakj | 71dabfa | 2022-02-24 14:32:14 | [diff] [blame] | 128 | unsafe { |
Daniel Cheng | 79bdb0c | 2023-10-17 00:55:30 | [diff] [blame] | 129 | rust_gtest_add_failure_at( |
danakj | 662d527 | 2022-11-09 23:10:37 | [diff] [blame] | 130 | null_term_file.as_ptr(), |
danakj | 71dabfa | 2022-02-24 14:32:14 | [diff] [blame] | 131 | line.try_into().unwrap_or(-1), |
danakj | 662d527 | 2022-11-09 23:10:37 | [diff] [blame] | 132 | null_term_message.as_ptr(), |
danakj | 71dabfa | 2022-02-24 14:32:14 | [diff] [blame] | 133 | ) |
| 134 | } |
| 135 | } |
| 136 | |
Collin Baker | dbf27b4 | 2022-11-29 20:24:57 | [diff] [blame] | 137 | /// Turn a file!() string for a source file into a path from the root of the |
| 138 | /// source tree. |
danakj | 8295221f | 2022-09-30 14:05:10 | [diff] [blame] | 139 | pub fn make_canonical_file_path(file: &str) -> String { |
Collin Baker | dbf27b4 | 2022-11-29 20:24:57 | [diff] [blame] | 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. |
danakj | 8295221f | 2022-09-30 14:05:10 | [diff] [blame] | 148 | // |
Collin Baker | dbf27b4 | 2022-11-29 20:24:57 | [diff] [blame] | 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. |
danakj | 8295221f | 2022-09-30 14:05:10 | [diff] [blame] | 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 | ); |
Collin Baker | dbf27b4 | 2022-11-29 20:24:57 | [diff] [blame] | 171 | // Reverse the path components, join them together, and write them into a |
| 172 | // string. |
danakj | 8295221f | 2022-09-30 14:05:10 | [diff] [blame] | 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 | |
Daniel Cheng | 79bdb0c | 2023-10-17 00:55:30 | [diff] [blame] | 181 | extern "C" { |
Daniel Cheng | 79bdb0c | 2023-10-17 00:55:30 | [diff] [blame] | 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. |
| 185 | pub fn rust_gtest_default_factory( |
| 186 | f: extern "C" fn(Pin<&mut OpaqueTestingTest>), |
| 187 | ) -> Pin<&'static mut OpaqueTestingTest>; |
danakj | 2f9c627 | 2022-05-12 20:53:06 | [diff] [blame] | 188 | } |
| 189 | |
Daniel Cheng | 79bdb0c | 2023-10-17 00:55:30 | [diff] [blame] | 190 | extern "C" { |
Daniel Cheng | 79bdb0c | 2023-10-17 00:55:30 | [diff] [blame] | 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. |
| 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 | ); |
danakj | 71dabfa | 2022-02-24 14:32:14 | [diff] [blame] | 205 | } |
| 206 | |
Collin Baker | dbf27b4 | 2022-11-29 20:24:57 | [diff] [blame] | 207 | /// Information used to register a function pointer as a test with the C++ |
| 208 | /// Gtest framework. |
danakj | 71dabfa | 2022-02-24 14:32:14 | [diff] [blame] | 209 | pub struct TestRegistration { |
danakj | 2f9c627 | 2022-05-12 20:53:06 | [diff] [blame] | 210 | pub func: extern "C" fn(suite: Pin<&mut OpaqueTestingTest>), |
Collin Baker | dbf27b4 | 2022-11-29 20:24:57 | [diff] [blame] | 211 | // TODO(danakj): These a C-String-Literals. Maybe we should expose that as a type |
| 212 | // somewhere. |
danakj | 71dabfa | 2022-02-24 14:32:14 | [diff] [blame] | 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, |
danakj | 8029f80 | 2022-04-01 14:43:43 | [diff] [blame] | 217 | pub factory: GtestFactoryFunction, |
danakj | 71dabfa | 2022-02-24 14:32:14 | [diff] [blame] | 218 | } |
| 219 | |
| 220 | /// Register a given test function with the C++ Gtest framework. |
| 221 | /// |
Collin Baker | dbf27b4 | 2022-11-29 20:24:57 | [diff] [blame] | 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. |
danakj | 71dabfa | 2022-02-24 14:32:14 | [diff] [blame] | 225 | pub fn register_test(r: TestRegistration) { |
| 226 | let line = r.line.try_into().unwrap_or(-1); |
Collin Baker | dbf27b4 | 2022-11-29 20:24:57 | [diff] [blame] | 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 |
danakj | 8029f80 | 2022-04-01 14:43:43 | [diff] [blame] | 230 | // `rust_gtest_interop::rust_gtest_default_factory()` by default. |
danakj | 84ba211 | 2022-03-15 18:40:39 | [diff] [blame] | 231 | unsafe { |
danakj | 84ba211 | 2022-03-15 18:40:39 | [diff] [blame] | 232 | rust_gtest_add_test( |
danakj | 8029f80 | 2022-04-01 14:43:43 | [diff] [blame] | 233 | r.factory, |
danakj | 84ba211 | 2022-03-15 18:40:39 | [diff] [blame] | 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 | }; |
danakj | 71dabfa | 2022-02-24 14:32:14 | [diff] [blame] | 241 | } |
| 242 | } |
| 243 | |
| 244 | mod expect_macros; |