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