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