blob: 0e0231dfe5e8759aa24d8a2b1b1222e83b28cca3 [file] [log] [blame]
danakj71dabfa2022-02-24 14:32:141// 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
5/// Use `prelude:::*` to get access to all macros defined in this crate.
6pub mod prelude {
7 // The #[gtest(TestSuite, TestName)] macro.
8 pub use gtest_attribute::gtest;
9 // Gtest expectation macros, which should be used to verify test expectations. These replace the
10 // standard practice of using assert/panic in Rust tests which would crash the test binary.
11 pub use crate::expect_eq;
12 pub use crate::expect_false;
13 pub use crate::expect_ge;
14 pub use crate::expect_gt;
15 pub use crate::expect_le;
16 pub use crate::expect_lt;
17 pub use crate::expect_ne;
18 pub use crate::expect_true;
19}
20
21// The gtest_attribute proc-macro crate makes use of small_ctor, with a path through this crate here
22// to ensure it's available.
23#[doc(hidden)]
24pub extern crate small_ctor;
25
26pub trait TestResult {
27 fn into_error_message(self) -> Option<String>;
28}
29impl TestResult for () {
30 fn into_error_message(self) -> Option<String> {
31 None
32 }
33}
34// This impl requires an `Error` not just a `String` so that in the future we could print things
35// like the backtrace too (though that field is currently unstable).
36impl<E: Into<Box<dyn std::error::Error>>> TestResult for std::result::Result<(), E> {
37 fn into_error_message(self) -> Option<String> {
38 match self {
39 Ok(_) => None,
40 Err(e) => Some(format!("Test returned error: {}", e.into())),
41 }
42 }
43}
44
danakj8029f802022-04-01 14:43:4345/// Matches the C++ type `rust_gtest_interop::GtestFactoryFunction`, except replaces the return
46/// type with an opaque `GtestSuitePtr`
47///
48/// The actual return type is `testing::Test*` but we don't know that type in Rust currently, as we
49/// don't have a Rust generator generating access to that type.
50pub type GtestFactoryFunction = unsafe extern "C" fn(f: extern "C" fn()) -> GtestSuitePtr;
51
52/// Opaque replacement of a C++ `testing::Test*` pointer type. Only appears in the
53/// GtestFactoryFunction signature, which is a function pointer that passed to C++, and never run
54/// from within Rust.
55#[repr(C)]
56pub struct GtestSuitePtr(usize);
57
danakj71dabfa2022-02-24 14:32:1458// Internals used by code generated from the gtest-attriute proc-macro. Should not be used by
59// human-written code.
60#[doc(hidden)]
61pub mod __private {
danakj8029f802022-04-01 14:43:4362 use super::GtestFactoryFunction;
63
danakj71dabfa2022-02-24 14:32:1464 #[cxx::bridge(namespace=rust_gtest_interop)]
65 mod ffi {
66 unsafe extern "C++" {
67 include!("testing/rust_gtest_interop/rust_gtest_interop.h");
68 // TODO(danakj): C++ wants an int, but cxx doesn't support c_int, so we use i32.
69 // Similarly, C++ wants a char* but cxx doesn't support c_char, so we use u8.
70 // https://github.com/dtolnay/cxx/issues/1015
71 unsafe fn rust_gtest_add_failure_at(file: *const u8, line: i32, message: &str);
72 }
73 }
74
75 /// Rust wrapper around the same C++ method.
76 ///
77 /// We have a wrapper to convert the file name into a C++-friendly string, and the line number
78 /// into a C++-friendly signed int.
79 ///
80 /// TODO(crbug.com/1298175): We should be able to receive a C++-friendly file path.
81 ///
82 /// TODO(danakj): We should be able to pass a `c_int` directly to C++:
83 /// https://github.com/dtolnay/cxx/issues/1015.
84 pub fn add_failure_at(file: &'static str, line: u32, message: &str) {
85 // TODO(danakj): Our own file!() macro should strip "../../" from the front of the string.
86 let file = file.replace("../", "");
87 // TODO(danakj): Write a file!() macro that null-terminates the string so we can use it here
88 // directly and also for constructing base::Location. Then.. pass a base::Location here?
89 let null_term_file = std::ffi::CString::new(file).unwrap();
90 unsafe {
91 ffi::rust_gtest_add_failure_at(
92 null_term_file.as_ptr() as *const u8,
93 line.try_into().unwrap_or(-1),
94 message,
95 )
96 }
97 }
98
danakj71dabfa2022-02-24 14:32:1499 /// Wrapper that calls C++ rust_gtest_add_test().
danakj84ba2112022-03-15 18:40:39100 ///
101 /// Note that the `factory` parameter is actually a C++ function pointer, of type
danakj8029f802022-04-01 14:43:43102 /// rust_gtest_interop::GtestFactoryFunction.
danakj84ba2112022-03-15 18:40:39103 ///
104 /// # Safety
105 ///
danakj8029f802022-04-01 14:43:43106 /// The `factory` function pointer is to a C++ function that returns a `testing::Test*`
107 /// disguised as a `GtestSuitePtr` since we don't have generated bindings for the
108 /// `testing::Test` class.
danakj84ba2112022-03-15 18:40:39109 ///
110 /// TODO(danakj): We do this by hand because cxx doesn't support passing raw function pointers
111 /// nor passing `*const c_char`: https://github.com/dtolnay/cxx/issues/1011 and
112 /// https://github.com/dtolnay/cxx/issues/1015.
113 unsafe fn rust_gtest_add_test(
danakj8029f802022-04-01 14:43:43114 factory: GtestFactoryFunction,
danakj71dabfa2022-02-24 14:32:14115 func: extern "C" fn(),
116 test_suite_name: *const std::os::raw::c_char,
117 test_name: *const std::os::raw::c_char,
118 file: *const std::os::raw::c_char,
119 line: i32,
120 ) {
danakj84ba2112022-03-15 18:40:39121 extern "C" {
122 /// The C++ mangled name for rust_gtest_interop::rust_gtest_add_test(). This comes from
123 /// `objdump -t` on the C++ object file.
124 fn _ZN18rust_gtest_interop19rust_gtest_add_testEPFPN7testing4TestEPFvvEES4_PKcS8_S8_i(
danakj8029f802022-04-01 14:43:43125 factory: GtestFactoryFunction,
danakj84ba2112022-03-15 18:40:39126 func: extern "C" fn(),
127 test_suite_name: *const std::os::raw::c_char,
128 test_name: *const std::os::raw::c_char,
129 file: *const std::os::raw::c_char,
130 line: i32,
131 );
danakj71dabfa2022-02-24 14:32:14132 }
danakj84ba2112022-03-15 18:40:39133
134 _ZN18rust_gtest_interop19rust_gtest_add_testEPFPN7testing4TestEPFvvEES4_PKcS8_S8_i(
135 factory,
136 func,
137 test_suite_name,
138 test_name,
139 file,
140 line,
141 )
danakj71dabfa2022-02-24 14:32:14142 }
143
144 /// Information used to register a function pointer as a test with the C++ Gtest framework.
145 pub struct TestRegistration {
146 pub func: extern "C" fn(),
147 // TODO(danakj): These a C-String-Literals. Maybe we should expose that as a type somewhere.
148 pub test_suite_name: &'static [std::os::raw::c_char],
149 pub test_name: &'static [std::os::raw::c_char],
150 pub file: &'static [std::os::raw::c_char],
151 pub line: u32,
danakj8029f802022-04-01 14:43:43152 pub factory: GtestFactoryFunction,
danakj71dabfa2022-02-24 14:32:14153 }
154
155 /// Register a given test function with the C++ Gtest framework.
156 ///
157 /// This function is called from static initializers. It may only be called from the main
158 /// thread, before main() is run. It may not panic, or call anything that may panic.
159 pub fn register_test(r: TestRegistration) {
160 let line = r.line.try_into().unwrap_or(-1);
danakj8029f802022-04-01 14:43:43161 // SAFETY: The `factory` parameter to rust_gtest_add_test() must be a C++ function that
162 // returns a `testing::Test*` disguised as a `GTestSuitePtr`. The #[gtest] macro will use
163 // `rust_gtest_interop::rust_gtest_default_factory()` by default.
danakj84ba2112022-03-15 18:40:39164 unsafe {
danakj84ba2112022-03-15 18:40:39165 rust_gtest_add_test(
danakj8029f802022-04-01 14:43:43166 r.factory,
danakj84ba2112022-03-15 18:40:39167 r.func,
168 r.test_suite_name.as_ptr(),
169 r.test_name.as_ptr(),
170 r.file.as_ptr(),
171 line,
172 )
173 };
danakj71dabfa2022-02-24 14:32:14174 }
175}
176
177mod expect_macros;