From 681a1657219f29c03bfd5c62cfaa5deeb73ee894 Mon Sep 17 00:00:00 2001 From: ridiculousfish Date: Sun, 15 Jan 2023 14:56:04 -0800 Subject: [PATCH] Add an FFI test facility This allow testing Rust functions (from fish_tests.cpp) which need to cross the FFI. See the example in smoke.rs. --- cmake/Rust.cmake | 1 + fish-rust/Cargo.lock | 32 +++++++++++++++++++ fish-rust/Cargo.toml | 7 +++++ fish-rust/build.rs | 1 + fish-rust/src/ffi_tests.rs | 63 ++++++++++++++++++++++++++++++++++++++ fish-rust/src/lib.rs | 1 + fish-rust/src/smoke.rs | 5 +++ src/fish_tests.cpp | 4 +++ 8 files changed, 114 insertions(+) create mode 100644 fish-rust/src/ffi_tests.rs diff --git a/cmake/Rust.cmake b/cmake/Rust.cmake index 79c5c8372..fc1b8a3b9 100644 --- a/cmake/Rust.cmake +++ b/cmake/Rust.cmake @@ -17,6 +17,7 @@ set(fish_autocxx_gen_dir "${CMAKE_BINARY_DIR}/fish-autocxx-gen/") corrosion_import_crate( MANIFEST_PATH "${CMAKE_SOURCE_DIR}/fish-rust/Cargo.toml" + FEATURES "fish-ffi-tests" ) # We need the build dir because cxx puts our headers in there. diff --git a/fish-rust/Cargo.lock b/fish-rust/Cargo.lock index 5dfdb98d3..51ede1746 100644 --- a/fish-rust/Cargo.lock +++ b/fish-rust/Cargo.lock @@ -232,6 +232,16 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "ctor" +version = "0.1.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d2301688392eb071b0bf1a37be05c469d3cc4dbbd95df672fe28ab021e6a096" +dependencies = [ + "quote", + "syn", +] + [[package]] name = "cxx" version = "1.0.81" @@ -343,6 +353,7 @@ dependencies = [ "cxx-build", "cxx-gen", "errno", + "inventory", "lazy_static", "libc", "miette", @@ -364,6 +375,17 @@ dependencies = [ "wasi", ] +[[package]] +name = "ghost" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41973d4c45f7a35af8753ba3457cc99d406d863941fd7f52663cff54a5ab99b3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "gimli" version = "0.27.0" @@ -432,6 +454,16 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "inventory" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16fe3b35d64bd1f72917f06425e7573a2f63f74f42c8f56e53ea6826dde3a2b5" +dependencies = [ + "ctor", + "ghost", +] + [[package]] name = "is_ci" version = "1.1.1" diff --git a/fish-rust/Cargo.toml b/fish-rust/Cargo.toml index a79abc3d1..d6e61d4e1 100644 --- a/fish-rust/Cargo.toml +++ b/fish-rust/Cargo.toml @@ -10,6 +10,7 @@ widestring-suffix = { path = "./widestring-suffix/" } autocxx = "0.23.1" cxx = "1.0" errno = "0.2.8" +inventory = { version = "0.3.3", optional = true} lazy_static = "1.4.0" libc = "0.2.137" nix = "0.25.0" @@ -26,6 +27,12 @@ miette = { version = "5", features = ["fancy"] } [lib] crate-type=["staticlib"] +[features] +# The fish-ffi-tests feature causes tests to be built which need to use the FFI. +# These tests are run by fish_tests(). +default = ["fish-ffi-tests"] +fish-ffi-tests = ["inventory"] + [patch.crates-io] cxx = { git = "https://github.com/ridiculousfish/cxx", branch = "fish" } cxx-gen = { git = "https://github.com/ridiculousfish/cxx", branch = "fish" } diff --git a/fish-rust/build.rs b/fish-rust/build.rs index a805721e6..cba23f92a 100644 --- a/fish-rust/build.rs +++ b/fish-rust/build.rs @@ -21,6 +21,7 @@ fn main() -> miette::Result<()> { let source_files = vec![ "src/fd_readable_set.rs", "src/ffi_init.rs", + "src/ffi_tests.rs", "src/smoke.rs", "src/topic_monitor.rs", ]; diff --git a/fish-rust/src/ffi_tests.rs b/fish-rust/src/ffi_tests.rs new file mode 100644 index 000000000..5899c3e44 --- /dev/null +++ b/fish-rust/src/ffi_tests.rs @@ -0,0 +1,63 @@ +/// Support for tests which need to cross the FFI. +/// Because the C++ is not compiled by `cargo test` and there is no natural way to +/// do it, use the following facilities for tests which need to use C++ types. +/// This uses the inventory crate to build a custom-test harness +/// as described at https://www.infinyon.com/blog/2021/04/rust-custom-test-harness/ +/// See smoke.rs add_test for an example of how to use this. + +#[cfg(all(feature = "fish-ffi-tests", not(test)))] +mod ffi_tests_impl { + use inventory; + + /// A test which needs to cross the FFI. + #[derive(Debug)] + pub struct FFITest { + pub name: &'static str, + pub func: fn(), + } + + /// Add a new test. + /// Example usage: + /// ``` + /// add_test!("test_name", || { + /// assert!(1 + 2 == 3); + /// }); + /// ``` + macro_rules! add_test { + ($name:literal, $func:expr) => { + inventory::submit!(crate::ffi_tests::FFITest { + name: $name, + func: $func, + }); + }; + } + pub(crate) use add_test; + + inventory::collect!(crate::ffi_tests::FFITest); + + /// Runs all ffi tests. + pub fn run_ffi_tests() { + for test in inventory::iter:: { + println!("Running ffi test {}", test.name); + (test.func)(); + } + } +} + +#[cfg(not(all(feature = "fish-ffi-tests", not(test))))] +mod ffi_tests_impl { + macro_rules! add_test { + ($name:literal, $func:expr) => {}; + } + pub(crate) use add_test; + pub fn run_ffi_tests() {} +} + +pub(crate) use ffi_tests_impl::*; + +#[cxx::bridge(namespace = rust)] +mod ffi_tests { + extern "Rust" { + fn run_ffi_tests(); + } +} diff --git a/fish-rust/src/lib.rs b/fish-rust/src/lib.rs index 80eb3466c..54ee35a2d 100644 --- a/fish-rust/src/lib.rs +++ b/fish-rust/src/lib.rs @@ -10,6 +10,7 @@ mod fd_readable_set; mod fds; mod ffi; mod ffi_init; +mod ffi_tests; mod flog; mod signal; mod smoke; diff --git a/fish-rust/src/smoke.rs b/fish-rust/src/smoke.rs index 105d065c1..853db4dc6 100644 --- a/fish-rust/src/smoke.rs +++ b/fish-rust/src/smoke.rs @@ -19,3 +19,8 @@ mod tests { assert_eq!(result, 4); } } + +use crate::ffi_tests::add_test; +add_test!("test_add", || { + assert_eq!(add(2, 3), 5); +}); diff --git a/src/fish_tests.cpp b/src/fish_tests.cpp index bdc2c4d68..49d39855c 100644 --- a/src/fish_tests.cpp +++ b/src/fish_tests.cpp @@ -63,6 +63,7 @@ #include "fd_readable_set.rs.h" #include "fds.h" #include "ffi_init.rs.h" +#include "ffi_tests.rs.h" #include "function.h" #include "future_feature_flags.h" #include "global_safety.h" @@ -7148,6 +7149,8 @@ void test_rust_smoke() { do_test(x == 42); } +void test_rust_ffi() { rust::run_ffi_tests(); } + // typedef void (test_entry_point_t)(); using test_entry_point_t = void (*)(); struct test_t { @@ -7269,6 +7272,7 @@ static const test_t s_tests[]{ {TEST_GROUP("re"), test_re_substitute}, {TEST_GROUP("wgetopt"), test_wgetopt}, {TEST_GROUP("rust_smoke"), test_rust_smoke}, + {TEST_GROUP("rust_ffi"), test_rust_ffi}, }; void list_tests() {