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