rust_gtest_interop.rs 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236
  1. // Copyright 2022 The Chromium Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style license that can be
  3. // found in the LICENSE file.
  4. use std::pin::Pin;
  5. /// Use `prelude:::*` to get access to all macros defined in this crate.
  6. pub mod prelude {
  7. // The #[extern_test_suite("cplusplus::Type") macro.
  8. pub use gtest_attribute::extern_test_suite;
  9. // The #[gtest(TestSuite, TestName)] macro.
  10. pub use gtest_attribute::gtest;
  11. // Gtest expectation macros, which should be used to verify test expectations. These replace the
  12. // standard practice of using assert/panic in Rust tests which would crash the test binary.
  13. pub use crate::expect_eq;
  14. pub use crate::expect_false;
  15. pub use crate::expect_ge;
  16. pub use crate::expect_gt;
  17. pub use crate::expect_le;
  18. pub use crate::expect_lt;
  19. pub use crate::expect_ne;
  20. pub use crate::expect_true;
  21. }
  22. // The gtest_attribute proc-macro crate makes use of small_ctor, with a path through this crate here
  23. // to ensure it's available.
  24. #[doc(hidden)]
  25. pub extern crate small_ctor;
  26. /// A marker trait that promises the Rust type is an FFI wrapper around a C++ class which subclasses
  27. /// `testing::Test`. In particular, casting a `testing::Test` pointer to the implementing class type
  28. /// is promised to be valid.
  29. ///
  30. /// Implement this trait with the `#[extern_test_suite]` macro:
  31. /// ```rs
  32. /// #[extern_test_suite("cpp::type::wrapped::by::Foo")
  33. /// unsafe impl TestSuite for Foo {}
  34. /// ```
  35. pub unsafe trait TestSuite {
  36. /// Gives the Gtest factory function on the C++ side which constructs the C++ class for which
  37. /// the implementing Rust type is an FFI wrapper.
  38. #[doc(hidden)]
  39. fn gtest_factory_fn_ptr() -> GtestFactoryFunction;
  40. }
  41. /// Matches the C++ type `rust_gtest_interop::GtestFactoryFunction`, with the `testing::Test` type
  42. /// erased to `OpaqueTestingTest`.
  43. ///
  44. /// We replace `testing::Test*` with `OpaqueTestingTest` because but we don't know that C++ type in
  45. /// Rust, as we don't have a Rust generator giving access to that type.
  46. #[doc(hidden)]
  47. pub type GtestFactoryFunction = unsafe extern "C" fn(
  48. f: extern "C" fn(Pin<&mut OpaqueTestingTest>),
  49. ) -> Pin<&'static mut OpaqueTestingTest>;
  50. /// Opaque replacement of a C++ `testing::Test` type, which can only be used as a pointer, since its
  51. /// size is incorrect. Only appears in the GtestFactoryFunction signature, which is a function
  52. /// pointer that passed to C++, and never run from within Rust.
  53. ///
  54. /// TODO(danakj): If there was a way, without making references to it into wide pointers, we should
  55. /// make this type be !Sized.
  56. #[repr(C)]
  57. #[doc(hidden)]
  58. pub struct OpaqueTestingTest(u8);
  59. /// Implements casting from a `&mut OpaqueTestingTest` to a `&mut T` when `T: TestSuite`.
  60. /// Implementing TestSuite for a Rust type `T` promises that `T` is an FFI wrapper around a C++
  61. /// class which subclasses `testing::Test`, which makes this casting okay.
  62. impl<T: TestSuite> AsMut<T> for OpaqueTestingTest {
  63. fn as_mut(&mut self) -> &mut T {
  64. // SAFETY: This horrible casting situation is okay because:
  65. // 1) The Rust type is a wrapper around a C++ type. It's repr(C) and we can cast to the Rust
  66. // type.
  67. // 2) OpaqueTestingTest is a placeholder pointer to represent `testing::Test` which is the
  68. // base class of the C++ type the Rust type is wrapping.
  69. // 3) So this acts as a C++ downcast to the C++ type, and then a cast to the Rust wrapper
  70. // type.
  71. // 4) We've been given a &mut reference, and we reborrow it. The reborrow sits higher on the
  72. // "borrow stack" and will be discarded first as well.
  73. // 5) C++ wrapper types are not Unpin because C++ types are not Unpin, so the type we're
  74. // casting to won't be moved by Rust incorrectly.
  75. unsafe { &mut *(self as *mut OpaqueTestingTest as *mut T) }
  76. }
  77. }
  78. #[doc(hidden)]
  79. pub trait TestResult {
  80. fn into_error_message(self) -> Option<String>;
  81. }
  82. impl TestResult for () {
  83. fn into_error_message(self) -> Option<String> {
  84. None
  85. }
  86. }
  87. // This impl requires an `Error` not just a `String` so that in the future we could print things
  88. // like the backtrace too (though that field is currently unstable).
  89. impl<E: Into<Box<dyn std::error::Error>>> TestResult for std::result::Result<(), E> {
  90. fn into_error_message(self) -> Option<String> {
  91. match self {
  92. Ok(_) => None,
  93. Err(e) => Some(format!("Test returned error: {}", e.into())),
  94. }
  95. }
  96. }
  97. // Internals used by code generated from the gtest-attriute proc-macro. Should not be used by
  98. // human-written code.
  99. #[doc(hidden)]
  100. pub mod __private {
  101. use super::{GtestFactoryFunction, OpaqueTestingTest, Pin};
  102. #[cxx::bridge(namespace=rust_gtest_interop)]
  103. mod ffi {
  104. unsafe extern "C++" {
  105. include!("testing/rust_gtest_interop/rust_gtest_interop.h");
  106. // TODO(danakj): C++ wants an int, but cxx doesn't support c_int, so we use i32.
  107. // Similarly, C++ wants a char* but cxx doesn't support c_char, so we use u8.
  108. // https://github.com/dtolnay/cxx/issues/1015
  109. unsafe fn rust_gtest_add_failure_at(file: *const u8, line: i32, message: &str);
  110. }
  111. }
  112. /// Rust wrapper around the same C++ method.
  113. ///
  114. /// We have a wrapper to convert the file name into a C++-friendly string, and the line number
  115. /// into a C++-friendly signed int.
  116. ///
  117. /// TODO(crbug.com/1298175): We should be able to receive a C++-friendly file path.
  118. ///
  119. /// TODO(danakj): We should be able to pass a `c_int` directly to C++:
  120. /// https://github.com/dtolnay/cxx/issues/1015.
  121. pub fn add_failure_at(file: &'static str, line: u32, message: &str) {
  122. // TODO(danakj): Our own file!() macro should strip "../../" from the front of the string.
  123. let file = file.replace("../", "");
  124. // TODO(danakj): Write a file!() macro that null-terminates the string so we can use it here
  125. // directly and also for constructing base::Location. Then.. pass a base::Location here?
  126. let null_term_file = std::ffi::CString::new(file).unwrap();
  127. unsafe {
  128. ffi::rust_gtest_add_failure_at(
  129. null_term_file.as_ptr() as *const u8,
  130. line.try_into().unwrap_or(-1),
  131. message,
  132. )
  133. }
  134. }
  135. /// Wrapper that calls C++ rust_gtest_default_factory().
  136. ///
  137. /// TODO(danakj): We do this by hand because cxx doesn't support passing raw function pointers:
  138. /// https://github.com/dtolnay/cxx/issues/1011.
  139. pub unsafe extern "C" fn rust_gtest_default_factory(
  140. f: extern "C" fn(Pin<&mut OpaqueTestingTest>),
  141. ) -> Pin<&'static mut OpaqueTestingTest> {
  142. extern "C" {
  143. // The C++ mangled name for rust_gtest_interop::rust_gtest_default_factory(). This comes
  144. // from `objdump -t` on the C++ object file.
  145. fn _ZN18rust_gtest_interop26rust_gtest_default_factoryEPFvPN7testing4TestEE(
  146. f: extern "C" fn(Pin<&mut OpaqueTestingTest>),
  147. ) -> Pin<&'static mut OpaqueTestingTest>;
  148. }
  149. _ZN18rust_gtest_interop26rust_gtest_default_factoryEPFvPN7testing4TestEE(f)
  150. }
  151. /// Wrapper that calls C++ rust_gtest_add_test().
  152. ///
  153. /// Note that the `factory` parameter is actually a C++ function pointer.
  154. ///
  155. /// TODO(danakj): We do this by hand because cxx doesn't support passing raw function pointers
  156. /// nor passing `*const c_char`: https://github.com/dtolnay/cxx/issues/1011 and
  157. /// https://github.com/dtolnay/cxx/issues/1015.
  158. unsafe fn rust_gtest_add_test(
  159. factory: GtestFactoryFunction,
  160. run_test_fn: extern "C" fn(Pin<&mut OpaqueTestingTest>),
  161. test_suite_name: *const std::os::raw::c_char,
  162. test_name: *const std::os::raw::c_char,
  163. file: *const std::os::raw::c_char,
  164. line: i32,
  165. ) {
  166. extern "C" {
  167. /// The C++ mangled name for rust_gtest_interop::rust_gtest_add_test(). This comes from
  168. /// `objdump -t` on the C++ object file.
  169. fn _ZN18rust_gtest_interop19rust_gtest_add_testEPFPN7testing4TestEPFvS2_EES4_PKcS8_S8_i(
  170. factory: GtestFactoryFunction,
  171. run_test_fn: extern "C" fn(Pin<&mut OpaqueTestingTest>),
  172. test_suite_name: *const std::os::raw::c_char,
  173. test_name: *const std::os::raw::c_char,
  174. file: *const std::os::raw::c_char,
  175. line: i32,
  176. );
  177. }
  178. _ZN18rust_gtest_interop19rust_gtest_add_testEPFPN7testing4TestEPFvS2_EES4_PKcS8_S8_i(
  179. factory,
  180. run_test_fn,
  181. test_suite_name,
  182. test_name,
  183. file,
  184. line,
  185. )
  186. }
  187. /// Information used to register a function pointer as a test with the C++ Gtest framework.
  188. pub struct TestRegistration {
  189. pub func: extern "C" fn(suite: Pin<&mut OpaqueTestingTest>),
  190. // TODO(danakj): These a C-String-Literals. Maybe we should expose that as a type somewhere.
  191. pub test_suite_name: &'static [std::os::raw::c_char],
  192. pub test_name: &'static [std::os::raw::c_char],
  193. pub file: &'static [std::os::raw::c_char],
  194. pub line: u32,
  195. pub factory: GtestFactoryFunction,
  196. }
  197. /// Register a given test function with the C++ Gtest framework.
  198. ///
  199. /// This function is called from static initializers. It may only be called from the main
  200. /// thread, before main() is run. It may not panic, or call anything that may panic.
  201. pub fn register_test(r: TestRegistration) {
  202. let line = r.line.try_into().unwrap_or(-1);
  203. // SAFETY: The `factory` parameter to rust_gtest_add_test() must be a C++ function that
  204. // returns a `testing::Test*` disguised as a `OpaqueTestingTest`. The #[gtest] macro will use
  205. // `rust_gtest_interop::rust_gtest_default_factory()` by default.
  206. unsafe {
  207. rust_gtest_add_test(
  208. r.factory,
  209. r.func,
  210. r.test_suite_name.as_ptr(),
  211. r.test_name.as_ptr(),
  212. r.file.as_ptr(),
  213. line,
  214. )
  215. };
  216. }
  217. }
  218. mod expect_macros;