File: rust_gtest_interop.rs

package info (click to toggle)
chromium 139.0.7258.127-1
  • links: PTS, VCS
  • area: main
  • in suites:
  • size: 6,122,068 kB
  • sloc: cpp: 35,100,771; ansic: 7,163,530; javascript: 4,103,002; python: 1,436,920; asm: 946,517; xml: 746,709; pascal: 187,653; perl: 88,691; sh: 88,436; objc: 79,953; sql: 51,488; cs: 44,583; fortran: 24,137; makefile: 22,147; tcl: 15,277; php: 13,980; yacc: 8,984; ruby: 7,485; awk: 3,720; lisp: 3,096; lex: 1,327; ada: 727; jsp: 228; sed: 36
file content (244 lines) | stat: -rw-r--r-- 9,839 bytes parent folder | download | duplicates (9)
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;