From 3b6bc91b8d16048cb21d2921e090830902bcb202 Mon Sep 17 00:00:00 2001 From: Amos Wenger Date: Tue, 23 Jul 2024 12:26:38 +0200 Subject: [PATCH 01/35] Try some things out --- rubicon/Cargo.lock | 62 +++++++++++++++++++++++++++++ rubicon/Cargo.toml | 9 ++++- rubicon/build.rs | 14 +++++++ rubicon/src/lib.rs | 76 ++++++++++++++++++++++++++++++++++++ test-crates/bin/Cargo.lock | 24 ++++++++++-- test-crates/mod_a/Cargo.lock | 62 +++++++++++++++++++++++++++++ test-crates/mod_b/Cargo.lock | 62 +++++++++++++++++++++++++++++ test-crates/mokio/Cargo.lock | 64 +++++++++++++++++++++++++++++- test-crates/mokio/Cargo.toml | 2 + test-crates/mokio/src/lib.rs | 27 ++++++++++++- 10 files changed, 393 insertions(+), 9 deletions(-) create mode 100644 rubicon/build.rs diff --git a/rubicon/Cargo.lock b/rubicon/Cargo.lock index 40127dd..fbdfc31 100644 --- a/rubicon/Cargo.lock +++ b/rubicon/Cargo.lock @@ -2,15 +2,77 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "ctor" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edb49164822f3ee45b17acd4a208cfc1251410cf0cad9a833234c9890774dd9f" +dependencies = [ + "quote", + "syn", +] + [[package]] name = "paste" version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" +[[package]] +name = "proc-macro2" +version = "1.0.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" +dependencies = [ + "proc-macro2", +] + [[package]] name = "rubicon" version = "3.0.1" dependencies = [ + "ctor", "paste", + "rustc_version", ] + +[[package]] +name = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver", +] + +[[package]] +name = "semver" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" + +[[package]] +name = "syn" +version = "2.0.72" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc4b9b9bf2add8093d3f2c0204471e951b2285580335de42f9d2534f3ae7a8af" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" diff --git a/rubicon/Cargo.toml b/rubicon/Cargo.toml index 69573ad..18e0518 100644 --- a/rubicon/Cargo.toml +++ b/rubicon/Cargo.toml @@ -14,9 +14,14 @@ keywords = ["ffi", "thread-local"] crate-type = ["dylib"] [dependencies] +ctor = { version = "0.2.8", optional = true } paste = { version = "1.0.15", optional = true } +[build-dependencies] +rustc_version = { version = "0.4.0", optional = true } + [features] default = [] -export-globals = ["dep:paste"] -import-globals = ["dep:paste"] +export-globals = ["dep:paste", "dep:rustc_version"] +import-globals = ["dep:paste", "dep:rustc_version", "dep:ctor"] +ctor = ["dep:ctor"] diff --git a/rubicon/build.rs b/rubicon/build.rs new file mode 100644 index 0000000..0b6b312 --- /dev/null +++ b/rubicon/build.rs @@ -0,0 +1,14 @@ +fn main() { + #[cfg(any(feature = "export-globals", feature = "import-globals"))] + { + use std::env; + + // Get the Rust compiler version and set it as an environment variable. + let rustc_version = rustc_version::version().unwrap(); + println!("cargo:rustc-env=RUBICON_RUSTC_VERSION={}", rustc_version); + + // Pass the target triple. + let target = env::var("TARGET").unwrap(); + println!("cargo:rustc-env=RUBICON_TARGET_TRIPLE={}", target); + } +} diff --git a/rubicon/src/lib.rs b/rubicon/src/lib.rs index 1bd3652..8687921 100644 --- a/rubicon/src/lib.rs +++ b/rubicon/src/lib.rs @@ -36,6 +36,15 @@ compile_error!("The features `export-globals` and `import-globals` are mutually #[cfg(any(feature = "export-globals", feature = "import-globals"))] pub use paste::paste; +#[cfg(feature = "import-globals")] +pub use ctor; + +#[cfg(any(feature = "export-globals", feature = "import-globals"))] +pub const RUBICON_RUSTC_VERSION: &str = env!("RUBICON_RUSTC_VERSION"); + +#[cfg(any(feature = "export-globals", feature = "import-globals"))] +pub const RUBICON_TARGET_TRIPLE: &str = env!("RUBICON_TARGET_TRIPLE"); + //============================================================================== // Wrappers //============================================================================== @@ -288,3 +297,70 @@ macro_rules! process_local_inner_mut { } }; } + +//============================================================================== +// Compatibility check +//============================================================================== + +#[cfg(feature = "export-globals")] +#[macro_export] +macro_rules! compatibility_check { + ($($feature:tt)*) => { + use std::env; + + $crate::paste! { + #[no_mangle] + #[export_name = concat!(env!("CARGO_PKG_NAME"), "_compatibility_info")] + static __RUBICON_COMPATIBILITY_INFO_: &'static [(&'static str, &'static str)] = &[ + ("rustc-version", $crate::RUBICON_RUSTC_VERSION), + ("target-triple", $crate::RUBICON_TARGET_TRIPLE), + $($feature)* + ]; + } + }; +} + +#[cfg(feature = "import-globals")] +#[macro_export] +macro_rules! compatibility_check { + ($($feature:tt)*) => { + use std::env; + use $crate::ctor::ctor; + + extern "C" { + #[link_name = concat!(env!("CARGO_PKG_NAME"), "_compatibility_info")] + static COMPATIBILITY_INFO: &'static [(&'static str, &'static str)]; + } + + #[ctor] + fn check_compatibility() { + let ref_compatibility_info: &[(&str, &str)] = &[ + ("rustc-version", $crate::RUBICON_RUSTC_VERSION), + ("target-triple", $crate::RUBICON_TARGET_TRIPLE), + $($feature)* + ]; + + let exported = unsafe { COMPATIBILITY_INFO }; + + let missing: Vec<_> = ref_compatibility_info.iter().filter(|&item| !exported.contains(item)).collect(); + let extra: Vec<_> = exported.iter().filter(|&item| !ref_compatibility_info.contains(item)).collect(); + + if !missing.is_empty() || !extra.is_empty() { + eprintln!("Compatibility mismatch detected!"); + if !missing.is_empty() { + eprintln!("Missing features: {:?}", missing); + } + if !extra.is_empty() { + eprintln!("Extra features: {:?}", extra); + } + std::process::exit(1); + } + } + }; +} + +#[cfg(not(any(feature = "export-globals", feature = "import-globals")))] +#[macro_export] +macro_rules! compatibility_check { + ($($feature:tt)*) => {}; +} diff --git a/test-crates/bin/Cargo.lock b/test-crates/bin/Cargo.lock index 4447dcb..0588abd 100644 --- a/test-crates/bin/Cargo.lock +++ b/test-crates/bin/Cargo.lock @@ -28,9 +28,9 @@ dependencies = [ [[package]] name = "libloading" -version = "0.8.4" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e310b3a6b5907f99202fcdb4960ff45b93735d7c7d96b760fcff8db2dc0e103d" +checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4" dependencies = [ "cfg-if", "windows-targets", @@ -54,13 +54,29 @@ name = "rubicon" version = "3.0.1" dependencies = [ "paste", + "rustc_version", ] +[[package]] +name = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver", +] + +[[package]] +name = "semver" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" + [[package]] name = "soprintln" -version = "3.2.0" +version = "3.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2cc96941d6cac2e2654f62398ae8319ff55a730d6ee2edc78d112f37e5507613" +checksum = "7561c6c12cc21c549193bb82f86800ee0d5d69e85a4393ee1a2766917c2a35cb" [[package]] name = "windows-targets" diff --git a/test-crates/mod_a/Cargo.lock b/test-crates/mod_a/Cargo.lock index 80a3aab..90427c2 100644 --- a/test-crates/mod_a/Cargo.lock +++ b/test-crates/mod_a/Cargo.lock @@ -2,6 +2,16 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "ctor" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edb49164822f3ee45b17acd4a208cfc1251410cf0cad9a833234c9890774dd9f" +dependencies = [ + "quote", + "syn", +] + [[package]] name = "mod_a" version = "0.1.0" @@ -24,15 +34,67 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" +[[package]] +name = "proc-macro2" +version = "1.0.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" +dependencies = [ + "proc-macro2", +] + [[package]] name = "rubicon" version = "3.0.1" dependencies = [ + "ctor", "paste", + "rustc_version", ] +[[package]] +name = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver", +] + +[[package]] +name = "semver" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" + [[package]] name = "soprintln" version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2cc96941d6cac2e2654f62398ae8319ff55a730d6ee2edc78d112f37e5507613" + +[[package]] +name = "syn" +version = "2.0.72" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc4b9b9bf2add8093d3f2c0204471e951b2285580335de42f9d2534f3ae7a8af" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" diff --git a/test-crates/mod_b/Cargo.lock b/test-crates/mod_b/Cargo.lock index 0ac7beb..c434309 100644 --- a/test-crates/mod_b/Cargo.lock +++ b/test-crates/mod_b/Cargo.lock @@ -2,6 +2,16 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "ctor" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edb49164822f3ee45b17acd4a208cfc1251410cf0cad9a833234c9890774dd9f" +dependencies = [ + "quote", + "syn", +] + [[package]] name = "mod_b" version = "0.1.0" @@ -24,15 +34,67 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" +[[package]] +name = "proc-macro2" +version = "1.0.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" +dependencies = [ + "proc-macro2", +] + [[package]] name = "rubicon" version = "3.0.1" dependencies = [ + "ctor", "paste", + "rustc_version", ] +[[package]] +name = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver", +] + +[[package]] +name = "semver" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" + [[package]] name = "soprintln" version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2cc96941d6cac2e2654f62398ae8319ff55a730d6ee2edc78d112f37e5507613" + +[[package]] +name = "syn" +version = "2.0.72" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc4b9b9bf2add8093d3f2c0204471e951b2285580335de42f9d2534f3ae7a8af" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" diff --git a/test-crates/mokio/Cargo.lock b/test-crates/mokio/Cargo.lock index 299478c..2b05b96 100644 --- a/test-crates/mokio/Cargo.lock +++ b/test-crates/mokio/Cargo.lock @@ -2,6 +2,16 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "ctor" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edb49164822f3ee45b17acd4a208cfc1251410cf0cad9a833234c9890774dd9f" +dependencies = [ + "quote", + "syn", +] + [[package]] name = "mokio" version = "0.1.0" @@ -15,9 +25,61 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" +[[package]] +name = "proc-macro2" +version = "1.0.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" +dependencies = [ + "proc-macro2", +] + [[package]] name = "rubicon" -version = "2.0.0" +version = "3.0.1" dependencies = [ + "ctor", "paste", + "rustc_version", ] + +[[package]] +name = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver", +] + +[[package]] +name = "semver" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" + +[[package]] +name = "syn" +version = "2.0.72" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc4b9b9bf2add8093d3f2c0204471e951b2285580335de42f9d2534f3ae7a8af" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" diff --git a/test-crates/mokio/Cargo.toml b/test-crates/mokio/Cargo.toml index 88cad95..4b77f5d 100644 --- a/test-crates/mokio/Cargo.toml +++ b/test-crates/mokio/Cargo.toml @@ -7,5 +7,7 @@ edition = "2021" rubicon = { path = "../../rubicon" } [features] +default = [] +timer = [] import-globals = ["rubicon/import-globals"] export-globals = ["rubicon/export-globals"] diff --git a/test-crates/mokio/src/lib.rs b/test-crates/mokio/src/lib.rs index 349c9ab..d852406 100644 --- a/test-crates/mokio/src/lib.rs +++ b/test-crates/mokio/src/lib.rs @@ -1,4 +1,27 @@ -use std::sync::atomic::AtomicU64; +use std::sync::{atomic::AtomicU64, Arc, Mutex}; + +rubicon::compatibility_check! { + #[cfg(feature = "timer")] + ("timer", "enabled"), +} + +#[derive(Default)] +#[cfg(feature = "timer")] +struct TimerInternals { + #[allow(dead_code)] + random_stuff: [u64; 4], +} + +#[derive(Default)] +pub struct Runtime { + #[cfg(feature = "timer")] + #[allow(dead_code)] + timer: TimerInternals, + + // this field is second on purpose so that it'll be offset + // if the feature is enabled/disabled + pub counter: u64, +} rubicon::process_local! { pub static MOKIO_PL1: AtomicU64 = AtomicU64::new(0); @@ -10,7 +33,7 @@ rubicon::process_local! { rubicon::thread_local! { pub static MOKIO_TL1: AtomicU64 = AtomicU64::new(0); - pub static MOKIO_TL2: AtomicU64 = AtomicU64::new(0); + pub static MOKIO_TL2: Arc> = Arc::new(Mutex::new(Runtime::default())); } pub fn inc_dangerous() -> u64 { From ab0803bcb01218428bf75b38ce5688a5fba8c920 Mon Sep 17 00:00:00 2001 From: Amos Wenger Date: Tue, 23 Jul 2024 12:40:49 +0200 Subject: [PATCH 02/35] compatibility misatch check --- rubicon/src/lib.rs | 46 ++++++++++++++++++++++++++++++------ test-crates/mod_a/Cargo.toml | 5 +++- 2 files changed, 43 insertions(+), 8 deletions(-) diff --git a/rubicon/src/lib.rs b/rubicon/src/lib.rs index 8687921..d937973 100644 --- a/rubicon/src/lib.rs +++ b/rubicon/src/lib.rs @@ -346,14 +346,46 @@ macro_rules! compatibility_check { let extra: Vec<_> = exported.iter().filter(|&item| !ref_compatibility_info.contains(item)).collect(); if !missing.is_empty() || !extra.is_empty() { - eprintln!("Compatibility mismatch detected!"); - if !missing.is_empty() { - eprintln!("Missing features: {:?}", missing); + let mut error_message = String::new(); + error_message.push_str("\n\x1b[31m=========================================================\x1b[0m\n"); + error_message.push_str(&format!(" ๐Ÿ’€ Compatibility mismatch for module \x1b[31m{}\x1b[0m", env!("CARGO_PKG_NAME"))); + error_message.push_str("\n\x1b[31m=========================================================\x1b[0m\n\n"); + + error_message.push_str("The loaded shared object doesn't have the exact same cargo \n"); + error_message.push_str("features and rust toolchain as the binary it's being loaded \n"); + error_message.push_str("in\n"); + error_message.push_str("\n"); + + error_message.push_str("Present in binary Expected by this module\n"); + error_message.push_str("------------------------------ ------------------------------\n"); + + let mut i = 0; + let mut j = 0; + + while i < exported.len() || j < ref_compatibility_info.len() { + if i < exported.len() && (j >= ref_compatibility_info.len() || exported[i].0 < ref_compatibility_info[j].0) { + // Item only in exported + error_message.push_str(&format!("\x1b[31m{:<30}\x1b[0m \n", format!("{}={}", exported[i].0, exported[i].1))); + i += 1; + } else if j < ref_compatibility_info.len() && (i >= exported.len() || ref_compatibility_info[j].0 < exported[i].0) { + // Item only in ref_compatibility_info + error_message.push_str(&format!(" \x1b[31m{:<30}\x1b[0m\n", format!("{}={}", ref_compatibility_info[j].0, ref_compatibility_info[j].1))); + j += 1; + } else { + // Item in both + let (name, value) = exported[i]; + let expected = ref_compatibility_info[j]; + let color = if (name, value) == expected { "\x1b[32m" } else { "\x1b[31m" }; + error_message.push_str(&format!("{}{:<30}\x1b[0m {}{:<30}\x1b[0m\n", + color, format!("{}={}", name, value), + color, format!("{}={}", expected.0, expected.1) + )); + i += 1; + j += 1; + } } - if !extra.is_empty() { - eprintln!("Extra features: {:?}", extra); - } - std::process::exit(1); + + panic!("{}", error_message); } } }; diff --git a/test-crates/mod_a/Cargo.toml b/test-crates/mod_a/Cargo.toml index b327edf..5bb93f6 100644 --- a/test-crates/mod_a/Cargo.toml +++ b/test-crates/mod_a/Cargo.toml @@ -7,6 +7,9 @@ edition = "2021" crate-type = ["cdylib"] [dependencies] -mokio = { version = "0.1.0", path = "../mokio", features = ["import-globals"] } +mokio = { version = "0.1.0", path = "../mokio", features = [ + "import-globals", + "timer", +] } rubicon = { path = "../../rubicon" } soprintln = { version = "3.0.0", features = ["print"] } From 8be5a8110fdc1cc290ef20353fe7d8ef8ce8d962 Mon Sep 17 00:00:00 2001 From: Amos Wenger Date: Tue, 23 Jul 2024 12:55:01 +0200 Subject: [PATCH 03/35] alright --- rubicon/src/lib.rs | 94 ++++++++++++++++++++++++++++++++++------------ 1 file changed, 69 insertions(+), 25 deletions(-) diff --git a/rubicon/src/lib.rs b/rubicon/src/lib.rs index d937973..9a49bb4 100644 --- a/rubicon/src/lib.rs +++ b/rubicon/src/lib.rs @@ -334,7 +334,7 @@ macro_rules! compatibility_check { #[ctor] fn check_compatibility() { - let ref_compatibility_info: &[(&str, &str)] = &[ + let imported: &[(&str, &str)] = &[ ("rustc-version", $crate::RUBICON_RUSTC_VERSION), ("target-triple", $crate::RUBICON_TARGET_TRIPLE), $($feature)* @@ -342,8 +342,8 @@ macro_rules! compatibility_check { let exported = unsafe { COMPATIBILITY_INFO }; - let missing: Vec<_> = ref_compatibility_info.iter().filter(|&item| !exported.contains(item)).collect(); - let extra: Vec<_> = exported.iter().filter(|&item| !ref_compatibility_info.contains(item)).collect(); + let missing: Vec<_> = imported.iter().filter(|&item| !exported.contains(item)).collect(); + let extra: Vec<_> = exported.iter().filter(|&item| !imported.contains(item)).collect(); if !missing.is_empty() || !extra.is_empty() { let mut error_message = String::new(); @@ -356,32 +356,76 @@ macro_rules! compatibility_check { error_message.push_str("in\n"); error_message.push_str("\n"); - error_message.push_str("Present in binary Expected by this module\n"); - error_message.push_str("------------------------------ ------------------------------\n"); + // Compute max lengths for alignment + let max_exported_len = exported.iter().map(|(k, v)| format!("{}={}", k, v).len()).max().unwrap_or(0); + let max_ref_len = imported.iter().map(|(k, v)| format!("{}={}", k, v).len()).max().unwrap_or(0); + let column_width = max_exported_len.max(max_ref_len); + + error_message.push_str(&format!("Present in binary{:width$}Expected by this module\n", "", width = column_width - 17 + 4)); + error_message.push_str(&format!("{:-= ref_compatibility_info.len() || exported[i].0 < ref_compatibility_info[j].0) { - // Item only in exported - error_message.push_str(&format!("\x1b[31m{:<30}\x1b[0m \n", format!("{}={}", exported[i].0, exported[i].1))); - i += 1; - } else if j < ref_compatibility_info.len() && (i >= exported.len() || ref_compatibility_info[j].0 < exported[i].0) { - // Item only in ref_compatibility_info - error_message.push_str(&format!(" \x1b[31m{:<30}\x1b[0m\n", format!("{}={}", ref_compatibility_info[j].0, ref_compatibility_info[j].1))); - j += 1; - } else { - // Item in both - let (name, value) = exported[i]; - let expected = ref_compatibility_info[j]; - let color = if (name, value) == expected { "\x1b[32m" } else { "\x1b[31m" }; - error_message.push_str(&format!("{}{:<30}\x1b[0m {}{:<30}\x1b[0m\n", - color, format!("{}={}", name, value), - color, format!("{}={}", expected.0, expected.1) - )); - i += 1; - j += 1; + struct AnsiEscape(u64, D); + + impl std::fmt::Display for AnsiEscape { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "\x1b[{}m{}\x1b[0m", self.0, self.1) + } + } + + fn blue(d: D) -> AnsiEscape { + AnsiEscape(34, d) + } + fn green(d: D) -> AnsiEscape { + AnsiEscape(32, d) + } + fn red(d: D) -> AnsiEscape { + AnsiEscape(31, d) + } + + // Gather all unique keys + let mut all_keys: Vec<&str> = Vec::new(); + for (key, _) in exported.iter() { + if !all_keys.contains(key) { + all_keys.push(key); + } + } + for (key, _) in imported.iter() { + if !all_keys.contains(key) { + all_keys.push(key); + } + } + + for key in all_keys.iter() { + let exported_value = exported.iter().find(|&(k, _)| k == key).map(|(_, v)| v); + let imported_value = imported.iter().find(|&(k, _)| k == key).map(|(_, v)| v); + + match (exported_value, imported_value) { + (Some(value), Some(expected_value)) => { + // Item in both + let color = if value == expected_value { green } else { red }; + let left_item = format!("{}={}", blue(key), color(value)); + let right_item = format!("{}={}", blue(key), color(expected_value)); + error_message.push_str(&format!("{: { + // Item only in exported + let item = format!("{}={}", blue(key), red(value)); + error_message.push_str(&format!("{: { + // Item only in imported + let item = format!("{}={}", blue(key), red(value)); + error_message.push_str(&format!("{: { + // This should never happen as the key is from all_keys + unreachable!() + } } } From cb74d9d7225848d1eb362cb7a2ad03c3d4007d6c Mon Sep 17 00:00:00 2001 From: Amos Wenger Date: Tue, 23 Jul 2024 13:01:42 +0200 Subject: [PATCH 04/35] Coloring --- rubicon/src/lib.rs | 26 ++++++++++++++++---------- test-crates/mokio/src/lib.rs | 3 +++ 2 files changed, 19 insertions(+), 10 deletions(-) diff --git a/rubicon/src/lib.rs b/rubicon/src/lib.rs index 9a49bb4..6cb130d 100644 --- a/rubicon/src/lib.rs +++ b/rubicon/src/lib.rs @@ -361,7 +361,7 @@ macro_rules! compatibility_check { let max_ref_len = imported.iter().map(|(k, v)| format!("{}={}", k, v).len()).max().unwrap_or(0); let column_width = max_exported_len.max(max_ref_len); - error_message.push_str(&format!("Present in binary{:width$}Expected by this module\n", "", width = column_width - 17 + 4)); + error_message.push_str(&format!("{:(d: D) -> AnsiEscape { AnsiEscape(31, d) } + fn grey(d: D) -> AnsiEscape { + AnsiEscape(90, d) + } // Gather all unique keys let mut all_keys: Vec<&str> = Vec::new(); @@ -406,21 +409,24 @@ macro_rules! compatibility_check { (Some(value), Some(expected_value)) => { // Item in both let color = if value == expected_value { green } else { red }; - let left_item = format!("{}={}", blue(key), color(value)); - let right_item = format!("{}={}", blue(key), color(expected_value)); - error_message.push_str(&format!("{: { // Item only in exported - let item = format!("{}={}", blue(key), red(value)); - error_message.push_str(&format!("{: { // Item only in imported - let item = format!("{}={}", blue(key), red(value)); - error_message.push_str(&format!("{: { // This should never happen as the key is from all_keys diff --git a/test-crates/mokio/src/lib.rs b/test-crates/mokio/src/lib.rs index d852406..504d3a9 100644 --- a/test-crates/mokio/src/lib.rs +++ b/test-crates/mokio/src/lib.rs @@ -1,6 +1,9 @@ use std::sync::{atomic::AtomicU64, Arc, Mutex}; rubicon::compatibility_check! { + ("mokio_pkg_version", env!("CARGO_PKG_VERSION")), + #[cfg(not(feature = "timer"))] + ("timer", "disabled"), #[cfg(feature = "timer")] ("timer", "enabled"), } From 7e2197b79a4e8672c63d2147ec73c7f9cc5abe62 Mon Sep 17 00:00:00 2001 From: Amos Wenger Date: Tue, 23 Jul 2024 13:12:27 +0200 Subject: [PATCH 05/35] Looks good --- rubicon/src/lib.rs | 96 +++++++++++++++++++++--------------- test-crates/mokio/src/lib.rs | 4 ++ 2 files changed, 59 insertions(+), 41 deletions(-) diff --git a/rubicon/src/lib.rs b/rubicon/src/lib.rs index 6cb130d..65e9553 100644 --- a/rubicon/src/lib.rs +++ b/rubicon/src/lib.rs @@ -345,16 +345,34 @@ macro_rules! compatibility_check { let missing: Vec<_> = imported.iter().filter(|&item| !exported.contains(item)).collect(); let extra: Vec<_> = exported.iter().filter(|&item| !imported.contains(item)).collect(); + struct AnsiEscape(u64, D); + + impl std::fmt::Display for AnsiEscape { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "\x1b[{}m{}\x1b[0m", self.0, self.1) + } + } + + fn blue(d: D) -> AnsiEscape { + AnsiEscape(34, d) + } + fn green(d: D) -> AnsiEscape { + AnsiEscape(32, d) + } + fn red(d: D) -> AnsiEscape { + AnsiEscape(31, d) + } + fn grey(d: D) -> AnsiEscape { + AnsiEscape(90, d) + } + if !missing.is_empty() || !extra.is_empty() { let mut error_message = String::new(); - error_message.push_str("\n\x1b[31m=========================================================\x1b[0m\n"); - error_message.push_str(&format!(" ๐Ÿ’€ Compatibility mismatch for module \x1b[31m{}\x1b[0m", env!("CARGO_PKG_NAME"))); - error_message.push_str("\n\x1b[31m=========================================================\x1b[0m\n\n"); + error_message.push_str("\n\x1b[31m~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\x1b[0m\n"); + error_message.push_str(&format!(" ๐Ÿ’€ Compatibility mismatch for module \x1b[31m{}\x1b[0m", env!("CARGO_PKG_NAME"))); + error_message.push_str("\n\x1b[31m~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\x1b[0m\n\n"); - error_message.push_str("The loaded shared object doesn't have the exact same cargo \n"); - error_message.push_str("features and rust toolchain as the binary it's being loaded \n"); - error_message.push_str("in\n"); - error_message.push_str("\n"); + error_message.push_str(&format!("The crate '{}' doesn't have the exact same cargo features enabled in the main binary and in a module being loaded\n\n", red(env!("CARGO_PKG_NAME")))); // Compute max lengths for alignment let max_exported_len = exported.iter().map(|(k, v)| format!("{}={}", k, v).len()).max().unwrap_or(0); @@ -367,27 +385,6 @@ macro_rules! compatibility_check { let mut i = 0; let mut j = 0; - struct AnsiEscape(u64, D); - - impl std::fmt::Display for AnsiEscape { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "\x1b[{}m{}\x1b[0m", self.0, self.1) - } - } - - fn blue(d: D) -> AnsiEscape { - AnsiEscape(34, d) - } - fn green(d: D) -> AnsiEscape { - AnsiEscape(32, d) - } - fn red(d: D) -> AnsiEscape { - AnsiEscape(31, d) - } - fn grey(d: D) -> AnsiEscape { - AnsiEscape(90, d) - } - // Gather all unique keys let mut all_keys: Vec<&str> = Vec::new(); for (key, _) in exported.iter() { @@ -408,25 +405,35 @@ macro_rules! compatibility_check { match (exported_value, imported_value) { (Some(value), Some(expected_value)) => { // Item in both - let color = if value == expected_value { green } else { red }; - let left_item = format!("{}{}{}", blue(key), grey("="), color(value)); - let right_item = format!("{}{}{}", blue(key), grey("="), color(expected_value)); - let left_item_len = key.len() + value.len() + 1; // +1 for '=' - let padding = " ".repeat(column_width.saturating_sub(left_item_len)); - error_message.push_str(&format!("{}{} {}\n", left_item, padding, right_item)); + if value == expected_value { + let left_item = format!("{}{}{}", grey(key), grey("="), grey(value)); + let right_item = format!("{}{}{}", grey(key), grey("="), grey(expected_value)); + let left_item_len = key.len() + value.len() + 1; // +1 for '=' + let padding = " ".repeat(column_width.saturating_sub(left_item_len)); + error_message.push_str(&format!("{}{} {}\n", left_item, padding, right_item)); + } else { + let left_item = format!("{}{}{}", blue(key), grey("="), green(value)); + let right_item = format!("{}{}{}", blue(key), grey("="), red(expected_value)); + let left_item_len = key.len() + value.len() + 1; // +1 for '=' + let padding = " ".repeat(column_width.saturating_sub(left_item_len)); + error_message.push_str(&format!("{}{} {}\n", left_item, padding, right_item)); + } } (Some(value), None) => { // Item only in exported - let item = format!("{}{}{}", blue(key), grey("="), red(value)); - let item_len = key.len() + value.len() + 1; // +1 for '=' - let padding = " ".repeat(column_width.saturating_sub(item_len)); - error_message.push_str(&format!("{}{}\n", item, padding)); + let left_item = format!("{}{}{}", green(key), grey("="), green(value)); + let right_item = format!("{}", red("MISSING!")); + let left_item_len = key.len() + value.len() + 1; // +1 for '=' + let padding = " ".repeat(column_width.saturating_sub(left_item_len)); + error_message.push_str(&format!("{}{} {}\n", left_item, padding, right_item)); } (None, Some(value)) => { // Item only in imported - let item = format!("{}{}{}", blue(key), grey("="), red(value)); - let padding = " ".repeat(column_width); - error_message.push_str(&format!("{} {}\n", padding, item)); + let left_item = format!("{}", red("MISSING!")); + let right_item = format!("{}{}{}", green(key), grey("="), green(value)); + let left_item_len = "MISSING!".len(); + let padding = " ".repeat(column_width.saturating_sub(left_item_len)); + error_message.push_str(&format!("{}{} {}\n", left_item, padding, right_item)); } (None, None) => { // This should never happen as the key is from all_keys @@ -435,6 +442,13 @@ macro_rules! compatibility_check { } } + error_message.push_str("\nRefusing to proceed as this could cause memory corruption."); + error_message.push_str("\n\n > ๐Ÿ“ \x1b[34mNote:\x1b[0m To fix this issue, rebuild this module with the same cargo features"); + error_message.push_str(&format!("\n > as the main binary. Note that the {} dependency might be transitive", red(env!("CARGO_PKG_NAME")))); + error_message.push_str("\n > (ie. pulled indirectly by another dependency)."); + error_message.push_str(&format!("\n > Run `cargo tree -i {}` to figure out why you even have it.", red(env!("CARGO_PKG_NAME")))); + error_message.push_str("\n\n\x1b[31m~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\x1b[0m\n\n"); + panic!("{}", error_message); } } diff --git a/test-crates/mokio/src/lib.rs b/test-crates/mokio/src/lib.rs index 504d3a9..f339508 100644 --- a/test-crates/mokio/src/lib.rs +++ b/test-crates/mokio/src/lib.rs @@ -2,10 +2,14 @@ use std::sync::{atomic::AtomicU64, Arc, Mutex}; rubicon::compatibility_check! { ("mokio_pkg_version", env!("CARGO_PKG_VERSION")), + #[cfg(not(feature = "timer"))] ("timer", "disabled"), #[cfg(feature = "timer")] ("timer", "enabled"), + + #[cfg(feature = "timer")] + ("has_timer", "1"), } #[derive(Default)] From 9c51e7b9018acc746367d3d28d8dedc74e0c2fe3 Mon Sep 17 00:00:00 2001 From: Amos Wenger Date: Tue, 23 Jul 2024 13:17:50 +0200 Subject: [PATCH 06/35] box --- rubicon/src/lib.rs | 62 +++++++++++++++++++++++++++++++++++++++------- 1 file changed, 53 insertions(+), 9 deletions(-) diff --git a/rubicon/src/lib.rs b/rubicon/src/lib.rs index 65e9553..1bcb3f4 100644 --- a/rubicon/src/lib.rs +++ b/rubicon/src/lib.rs @@ -368,9 +368,9 @@ macro_rules! compatibility_check { if !missing.is_empty() || !extra.is_empty() { let mut error_message = String::new(); - error_message.push_str("\n\x1b[31m~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\x1b[0m\n"); + error_message.push_str("\n\x1b[31mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\x1b[0m\n"); error_message.push_str(&format!(" ๐Ÿ’€ Compatibility mismatch for module \x1b[31m{}\x1b[0m", env!("CARGO_PKG_NAME"))); - error_message.push_str("\n\x1b[31m~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\x1b[0m\n\n"); + error_message.push_str("\n\x1b[31mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\x1b[0m\n\n"); error_message.push_str(&format!("The crate '{}' doesn't have the exact same cargo features enabled in the main binary and in a module being loaded\n\n", red(env!("CARGO_PKG_NAME")))); @@ -380,7 +380,7 @@ macro_rules! compatibility_check { let column_width = max_exported_len.max(max_ref_len); error_message.push_str(&format!("{: ๐Ÿ“ \x1b[34mNote:\x1b[0m To fix this issue, rebuild this module with the same cargo features"); - error_message.push_str(&format!("\n > as the main binary. Note that the {} dependency might be transitive", red(env!("CARGO_PKG_NAME")))); - error_message.push_str("\n > (ie. pulled indirectly by another dependency)."); - error_message.push_str(&format!("\n > Run `cargo tree -i {}` to figure out why you even have it.", red(env!("CARGO_PKG_NAME")))); - error_message.push_str("\n\n\x1b[31m~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\x1b[0m\n\n"); + let transitive_line = format!("main binary. Note that the {} dependency might be transitive", red(env!("CARGO_PKG_NAME"))); + let cargo_tree_line = format!("Run `cargo tree -i {}` to figure out why you even have it.", red(env!("CARGO_PKG_NAME"))); + + let lines = vec![ + "Refusing to proceed as this could cause memory corruption.", + "", + "๐Ÿ“ \x1b[34mNote:\x1b[0m", + "To fix this issue, rebuild this module with the same cargo features as the", + &transitive_line, + "(i.e., pulled indirectly by another dependency).", + "", + &cargo_tree_line, + ]; + + // Helper function to count visible characters (ignoring ANSI escapes) + fn visible_len(s: &str) -> usize { + let mut len = 0; + let mut in_escape = false; + for c in s.chars() { + if c == '\x1b' { + in_escape = true; + } else if in_escape { + if c.is_alphabetic() { + in_escape = false; + } + } else { + len += 1; + } + } + len + } + + let max_width = lines.iter().map(|line| visible_len(line)).max().unwrap_or(0); + let box_width = max_width + 4; // Add 4 for left and right borders and spaces + + error_message.push_str("\n"); + error_message.push_str(&format!("โ”Œ{}โ”\n", "โ”€".repeat(box_width - 2))); + + for line in lines { + if line.is_empty() { + error_message.push_str(&format!("โ”‚{}โ”‚\n", " ".repeat(box_width - 2))); + } else { + let visible_line_len = visible_len(line); + let padding = " ".repeat(box_width - 2 - visible_line_len); + error_message.push_str(&format!("โ”‚ {}{} โ”‚\n", line, padding)); + } + } + + error_message.push_str(&format!("โ””{}โ”˜\n", "โ”€".repeat(box_width - 2))); + error_message.push_str("\n\x1b[31mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\x1b[0m\n\n"); panic!("{}", error_message); } From 568d4ed305d57e78b64e54ef850ec69e838be164 Mon Sep 17 00:00:00 2001 From: Amos Wenger Date: Tue, 23 Jul 2024 13:22:03 +0200 Subject: [PATCH 07/35] Box box box --- rubicon/src/lib.rs | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/rubicon/src/lib.rs b/rubicon/src/lib.rs index 1bcb3f4..e056fe3 100644 --- a/rubicon/src/lib.rs +++ b/rubicon/src/lib.rs @@ -368,11 +368,10 @@ macro_rules! compatibility_check { if !missing.is_empty() || !extra.is_empty() { let mut error_message = String::new(); - error_message.push_str("\n\x1b[31mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\x1b[0m\n"); - error_message.push_str(&format!(" ๐Ÿ’€ Compatibility mismatch for module \x1b[31m{}\x1b[0m", env!("CARGO_PKG_NAME"))); - error_message.push_str("\n\x1b[31mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\x1b[0m\n\n"); + error_message.push_str("\n\x1b[31mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\x1b[0m\n"); + error_message.push_str(&format!(" ๐Ÿ’€ Compatibility mismatch for module \x1b[31m{}\x1b[0m\n\n", env!("CARGO_PKG_NAME"))); - error_message.push_str(&format!("The crate '{}' doesn't have the exact same cargo features enabled in the main binary and in a module being loaded\n\n", red(env!("CARGO_PKG_NAME")))); + error_message.push_str(&format!("'{}' doesn't have the exact same cargo features enabled in the main binary and in the module being loaded\n\n", red(env!("CARGO_PKG_NAME")))); // Compute max lengths for alignment let max_exported_len = exported.iter().map(|(k, v)| format!("{}={}", k, v).len()).max().unwrap_or(0); @@ -446,9 +445,7 @@ macro_rules! compatibility_check { let cargo_tree_line = format!("Run `cargo tree -i {}` to figure out why you even have it.", red(env!("CARGO_PKG_NAME"))); let lines = vec![ - "Refusing to proceed as this could cause memory corruption.", - "", - "๐Ÿ“ \x1b[34mNote:\x1b[0m", + "\x1b[34mHINT:\x1b[0m", "To fix this issue, rebuild this module with the same cargo features as the", &transitive_line, "(i.e., pulled indirectly by another dependency).", @@ -485,13 +482,13 @@ macro_rules! compatibility_check { error_message.push_str(&format!("โ”‚{}โ”‚\n", " ".repeat(box_width - 2))); } else { let visible_line_len = visible_len(line); - let padding = " ".repeat(box_width - 2 - visible_line_len); + let padding = " ".repeat(box_width - 4 - visible_line_len); error_message.push_str(&format!("โ”‚ {}{} โ”‚\n", line, padding)); } } error_message.push_str(&format!("โ””{}โ”˜\n", "โ”€".repeat(box_width - 2))); - error_message.push_str("\n\x1b[31mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\x1b[0m\n\n"); + error_message.push_str("\n\x1b[31mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\x1b[0m\n"); panic!("{}", error_message); } From 458a918ec3ebfb04edfa7e7cf5b04bb158d89a9a Mon Sep 17 00:00:00 2001 From: Amos Wenger Date: Tue, 23 Jul 2024 17:29:41 +0200 Subject: [PATCH 08/35] Sfaety things --- Justfile | 15 +- rubicon/Cargo.lock | 7 + rubicon/Cargo.toml | 3 +- rubicon/src/lib.rs | 306 +++++++++++++++++++-------------- test-crates/bin/src/main.rs | 82 ++++++++- test-crates/exports/Cargo.toml | 3 + test-crates/mod_a/Cargo.lock | 7 + test-crates/mod_a/Cargo.toml | 5 +- test-crates/mod_b/Cargo.lock | 7 + 9 files changed, 293 insertions(+), 142 deletions(-) diff --git a/Justfile b/Justfile index d083e5e..38c9ccf 100644 --- a/Justfile +++ b/Justfile @@ -3,5 +3,16 @@ check: cargo hack --each-feature --exclude-all-features clippy --manifest-path rubicon/Cargo.toml -test: - SOPRINTLN=1 cargo run --manifest-path test-crates/bin/Cargo.toml +test *args: + #!/usr/bin/env bash -eux + BIN_CHANNEL="${BIN_CHANNEL:-stable}" + BIN_FLAGS="${BIN_FLAGS:-}" + + SOPRINTLN=1 cargo "+${BIN_CHANNEL}" build --manifest-path test-crates/bin/Cargo.toml "${BIN_FLAGS}" + + export DYLD_LIBRARY_PATH=$(rustc "+stable" --print sysroot)/lib + export DYLD_LIBRARY_PATH=$DYLD_LIBRARY_PATH:$(rustc "+nightly" --print sysroot)/lib + export LD_LIBRARY_PATH=$(rustc "+stable" --print sysroot)/lib + export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$(rustc "+nightly" --print sysroot)/lib + + ./test-crates/bin/target/debug/bin {{args}} diff --git a/rubicon/Cargo.lock b/rubicon/Cargo.lock index fbdfc31..7133382 100644 --- a/rubicon/Cargo.lock +++ b/rubicon/Cargo.lock @@ -12,6 +12,12 @@ dependencies = [ "syn", ] +[[package]] +name = "libc" +version = "0.2.155" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" + [[package]] name = "paste" version = "1.0.15" @@ -41,6 +47,7 @@ name = "rubicon" version = "3.0.1" dependencies = [ "ctor", + "libc", "paste", "rustc_version", ] diff --git a/rubicon/Cargo.toml b/rubicon/Cargo.toml index 18e0518..3bfa0cf 100644 --- a/rubicon/Cargo.toml +++ b/rubicon/Cargo.toml @@ -15,6 +15,7 @@ crate-type = ["dylib"] [dependencies] ctor = { version = "0.2.8", optional = true } +libc = { version = "0.2.155", optional = true } paste = { version = "1.0.15", optional = true } [build-dependencies] @@ -23,5 +24,5 @@ rustc_version = { version = "0.4.0", optional = true } [features] default = [] export-globals = ["dep:paste", "dep:rustc_version"] -import-globals = ["dep:paste", "dep:rustc_version", "dep:ctor"] +import-globals = ["dep:paste", "dep:rustc_version", "dep:ctor", "dep:libc"] ctor = ["dep:ctor"] diff --git a/rubicon/src/lib.rs b/rubicon/src/lib.rs index e056fe3..c1832c6 100644 --- a/rubicon/src/lib.rs +++ b/rubicon/src/lib.rs @@ -39,6 +39,9 @@ pub use paste::paste; #[cfg(feature = "import-globals")] pub use ctor; +#[cfg(feature = "import-globals")] +pub use libc; + #[cfg(any(feature = "export-globals", feature = "import-globals"))] pub const RUBICON_RUSTC_VERSION: &str = env!("RUBICON_RUSTC_VERSION"); @@ -332,6 +335,64 @@ macro_rules! compatibility_check { static COMPATIBILITY_INFO: &'static [(&'static str, &'static str)]; } + use $crate::libc::{c_void, Dl_info}; + use std::ffi::CStr; + use std::ptr; + + extern "C" { + fn dladdr(addr: *const c_void, info: *mut Dl_info) -> i32; + } + + fn get_shared_object_name() -> Option { + unsafe { + let mut info: Dl_info = std::mem::zeroed(); + if dladdr(get_shared_object_name as *const c_void, &mut info) != 0 { + let c_str = CStr::from_ptr(info.dli_fname); + return Some(c_str.to_string_lossy().into_owned()); + } + } + None + } + + struct AnsiEscape(u64, D); + + impl std::fmt::Display for AnsiEscape { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "\x1b[{}m{}\x1b[0m", self.0, self.1) + } + } + + fn blue(d: D) -> AnsiEscape { + AnsiEscape(34, d) + } + fn green(d: D) -> AnsiEscape { + AnsiEscape(32, d) + } + fn red(d: D) -> AnsiEscape { + AnsiEscape(31, d) + } + fn grey(d: D) -> AnsiEscape { + AnsiEscape(90, d) + } + + // Helper function to count visible characters (ignoring ANSI escapes) + fn visible_len(s: &str) -> usize { + let mut len = 0; + let mut in_escape = false; + for c in s.chars() { + if c == '\x1b' { + in_escape = true; + } else if in_escape { + if c.is_alphabetic() { + in_escape = false; + } + } else { + len += 1; + } + } + len + } + #[ctor] fn check_compatibility() { let imported: &[(&str, &str)] = &[ @@ -339,159 +400,150 @@ macro_rules! compatibility_check { ("target-triple", $crate::RUBICON_TARGET_TRIPLE), $($feature)* ]; - let exported = unsafe { COMPATIBILITY_INFO }; let missing: Vec<_> = imported.iter().filter(|&item| !exported.contains(item)).collect(); let extra: Vec<_> = exported.iter().filter(|&item| !imported.contains(item)).collect(); - struct AnsiEscape(u64, D); - - impl std::fmt::Display for AnsiEscape { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "\x1b[{}m{}\x1b[0m", self.0, self.1) - } + if missing.is_empty() && extra.is_empty() { + // all good + return; } - fn blue(d: D) -> AnsiEscape { - AnsiEscape(34, d) - } - fn green(d: D) -> AnsiEscape { - AnsiEscape(32, d) - } - fn red(d: D) -> AnsiEscape { - AnsiEscape(31, d) + let so_name = get_shared_object_name().unwrap_or("unknown_so".to_string()); + // get only the last bit of the path + let so_name = so_name.rsplit('/').next().unwrap_or("unknown_so"); + + let exe_name = std::env::current_exe().map(|p| p.file_name().unwrap().to_string_lossy().to_string()).unwrap_or_else(|_| "unknown_exe".to_string()); + + let mut error_message = String::new(); + error_message.push_str("\n\x1b[31mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\x1b[0m\n"); + error_message.push_str(&format!(" ๐Ÿ’€ Feature mismatch for crate \x1b[31m{}\x1b[0m\n\n", env!("CARGO_PKG_NAME"))); + + error_message.push_str(&format!("Loading this module would mix different configurations of the {} crate.\n\n", red(env!("CARGO_PKG_NAME")))); + + // Compute max lengths for alignment + let max_exported_len = exported.iter().map(|(k, v)| format!("{}={}", k, v).len()).max().unwrap_or(0); + let max_ref_len = imported.iter().map(|(k, v)| format!("{}={}", k, v).len()).max().unwrap_or(0); + let column_width = max_exported_len.max(max_ref_len); + + let binary_label = format!("Binary {}", blue(&exe_name)); + let module_label = format!("Module {}", blue(so_name)); + println!("visible_len(binary_label) = {}", visible_len(&binary_label)); + println!("visible_len(module_label) = {}", visible_len(&module_label)); + + let binary_label_width = visible_len(&binary_label); + let module_label_width = visible_len(&module_label); + let binary_padding = " ".repeat(column_width.saturating_sub(binary_label_width)); + let module_padding = " ".repeat(column_width.saturating_sub(module_label_width)); + + error_message.push_str(&format!("{}{} {}{}\n", + binary_label, + binary_padding, + module_label, + module_padding + )); + error_message.push_str(&format!("{:โ” = Vec::new(); + for (key, _) in exported.iter() { + if !all_keys.contains(key) { + all_keys.push(key); + } } - fn grey(d: D) -> AnsiEscape { - AnsiEscape(90, d) + for (key, _) in imported.iter() { + if !all_keys.contains(key) { + all_keys.push(key); + } } - if !missing.is_empty() || !extra.is_empty() { - let mut error_message = String::new(); - error_message.push_str("\n\x1b[31mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\x1b[0m\n"); - error_message.push_str(&format!(" ๐Ÿ’€ Compatibility mismatch for module \x1b[31m{}\x1b[0m\n\n", env!("CARGO_PKG_NAME"))); - - error_message.push_str(&format!("'{}' doesn't have the exact same cargo features enabled in the main binary and in the module being loaded\n\n", red(env!("CARGO_PKG_NAME")))); - - // Compute max lengths for alignment - let max_exported_len = exported.iter().map(|(k, v)| format!("{}={}", k, v).len()).max().unwrap_or(0); - let max_ref_len = imported.iter().map(|(k, v)| format!("{}={}", k, v).len()).max().unwrap_or(0); - let column_width = max_exported_len.max(max_ref_len); - - error_message.push_str(&format!("{: = Vec::new(); - for (key, _) in exported.iter() { - if !all_keys.contains(key) { - all_keys.push(key); - } - } - for (key, _) in imported.iter() { - if !all_keys.contains(key) { - all_keys.push(key); - } - } - - for key in all_keys.iter() { - let exported_value = exported.iter().find(|&(k, _)| k == key).map(|(_, v)| v); - let imported_value = imported.iter().find(|&(k, _)| k == key).map(|(_, v)| v); - - match (exported_value, imported_value) { - (Some(value), Some(expected_value)) => { - // Item in both - if value == expected_value { - let left_item = format!("{}{}{}", grey(key), grey("="), grey(value)); - let right_item = format!("{}{}{}", grey(key), grey("="), grey(expected_value)); - let left_item_len = key.len() + value.len() + 1; // +1 for '=' - let padding = " ".repeat(column_width.saturating_sub(left_item_len)); - error_message.push_str(&format!("{}{} {}\n", left_item, padding, right_item)); - } else { - let left_item = format!("{}{}{}", blue(key), grey("="), green(value)); - let right_item = format!("{}{}{}", blue(key), grey("="), red(expected_value)); - let left_item_len = key.len() + value.len() + 1; // +1 for '=' - let padding = " ".repeat(column_width.saturating_sub(left_item_len)); - error_message.push_str(&format!("{}{} {}\n", left_item, padding, right_item)); - } - } - (Some(value), None) => { - // Item only in exported - let left_item = format!("{}{}{}", green(key), grey("="), green(value)); - let right_item = format!("{}", red("MISSING!")); + match (exported_value, imported_value) { + (Some(value), Some(expected_value)) => { + // Item in both + if value == expected_value { + let left_item = format!("{}{}{}", grey(key), grey("="), grey(value)); + let right_item = format!("{}{}{}", grey(key), grey("="), grey(expected_value)); let left_item_len = key.len() + value.len() + 1; // +1 for '=' let padding = " ".repeat(column_width.saturating_sub(left_item_len)); error_message.push_str(&format!("{}{} {}\n", left_item, padding, right_item)); - } - (None, Some(value)) => { - // Item only in imported - let left_item = format!("{}", red("MISSING!")); - let right_item = format!("{}{}{}", green(key), grey("="), green(value)); - let left_item_len = "MISSING!".len(); + } else { + let left_item = format!("{}{}{}", blue(key), grey("="), green(value)); + let right_item = format!("{}{}{}", blue(key), grey("="), red(expected_value)); + let left_item_len = key.len() + value.len() + 1; // +1 for '=' let padding = " ".repeat(column_width.saturating_sub(left_item_len)); error_message.push_str(&format!("{}{} {}\n", left_item, padding, right_item)); } - (None, None) => { - // This should never happen as the key is from all_keys - unreachable!() - } } - } - - let transitive_line = format!("main binary. Note that the {} dependency might be transitive", red(env!("CARGO_PKG_NAME"))); - let cargo_tree_line = format!("Run `cargo tree -i {}` to figure out why you even have it.", red(env!("CARGO_PKG_NAME"))); - - let lines = vec![ - "\x1b[34mHINT:\x1b[0m", - "To fix this issue, rebuild this module with the same cargo features as the", - &transitive_line, - "(i.e., pulled indirectly by another dependency).", - "", - &cargo_tree_line, - ]; - - // Helper function to count visible characters (ignoring ANSI escapes) - fn visible_len(s: &str) -> usize { - let mut len = 0; - let mut in_escape = false; - for c in s.chars() { - if c == '\x1b' { - in_escape = true; - } else if in_escape { - if c.is_alphabetic() { - in_escape = false; - } - } else { - len += 1; - } + (Some(value), None) => { + // Item only in exported + let left_item = format!("{}{}{}", green(key), grey("="), green(value)); + let right_item = format!("{}", red("MISSING!")); + let left_item_len = key.len() + value.len() + 1; // +1 for '=' + let padding = " ".repeat(column_width.saturating_sub(left_item_len)); + error_message.push_str(&format!("{}{} {}\n", left_item, padding, right_item)); + } + (None, Some(value)) => { + // Item only in imported + let left_item = format!("{}", red("MISSING!")); + let right_item = format!("{}{}{}", green(key), grey("="), green(value)); + let left_item_len = "MISSING!".len(); + let padding = " ".repeat(column_width.saturating_sub(left_item_len)); + error_message.push_str(&format!("{}{} {}\n", left_item, padding, right_item)); + } + (None, None) => { + // This should never happen as the key is from all_keys + unreachable!() } - len } + } - let max_width = lines.iter().map(|line| visible_len(line)).max().unwrap_or(0); - let box_width = max_width + 4; // Add 4 for left and right borders and spaces + error_message.push_str("\nDifferent feature sets may result in different struct layouts, which\n"); + error_message.push_str("would lead to memory corruption. Instead we're going to panic now.\n\n"); - error_message.push_str("\n"); - error_message.push_str(&format!("โ”Œ{}โ”\n", "โ”€".repeat(box_width - 2))); + error_message.push_str("More info: \x1b[4m\x1b[34mhttps://crates.io/crates/rubicon\x1b[0m\n"); - for line in lines { - if line.is_empty() { - error_message.push_str(&format!("โ”‚{}โ”‚\n", " ".repeat(box_width - 2))); - } else { - let visible_line_len = visible_len(line); - let padding = " ".repeat(box_width - 4 - visible_line_len); - error_message.push_str(&format!("โ”‚ {}{} โ”‚\n", line, padding)); - } - } + let rebuild_line = format!("To fix this issue, {} needs to enable", blue(so_name)); + let transitive_line = format!("the same cargo features as {} for crate {}.", blue(&exe_name), red(env!("CARGO_PKG_NAME"))); + let empty_line = ""; + let hint_line = "\x1b[34mHINT:\x1b[0m"; + let cargo_tree_line = format!("Run `cargo tree -i {} -e features` from both.", red(env!("CARGO_PKG_NAME"))); + + let lines = vec![ + &rebuild_line, + &transitive_line, + empty_line, + hint_line, + &cargo_tree_line, + ]; + + let max_width = lines.iter().map(|line| visible_len(line)).max().unwrap_or(0); + let box_width = max_width + 4; // Add 4 for left and right borders and spaces - error_message.push_str(&format!("โ””{}โ”˜\n", "โ”€".repeat(box_width - 2))); - error_message.push_str("\n\x1b[31mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\x1b[0m\n"); + error_message.push_str("\n"); + error_message.push_str(&format!("โ”Œ{}โ”\n", "โ”€".repeat(box_width - 2))); - panic!("{}", error_message); + for line in lines { + if line.is_empty() { + error_message.push_str(&format!("โ”‚{}โ”‚\n", " ".repeat(box_width - 2))); + } else { + let visible_line_len = visible_len(line); + let padding = " ".repeat(box_width - 4 - visible_line_len); + error_message.push_str(&format!("โ”‚ {}{} โ”‚\n", line, padding)); + } } + + error_message.push_str(&format!("โ””{}โ”˜\n", "โ”€".repeat(box_width - 2))); + error_message.push_str("\n\x1b[31mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\x1b[0m\n"); + + panic!("{}", error_message); } }; } diff --git a/test-crates/bin/src/main.rs b/test-crates/bin/src/main.rs index a05ff3d..972b059 100644 --- a/test-crates/bin/src/main.rs +++ b/test-crates/bin/src/main.rs @@ -4,6 +4,64 @@ use exports::{self as _, mokio}; use soprintln::soprintln; fn main() { + struct ModuleSpec { + name: &'static str, + channel: String, + features: Vec, + } + + let mut modules = [ + ModuleSpec { + name: "mod_a", + channel: "stable".to_string(), + features: Default::default(), + }, + ModuleSpec { + name: "mod_b", + channel: "stable".to_string(), + features: Default::default(), + }, + ]; + + for arg in std::env::args().skip(1) { + if let Some(rest) = arg.strip_prefix("--features:") { + let parts: Vec<&str> = rest.splitn(2, '=').collect(); + if parts.len() != 2 { + panic!("Invalid argument format: expected --features:module=feature1,feature2"); + } + let mod_name = parts[0]; + let features = parts[1].split(',').map(|s| s.to_owned()); + let module = modules + .iter_mut() + .find(|m| m.name == mod_name) + .unwrap_or_else(|| panic!("Unknown module: {}", mod_name)); + + for feature in features { + module.features.push(feature); + } + } else if let Some(rest) = arg.strip_prefix("--channel:") { + let parts: Vec<&str> = rest.splitn(2, '=').collect(); + if parts.len() != 2 { + panic!("Invalid argument format: expected --channel:module=(stable|nightly)"); + } + let mod_name = parts[0]; + let channel = parts[1]; + if channel != "stable" && channel != "nightly" { + panic!( + "Invalid channel: {}. Expected 'stable' or 'nightly'", + channel + ); + } + let module = modules + .iter_mut() + .find(|m| m.name == mod_name) + .unwrap_or_else(|| panic!("Unknown module: {}", mod_name)); + module.channel = channel.to_string(); + } else { + panic!("Unknown argument: {}", arg); + } + } + soprintln::init!(); let exe_path = std::env::current_exe().expect("Failed to get current exe path"); let project_root = exe_path @@ -17,9 +75,12 @@ fn main() { soprintln!("app starting up..."); - let modules = ["../mod_a", "../mod_b"]; for module in modules { - soprintln!("building {module}"); + soprintln!( + "building {} with features {:?}", + module.name, + module.features.join(", ") + ); cfg_if::cfg_if! { if #[cfg(target_os = "macos")] { @@ -31,19 +92,24 @@ fn main() { } } - let output = std::process::Command::new("cargo") - .arg("b") + let mut cmd = std::process::Command::new("cargo"); + cmd.arg(format!("+{}", module.channel)) + .arg("build") .env("RUSTFLAGS", rustflags) - .current_dir(module) - .output() - .expect("Failed to execute cargo build"); + .current_dir(format!("../{}", module.name)); + if !module.features.is_empty() { + cmd.arg("--features").arg(module.features.join(",")); + } + + let output = cmd.output().expect("Failed to execute cargo build"); if !output.status.success() { eprintln!( "Error building {}: {}", - module, + module.name, String::from_utf8_lossy(&output.stderr) ); + std::process::exit(1); } } diff --git a/test-crates/exports/Cargo.toml b/test-crates/exports/Cargo.toml index 268af60..8fcc4fc 100644 --- a/test-crates/exports/Cargo.toml +++ b/test-crates/exports/Cargo.toml @@ -8,3 +8,6 @@ crate-type = ["dylib"] [dependencies] mokio = { version = "0.1.0", path = "../mokio", features = ["export-globals"] } + +[features] +mokio-timer = ["mokio/timer"] diff --git a/test-crates/mod_a/Cargo.lock b/test-crates/mod_a/Cargo.lock index 90427c2..4f8beef 100644 --- a/test-crates/mod_a/Cargo.lock +++ b/test-crates/mod_a/Cargo.lock @@ -12,6 +12,12 @@ dependencies = [ "syn", ] +[[package]] +name = "libc" +version = "0.2.155" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" + [[package]] name = "mod_a" version = "0.1.0" @@ -57,6 +63,7 @@ name = "rubicon" version = "3.0.1" dependencies = [ "ctor", + "libc", "paste", "rustc_version", ] diff --git a/test-crates/mod_a/Cargo.toml b/test-crates/mod_a/Cargo.toml index 5bb93f6..b327edf 100644 --- a/test-crates/mod_a/Cargo.toml +++ b/test-crates/mod_a/Cargo.toml @@ -7,9 +7,6 @@ edition = "2021" crate-type = ["cdylib"] [dependencies] -mokio = { version = "0.1.0", path = "../mokio", features = [ - "import-globals", - "timer", -] } +mokio = { version = "0.1.0", path = "../mokio", features = ["import-globals"] } rubicon = { path = "../../rubicon" } soprintln = { version = "3.0.0", features = ["print"] } diff --git a/test-crates/mod_b/Cargo.lock b/test-crates/mod_b/Cargo.lock index c434309..f1cbf2f 100644 --- a/test-crates/mod_b/Cargo.lock +++ b/test-crates/mod_b/Cargo.lock @@ -12,6 +12,12 @@ dependencies = [ "syn", ] +[[package]] +name = "libc" +version = "0.2.155" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" + [[package]] name = "mod_b" version = "0.1.0" @@ -57,6 +63,7 @@ name = "rubicon" version = "3.0.1" dependencies = [ "ctor", + "libc", "paste", "rustc_version", ] From b2df44822f4bd74577728b9b69a4562c2b7d2ff6 Mon Sep 17 00:00:00 2001 From: Amos Wenger Date: Tue, 23 Jul 2024 17:30:09 +0200 Subject: [PATCH 09/35] comma --- rubicon/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rubicon/src/lib.rs b/rubicon/src/lib.rs index c1832c6..3f45a19 100644 --- a/rubicon/src/lib.rs +++ b/rubicon/src/lib.rs @@ -506,7 +506,7 @@ macro_rules! compatibility_check { } error_message.push_str("\nDifferent feature sets may result in different struct layouts, which\n"); - error_message.push_str("would lead to memory corruption. Instead we're going to panic now.\n\n"); + error_message.push_str("would lead to memory corruption. Instead, we're going to panic now.\n\n"); error_message.push_str("More info: \x1b[4m\x1b[34mhttps://crates.io/crates/rubicon\x1b[0m\n"); From 721392965fc283f5a558fa1da9b62458e1eee948 Mon Sep 17 00:00:00 2001 From: Amos Wenger Date: Tue, 23 Jul 2024 17:40:18 +0200 Subject: [PATCH 10/35] Lots more tests --- .github/workflows/test.yml | 42 +++++++++++++++++----- Justfile | 4 +-- rubicon/src/lib.rs | 2 +- test-crates/{bin => samplebin}/Cargo.lock | 22 ++++++------ test-crates/{bin => samplebin}/Cargo.toml | 2 +- test-crates/{bin => samplebin}/src/main.rs | 0 6 files changed, 49 insertions(+), 23 deletions(-) rename test-crates/{bin => samplebin}/Cargo.lock (99%) rename test-crates/{bin => samplebin}/Cargo.toml (92%) rename test-crates/{bin => samplebin}/src/main.rs (100%) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 252f981..58d2f77 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -22,16 +22,42 @@ jobs: toolchain: stable profile: minimal override: true - - name: Run cargo command + - name: Tests pass (debug) run: | - cargo run --manifest-path test-crates/bin/Cargo.toml + cargo run --manifest-path test-crates/samplebin/Cargo.toml continue-on-error: ${{ matrix.os == 'windows-latest' }} - - name: Run cargo command (release) + - name: Tests pass (release) run: | - cargo run --manifest-path test-crates/bin/Cargo.toml --release + cargo run --manifest-path test-crates/samplebin/Cargo.toml --release continue-on-error: ${{ matrix.os == 'windows-latest' }} - - name: Run cargo command (release with SOPRINTLN=1) - if: matrix.os != 'windows-latest' + - name: Bin stable, mod_a nightly (should fail) run: | - export SOPRINTLN=1 - cargo run --manifest-path test-crates/bin/Cargo.toml --release + ! cargo +stable run --manifest-path test-crates/samplebin/Cargo.toml -- --channel:mod_a=nightly + shell: bash + - name: Bin nightly, mod_a stable (should fail) + run: | + ! cargo +nightly run --manifest-path test-crates/samplebin/Cargo.toml -- --channel:mod_a=stable + shell: bash + - name: All nightly (should work) + run: | + cargo +nightly run --manifest-path test-crates/samplebin/Cargo.toml -- --channel:mod_a=nightly --channel:mod_b=nightly + shell: bash + - name: Bin has mokio-timer feature (should fail) + run: | + ! cargo run --features=exports/mokio-timer --manifest-path test-crates/samplebin/Cargo.toml + shell: bash + - name: mod_a has mokio-timer feature (should fail) + run: | + ! cargo run --manifest-path test-crates/mod_a/Cargo.toml -- --features:mod_a=mokio/timer + shell: bash + - name: mod_b has mokio-timer feature (should fail) + run: | + ! cargo run --manifest-path test-crates/mod_b/Cargo.toml -- --features:mod_b=mokio/timer + shell: bash + - name: all mods have mokio-timer feature (should fail) + run: | + ! cargo run --manifest-path test-crates/samplebin/Cargo.toml -- --features:mod_a=mokio/timer --features:mod_b=mokio/timer + - name: bin and mods have mokio-timer feature (should work) + run: | + cargo run --features=exports/mokio-timer --manifest-path test-crates/samplebin/Cargo.toml -- --features:mod_a=mokio/timer --features:mod_b=mokio/timer + shell: bash diff --git a/Justfile b/Justfile index 38c9ccf..528967d 100644 --- a/Justfile +++ b/Justfile @@ -8,11 +8,11 @@ test *args: BIN_CHANNEL="${BIN_CHANNEL:-stable}" BIN_FLAGS="${BIN_FLAGS:-}" - SOPRINTLN=1 cargo "+${BIN_CHANNEL}" build --manifest-path test-crates/bin/Cargo.toml "${BIN_FLAGS}" + SOPRINTLN=1 cargo "+${BIN_CHANNEL}" build --manifest-path test-crates/samplebin/Cargo.toml "${BIN_FLAGS}" export DYLD_LIBRARY_PATH=$(rustc "+stable" --print sysroot)/lib export DYLD_LIBRARY_PATH=$DYLD_LIBRARY_PATH:$(rustc "+nightly" --print sysroot)/lib export LD_LIBRARY_PATH=$(rustc "+stable" --print sysroot)/lib export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$(rustc "+nightly" --print sysroot)/lib - ./test-crates/bin/target/debug/bin {{args}} + ./test-crates/samplebin/target/debug/samplebin {{args}} diff --git a/rubicon/src/lib.rs b/rubicon/src/lib.rs index 3f45a19..e8352e1 100644 --- a/rubicon/src/lib.rs +++ b/rubicon/src/lib.rs @@ -420,7 +420,7 @@ macro_rules! compatibility_check { error_message.push_str("\n\x1b[31mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\x1b[0m\n"); error_message.push_str(&format!(" ๐Ÿ’€ Feature mismatch for crate \x1b[31m{}\x1b[0m\n\n", env!("CARGO_PKG_NAME"))); - error_message.push_str(&format!("Loading this module would mix different configurations of the {} crate.\n\n", red(env!("CARGO_PKG_NAME")))); + error_message.push_str(&format!("Loading {} would mix different configurations of the {} crate.\n\n", blue(so_name), red(env!("CARGO_PKG_NAME")))); // Compute max lengths for alignment let max_exported_len = exported.iter().map(|(k, v)| format!("{}={}", k, v).len()).max().unwrap_or(0); diff --git a/test-crates/bin/Cargo.lock b/test-crates/samplebin/Cargo.lock similarity index 99% rename from test-crates/bin/Cargo.lock rename to test-crates/samplebin/Cargo.lock index 0588abd..91bad90 100644 --- a/test-crates/bin/Cargo.lock +++ b/test-crates/samplebin/Cargo.lock @@ -2,17 +2,6 @@ # It is not intended for manual editing. version = 3 -[[package]] -name = "bin" -version = "0.1.0" -dependencies = [ - "cfg-if", - "exports", - "libloading", - "rubicon", - "soprintln", -] - [[package]] name = "cfg-if" version = "1.0.0" @@ -66,6 +55,17 @@ dependencies = [ "semver", ] +[[package]] +name = "samplebin" +version = "0.1.0" +dependencies = [ + "cfg-if", + "exports", + "libloading", + "rubicon", + "soprintln", +] + [[package]] name = "semver" version = "1.0.23" diff --git a/test-crates/bin/Cargo.toml b/test-crates/samplebin/Cargo.toml similarity index 92% rename from test-crates/bin/Cargo.toml rename to test-crates/samplebin/Cargo.toml index 8b5f6e7..3cff505 100644 --- a/test-crates/bin/Cargo.toml +++ b/test-crates/samplebin/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "bin" +name = "samplebin" version = "0.1.0" edition = "2021" diff --git a/test-crates/bin/src/main.rs b/test-crates/samplebin/src/main.rs similarity index 100% rename from test-crates/bin/src/main.rs rename to test-crates/samplebin/src/main.rs From ce4e44edb30da7c6a5ca822595289c5c7f9ffa83 Mon Sep 17 00:00:00 2001 From: Amos Wenger Date: Tue, 23 Jul 2024 17:43:58 +0200 Subject: [PATCH 11/35] Install nightly I suppose --- .github/workflows/test.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 58d2f77..bb6da5b 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -30,6 +30,8 @@ jobs: run: | cargo run --manifest-path test-crates/samplebin/Cargo.toml --release continue-on-error: ${{ matrix.os == 'windows-latest' }} + - name: Add nightly + run: rustup toolchain add nightly - name: Bin stable, mod_a nightly (should fail) run: | ! cargo +stable run --manifest-path test-crates/samplebin/Cargo.toml -- --channel:mod_a=nightly From 327500d6cd1adb07f70c13dbfeaf05a7cad37756 Mon Sep 17 00:00:00 2001 From: Amos Wenger Date: Tue, 23 Jul 2024 18:08:37 +0200 Subject: [PATCH 12/35] Introduce node.js test runner --- .github/workflows/test.yml | 54 +++++++- rubicon/src/lib.rs | 2 - test-crates/samplebin/src/main.rs | 10 ++ tests/.gitignore | 1 + tests/index.mjs | 215 ++++++++++++++++++++++++++++++ tests/package-lock.json | 28 ++++ tests/package.json | 14 ++ 7 files changed, 316 insertions(+), 8 deletions(-) create mode 100644 tests/.gitignore create mode 100644 tests/index.mjs create mode 100644 tests/package-lock.json create mode 100644 tests/package.json diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index bb6da5b..154d36f 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -22,23 +22,44 @@ jobs: toolchain: stable profile: minimal override: true + - name: Set dynamic linker paths + run: | + export DYLD_LIBRARY_PATH=$(rustc --print sysroot)/lib + export LD_LIBRARY_PATH=$(rustc --print sysroot)/lib + export PATH=$(rustc --print sysroot)/lib:$PATH + export DYLD_LIBRARY_PATH_NIGHTLY=$(rustc +nightly --print sysroot)/lib + export LD_LIBRARY_PATH_NIGHTLY=$(rustc +nightly --print sysroot)/lib + export PATH_NIGHTLY=$(rustc +nightly --print sysroot)/lib:$PATH + shell: bash - name: Tests pass (debug) run: | cargo run --manifest-path test-crates/samplebin/Cargo.toml continue-on-error: ${{ matrix.os == 'windows-latest' }} + shell: bash - name: Tests pass (release) run: | cargo run --manifest-path test-crates/samplebin/Cargo.toml --release continue-on-error: ${{ matrix.os == 'windows-latest' }} + shell: bash - name: Add nightly run: rustup toolchain add nightly - name: Bin stable, mod_a nightly (should fail) run: | - ! cargo +stable run --manifest-path test-crates/samplebin/Cargo.toml -- --channel:mod_a=nightly + output=$(cargo +stable run --manifest-path test-crates/samplebin/Cargo.toml -- --channel:mod_a=nightly 2>&1) + if [[ $output != *"feature mismatch for crate"* ]]; then + echo "Expected feature mismatch error, but got:" + echo "$output" + exit 1 + fi shell: bash - name: Bin nightly, mod_a stable (should fail) run: | - ! cargo +nightly run --manifest-path test-crates/samplebin/Cargo.toml -- --channel:mod_a=stable + output=$(cargo +nightly run --manifest-path test-crates/samplebin/Cargo.toml -- --channel:mod_a=stable 2>&1) + if [[ $output != *"feature mismatch for crate"* ]]; then + echo "Expected feature mismatch error, but got:" + echo "$output" + exit 1 + fi shell: bash - name: All nightly (should work) run: | @@ -46,19 +67,40 @@ jobs: shell: bash - name: Bin has mokio-timer feature (should fail) run: | - ! cargo run --features=exports/mokio-timer --manifest-path test-crates/samplebin/Cargo.toml + output=$(cargo run --features=exports/mokio-timer --manifest-path test-crates/samplebin/Cargo.toml 2>&1) + if [[ $output != *"feature mismatch for crate"* ]]; then + echo "Expected feature mismatch error, but got:" + echo "$output" + exit 1 + fi shell: bash - name: mod_a has mokio-timer feature (should fail) run: | - ! cargo run --manifest-path test-crates/mod_a/Cargo.toml -- --features:mod_a=mokio/timer + output=$(cargo run --manifest-path test-crates/mod_a/Cargo.toml -- --features:mod_a=mokio/timer 2>&1) + if [[ $output != *"feature mismatch for crate"* ]]; then + echo "Expected feature mismatch error, but got:" + echo "$output" + exit 1 + fi shell: bash - name: mod_b has mokio-timer feature (should fail) run: | - ! cargo run --manifest-path test-crates/mod_b/Cargo.toml -- --features:mod_b=mokio/timer + output=$(cargo run --manifest-path test-crates/mod_b/Cargo.toml -- --features:mod_b=mokio/timer 2>&1) + if [[ $output != *"feature mismatch for crate"* ]]; then + echo "Expected feature mismatch error, but got:" + echo "$output" + exit 1 + fi shell: bash - name: all mods have mokio-timer feature (should fail) run: | - ! cargo run --manifest-path test-crates/samplebin/Cargo.toml -- --features:mod_a=mokio/timer --features:mod_b=mokio/timer + output=$(cargo run --manifest-path test-crates/samplebin/Cargo.toml -- --features:mod_a=mokio/timer --features:mod_b=mokio/timer 2>&1) + if [[ $output != *"feature mismatch for crate"* ]]; then + echo "Expected feature mismatch error, but got:" + echo "$output" + exit 1 + fi + shell: bash - name: bin and mods have mokio-timer feature (should work) run: | cargo run --features=exports/mokio-timer --manifest-path test-crates/samplebin/Cargo.toml -- --features:mod_a=mokio/timer --features:mod_b=mokio/timer diff --git a/rubicon/src/lib.rs b/rubicon/src/lib.rs index e8352e1..49cfcfb 100644 --- a/rubicon/src/lib.rs +++ b/rubicon/src/lib.rs @@ -429,8 +429,6 @@ macro_rules! compatibility_check { let binary_label = format!("Binary {}", blue(&exe_name)); let module_label = format!("Module {}", blue(so_name)); - println!("visible_len(binary_label) = {}", visible_len(&binary_label)); - println!("visible_len(module_label) = {}", visible_len(&module_label)); let binary_label_width = visible_len(&binary_label); let module_label_width = visible_len(&module_label); diff --git a/test-crates/samplebin/src/main.rs b/test-crates/samplebin/src/main.rs index 972b059..2a53e02 100644 --- a/test-crates/samplebin/src/main.rs +++ b/test-crates/samplebin/src/main.rs @@ -132,6 +132,16 @@ fn main() { ) } + soprintln!( + "DYLD_LIBRARY_PATH = {}", + std::env::var("DYLD_LIBRARY_PATH").unwrap_or_default() + ); + soprintln!( + "LD_LIBRARY_PATH = {}", + std::env::var("LD_LIBRARY_PATH").unwrap_or_default() + ); + soprintln!("PATH = {}", std::env::var("PATH").unwrap_or_default()); + soprintln!("loading modules..."); let lib_a = unsafe { libloading::Library::new(module_path("a")).unwrap() }; let lib_a = Box::leak(Box::new(lib_a)); diff --git a/tests/.gitignore b/tests/.gitignore new file mode 100644 index 0000000..3c3629e --- /dev/null +++ b/tests/.gitignore @@ -0,0 +1 @@ +node_modules diff --git a/tests/index.mjs b/tests/index.mjs new file mode 100644 index 0000000..eb51ff2 --- /dev/null +++ b/tests/index.mjs @@ -0,0 +1,215 @@ +import { spawn, execSync } from "child_process"; +import chalk from "chalk"; +import os from "os"; +import { existsSync } from "fs"; +import { dirname } from "path"; + +let ENV_VARS = {}; + +// Helper function to set environment variables +function setEnvVariables() { + const rustSysroot = execSync("rustc --print sysroot").toString().trim(); + const rustNightlySysroot = execSync("rustc +nightly --print sysroot") + .toString() + .trim(); + + const platform = os.platform(); + if (platform === "darwin") { + console.log("๐ŸŽ Detected macOS"); + ENV_VARS.DYLD_LIBRARY_PATH = `${rustSysroot}/lib:${rustNightlySysroot}/lib`; + } else if (platform === "win32") { + console.log("๐ŸชŸ Detected Windows"); + ENV_VARS.PATH += `;${process.env.PATH};${rustSysroot}/lib;${rustNightlySysroot}/lib`; + } else if (platform === "linux") { + console.log("๐Ÿง Detected Linux"); + ENV_VARS.LD_LIBRARY_PATH = `${rustSysroot}/lib:${rustNightlySysroot}/lib`; + } else { + console.log(`โŒ Unsupported platform: ${platform}`); + process.exit(1); + } + + console.log("\nEnvironment Variables Summary:"); + for (const [key, value] of Object.entries(ENV_VARS)) { + console.log(`${key}: ${value}`); + } +} + +// Helper function to run a command and capture output +function runCommand(command) { + try { + const child = spawn(command, [], { + shell: true, + stdio: ["inherit", "pipe", "pipe"], + env: { + SOPRINTLN: "1", + PATH: process.env.PATH, + ...ENV_VARS, + }, + }); + console.log("Set ENV_VARS to: ", ENV_VARS); + + let output = ""; + + child.stdout.on("data", (data) => { + process.stdout.write(data); + output += data; + }); + + child.stderr.on("data", (data) => { + process.stderr.write(data); + output += data; + }); + + return new Promise((resolve) => { + child.on("close", (code) => { + resolve({ + success: code === 0, + output: output, + }); + }); + }); + } catch (error) { + process.stderr.write(chalk.red(error.toString())); + return Promise.resolve({ + success: false, + output: error.toString(), + }); + } +} + +// Helper function to check for feature mismatch +function checkFeatureMismatch(output) { + return output.includes("feature mismatch for crate"); +} + +// Test cases +const testCases = [ + { + name: "Tests pass (debug)", + command: "cargo run --manifest-path test-crates/samplebin/Cargo.toml", + expectedResult: "success", + }, + { + name: "Tests pass (release)", + command: + "cargo run --manifest-path test-crates/samplebin/Cargo.toml --release", + expectedResult: "success", + }, + { + name: "Bin stable, mod_a nightly (should fail)", + command: + "cargo +stable run --manifest-path test-crates/samplebin/Cargo.toml -- --channel:mod_a=nightly", + expectedResult: "fail", + checkFeatureMismatch: true, + }, + { + name: "Bin nightly, mod_a stable (should fail)", + command: + "cargo +nightly run --manifest-path test-crates/samplebin/Cargo.toml -- --channel:mod_a=stable", + expectedResult: "fail", + checkFeatureMismatch: true, + }, + { + name: "All nightly (should work)", + command: + "cargo +nightly run --manifest-path test-crates/samplebin/Cargo.toml -- --channel:mod_a=nightly --channel:mod_b=nightly", + expectedResult: "success", + }, + { + name: "Bin has mokio-timer feature (should fail)", + command: + "cargo run --features=exports/mokio-timer --manifest-path test-crates/samplebin/Cargo.toml", + expectedResult: "fail", + checkFeatureMismatch: true, + }, + { + name: "mod_a has mokio-timer feature (should fail)", + command: + "cargo run --manifest-path test-crates/mod_a/Cargo.toml -- --features:mod_a=mokio/timer", + expectedResult: "fail", + checkFeatureMismatch: true, + }, + { + name: "mod_b has mokio-timer feature (should fail)", + command: + "cargo run --manifest-path test-crates/mod_b/Cargo.toml -- --features:mod_b=mokio/timer", + expectedResult: "fail", + checkFeatureMismatch: true, + }, + { + name: "all mods have mokio-timer feature (should fail)", + command: + "cargo run --manifest-path test-crates/samplebin/Cargo.toml -- --features:mod_a=mokio/timer --features:mod_b=mokio/timer", + expectedResult: "fail", + checkFeatureMismatch: true, + }, + { + name: "bin and mods have mokio-timer feature (should work)", + command: + "cargo run --features=exports/mokio-timer --manifest-path test-crates/samplebin/Cargo.toml -- --features:mod_a=mokio/timer --features:mod_b=mokio/timer", + expectedResult: "success", + }, +]; + +// Main function to run tests +async function runTests() { + console.log(chalk.blue("Changing working directory to Git root...")); + let currentDir = process.cwd(); + + while (!existsSync(`${currentDir}/.git`)) { + const parentDir = dirname(currentDir); + if (parentDir === currentDir) { + console.log(chalk.red("Git root not found. Exiting.")); + process.exit(1); + } + currentDir = parentDir; + } + process.chdir(currentDir); + console.log(chalk.green(`Changed working directory to: ${currentDir}`)); + console.log(chalk.blue("Checking Rust version and toolchain...")); + console.log(chalk.yellow("rustc --version:")); + await runCommand("rustc --version"); + console.log(chalk.yellow("\nrustup which rustc:")); + await runCommand("rustup which rustc"); + console.log(""); + + console.log(chalk.blue("Setting up environment variables...")); + setEnvVariables(); + + console.log(chalk.blue("Installing nightly Rust...")); + await runCommand("rustup toolchain add nightly"); + + console.log(chalk.blue("Running tests...")); + for (const [index, test] of testCases.entries()) { + console.log(chalk.yellow(`\nRunning test ${index + 1}: ${test.name}`)); + const { success, output } = await runCommand(test.command); + + if (test.expectedResult === "success" && success) { + console.log(chalk.green("Test passed as expected.")); + } else if (test.expectedResult === "fail" && !success) { + if (test.checkFeatureMismatch && checkFeatureMismatch(output)) { + console.log( + chalk.green("Test failed with feature mismatch as expected."), + ); + } else { + console.log( + chalk.red( + "Test failed, but not with the expected feature mismatch error.", + ), + ); + } + } else { + console.log( + chalk.red( + `Test result unexpected. Expected ${test.expectedResult}, but got ${success ? "success" : "failure"}.`, + ), + ); + } + } +} + +// Run the tests +runTests().catch((error) => { + console.error(chalk.red(`An error occurred: ${error.message}`)); + process.exit(1); +}); diff --git a/tests/package-lock.json b/tests/package-lock.json new file mode 100644 index 0000000..5259b3f --- /dev/null +++ b/tests/package-lock.json @@ -0,0 +1,28 @@ +{ + "name": "tests", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "tests", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "chalk": "^5.3.0" + } + }, + "node_modules/chalk": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", + "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + } + } +} diff --git a/tests/package.json b/tests/package.json new file mode 100644 index 0000000..e40d66c --- /dev/null +++ b/tests/package.json @@ -0,0 +1,14 @@ +{ + "name": "tests", + "version": "1.0.0", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "", + "license": "ISC", + "description": "", + "dependencies": { + "chalk": "^5.3.0" + } +} From f92e08277f83de9395c15fa991b7b46988ce1b67 Mon Sep 17 00:00:00 2001 From: Amos Wenger Date: Tue, 23 Jul 2024 18:26:12 +0200 Subject: [PATCH 13/35] tests refinement --- Justfile | 2 +- tests/.gitignore | 1 + tests/Cargo.lock | 7 ++ tests/Cargo.toml | 6 + tests/index.mjs | 86 ++++++++----- tests/src/main.rs | 302 ++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 376 insertions(+), 28 deletions(-) create mode 100644 tests/Cargo.lock create mode 100644 tests/Cargo.toml create mode 100644 tests/src/main.rs diff --git a/Justfile b/Justfile index 528967d..e9743ba 100644 --- a/Justfile +++ b/Justfile @@ -8,7 +8,7 @@ test *args: BIN_CHANNEL="${BIN_CHANNEL:-stable}" BIN_FLAGS="${BIN_FLAGS:-}" - SOPRINTLN=1 cargo "+${BIN_CHANNEL}" build --manifest-path test-crates/samplebin/Cargo.toml "${BIN_FLAGS}" + SOPRINTLN=1 cargo "+${BIN_CHANNEL}" build --manifest-path test-crates/samplebin/Cargo.toml ${BIN_FLAGS} export DYLD_LIBRARY_PATH=$(rustc "+stable" --print sysroot)/lib export DYLD_LIBRARY_PATH=$DYLD_LIBRARY_PATH:$(rustc "+nightly" --print sysroot)/lib diff --git a/tests/.gitignore b/tests/.gitignore index 3c3629e..2c085d1 100644 --- a/tests/.gitignore +++ b/tests/.gitignore @@ -1 +1,2 @@ node_modules +target diff --git a/tests/Cargo.lock b/tests/Cargo.lock new file mode 100644 index 0000000..7f9e7f1 --- /dev/null +++ b/tests/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "tests" +version = "0.1.0" diff --git a/tests/Cargo.toml b/tests/Cargo.toml new file mode 100644 index 0000000..9800495 --- /dev/null +++ b/tests/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "tests" +version = "0.1.0" +edition = "2021" + +[dependencies] diff --git a/tests/index.mjs b/tests/index.mjs index eb51ff2..a8921f6 100644 --- a/tests/index.mjs +++ b/tests/index.mjs @@ -2,7 +2,7 @@ import { spawn, execSync } from "child_process"; import chalk from "chalk"; import os from "os"; import { existsSync } from "fs"; -import { dirname } from "path"; +import { dirname, join } from "path"; let ENV_VARS = {}; @@ -37,16 +37,17 @@ function setEnvVariables() { // Helper function to run a command and capture output function runCommand(command) { try { + let env = { + SOPRINTLN: "1", + PATH: process.env.PATH, + ...ENV_VARS, + }; + console.log("Running with env: ", env); const child = spawn(command, [], { shell: true, stdio: ["inherit", "pipe", "pipe"], - env: { - SOPRINTLN: "1", - PATH: process.env.PATH, - ...ENV_VARS, - }, + env, }); - console.log("Set ENV_VARS to: ", ENV_VARS); let output = ""; @@ -86,67 +87,85 @@ function checkFeatureMismatch(output) { const testCases = [ { name: "Tests pass (debug)", - command: "cargo run --manifest-path test-crates/samplebin/Cargo.toml", + buildCommand: + "cargo build --manifest-path test-crates/samplebin/Cargo.toml", + runCommand: "./test-crates/samplebin/target/debug/samplebin", expectedResult: "success", }, { name: "Tests pass (release)", - command: - "cargo run --manifest-path test-crates/samplebin/Cargo.toml --release", + buildCommand: + "cargo build --manifest-path test-crates/samplebin/Cargo.toml --release", + runCommand: "./test-crates/samplebin/target/release/samplebin", expectedResult: "success", }, { name: "Bin stable, mod_a nightly (should fail)", - command: - "cargo +stable run --manifest-path test-crates/samplebin/Cargo.toml -- --channel:mod_a=nightly", + buildCommand: + "cargo +stable build --manifest-path test-crates/samplebin/Cargo.toml", + runCommand: + "./test-crates/samplebin/target/debug/samplebin --channel:mod_a=nightly", expectedResult: "fail", checkFeatureMismatch: true, }, { name: "Bin nightly, mod_a stable (should fail)", - command: - "cargo +nightly run --manifest-path test-crates/samplebin/Cargo.toml -- --channel:mod_a=stable", + buildCommand: + "cargo +nightly build --manifest-path test-crates/samplebin/Cargo.toml", + runCommand: + "./test-crates/samplebin/target/debug/samplebin --channel:mod_a=stable", expectedResult: "fail", checkFeatureMismatch: true, }, { name: "All nightly (should work)", - command: - "cargo +nightly run --manifest-path test-crates/samplebin/Cargo.toml -- --channel:mod_a=nightly --channel:mod_b=nightly", + buildCommand: + "cargo +nightly build --manifest-path test-crates/samplebin/Cargo.toml", + runCommand: + "./test-crates/samplebin/target/debug/samplebin --channel:mod_a=nightly --channel:mod_b=nightly", expectedResult: "success", }, { name: "Bin has mokio-timer feature (should fail)", - command: - "cargo run --features=exports/mokio-timer --manifest-path test-crates/samplebin/Cargo.toml", + buildCommand: + "cargo build --features=exports/mokio-timer --manifest-path test-crates/samplebin/Cargo.toml", + runCommand: "./test-crates/samplebin/target/debug/samplebin", expectedResult: "fail", checkFeatureMismatch: true, }, { name: "mod_a has mokio-timer feature (should fail)", - command: - "cargo run --manifest-path test-crates/mod_a/Cargo.toml -- --features:mod_a=mokio/timer", + buildCommand: + "cargo build --manifest-path test-crates/samplebin/Cargo.toml", + runCommand: + "./test-crates/samplebin/target/debug/samplebin --features:mod_a=mokio/timer", expectedResult: "fail", checkFeatureMismatch: true, }, { name: "mod_b has mokio-timer feature (should fail)", - command: - "cargo run --manifest-path test-crates/mod_b/Cargo.toml -- --features:mod_b=mokio/timer", + buildCommand: + "cargo build --manifest-path test-crates/samplebin/Cargo.toml", + runCommand: + "./test-crates/samplebin/target/debug/samplebin --features:mod_b=mokio/timer", expectedResult: "fail", checkFeatureMismatch: true, }, { name: "all mods have mokio-timer feature (should fail)", - command: - "cargo run --manifest-path test-crates/samplebin/Cargo.toml -- --features:mod_a=mokio/timer --features:mod_b=mokio/timer", + buildCommand: + "cargo build --manifest-path test-crates/samplebin/Cargo.toml", + runCommand: + "./test-crates/samplebin/target/debug/samplebin --features:mod_a=mokio/timer --features:mod_b=mokio/timer", expectedResult: "fail", checkFeatureMismatch: true, }, { name: "bin and mods have mokio-timer feature (should work)", - command: - "cargo run --features=exports/mokio-timer --manifest-path test-crates/samplebin/Cargo.toml -- --features:mod_a=mokio/timer --features:mod_b=mokio/timer", + buildCommand: + "cargo build --features=exports/mokio-timer --manifest-path test-crates/samplebin/Cargo.toml", + runCommand: + "./test-crates/samplebin/target/debug/samplebin --features:mod_a=mokio/timer --features:mod_b=mokio/timer", expectedResult: "success", }, ]; @@ -182,7 +201,16 @@ async function runTests() { console.log(chalk.blue("Running tests...")); for (const [index, test] of testCases.entries()) { console.log(chalk.yellow(`\nRunning test ${index + 1}: ${test.name}`)); - const { success, output } = await runCommand(test.command); + + console.log(chalk.cyan("Building...")); + const buildResult = await runCommand(test.buildCommand); + if (!buildResult.success) { + console.log(chalk.red("Build failed. Exiting tests.")); + process.exit(1); + } + + console.log(chalk.cyan("Running...")); + const { success, output } = await runCommand(test.runCommand); if (test.expectedResult === "success" && success) { console.log(chalk.green("Test passed as expected.")); @@ -197,6 +225,7 @@ async function runTests() { "Test failed, but not with the expected feature mismatch error.", ), ); + process.exit(1); } } else { console.log( @@ -204,8 +233,11 @@ async function runTests() { `Test result unexpected. Expected ${test.expectedResult}, but got ${success ? "success" : "failure"}.`, ), ); + process.exit(1); } } + + console.log(chalk.green("All tests passed successfully.")); } // Run the tests diff --git a/tests/src/main.rs b/tests/src/main.rs new file mode 100644 index 0000000..71148dc --- /dev/null +++ b/tests/src/main.rs @@ -0,0 +1,302 @@ +use std::collections::HashMap; +use std::env; +use std::io; +use std::path::Path; +use std::process::{Command, Stdio}; + +struct EnvVars { + vars: HashMap, +} + +impl EnvVars { + fn new() -> Self { + EnvVars { + vars: HashMap::new(), + } + } + + fn set(&mut self, key: &str, value: String) { + self.vars.insert(key.to_string(), value); + } +} + +fn set_env_variables() -> EnvVars { + let mut env_vars = EnvVars::new(); + + let rust_sysroot = Command::new("rustc") + .arg("--print") + .arg("sysroot") + .output() + .expect("Failed to execute rustc") + .stdout; + let rust_sysroot = String::from_utf8_lossy(&rust_sysroot).trim().to_string(); + + let rust_nightly_sysroot = Command::new("rustc") + .args(["+nightly", "--print", "sysroot"]) + .output() + .expect("Failed to execute rustc +nightly") + .stdout; + let rust_nightly_sysroot = String::from_utf8_lossy(&rust_nightly_sysroot) + .trim() + .to_string(); + + let platform = env::consts::OS; + match platform { + "macos" => { + println!("๐ŸŽ Detected macOS"); + env_vars.set( + "DYLD_LIBRARY_PATH", + format!("{}/lib:{}/lib", rust_sysroot, rust_nightly_sysroot), + ); + } + "windows" => { + println!("๐ŸชŸ Detected Windows"); + let current_path = env::var("PATH").unwrap_or_default(); + env_vars.set( + "PATH", + format!( + "{};{}/lib;{}/lib", + current_path, rust_sysroot, rust_nightly_sysroot + ), + ); + } + "linux" => { + println!("๐Ÿง Detected Linux"); + env_vars.set( + "LD_LIBRARY_PATH", + format!("{}/lib:{}/lib", rust_sysroot, rust_nightly_sysroot), + ); + } + _ => { + eprintln!("โŒ Unsupported platform: {}", platform); + std::process::exit(1); + } + } + + println!("\nEnvironment Variables Summary:"); + for (key, value) in &env_vars.vars { + println!("{}: {}", key, value); + } + + env_vars +} + +fn run_command(command: &str, env_vars: &EnvVars) -> io::Result<(bool, String)> { + use std::io::{BufRead, BufReader}; + use std::sync::mpsc; + use std::thread; + + let mut command_parts = command.split_whitespace(); + let program = command_parts.next().expect("Command is empty"); + let args: Vec<&str> = command_parts.collect(); + + println!("Running command: {} {:?}", program, args); + + let mut child = Command::new(program) + .args(args) + .envs(&env_vars.vars) + .env("PATH", std::env::var("PATH").unwrap()) + .stdin(Stdio::inherit()) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn()?; + + let (tx_stdout, rx_stdout) = mpsc::channel(); + let (tx_stderr, rx_stderr) = mpsc::channel(); + + let stdout = child.stdout.take().expect("Failed to capture stdout"); + let stderr = child.stderr.take().expect("Failed to capture stderr"); + + let stdout_thread = thread::spawn(move || { + let reader = BufReader::new(stdout); + for line in reader.lines() { + let line = line.expect("Failed to read line from stdout"); + println!("{}", line); + tx_stdout.send(line).expect("Failed to send stdout line"); + } + }); + + let stderr_thread = thread::spawn(move || { + let reader = BufReader::new(stderr); + for line in reader.lines() { + let line = line.expect("Failed to read line from stderr"); + eprintln!("{}", line); + tx_stderr.send(line).expect("Failed to send stderr line"); + } + }); + + let mut output = String::new(); + + for line in rx_stdout.iter() { + output.push_str(&line); + output.push('\n'); + } + + for line in rx_stderr.iter() { + output.push_str(&line); + output.push('\n'); + } + + stdout_thread.join().expect("stdout thread panicked"); + stderr_thread.join().expect("stderr thread panicked"); + + let status = child.wait()?; + Ok((status.success(), output)) +} + +fn check_feature_mismatch(output: &str) -> bool { + output.contains("feature mismatch for crate") +} + +struct TestCase { + name: &'static str, + build_command: &'static str, + run_command: &'static str, + expected_result: &'static str, + check_feature_mismatch: bool, +} + +fn run_tests() -> io::Result<()> { + println!("Changing working directory to Git root..."); + let mut current_dir = env::current_dir()?; + + while !Path::new(¤t_dir).join(".git").exists() { + if let Some(parent) = current_dir.parent() { + current_dir = parent.to_path_buf(); + } else { + eprintln!("Git root not found. Exiting."); + std::process::exit(1); + } + } + + env::set_current_dir(¤t_dir)?; + println!("Changed working directory to: {}", current_dir.display()); + + println!("Checking Rust version and toolchain..."); + println!("rustc --version:"); + run_command("rustc --version", &EnvVars::new())?; + println!("\nrustup which rustc:"); + run_command("rustup which rustc", &EnvVars::new())?; + println!(); + + println!("Setting up environment variables..."); + let env_vars = set_env_variables(); + + println!("Installing nightly Rust..."); + run_command("rustup toolchain add nightly", &env_vars)?; + + println!("Running tests..."); + + let test_cases = [ + TestCase { + name: "Tests pass (debug)", + build_command: "cargo build --manifest-path test-crates/samplebin/Cargo.toml", + run_command: "./test-crates/samplebin/target/debug/samplebin", + expected_result: "success", + check_feature_mismatch: false, + }, + TestCase { + name: "Tests pass (release)", + build_command: "cargo build --manifest-path test-crates/samplebin/Cargo.toml --release", + run_command: "./test-crates/samplebin/target/release/samplebin", + expected_result: "success", + check_feature_mismatch: false, + }, + TestCase { + name: "Bin stable, mod_a nightly (should fail)", + build_command: "cargo +stable build --manifest-path test-crates/samplebin/Cargo.toml", + run_command: "./test-crates/samplebin/target/debug/samplebin --channel:mod_a=nightly", + expected_result: "fail", + check_feature_mismatch: true, + }, + TestCase { + name: "Bin nightly, mod_a stable (should fail)", + build_command: "cargo +nightly build --manifest-path test-crates/samplebin/Cargo.toml", + run_command: "./test-crates/samplebin/target/debug/samplebin --channel:mod_a=stable", + expected_result: "fail", + check_feature_mismatch: true, + }, + TestCase { + name: "All nightly (should work)", + build_command: "cargo +nightly build --manifest-path test-crates/samplebin/Cargo.toml", + run_command: "./test-crates/samplebin/target/debug/samplebin --channel:mod_a=nightly --channel:mod_b=nightly", + expected_result: "success", + check_feature_mismatch: false, + }, + TestCase { + name: "Bin has mokio-timer feature (should fail)", + build_command: "cargo build --features=exports/mokio-timer --manifest-path test-crates/samplebin/Cargo.toml", + run_command: "./test-crates/samplebin/target/debug/samplebin", + expected_result: "fail", + check_feature_mismatch: true, + }, + TestCase { + name: "mod_a has mokio-timer feature (should fail)", + build_command: "cargo build --manifest-path test-crates/samplebin/Cargo.toml", + run_command: "./test-crates/samplebin/target/debug/samplebin --features:mod_a=mokio/timer", + expected_result: "fail", + check_feature_mismatch: true, + }, + TestCase { + name: "mod_b has mokio-timer feature (should fail)", + build_command: "cargo build --manifest-path test-crates/samplebin/Cargo.toml", + run_command: "./test-crates/samplebin/target/debug/samplebin --features:mod_b=mokio/timer", + expected_result: "fail", + check_feature_mismatch: true, + }, + TestCase { + name: "all mods have mokio-timer feature (should fail)", + build_command: "cargo build --manifest-path test-crates/samplebin/Cargo.toml", + run_command: "./test-crates/samplebin/target/debug/samplebin --features:mod_a=mokio/timer --features:mod_b=mokio/timer", + expected_result: "fail", + check_feature_mismatch: true, + }, + TestCase { + name: "bin and mods have mokio-timer feature (should work)", + build_command: "cargo build --features=exports/mokio-timer --manifest-path test-crates/samplebin/Cargo.toml", + run_command: "./test-crates/samplebin/target/debug/samplebin --features:mod_a=mokio/timer --features:mod_b=mokio/timer", + expected_result: "success", + check_feature_mismatch: false, + }, + ]; + + for (index, test) in test_cases.iter().enumerate() { + println!("\nRunning test {}: {}", index + 1, test.name); + + println!("Building..."); + let build_result = run_command(test.build_command, &env_vars)?; + if !build_result.0 { + eprintln!("Build failed. Exiting tests."); + std::process::exit(1); + } + + println!("Running..."); + let (success, output) = run_command(test.run_command, &env_vars)?; + + match (test.expected_result, success) { + ("success", true) => println!("Test passed as expected."), + ("fail", false) if test.check_feature_mismatch && check_feature_mismatch(&output) => { + println!("Test failed with feature mismatch as expected.") + } + ("fail", false) if test.check_feature_mismatch => { + eprintln!("Test failed, but not with the expected feature mismatch error."); + std::process::exit(1); + } + _ => { + eprintln!( + "Test result unexpected. Expected {}, but got {}.", + test.expected_result, + if success { "success" } else { "failure" } + ); + std::process::exit(1); + } + } + } + + println!("All tests passed successfully."); + Ok(()) +} + +fn main() -> io::Result<()> { + run_tests() +} From bea77ff5c2705be810197323bf08f6427340b522 Mon Sep 17 00:00:00 2001 From: Amos Wenger Date: Tue, 23 Jul 2024 18:55:59 +0200 Subject: [PATCH 14/35] Better box rendering --- rubicon/src/lib.rs | 218 +++++++++++++++------- test-crates/mokio/src/lib.rs | 4 +- test-crates/samplebin/src/main.rs | 16 -- tests/index.mjs | 247 ------------------------ tests/package.json | 14 -- tests/src/main.rs | 300 +++++++++++++++++++----------- 6 files changed, 342 insertions(+), 457 deletions(-) delete mode 100644 tests/index.mjs delete mode 100644 tests/package.json diff --git a/rubicon/src/lib.rs b/rubicon/src/lib.rs index 49cfcfb..3e74117 100644 --- a/rubicon/src/lib.rs +++ b/rubicon/src/lib.rs @@ -362,6 +362,19 @@ macro_rules! compatibility_check { } } + #[derive(Clone, Copy)] + struct AnsiColor(u64); + + impl AnsiColor { + const BLUE: AnsiColor = AnsiColor(34); + const GREEN: AnsiColor = AnsiColor(32); + const RED: AnsiColor = AnsiColor(31); + const GREY: AnsiColor = AnsiColor(37); + } + + fn colored(color: AnsiColor, d: D) -> AnsiEscape { + AnsiEscape(color.0, d) + } fn blue(d: D) -> AnsiEscape { AnsiEscape(34, d) } @@ -372,7 +385,7 @@ macro_rules! compatibility_check { AnsiEscape(31, d) } fn grey(d: D) -> AnsiEscape { - AnsiEscape(90, d) + AnsiEscape(35, d) } // Helper function to count visible characters (ignoring ANSI escapes) @@ -427,25 +440,6 @@ macro_rules! compatibility_check { let max_ref_len = imported.iter().map(|(k, v)| format!("{}={}", k, v).len()).max().unwrap_or(0); let column_width = max_exported_len.max(max_ref_len); - let binary_label = format!("Binary {}", blue(&exe_name)); - let module_label = format!("Module {}", blue(so_name)); - - let binary_label_width = visible_len(&binary_label); - let module_label_width = visible_len(&module_label); - let binary_padding = " ".repeat(column_width.saturating_sub(binary_label_width)); - let module_padding = " ".repeat(column_width.saturating_sub(module_label_width)); - - error_message.push_str(&format!("{}{} {}{}\n", - binary_label, - binary_padding, - module_label, - module_padding - )); - error_message.push_str(&format!("{:โ” = Vec::new(); for (key, _) in exported.iter() { @@ -459,86 +453,166 @@ macro_rules! compatibility_check { } } + struct Grid { + rows: Vec>, + column_widths: Vec, + } + + impl Grid { + fn new() -> Self { + Grid { + rows: Vec::new(), + column_widths: Vec::new(), + } + } + + fn add_row(&mut self, row: Vec) { + if self.column_widths.len() < row.len() { + self.column_widths.resize(row.len(), 0); + } + for (i, cell) in row.iter().enumerate() { + self.column_widths[i] = self.column_widths[i].max(visible_len(cell)); + } + self.rows.push(row); + } + + fn write_to(&self, out: &mut String) { + let total_width: usize = self.column_widths.iter().sum::() + self.column_widths.len() * 3 - 1; + + // Top border + out.push_str(&format!("โ”Œ{}โ”\n", "โ”€".repeat(total_width))); + + for (i, row) in self.rows.iter().enumerate() { + if i == 1 { + // Separator after header + out.push_str(&format!("โ•ž{}โ•ก\n", "โ•".repeat(total_width))); + } + + for (j, cell) in row.iter().enumerate() { + out.push_str("โ”‚ "); + out.push_str(cell); + out.push_str(&" ".repeat(self.column_widths[j] - visible_len(cell))); + out.push_str(" "); + } + out.push_str("โ”‚\n"); + } + + // Bottom border + out.push_str(&format!("โ””{}โ”˜\n", "โ”€".repeat(total_width))); + } + } + + let mut grid = Grid::new(); + + // Add header + grid.add_row(vec![format!("Binary {}", blue(&exe_name)), format!("Module {}", blue(so_name))]); + + struct ItemFormatter { + max_key_len: usize, + max_value_len: usize, + } + + impl ItemFormatter { + fn format(&self, k: &str, v: &str, color: AnsiColor) -> String { + format!("{: { - // Item in both if value == expected_value { - let left_item = format!("{}{}{}", grey(key), grey("="), grey(value)); - let right_item = format!("{}{}{}", grey(key), grey("="), grey(expected_value)); - let left_item_len = key.len() + value.len() + 1; // +1 for '=' - let padding = " ".repeat(column_width.saturating_sub(left_item_len)); - error_message.push_str(&format!("{}{} {}\n", left_item, padding, right_item)); + let item = formatter.format(key, value, AnsiColor::GREY); + grid.add_row(vec![item.clone(), item]); } else { - let left_item = format!("{}{}{}", blue(key), grey("="), green(value)); - let right_item = format!("{}{}{}", blue(key), grey("="), red(expected_value)); - let left_item_len = key.len() + value.len() + 1; // +1 for '=' - let padding = " ".repeat(column_width.saturating_sub(left_item_len)); - error_message.push_str(&format!("{}{} {}\n", left_item, padding, right_item)); + let left_item = formatter.format(key, value, AnsiColor::GREEN); + let right_item = formatter.format(key, expected_value, AnsiColor::RED); + grid.add_row(vec![left_item, right_item]); } } (Some(value), None) => { - // Item only in exported - let left_item = format!("{}{}{}", green(key), grey("="), green(value)); - let right_item = format!("{}", red("MISSING!")); - let left_item_len = key.len() + value.len() + 1; // +1 for '=' - let padding = " ".repeat(column_width.saturating_sub(left_item_len)); - error_message.push_str(&format!("{}{} {}\n", left_item, padding, right_item)); + let left_item = formatter.format(key, value, AnsiColor::GREEN); + let right_item = red("MISSING!").to_string(); + grid.add_row(vec![left_item, right_item]); } (None, Some(value)) => { - // Item only in imported - let left_item = format!("{}", red("MISSING!")); - let right_item = format!("{}{}{}", green(key), grey("="), green(value)); - let left_item_len = "MISSING!".len(); - let padding = " ".repeat(column_width.saturating_sub(left_item_len)); - error_message.push_str(&format!("{}{} {}\n", left_item, padding, right_item)); + let left_item = red("MISSING!").to_string(); + let right_item = formatter.format(key, value, AnsiColor::GREEN); + grid.add_row(vec![left_item, right_item]); } (None, None) => { - // This should never happen as the key is from all_keys unreachable!() } } } - error_message.push_str("\nDifferent feature sets may result in different struct layouts, which\n"); - error_message.push_str("would lead to memory corruption. Instead, we're going to panic now.\n\n"); + grid.write_to(&mut error_message); - error_message.push_str("More info: \x1b[4m\x1b[34mhttps://crates.io/crates/rubicon\x1b[0m\n"); + struct MessageBox { + lines: Vec, + max_width: usize, + } - let rebuild_line = format!("To fix this issue, {} needs to enable", blue(so_name)); - let transitive_line = format!("the same cargo features as {} for crate {}.", blue(&exe_name), red(env!("CARGO_PKG_NAME"))); - let empty_line = ""; - let hint_line = "\x1b[34mHINT:\x1b[0m"; - let cargo_tree_line = format!("Run `cargo tree -i {} -e features` from both.", red(env!("CARGO_PKG_NAME"))); - - let lines = vec![ - &rebuild_line, - &transitive_line, - empty_line, - hint_line, - &cargo_tree_line, - ]; + impl MessageBox { + fn new() -> Self { + MessageBox { + lines: Vec::new(), + max_width: 0, + } + } + + fn add_line(&mut self, line: String) { + self.max_width = self.max_width.max(visible_len(&line)); + self.lines.push(line); + } + + fn add_empty_line(&mut self) { + self.lines.push(String::new()); + } - let max_width = lines.iter().map(|line| visible_len(line)).max().unwrap_or(0); - let box_width = max_width + 4; // Add 4 for left and right borders and spaces + fn write_to(&self, out: &mut String) { + let box_width = self.max_width + 4; - error_message.push_str("\n"); - error_message.push_str(&format!("โ”Œ{}โ”\n", "โ”€".repeat(box_width - 2))); + out.push_str("\n"); + out.push_str(&format!("โ”Œ{}โ”\n", "โ”€".repeat(box_width - 2))); - for line in lines { - if line.is_empty() { - error_message.push_str(&format!("โ”‚{}โ”‚\n", " ".repeat(box_width - 2))); - } else { - let visible_line_len = visible_len(line); - let padding = " ".repeat(box_width - 4 - visible_line_len); - error_message.push_str(&format!("โ”‚ {}{} โ”‚\n", line, padding)); + for line in &self.lines { + if line.is_empty() { + out.push_str(&format!("โ”‚{}โ”‚\n", " ".repeat(box_width - 2))); + } else { + let visible_line_len = visible_len(line); + let padding = " ".repeat(box_width - 4 - visible_line_len); + out.push_str(&format!("โ”‚ {}{} โ”‚\n", line, padding)); + } + } + + out.push_str(&format!("โ””{}โ”˜", "โ”€".repeat(box_width - 2))); } } - error_message.push_str(&format!("โ””{}โ”˜\n", "โ”€".repeat(box_width - 2))); + error_message.push_str("\nDifferent feature sets may result in different struct layouts, which\n"); + error_message.push_str("would lead to memory corruption. Instead, we're going to panic now.\n\n"); + + error_message.push_str("More info: \x1b[4m\x1b[34mhttps://crates.io/crates/rubicon\x1b[0m\n"); + + let mut message_box = MessageBox::new(); + message_box.add_line(format!("To fix this issue, {} needs to enable", blue(so_name))); + message_box.add_line(format!("the same cargo features as {} for crate {}.", blue(&exe_name), red(env!("CARGO_PKG_NAME")))); + message_box.add_empty_line(); + message_box.add_line("\x1b[34mHINT:\x1b[0m".to_string()); + message_box.add_line(format!("Run `cargo tree -i {} -e features` from both.", red(env!("CARGO_PKG_NAME")))); + + message_box.write_to(&mut error_message); error_message.push_str("\n\x1b[31mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\x1b[0m\n"); panic!("{}", error_message); diff --git a/test-crates/mokio/src/lib.rs b/test-crates/mokio/src/lib.rs index f339508..e1c64b4 100644 --- a/test-crates/mokio/src/lib.rs +++ b/test-crates/mokio/src/lib.rs @@ -8,8 +8,8 @@ rubicon::compatibility_check! { #[cfg(feature = "timer")] ("timer", "enabled"), - #[cfg(feature = "timer")] - ("has_timer", "1"), + #[cfg(not(feature = "timer"))] + ("timer_is_disabled", "1"), } #[derive(Default)] diff --git a/test-crates/samplebin/src/main.rs b/test-crates/samplebin/src/main.rs index 2a53e02..afdaf36 100644 --- a/test-crates/samplebin/src/main.rs +++ b/test-crates/samplebin/src/main.rs @@ -76,12 +76,6 @@ fn main() { soprintln!("app starting up..."); for module in modules { - soprintln!( - "building {} with features {:?}", - module.name, - module.features.join(", ") - ); - cfg_if::cfg_if! { if #[cfg(target_os = "macos")] { let rustflags = "-Clink-arg=-undefined -Clink-arg=dynamic_lookup"; @@ -132,16 +126,6 @@ fn main() { ) } - soprintln!( - "DYLD_LIBRARY_PATH = {}", - std::env::var("DYLD_LIBRARY_PATH").unwrap_or_default() - ); - soprintln!( - "LD_LIBRARY_PATH = {}", - std::env::var("LD_LIBRARY_PATH").unwrap_or_default() - ); - soprintln!("PATH = {}", std::env::var("PATH").unwrap_or_default()); - soprintln!("loading modules..."); let lib_a = unsafe { libloading::Library::new(module_path("a")).unwrap() }; let lib_a = Box::leak(Box::new(lib_a)); diff --git a/tests/index.mjs b/tests/index.mjs deleted file mode 100644 index a8921f6..0000000 --- a/tests/index.mjs +++ /dev/null @@ -1,247 +0,0 @@ -import { spawn, execSync } from "child_process"; -import chalk from "chalk"; -import os from "os"; -import { existsSync } from "fs"; -import { dirname, join } from "path"; - -let ENV_VARS = {}; - -// Helper function to set environment variables -function setEnvVariables() { - const rustSysroot = execSync("rustc --print sysroot").toString().trim(); - const rustNightlySysroot = execSync("rustc +nightly --print sysroot") - .toString() - .trim(); - - const platform = os.platform(); - if (platform === "darwin") { - console.log("๐ŸŽ Detected macOS"); - ENV_VARS.DYLD_LIBRARY_PATH = `${rustSysroot}/lib:${rustNightlySysroot}/lib`; - } else if (platform === "win32") { - console.log("๐ŸชŸ Detected Windows"); - ENV_VARS.PATH += `;${process.env.PATH};${rustSysroot}/lib;${rustNightlySysroot}/lib`; - } else if (platform === "linux") { - console.log("๐Ÿง Detected Linux"); - ENV_VARS.LD_LIBRARY_PATH = `${rustSysroot}/lib:${rustNightlySysroot}/lib`; - } else { - console.log(`โŒ Unsupported platform: ${platform}`); - process.exit(1); - } - - console.log("\nEnvironment Variables Summary:"); - for (const [key, value] of Object.entries(ENV_VARS)) { - console.log(`${key}: ${value}`); - } -} - -// Helper function to run a command and capture output -function runCommand(command) { - try { - let env = { - SOPRINTLN: "1", - PATH: process.env.PATH, - ...ENV_VARS, - }; - console.log("Running with env: ", env); - const child = spawn(command, [], { - shell: true, - stdio: ["inherit", "pipe", "pipe"], - env, - }); - - let output = ""; - - child.stdout.on("data", (data) => { - process.stdout.write(data); - output += data; - }); - - child.stderr.on("data", (data) => { - process.stderr.write(data); - output += data; - }); - - return new Promise((resolve) => { - child.on("close", (code) => { - resolve({ - success: code === 0, - output: output, - }); - }); - }); - } catch (error) { - process.stderr.write(chalk.red(error.toString())); - return Promise.resolve({ - success: false, - output: error.toString(), - }); - } -} - -// Helper function to check for feature mismatch -function checkFeatureMismatch(output) { - return output.includes("feature mismatch for crate"); -} - -// Test cases -const testCases = [ - { - name: "Tests pass (debug)", - buildCommand: - "cargo build --manifest-path test-crates/samplebin/Cargo.toml", - runCommand: "./test-crates/samplebin/target/debug/samplebin", - expectedResult: "success", - }, - { - name: "Tests pass (release)", - buildCommand: - "cargo build --manifest-path test-crates/samplebin/Cargo.toml --release", - runCommand: "./test-crates/samplebin/target/release/samplebin", - expectedResult: "success", - }, - { - name: "Bin stable, mod_a nightly (should fail)", - buildCommand: - "cargo +stable build --manifest-path test-crates/samplebin/Cargo.toml", - runCommand: - "./test-crates/samplebin/target/debug/samplebin --channel:mod_a=nightly", - expectedResult: "fail", - checkFeatureMismatch: true, - }, - { - name: "Bin nightly, mod_a stable (should fail)", - buildCommand: - "cargo +nightly build --manifest-path test-crates/samplebin/Cargo.toml", - runCommand: - "./test-crates/samplebin/target/debug/samplebin --channel:mod_a=stable", - expectedResult: "fail", - checkFeatureMismatch: true, - }, - { - name: "All nightly (should work)", - buildCommand: - "cargo +nightly build --manifest-path test-crates/samplebin/Cargo.toml", - runCommand: - "./test-crates/samplebin/target/debug/samplebin --channel:mod_a=nightly --channel:mod_b=nightly", - expectedResult: "success", - }, - { - name: "Bin has mokio-timer feature (should fail)", - buildCommand: - "cargo build --features=exports/mokio-timer --manifest-path test-crates/samplebin/Cargo.toml", - runCommand: "./test-crates/samplebin/target/debug/samplebin", - expectedResult: "fail", - checkFeatureMismatch: true, - }, - { - name: "mod_a has mokio-timer feature (should fail)", - buildCommand: - "cargo build --manifest-path test-crates/samplebin/Cargo.toml", - runCommand: - "./test-crates/samplebin/target/debug/samplebin --features:mod_a=mokio/timer", - expectedResult: "fail", - checkFeatureMismatch: true, - }, - { - name: "mod_b has mokio-timer feature (should fail)", - buildCommand: - "cargo build --manifest-path test-crates/samplebin/Cargo.toml", - runCommand: - "./test-crates/samplebin/target/debug/samplebin --features:mod_b=mokio/timer", - expectedResult: "fail", - checkFeatureMismatch: true, - }, - { - name: "all mods have mokio-timer feature (should fail)", - buildCommand: - "cargo build --manifest-path test-crates/samplebin/Cargo.toml", - runCommand: - "./test-crates/samplebin/target/debug/samplebin --features:mod_a=mokio/timer --features:mod_b=mokio/timer", - expectedResult: "fail", - checkFeatureMismatch: true, - }, - { - name: "bin and mods have mokio-timer feature (should work)", - buildCommand: - "cargo build --features=exports/mokio-timer --manifest-path test-crates/samplebin/Cargo.toml", - runCommand: - "./test-crates/samplebin/target/debug/samplebin --features:mod_a=mokio/timer --features:mod_b=mokio/timer", - expectedResult: "success", - }, -]; - -// Main function to run tests -async function runTests() { - console.log(chalk.blue("Changing working directory to Git root...")); - let currentDir = process.cwd(); - - while (!existsSync(`${currentDir}/.git`)) { - const parentDir = dirname(currentDir); - if (parentDir === currentDir) { - console.log(chalk.red("Git root not found. Exiting.")); - process.exit(1); - } - currentDir = parentDir; - } - process.chdir(currentDir); - console.log(chalk.green(`Changed working directory to: ${currentDir}`)); - console.log(chalk.blue("Checking Rust version and toolchain...")); - console.log(chalk.yellow("rustc --version:")); - await runCommand("rustc --version"); - console.log(chalk.yellow("\nrustup which rustc:")); - await runCommand("rustup which rustc"); - console.log(""); - - console.log(chalk.blue("Setting up environment variables...")); - setEnvVariables(); - - console.log(chalk.blue("Installing nightly Rust...")); - await runCommand("rustup toolchain add nightly"); - - console.log(chalk.blue("Running tests...")); - for (const [index, test] of testCases.entries()) { - console.log(chalk.yellow(`\nRunning test ${index + 1}: ${test.name}`)); - - console.log(chalk.cyan("Building...")); - const buildResult = await runCommand(test.buildCommand); - if (!buildResult.success) { - console.log(chalk.red("Build failed. Exiting tests.")); - process.exit(1); - } - - console.log(chalk.cyan("Running...")); - const { success, output } = await runCommand(test.runCommand); - - if (test.expectedResult === "success" && success) { - console.log(chalk.green("Test passed as expected.")); - } else if (test.expectedResult === "fail" && !success) { - if (test.checkFeatureMismatch && checkFeatureMismatch(output)) { - console.log( - chalk.green("Test failed with feature mismatch as expected."), - ); - } else { - console.log( - chalk.red( - "Test failed, but not with the expected feature mismatch error.", - ), - ); - process.exit(1); - } - } else { - console.log( - chalk.red( - `Test result unexpected. Expected ${test.expectedResult}, but got ${success ? "success" : "failure"}.`, - ), - ); - process.exit(1); - } - } - - console.log(chalk.green("All tests passed successfully.")); -} - -// Run the tests -runTests().catch((error) => { - console.error(chalk.red(`An error occurred: ${error.message}`)); - process.exit(1); -}); diff --git a/tests/package.json b/tests/package.json deleted file mode 100644 index e40d66c..0000000 --- a/tests/package.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "name": "tests", - "version": "1.0.0", - "main": "index.js", - "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" - }, - "author": "", - "license": "ISC", - "description": "", - "dependencies": { - "chalk": "^5.3.0" - } -} diff --git a/tests/src/main.rs b/tests/src/main.rs index 71148dc..b53ecb2 100644 --- a/tests/src/main.rs +++ b/tests/src/main.rs @@ -81,14 +81,13 @@ fn set_env_variables() -> EnvVars { env_vars } -fn run_command(command: &str, env_vars: &EnvVars) -> io::Result<(bool, String)> { +fn run_command(command: &[&str], env_vars: &EnvVars) -> io::Result<(bool, String)> { use std::io::{BufRead, BufReader}; use std::sync::mpsc; use std::thread; - let mut command_parts = command.split_whitespace(); - let program = command_parts.next().expect("Command is empty"); - let args: Vec<&str> = command_parts.collect(); + let program = command[0]; + let args = &command[1..]; println!("Running command: {} {:?}", program, args); @@ -145,146 +144,235 @@ fn run_command(command: &str, env_vars: &EnvVars) -> io::Result<(bool, String)> } fn check_feature_mismatch(output: &str) -> bool { - output.contains("feature mismatch for crate") + output.contains("Feature mismatch for crate") } struct TestCase { name: &'static str, - build_command: &'static str, - run_command: &'static str, + build_command: &'static [&'static str], + run_command: &'static [&'static str], expected_result: &'static str, check_feature_mismatch: bool, } +static TEST_CASES: &[TestCase] = &[ + TestCase { + name: "Tests pass (debug)", + build_command: &[ + "cargo", + "build", + "--manifest-path", + "test-crates/samplebin/Cargo.toml", + ], + run_command: &["./test-crates/samplebin/target/debug/samplebin"], + expected_result: "success", + check_feature_mismatch: false, + }, + TestCase { + name: "Tests pass (release)", + build_command: &[ + "cargo", + "build", + "--manifest-path", + "test-crates/samplebin/Cargo.toml", + "--release", + ], + run_command: &["./test-crates/samplebin/target/release/samplebin"], + expected_result: "success", + check_feature_mismatch: false, + }, + TestCase { + name: "Bin stable, mod_a nightly (should fail)", + build_command: &[ + "cargo", + "+stable", + "build", + "--manifest-path", + "test-crates/samplebin/Cargo.toml", + ], + run_command: &[ + "./test-crates/samplebin/target/debug/samplebin", + "--channel:mod_a=nightly", + ], + expected_result: "fail", + check_feature_mismatch: true, + }, + TestCase { + name: "Bin nightly, mod_a stable (should fail)", + build_command: &[ + "cargo", + "+nightly", + "build", + "--manifest-path", + "test-crates/samplebin/Cargo.toml", + ], + run_command: &[ + "./test-crates/samplebin/target/debug/samplebin", + "--channel:mod_a=stable", + ], + expected_result: "fail", + check_feature_mismatch: true, + }, + TestCase { + name: "All nightly (should work)", + build_command: &[ + "cargo", + "+nightly", + "build", + "--manifest-path", + "test-crates/samplebin/Cargo.toml", + ], + run_command: &[ + "./test-crates/samplebin/target/debug/samplebin", + "--channel:mod_a=nightly", + "--channel:mod_b=nightly", + ], + expected_result: "success", + check_feature_mismatch: false, + }, + TestCase { + name: "Bin has mokio-timer feature (should fail)", + build_command: &[ + "cargo", + "build", + "--features=exports/mokio-timer", + "--manifest-path", + "test-crates/samplebin/Cargo.toml", + ], + run_command: &["./test-crates/samplebin/target/debug/samplebin"], + expected_result: "fail", + check_feature_mismatch: true, + }, + TestCase { + name: "mod_a has mokio-timer feature (should fail)", + build_command: &[ + "cargo", + "build", + "--manifest-path", + "test-crates/samplebin/Cargo.toml", + ], + run_command: &[ + "./test-crates/samplebin/target/debug/samplebin", + "--features:mod_a=mokio/timer", + ], + expected_result: "fail", + check_feature_mismatch: true, + }, + TestCase { + name: "mod_b has mokio-timer feature (should fail)", + build_command: &[ + "cargo", + "build", + "--manifest-path", + "test-crates/samplebin/Cargo.toml", + ], + run_command: &[ + "./test-crates/samplebin/target/debug/samplebin", + "--features:mod_b=mokio/timer", + ], + expected_result: "fail", + check_feature_mismatch: true, + }, + TestCase { + name: "all mods have mokio-timer feature (should fail)", + build_command: &[ + "cargo", + "build", + "--manifest-path", + "test-crates/samplebin/Cargo.toml", + ], + run_command: &[ + "./test-crates/samplebin/target/debug/samplebin", + "--features:mod_a=mokio/timer", + "--features:mod_b=mokio/timer", + ], + expected_result: "fail", + check_feature_mismatch: true, + }, + TestCase { + name: "bin and mods have mokio-timer feature (should work)", + build_command: &[ + "cargo", + "build", + "--features=exports/mokio-timer", + "--manifest-path", + "test-crates/samplebin/Cargo.toml", + ], + run_command: &[ + "./test-crates/samplebin/target/debug/samplebin", + "--features:mod_a=mokio/timer", + "--features:mod_b=mokio/timer", + ], + expected_result: "success", + check_feature_mismatch: false, + }, +]; + fn run_tests() -> io::Result<()> { - println!("Changing working directory to Git root..."); + println!("\n๐Ÿš€ \x1b[1;36mChanging working directory to Git root...\x1b[0m"); let mut current_dir = env::current_dir()?; while !Path::new(¤t_dir).join(".git").exists() { if let Some(parent) = current_dir.parent() { current_dir = parent.to_path_buf(); } else { - eprintln!("Git root not found. Exiting."); + eprintln!("โŒ \x1b[1;31mGit root not found. Exiting.\x1b[0m"); std::process::exit(1); } } env::set_current_dir(¤t_dir)?; - println!("Changed working directory to: {}", current_dir.display()); - - println!("Checking Rust version and toolchain..."); - println!("rustc --version:"); - run_command("rustc --version", &EnvVars::new())?; - println!("\nrustup which rustc:"); - run_command("rustup which rustc", &EnvVars::new())?; + println!( + "๐Ÿ“‚ \x1b[1;32mChanged working directory to:\x1b[0m {}", + current_dir.display() + ); + + println!("\n๐Ÿ” \x1b[1;35mChecking Rust version and toolchain...\x1b[0m"); + println!("๐Ÿฆ€ \x1b[1;33mrustc --version:\x1b[0m"); + run_command(&["rustc", "--version"], &EnvVars::new())?; + println!("\n๐Ÿ”ง \x1b[1;33mrustup which rustc:\x1b[0m"); + run_command(&["rustup", "which", "rustc"], &EnvVars::new())?; println!(); - println!("Setting up environment variables..."); + println!("๐ŸŒŸ \x1b[1;36mSetting up environment variables...\x1b[0m"); let env_vars = set_env_variables(); - println!("Installing nightly Rust..."); - run_command("rustup toolchain add nightly", &env_vars)?; - - println!("Running tests..."); - - let test_cases = [ - TestCase { - name: "Tests pass (debug)", - build_command: "cargo build --manifest-path test-crates/samplebin/Cargo.toml", - run_command: "./test-crates/samplebin/target/debug/samplebin", - expected_result: "success", - check_feature_mismatch: false, - }, - TestCase { - name: "Tests pass (release)", - build_command: "cargo build --manifest-path test-crates/samplebin/Cargo.toml --release", - run_command: "./test-crates/samplebin/target/release/samplebin", - expected_result: "success", - check_feature_mismatch: false, - }, - TestCase { - name: "Bin stable, mod_a nightly (should fail)", - build_command: "cargo +stable build --manifest-path test-crates/samplebin/Cargo.toml", - run_command: "./test-crates/samplebin/target/debug/samplebin --channel:mod_a=nightly", - expected_result: "fail", - check_feature_mismatch: true, - }, - TestCase { - name: "Bin nightly, mod_a stable (should fail)", - build_command: "cargo +nightly build --manifest-path test-crates/samplebin/Cargo.toml", - run_command: "./test-crates/samplebin/target/debug/samplebin --channel:mod_a=stable", - expected_result: "fail", - check_feature_mismatch: true, - }, - TestCase { - name: "All nightly (should work)", - build_command: "cargo +nightly build --manifest-path test-crates/samplebin/Cargo.toml", - run_command: "./test-crates/samplebin/target/debug/samplebin --channel:mod_a=nightly --channel:mod_b=nightly", - expected_result: "success", - check_feature_mismatch: false, - }, - TestCase { - name: "Bin has mokio-timer feature (should fail)", - build_command: "cargo build --features=exports/mokio-timer --manifest-path test-crates/samplebin/Cargo.toml", - run_command: "./test-crates/samplebin/target/debug/samplebin", - expected_result: "fail", - check_feature_mismatch: true, - }, - TestCase { - name: "mod_a has mokio-timer feature (should fail)", - build_command: "cargo build --manifest-path test-crates/samplebin/Cargo.toml", - run_command: "./test-crates/samplebin/target/debug/samplebin --features:mod_a=mokio/timer", - expected_result: "fail", - check_feature_mismatch: true, - }, - TestCase { - name: "mod_b has mokio-timer feature (should fail)", - build_command: "cargo build --manifest-path test-crates/samplebin/Cargo.toml", - run_command: "./test-crates/samplebin/target/debug/samplebin --features:mod_b=mokio/timer", - expected_result: "fail", - check_feature_mismatch: true, - }, - TestCase { - name: "all mods have mokio-timer feature (should fail)", - build_command: "cargo build --manifest-path test-crates/samplebin/Cargo.toml", - run_command: "./test-crates/samplebin/target/debug/samplebin --features:mod_a=mokio/timer --features:mod_b=mokio/timer", - expected_result: "fail", - check_feature_mismatch: true, - }, - TestCase { - name: "bin and mods have mokio-timer feature (should work)", - build_command: "cargo build --features=exports/mokio-timer --manifest-path test-crates/samplebin/Cargo.toml", - run_command: "./test-crates/samplebin/target/debug/samplebin --features:mod_a=mokio/timer --features:mod_b=mokio/timer", - expected_result: "success", - check_feature_mismatch: false, - }, - ]; - - for (index, test) in test_cases.iter().enumerate() { - println!("\nRunning test {}: {}", index + 1, test.name); - - println!("Building..."); + println!("๐ŸŒ™ \x1b[1;34mInstalling nightly Rust...\x1b[0m"); + run_command(&["rustup", "toolchain", "add", "nightly"], &env_vars)?; + + println!("\n๐Ÿงช \x1b[1;35mRunning tests...\x1b[0m"); + + for (index, test) in TEST_CASES.iter().enumerate() { + println!("\n\x1b[1;33mโ•”โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•—\x1b[0m"); + println!( + "\x1b[1;33mโ•‘\x1b[0m ๐ŸŽ‰๐Ÿ”ฌ \x1b[1;36mRunning test {}: {}\x1b[0m", + index + 1, + test.name + ); + println!("\x1b[1;33mโ•šโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•\x1b[0m"); + + println!("๐Ÿ—๏ธ \x1b[1;34mBuilding...\x1b[0m"); let build_result = run_command(test.build_command, &env_vars)?; if !build_result.0 { - eprintln!("Build failed. Exiting tests."); + eprintln!("โŒ \x1b[1;31mBuild failed. Exiting tests.\x1b[0m"); std::process::exit(1); } - println!("Running..."); + println!("โ–ถ๏ธ \x1b[1;32mRunning...\x1b[0m"); let (success, output) = run_command(test.run_command, &env_vars)?; match (test.expected_result, success) { - ("success", true) => println!("Test passed as expected."), + ("success", true) => println!("โœ… \x1b[1;32mTest passed as expected.\x1b[0m"), ("fail", false) if test.check_feature_mismatch && check_feature_mismatch(&output) => { - println!("Test failed with feature mismatch as expected.") + println!("โœ… \x1b[1;33mTest failed with feature mismatch as expected.\x1b[0m") } ("fail", false) if test.check_feature_mismatch => { - eprintln!("Test failed, but not with the expected feature mismatch error."); + eprintln!("โŒ \x1b[1;31mTest failed, but not with the expected feature mismatch error.\x1b[0m"); std::process::exit(1); } _ => { eprintln!( - "Test result unexpected. Expected {}, but got {}.", + "โŒ \x1b[1;31mTest result unexpected. Expected {}, but got {}.\x1b[0m", test.expected_result, if success { "success" } else { "failure" } ); @@ -293,7 +381,7 @@ fn run_tests() -> io::Result<()> { } } - println!("All tests passed successfully."); + println!("\n๐ŸŽ‰ \x1b[1;32mAll tests passed successfully.\x1b[0m"); Ok(()) } From 7797e3dcd7c5ab135b64739f9e1620d1dc31d09e Mon Sep 17 00:00:00 2001 From: Amos Wenger Date: Tue, 23 Jul 2024 18:56:27 +0200 Subject: [PATCH 15/35] Use rust test runner --- .github/workflows/test.yml | 82 ++------------------------------------ 1 file changed, 3 insertions(+), 79 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 154d36f..a12dfc2 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -22,86 +22,10 @@ jobs: toolchain: stable profile: minimal override: true - - name: Set dynamic linker paths - run: | - export DYLD_LIBRARY_PATH=$(rustc --print sysroot)/lib - export LD_LIBRARY_PATH=$(rustc --print sysroot)/lib - export PATH=$(rustc --print sysroot)/lib:$PATH - export DYLD_LIBRARY_PATH_NIGHTLY=$(rustc +nightly --print sysroot)/lib - export LD_LIBRARY_PATH_NIGHTLY=$(rustc +nightly --print sysroot)/lib - export PATH_NIGHTLY=$(rustc +nightly --print sysroot)/lib:$PATH - shell: bash - - name: Tests pass (debug) - run: | - cargo run --manifest-path test-crates/samplebin/Cargo.toml - continue-on-error: ${{ matrix.os == 'windows-latest' }} - shell: bash - - name: Tests pass (release) - run: | - cargo run --manifest-path test-crates/samplebin/Cargo.toml --release - continue-on-error: ${{ matrix.os == 'windows-latest' }} - shell: bash - name: Add nightly run: rustup toolchain add nightly - - name: Bin stable, mod_a nightly (should fail) - run: | - output=$(cargo +stable run --manifest-path test-crates/samplebin/Cargo.toml -- --channel:mod_a=nightly 2>&1) - if [[ $output != *"feature mismatch for crate"* ]]; then - echo "Expected feature mismatch error, but got:" - echo "$output" - exit 1 - fi - shell: bash - - name: Bin nightly, mod_a stable (should fail) - run: | - output=$(cargo +nightly run --manifest-path test-crates/samplebin/Cargo.toml -- --channel:mod_a=stable 2>&1) - if [[ $output != *"feature mismatch for crate"* ]]; then - echo "Expected feature mismatch error, but got:" - echo "$output" - exit 1 - fi - shell: bash - - name: All nightly (should work) - run: | - cargo +nightly run --manifest-path test-crates/samplebin/Cargo.toml -- --channel:mod_a=nightly --channel:mod_b=nightly - shell: bash - - name: Bin has mokio-timer feature (should fail) - run: | - output=$(cargo run --features=exports/mokio-timer --manifest-path test-crates/samplebin/Cargo.toml 2>&1) - if [[ $output != *"feature mismatch for crate"* ]]; then - echo "Expected feature mismatch error, but got:" - echo "$output" - exit 1 - fi - shell: bash - - name: mod_a has mokio-timer feature (should fail) - run: | - output=$(cargo run --manifest-path test-crates/mod_a/Cargo.toml -- --features:mod_a=mokio/timer 2>&1) - if [[ $output != *"feature mismatch for crate"* ]]; then - echo "Expected feature mismatch error, but got:" - echo "$output" - exit 1 - fi - shell: bash - - name: mod_b has mokio-timer feature (should fail) - run: | - output=$(cargo run --manifest-path test-crates/mod_b/Cargo.toml -- --features:mod_b=mokio/timer 2>&1) - if [[ $output != *"feature mismatch for crate"* ]]; then - echo "Expected feature mismatch error, but got:" - echo "$output" - exit 1 - fi - shell: bash - - name: all mods have mokio-timer feature (should fail) - run: | - output=$(cargo run --manifest-path test-crates/samplebin/Cargo.toml -- --features:mod_a=mokio/timer --features:mod_b=mokio/timer 2>&1) - if [[ $output != *"feature mismatch for crate"* ]]; then - echo "Expected feature mismatch error, but got:" - echo "$output" - exit 1 - fi - shell: bash - - name: bin and mods have mokio-timer feature (should work) + - name: Run tests runner run: | - cargo run --features=exports/mokio-timer --manifest-path test-crates/samplebin/Cargo.toml -- --features:mod_a=mokio/timer --features:mod_b=mokio/timer + cd tests/ + cargo run shell: bash From 13fef1ec53d509d8def66111e03e3370112253b2 Mon Sep 17 00:00:00 2001 From: Amos Wenger Date: Tue, 23 Jul 2024 19:06:50 +0200 Subject: [PATCH 16/35] Prettier formatting --- rubicon/src/lib.rs | 67 +++++++++++++++++----------------------------- 1 file changed, 24 insertions(+), 43 deletions(-) diff --git a/rubicon/src/lib.rs b/rubicon/src/lib.rs index 3e74117..895076c 100644 --- a/rubicon/src/lib.rs +++ b/rubicon/src/lib.rs @@ -358,7 +358,8 @@ macro_rules! compatibility_check { impl std::fmt::Display for AnsiEscape { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "\x1b[{}m{}\x1b[0m", self.0, self.1) + let inner = format!("\x1b[{}m{}\x1b[0m", self.0, self.1); + f.pad(&inner) } } @@ -505,55 +506,35 @@ macro_rules! compatibility_check { let mut grid = Grid::new(); // Add header - grid.add_row(vec![format!("Binary {}", blue(&exe_name)), format!("Module {}", blue(so_name))]); - - struct ItemFormatter { - max_key_len: usize, - max_value_len: usize, - } - - impl ItemFormatter { - fn format(&self, k: &str, v: &str, color: AnsiColor) -> String { - format!("{: { - if value == expected_value { - let item = formatter.format(key, value, AnsiColor::GREY); - grid.add_row(vec![item.clone(), item]); + let key_column = grey(key).to_string(); + let binary_column = match imported_value { + Some(value) => { + if exported_value.map_or(false, |v| v == value) { + format!("{}", grey(value)) } else { - let left_item = formatter.format(key, value, AnsiColor::GREEN); - let right_item = formatter.format(key, expected_value, AnsiColor::RED); - grid.add_row(vec![left_item, right_item]); + format!("{}", red(value)) } - } - (Some(value), None) => { - let left_item = formatter.format(key, value, AnsiColor::GREEN); - let right_item = red("MISSING!").to_string(); - grid.add_row(vec![left_item, right_item]); - } - (None, Some(value)) => { - let left_item = red("MISSING!").to_string(); - let right_item = formatter.format(key, value, AnsiColor::GREEN); - grid.add_row(vec![left_item, right_item]); - } - (None, None) => { - unreachable!() - } - } + }, + None => format!("{}", grey("(โˆ…)")), + }; + let module_column = match exported_value { + Some(value) => { + if imported_value.map_or(false, |v| v == value) { + format!("{}", grey(value)) + } else { + format!("{}", green(value)) + } + }, + None => format!("{}", grey("(โˆ…)")), + }; + + grid.add_row(vec![key_column, binary_column, module_column]); } grid.write_to(&mut error_message); From 0797a65882741aa1bae365f2351b21dfbce940aa Mon Sep 17 00:00:00 2001 From: Amos Wenger Date: Tue, 23 Jul 2024 19:10:30 +0200 Subject: [PATCH 17/35] Fix tests on mac/windows mayhaps? --- tests/src/main.rs | 39 +++++++++++++++++++++------------------ 1 file changed, 21 insertions(+), 18 deletions(-) diff --git a/tests/src/main.rs b/tests/src/main.rs index b53ecb2..d108523 100644 --- a/tests/src/main.rs +++ b/tests/src/main.rs @@ -20,7 +20,7 @@ impl EnvVars { } } -fn set_env_variables() -> EnvVars { +fn set_env_variables(git_root: &Path) -> EnvVars { let mut env_vars = EnvVars::new(); let rust_sysroot = Command::new("rustc") @@ -41,6 +41,8 @@ fn set_env_variables() -> EnvVars { .to_string(); let platform = env::consts::OS; + let debug_lib_path = git_root.join("test-crates/samplebin/target/debug"); + match platform { "macos" => { println!("๐ŸŽ Detected macOS"); @@ -55,8 +57,11 @@ fn set_env_variables() -> EnvVars { env_vars.set( "PATH", format!( - "{};{}/lib;{}/lib", - current_path, rust_sysroot, rust_nightly_sysroot + "{};{}/lib;{}/lib;{}", + current_path, + rust_sysroot, + rust_nightly_sysroot, + debug_lib_path.display() ), ); } @@ -64,7 +69,12 @@ fn set_env_variables() -> EnvVars { println!("๐Ÿง Detected Linux"); env_vars.set( "LD_LIBRARY_PATH", - format!("{}/lib:{}/lib", rust_sysroot, rust_nightly_sysroot), + format!( + "{}/lib:{}/lib:{}", + rust_sysroot, + rust_nightly_sysroot, + debug_lib_path.display() + ), ); } _ => { @@ -310,32 +320,25 @@ static TEST_CASES: &[TestCase] = &[ fn run_tests() -> io::Result<()> { println!("\n๐Ÿš€ \x1b[1;36mChanging working directory to Git root...\x1b[0m"); - let mut current_dir = env::current_dir()?; + let mut git_root = env::current_dir()?; - while !Path::new(¤t_dir).join(".git").exists() { - if let Some(parent) = current_dir.parent() { - current_dir = parent.to_path_buf(); + while !Path::new(&git_root).join(".git").exists() { + if let Some(parent) = git_root.parent() { + git_root = parent.to_path_buf(); } else { eprintln!("โŒ \x1b[1;31mGit root not found. Exiting.\x1b[0m"); std::process::exit(1); } } - env::set_current_dir(¤t_dir)?; + env::set_current_dir(&git_root)?; println!( "๐Ÿ“‚ \x1b[1;32mChanged working directory to:\x1b[0m {}", - current_dir.display() + git_root.display() ); - println!("\n๐Ÿ” \x1b[1;35mChecking Rust version and toolchain...\x1b[0m"); - println!("๐Ÿฆ€ \x1b[1;33mrustc --version:\x1b[0m"); - run_command(&["rustc", "--version"], &EnvVars::new())?; - println!("\n๐Ÿ”ง \x1b[1;33mrustup which rustc:\x1b[0m"); - run_command(&["rustup", "which", "rustc"], &EnvVars::new())?; - println!(); - println!("๐ŸŒŸ \x1b[1;36mSetting up environment variables...\x1b[0m"); - let env_vars = set_env_variables(); + let env_vars = set_env_variables(&git_root); println!("๐ŸŒ™ \x1b[1;34mInstalling nightly Rust...\x1b[0m"); run_command(&["rustup", "toolchain", "add", "nightly"], &env_vars)?; From b43719beb19c3669df0e6963aae86cd7d6162119 Mon Sep 17 00:00:00 2001 From: Amos Wenger Date: Tue, 23 Jul 2024 19:18:26 +0200 Subject: [PATCH 18/35] Add target dir to library path for Linux --- tests/src/main.rs | 99 +++++++++++++++++++++++++++++------------------ 1 file changed, 62 insertions(+), 37 deletions(-) diff --git a/tests/src/main.rs b/tests/src/main.rs index d108523..2a659ea 100644 --- a/tests/src/main.rs +++ b/tests/src/main.rs @@ -1,22 +1,47 @@ -use std::collections::HashMap; use std::env; use std::io; use std::path::Path; use std::process::{Command, Stdio}; +#[derive(Clone, Default)] struct EnvVars { - vars: HashMap, + library_search_paths: Vec, } impl EnvVars { fn new() -> Self { EnvVars { - vars: HashMap::new(), + library_search_paths: Vec::new(), } } - fn set(&mut self, key: &str, value: String) { - self.vars.insert(key.to_string(), value); + fn add_library_path(&mut self, path: String) { + self.library_search_paths.push(path); + } + + fn each_kv(&self, mut f: F) + where + F: FnMut(&str, &str), + { + let platform = env::consts::OS; + let (env_var, separator) = match platform { + "macos" => ("DYLD_LIBRARY_PATH", ":"), + "windows" => ("PATH", ";"), + "linux" => ("LD_LIBRARY_PATH", ":"), + _ => { + eprintln!("โŒ Unsupported platform: {}", platform); + std::process::exit(1); + } + }; + + let value = self.library_search_paths.join(separator); + f(env_var, &value); + } + + fn with_additional_library_path(&self, path: String) -> Self { + let mut new_env_vars = self.clone(); + new_env_vars.add_library_path(path); + new_env_vars } } @@ -43,39 +68,22 @@ fn set_env_variables(git_root: &Path) -> EnvVars { let platform = env::consts::OS; let debug_lib_path = git_root.join("test-crates/samplebin/target/debug"); + env_vars.add_library_path(format!("{}/lib", rust_sysroot)); + env_vars.add_library_path(format!("{}/lib", rust_nightly_sysroot)); + match platform { "macos" => { println!("๐ŸŽ Detected macOS"); - env_vars.set( - "DYLD_LIBRARY_PATH", - format!("{}/lib:{}/lib", rust_sysroot, rust_nightly_sysroot), - ); } "windows" => { println!("๐ŸชŸ Detected Windows"); let current_path = env::var("PATH").unwrap_or_default(); - env_vars.set( - "PATH", - format!( - "{};{}/lib;{}/lib;{}", - current_path, - rust_sysroot, - rust_nightly_sysroot, - debug_lib_path.display() - ), - ); + env_vars.add_library_path(current_path); + env_vars.add_library_path(debug_lib_path.display().to_string()); } "linux" => { println!("๐Ÿง Detected Linux"); - env_vars.set( - "LD_LIBRARY_PATH", - format!( - "{}/lib:{}/lib:{}", - rust_sysroot, - rust_nightly_sysroot, - debug_lib_path.display() - ), - ); + env_vars.add_library_path(debug_lib_path.display().to_string()); } _ => { eprintln!("โŒ Unsupported platform: {}", platform); @@ -84,9 +92,9 @@ fn set_env_variables(git_root: &Path) -> EnvVars { } println!("\nEnvironment Variables Summary:"); - for (key, value) in &env_vars.vars { + env_vars.each_kv(|key, value| { println!("{}: {}", key, value); - } + }); env_vars } @@ -101,14 +109,18 @@ fn run_command(command: &[&str], env_vars: &EnvVars) -> io::Result<(bool, String println!("Running command: {} {:?}", program, args); - let mut child = Command::new(program) + let mut command = Command::new(program); + command .args(args) - .envs(&env_vars.vars) - .env("PATH", std::env::var("PATH").unwrap()) .stdin(Stdio::inherit()) .stdout(Stdio::piped()) - .stderr(Stdio::piped()) - .spawn()?; + .stderr(Stdio::piped()); + + env_vars.each_kv(|key, value| { + command.env(key, value); + }); + + let mut child = command.spawn()?; let (tx_stdout, rx_stdout) = mpsc::channel(); let (tx_stderr, rx_stderr) = mpsc::channel(); @@ -355,14 +367,27 @@ fn run_tests() -> io::Result<()> { println!("\x1b[1;33mโ•šโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•\x1b[0m"); println!("๐Ÿ—๏ธ \x1b[1;34mBuilding...\x1b[0m"); - let build_result = run_command(test.build_command, &env_vars)?; + let build_result = run_command(test.build_command, &Default::default())?; if !build_result.0 { eprintln!("โŒ \x1b[1;31mBuild failed. Exiting tests.\x1b[0m"); std::process::exit(1); } println!("โ–ถ๏ธ \x1b[1;32mRunning...\x1b[0m"); - let (success, output) = run_command(test.run_command, &env_vars)?; + let profile = if test.build_command.contains(&"--release") { + "release" + } else { + "debug" + }; + let additional_path = git_root + .join("test-crates") + .join("samplebin") + .join("target") + .join(profile); + let (success, output) = run_command( + test.run_command, + &env_vars.with_additional_library_path(additional_path.to_string_lossy().into_owned()), + )?; match (test.expected_result, success) { ("success", true) => println!("โœ… \x1b[1;32mTest passed as expected.\x1b[0m"), From c6abd282b5c21e1f0c4c66cfb7ab71576c7750c3 Mon Sep 17 00:00:00 2001 From: Amos Wenger Date: Tue, 23 Jul 2024 19:18:59 +0200 Subject: [PATCH 19/35] Print summary --- tests/src/main.rs | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/tests/src/main.rs b/tests/src/main.rs index 2a659ea..42b7dd7 100644 --- a/tests/src/main.rs +++ b/tests/src/main.rs @@ -384,10 +384,15 @@ fn run_tests() -> io::Result<()> { .join("samplebin") .join("target") .join(profile); - let (success, output) = run_command( - test.run_command, - &env_vars.with_additional_library_path(additional_path.to_string_lossy().into_owned()), - )?; + let env_vars = + env_vars.with_additional_library_path(additional_path.to_string_lossy().into_owned()); + + println!("\nEnvironment Variables Summary:"); + env_vars.each_kv(|key, value| { + println!("{}: {}", key, value); + }); + + let (success, output) = run_command(test.run_command, &env_vars)?; match (test.expected_result, success) { ("success", true) => println!("โœ… \x1b[1;32mTest passed as expected.\x1b[0m"), From 990006634c662d7449899cc3f08b309c0fb1b791 Mon Sep 17 00:00:00 2001 From: Amos Wenger Date: Tue, 23 Jul 2024 19:22:14 +0200 Subject: [PATCH 20/35] some fixes --- tests/src/main.rs | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/tests/src/main.rs b/tests/src/main.rs index 42b7dd7..38fea16 100644 --- a/tests/src/main.rs +++ b/tests/src/main.rs @@ -66,24 +66,17 @@ fn set_env_variables(git_root: &Path) -> EnvVars { .to_string(); let platform = env::consts::OS; - let debug_lib_path = git_root.join("test-crates/samplebin/target/debug"); env_vars.add_library_path(format!("{}/lib", rust_sysroot)); env_vars.add_library_path(format!("{}/lib", rust_nightly_sysroot)); match platform { - "macos" => { - println!("๐ŸŽ Detected macOS"); + "macos" | "linux" => { + // okay } "windows" => { - println!("๐ŸชŸ Detected Windows"); let current_path = env::var("PATH").unwrap_or_default(); env_vars.add_library_path(current_path); - env_vars.add_library_path(debug_lib_path.display().to_string()); - } - "linux" => { - println!("๐Ÿง Detected Linux"); - env_vars.add_library_path(debug_lib_path.display().to_string()); } _ => { eprintln!("โŒ Unsupported platform: {}", platform); From 76f101e783a4be1a5df2c7244924a67097047912 Mon Sep 17 00:00:00 2001 From: Amos Wenger Date: Tue, 23 Jul 2024 19:26:11 +0200 Subject: [PATCH 21/35] Mh --- tests/src/main.rs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/tests/src/main.rs b/tests/src/main.rs index 38fea16..3eeada0 100644 --- a/tests/src/main.rs +++ b/tests/src/main.rs @@ -380,11 +380,6 @@ fn run_tests() -> io::Result<()> { let env_vars = env_vars.with_additional_library_path(additional_path.to_string_lossy().into_owned()); - println!("\nEnvironment Variables Summary:"); - env_vars.each_kv(|key, value| { - println!("{}: {}", key, value); - }); - let (success, output) = run_command(test.run_command, &env_vars)?; match (test.expected_result, success) { From 640e7c1bec3f794182fb560df8f0b801b2a2cb07 Mon Sep 17 00:00:00 2001 From: Amos Wenger Date: Tue, 23 Jul 2024 20:08:25 +0200 Subject: [PATCH 22/35] allow some tests to fail on linux --- tests/src/main.rs | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/tests/src/main.rs b/tests/src/main.rs index 3eeada0..58e8da7 100644 --- a/tests/src/main.rs +++ b/tests/src/main.rs @@ -168,6 +168,7 @@ struct TestCase { run_command: &'static [&'static str], expected_result: &'static str, check_feature_mismatch: bool, + allowed_to_fail: bool, } static TEST_CASES: &[TestCase] = &[ @@ -182,6 +183,7 @@ static TEST_CASES: &[TestCase] = &[ run_command: &["./test-crates/samplebin/target/debug/samplebin"], expected_result: "success", check_feature_mismatch: false, + allowed_to_fail: false, }, TestCase { name: "Tests pass (release)", @@ -195,6 +197,7 @@ static TEST_CASES: &[TestCase] = &[ run_command: &["./test-crates/samplebin/target/release/samplebin"], expected_result: "success", check_feature_mismatch: false, + allowed_to_fail: false, }, TestCase { name: "Bin stable, mod_a nightly (should fail)", @@ -211,6 +214,7 @@ static TEST_CASES: &[TestCase] = &[ ], expected_result: "fail", check_feature_mismatch: true, + allowed_to_fail: cfg!(target_os = "linux"), }, TestCase { name: "Bin nightly, mod_a stable (should fail)", @@ -227,6 +231,7 @@ static TEST_CASES: &[TestCase] = &[ ], expected_result: "fail", check_feature_mismatch: true, + allowed_to_fail: cfg!(target_os = "linux"), }, TestCase { name: "All nightly (should work)", @@ -244,6 +249,7 @@ static TEST_CASES: &[TestCase] = &[ ], expected_result: "success", check_feature_mismatch: false, + allowed_to_fail: false, }, TestCase { name: "Bin has mokio-timer feature (should fail)", @@ -257,6 +263,7 @@ static TEST_CASES: &[TestCase] = &[ run_command: &["./test-crates/samplebin/target/debug/samplebin"], expected_result: "fail", check_feature_mismatch: true, + allowed_to_fail: false, }, TestCase { name: "mod_a has mokio-timer feature (should fail)", @@ -272,6 +279,7 @@ static TEST_CASES: &[TestCase] = &[ ], expected_result: "fail", check_feature_mismatch: true, + allowed_to_fail: false, }, TestCase { name: "mod_b has mokio-timer feature (should fail)", @@ -287,6 +295,7 @@ static TEST_CASES: &[TestCase] = &[ ], expected_result: "fail", check_feature_mismatch: true, + allowed_to_fail: false, }, TestCase { name: "all mods have mokio-timer feature (should fail)", @@ -303,6 +312,7 @@ static TEST_CASES: &[TestCase] = &[ ], expected_result: "fail", check_feature_mismatch: true, + allowed_to_fail: false, }, TestCase { name: "bin and mods have mokio-timer feature (should work)", @@ -320,6 +330,7 @@ static TEST_CASES: &[TestCase] = &[ ], expected_result: "success", check_feature_mismatch: false, + allowed_to_fail: false, }, ]; @@ -389,7 +400,11 @@ fn run_tests() -> io::Result<()> { } ("fail", false) if test.check_feature_mismatch => { eprintln!("โŒ \x1b[1;31mTest failed, but not with the expected feature mismatch error.\x1b[0m"); - std::process::exit(1); + if test.allowed_to_fail { + println!("โš ๏ธ \x1b[1;33mTest was allowed to fail.\x1b[0m"); + } else { + std::process::exit(1); + } } _ => { eprintln!( @@ -397,7 +412,11 @@ fn run_tests() -> io::Result<()> { test.expected_result, if success { "success" } else { "failure" } ); - std::process::exit(1); + if test.allowed_to_fail { + println!("โš ๏ธ \x1b[1;33mTest was allowed to fail.\x1b[0m"); + } else { + std::process::exit(1); + } } } } From 5e34f2b03826a6a3d24f2f3e00489edea238e8b7 Mon Sep 17 00:00:00 2001 From: Amos Wenger Date: Tue, 23 Jul 2024 20:10:18 +0200 Subject: [PATCH 23/35] exit codes --- tests/src/main.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tests/src/main.rs b/tests/src/main.rs index 58e8da7..52e30bc 100644 --- a/tests/src/main.rs +++ b/tests/src/main.rs @@ -155,6 +155,13 @@ fn run_command(command: &[&str], env_vars: &EnvVars) -> io::Result<(bool, String stderr_thread.join().expect("stderr thread panicked"); let status = child.wait()?; + if !status.success() { + let exit_code = status.code().unwrap_or(-1); + output.push_str(&format!( + "\nProcess exited with code {} (0x{:X})", + exit_code, exit_code + )); + } Ok((status.success(), output)) } From 81a9839875090dba765b7b652320519da1eb4e1c Mon Sep 17 00:00:00 2001 From: Amos Wenger Date: Tue, 23 Jul 2024 20:14:45 +0200 Subject: [PATCH 24/35] Box drawing (again) --- tests/src/main.rs | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/tests/src/main.rs b/tests/src/main.rs index 52e30bc..22076ee 100644 --- a/tests/src/main.rs +++ b/tests/src/main.rs @@ -369,13 +369,22 @@ fn run_tests() -> io::Result<()> { println!("\n๐Ÿงช \x1b[1;35mRunning tests...\x1b[0m"); for (index, test) in TEST_CASES.iter().enumerate() { - println!("\n\x1b[1;33mโ•”โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•—\x1b[0m"); - println!( - "\x1b[1;33mโ•‘\x1b[0m ๐ŸŽ‰๐Ÿ”ฌ \x1b[1;36mRunning test {}: {}\x1b[0m", - index + 1, - test.name - ); - println!("\x1b[1;33mโ•šโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•\x1b[0m"); + { + let test_info = format!("Running test {}: {}", index + 1, test.name); + let box_width = test_info.chars().count() + 4; + let padding = box_width - 2 - test_info.chars().count(); + let left_padding = padding / 2; + let right_padding = padding - left_padding; + + println!("\n\x1b[1;33mโ•”{}โ•—\x1b[0m", "โ•".repeat(box_width - 2)); + println!( + "\x1b[1;33mโ•‘\x1b[0m{}\x1b[1;36m{}\x1b[0m{}\x1b[1;33mโ•‘\x1b[0m", + " ".repeat(left_padding), + test_info, + " ".repeat(right_padding), + ); + println!("\x1b[1;33mโ•š{}โ•\x1b[0m", "โ•".repeat(box_width - 2)); + } println!("๐Ÿ—๏ธ \x1b[1;34mBuilding...\x1b[0m"); let build_result = run_command(test.build_command, &Default::default())?; From 73e89c381613124f442b255922d871851f619c56 Mon Sep 17 00:00:00 2001 From: Amos Wenger Date: Tue, 23 Jul 2024 20:15:39 +0200 Subject: [PATCH 25/35] eprintln --- tests/src/main.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/src/main.rs b/tests/src/main.rs index 22076ee..bb7680b 100644 --- a/tests/src/main.rs +++ b/tests/src/main.rs @@ -157,10 +157,10 @@ fn run_command(command: &[&str], env_vars: &EnvVars) -> io::Result<(bool, String let status = child.wait()?; if !status.success() { let exit_code = status.code().unwrap_or(-1); - output.push_str(&format!( + eprintln!( "\nProcess exited with code {} (0x{:X})", exit_code, exit_code - )); + ); } Ok((status.success(), output)) } From 7094c11277d6be975f61cf504263ef752d13f19e Mon Sep 17 00:00:00 2001 From: Amos Wenger Date: Tue, 23 Jul 2024 20:21:26 +0200 Subject: [PATCH 26/35] more DX --- rubicon/src/lib.rs | 37 ++++++++++++++++--------------------- tests/src/main.rs | 42 +++++++++++++++++++++++++++++++++++++----- 2 files changed, 53 insertions(+), 26 deletions(-) diff --git a/rubicon/src/lib.rs b/rubicon/src/lib.rs index 895076c..7f7305b 100644 --- a/rubicon/src/lib.rs +++ b/rubicon/src/lib.rs @@ -512,27 +512,22 @@ macro_rules! compatibility_check { let exported_value = exported.iter().find(|&(k, _)| k == key).map(|(_, v)| v); let imported_value = imported.iter().find(|&(k, _)| k == key).map(|(_, v)| v); - let key_column = grey(key).to_string(); - let binary_column = match imported_value { - Some(value) => { - if exported_value.map_or(false, |v| v == value) { - format!("{}", grey(value)) - } else { - format!("{}", red(value)) - } - }, - None => format!("{}", grey("(โˆ…)")), - }; - let module_column = match exported_value { - Some(value) => { - if imported_value.map_or(false, |v| v == value) { - format!("{}", grey(value)) - } else { - format!("{}", green(value)) - } - }, - None => format!("{}", grey("(โˆ…)")), - }; + let key_column = colored(AnsiColor::GREY, key).to_string(); + let binary_column = format_column(imported_value.as_deref().copied(), exported_value.as_deref().copied(), AnsiColor::RED); + let module_column = format_column(exported_value.as_deref().copied(), imported_value.as_deref().copied(), AnsiColor::GREEN); + + fn format_column(primary: Option<&str>, secondary: Option<&str>, highlight_color: AnsiColor) -> String { + match primary { + Some(value) => { + if secondary.map_or(false, |v| v == value) { + colored(AnsiColor::GREY, value).to_string() + } else { + colored(highlight_color, value).to_string() + } + }, + None => colored(AnsiColor::GREY, "โˆ…").to_string(), + } + } grid.add_row(vec![key_column, binary_column, module_column]); } diff --git a/tests/src/main.rs b/tests/src/main.rs index bb7680b..880534e 100644 --- a/tests/src/main.rs +++ b/tests/src/main.rs @@ -156,11 +156,43 @@ fn run_command(command: &[&str], env_vars: &EnvVars) -> io::Result<(bool, String let status = child.wait()?; if !status.success() { - let exit_code = status.code().unwrap_or(-1); - eprintln!( - "\nProcess exited with code {} (0x{:X})", - exit_code, exit_code - ); + if let Some(exit_code) = status.code() { + eprintln!( + "\n๐Ÿ” \x1b[1;90mProcess exited with code {} (0x{:X})\x1b[0m", + exit_code, exit_code + ); + } else { + #[cfg(unix)] + { + use std::os::unix::process::ExitStatusExt; + if let Some(signal) = status.signal() { + let signal_name = match signal { + 1 => "SIGHUP", + 2 => "SIGINT", + 3 => "SIGQUIT", + 4 => "SIGILL", + 6 => "SIGABRT", + 8 => "SIGFPE", + 9 => "SIGKILL", + 11 => "SIGSEGV", + 13 => "SIGPIPE", + 14 => "SIGALRM", + 15 => "SIGTERM", + _ => "Unknown", + }; + eprintln!( + "\n๐Ÿ” \x1b[1;90mProcess terminated by signal {} ({})\x1b[0m", + signal, signal_name + ); + } else { + eprintln!("\n๐Ÿ” \x1b[1;90mProcess exited with unknown status\x1b[0m"); + } + } + #[cfg(not(unix))] + { + eprintln!("\n๐Ÿ” \x1b[1;90mProcess exited with unknown status\x1b[0m"); + } + } } Ok((status.success(), output)) } From d3491e9127b5ceab59d61809336b52722fbf15ec Mon Sep 17 00:00:00 2001 From: Amos Wenger Date: Tue, 23 Jul 2024 20:29:20 +0200 Subject: [PATCH 27/35] Tentative windows impl --- rubicon/src/lib.rs | 81 ++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 74 insertions(+), 7 deletions(-) diff --git a/rubicon/src/lib.rs b/rubicon/src/lib.rs index 7f7305b..d13e044 100644 --- a/rubicon/src/lib.rs +++ b/rubicon/src/lib.rs @@ -335,15 +335,17 @@ macro_rules! compatibility_check { static COMPATIBILITY_INFO: &'static [(&'static str, &'static str)]; } - use $crate::libc::{c_void, Dl_info}; - use std::ffi::CStr; - use std::ptr; - - extern "C" { - fn dladdr(addr: *const c_void, info: *mut Dl_info) -> i32; - } + #[cfg(unix)] fn get_shared_object_name() -> Option { + use $crate::libc::{c_void, Dl_info}; + use std::ffi::CStr; + use std::ptr; + + extern "C" { + fn dladdr(addr: *const c_void, info: *mut Dl_info) -> i32; + } + unsafe { let mut info: Dl_info = std::mem::zeroed(); if dladdr(get_shared_object_name as *const c_void, &mut info) != 0 { @@ -354,6 +356,71 @@ macro_rules! compatibility_check { None } + #[cfg(windows)] + fn get_shared_object_name() -> Option { + use std::mem::MaybeUninit; + use std::ptr; + use std::ffi::OsString; + use std::os::windows::ffi::OsStringExt; + + #[allow(non_snake_case)] + #[repr(C)] + struct MODULEINFO { + lpBaseOfDll: *mut std::ffi::c_void, + SizeOfImage: u32, + EntryPoint: *mut std::ffi::c_void, + } + + type HANDLE = *mut std::ffi::c_void; + type HMODULE = HANDLE; + type DWORD = u32; + type BOOL = i32; + type LPCWSTR = *const u16; + type LPWSTR = *mut u16; + + const MAX_PATH: u32 = 260; + const ERROR_INSUFFICIENT_BUFFER: u32 = 122; + + extern "system" { + fn GetModuleHandleW(lpModuleName: LPCWSTR) -> HMODULE; + fn GetModuleFileNameW(hModule: HMODULE, lpFilename: LPWSTR, nSize: DWORD) -> DWORD; + fn GetLastError() -> DWORD; + } + + unsafe { + let module = GetModuleHandleW(ptr::null()); + if module.is_null() { + return None; + } + + let mut buffer_size = MAX_PATH; + loop { + let mut buffer = Vec::::with_capacity(buffer_size as usize); + let result = GetModuleFileNameW(module, buffer.as_mut_ptr(), buffer_size); + + if result == 0 { + return None; + } + + if result < buffer_size { + buffer.set_len(result as usize); + return Some(OsString::from_wide(&buffer).to_string_lossy().into_owned()); + } + + if GetLastError() == ERROR_INSUFFICIENT_BUFFER { + buffer_size *= 2; + } else { + return None; + } + } + } + } + + #[cfg(not(any(unix, windows)))] + fn get_shared_object_name() -> Option { + None + } + struct AnsiEscape(u64, D); impl std::fmt::Display for AnsiEscape { From f1601fd3f7d29ce5af4df50b097e70067b257f27 Mon Sep 17 00:00:00 2001 From: Amos Wenger Date: Tue, 23 Jul 2024 20:45:35 +0200 Subject: [PATCH 28/35] printf debugging aw yiss --- rubicon/src/lib.rs | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/rubicon/src/lib.rs b/rubicon/src/lib.rs index d13e044..69fa958 100644 --- a/rubicon/src/lib.rs +++ b/rubicon/src/lib.rs @@ -358,6 +358,7 @@ macro_rules! compatibility_check { #[cfg(windows)] fn get_shared_object_name() -> Option { + eprintln!("Entering get_shared_object_name function"); use std::mem::MaybeUninit; use std::ptr; use std::ffi::OsString; @@ -388,28 +389,39 @@ macro_rules! compatibility_check { } unsafe { + eprintln!("Calling GetModuleHandleW"); let module = GetModuleHandleW(ptr::null()); if module.is_null() { + eprintln!("GetModuleHandleW returned null"); return None; } let mut buffer_size = MAX_PATH; loop { + eprintln!("Entering loop with buffer_size: {}", buffer_size); let mut buffer = Vec::::with_capacity(buffer_size as usize); + eprintln!("Calling GetModuleFileNameW"); let result = GetModuleFileNameW(module, buffer.as_mut_ptr(), buffer_size); if result == 0 { + eprintln!("GetModuleFileNameW returned 0"); return None; } if result < buffer_size { + eprintln!("GetModuleFileNameW succeeded with result: {}", result); buffer.set_len(result as usize); - return Some(OsString::from_wide(&buffer).to_string_lossy().into_owned()); + let os_string = OsString::from_wide(&buffer); + let string = os_string.to_string_lossy().into_owned(); + eprintln!("Returning string: {}", string); + return Some(string); } if GetLastError() == ERROR_INSUFFICIENT_BUFFER { + eprintln!("Buffer insufficient, doubling size"); buffer_size *= 2; } else { + eprintln!("Unexpected error: {}", GetLastError()); return None; } } @@ -476,6 +488,7 @@ macro_rules! compatibility_check { #[ctor] fn check_compatibility() { + eprintln!("Entering check_compatibility function"); let imported: &[(&str, &str)] = &[ ("rustc-version", $crate::RUBICON_RUSTC_VERSION), ("target-triple", $crate::RUBICON_TARGET_TRIPLE), @@ -483,14 +496,17 @@ macro_rules! compatibility_check { ]; let exported = unsafe { COMPATIBILITY_INFO }; + eprintln!("Comparing imported and exported compatibility info"); let missing: Vec<_> = imported.iter().filter(|&item| !exported.contains(item)).collect(); let extra: Vec<_> = exported.iter().filter(|&item| !imported.contains(item)).collect(); if missing.is_empty() && extra.is_empty() { + eprintln!("No compatibility issues found"); // all good return; } + eprintln!("Compatibility issues detected, preparing error message"); let so_name = get_shared_object_name().unwrap_or("unknown_so".to_string()); // get only the last bit of the path let so_name = so_name.rsplit('/').next().unwrap_or("unknown_so"); @@ -503,6 +519,7 @@ macro_rules! compatibility_check { error_message.push_str(&format!("Loading {} would mix different configurations of the {} crate.\n\n", blue(so_name), red(env!("CARGO_PKG_NAME")))); + eprintln!("Calculating column widths for grid display"); // Compute max lengths for alignment let max_exported_len = exported.iter().map(|(k, v)| format!("{}={}", k, v).len()).max().unwrap_or(0); let max_ref_len = imported.iter().map(|(k, v)| format!("{}={}", k, v).len()).max().unwrap_or(0); @@ -570,6 +587,7 @@ macro_rules! compatibility_check { } } + eprintln!("Creating grid for compatibility info display"); let mut grid = Grid::new(); // Add header @@ -599,6 +617,7 @@ macro_rules! compatibility_check { grid.add_row(vec![key_column, binary_column, module_column]); } + eprintln!("Writing grid to error message"); grid.write_to(&mut error_message); struct MessageBox { @@ -648,6 +667,7 @@ macro_rules! compatibility_check { error_message.push_str("More info: \x1b[4m\x1b[34mhttps://crates.io/crates/rubicon\x1b[0m\n"); + eprintln!("Creating message box for additional information"); let mut message_box = MessageBox::new(); message_box.add_line(format!("To fix this issue, {} needs to enable", blue(so_name))); message_box.add_line(format!("the same cargo features as {} for crate {}.", blue(&exe_name), red(env!("CARGO_PKG_NAME")))); @@ -658,6 +678,7 @@ macro_rules! compatibility_check { message_box.write_to(&mut error_message); error_message.push_str("\n\x1b[31mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\x1b[0m\n"); + eprintln!("Panicking with error message"); panic!("{}", error_message); } }; From bf46f35849e7a6a09817ccbf34daff9f38c57b5d Mon Sep 17 00:00:00 2001 From: Amos Wenger Date: Tue, 23 Jul 2024 20:51:50 +0200 Subject: [PATCH 29/35] in a thread?? I knew DLL initializers were limited --- rubicon/src/lib.rs | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/rubicon/src/lib.rs b/rubicon/src/lib.rs index 69fa958..1a9ad23 100644 --- a/rubicon/src/lib.rs +++ b/rubicon/src/lib.rs @@ -486,7 +486,6 @@ macro_rules! compatibility_check { len } - #[ctor] fn check_compatibility() { eprintln!("Entering check_compatibility function"); let imported: &[(&str, &str)] = &[ @@ -681,6 +680,20 @@ macro_rules! compatibility_check { eprintln!("Panicking with error message"); panic!("{}", error_message); } + + #[cfg(unix)] + #[ctor] + fn compatibility_check_caller() { + check_compatibility(); + } + + #[cfg(windows)] + #[ctor] + fn compatibility_check_caller() { + std::thread::spawn(|| { + check_compatibility(); + }); + } }; } From c6b0affcbef9c37104787c86c7a36dd56508ec86 Mon Sep 17 00:00:00 2001 From: Amos Wenger Date: Tue, 23 Jul 2024 20:57:19 +0200 Subject: [PATCH 30/35] Simpler window check --- rubicon/src/lib.rs | 34 ++++++++++++++++++++++++---------- 1 file changed, 24 insertions(+), 10 deletions(-) diff --git a/rubicon/src/lib.rs b/rubicon/src/lib.rs index 1a9ad23..5085770 100644 --- a/rubicon/src/lib.rs +++ b/rubicon/src/lib.rs @@ -486,6 +486,8 @@ macro_rules! compatibility_check { len } + #[cfg(unix)] + #[ctor] fn check_compatibility() { eprintln!("Entering check_compatibility function"); let imported: &[(&str, &str)] = &[ @@ -681,18 +683,30 @@ macro_rules! compatibility_check { panic!("{}", error_message); } - #[cfg(unix)] - #[ctor] - fn compatibility_check_caller() { - check_compatibility(); - } - #[cfg(windows)] #[ctor] - fn compatibility_check_caller() { - std::thread::spawn(|| { - check_compatibility(); - }); + fn check_compatibility() { + // on Windows we cannot allocate at all from a DLL initializer, + + let imported: &[(&str, &str)] = &[ + ("rustc-version", $crate::RUBICON_RUSTC_VERSION), + ("target-triple", $crate::RUBICON_TARGET_TRIPLE), + $($feature)* + ]; + let exported = unsafe { COMPATIBILITY_INFO }; + + for item in imported.iter() { + if !exported.contains(item) { + eprintln!("Compatibility mismatch detected: {} (imported) != {} (exported)", item.0, item.1); + std::process::exit(1); + } + } + for item in exported.iter() { + if !imported.contains(item) { + eprintln!("Compatibility mismatch detected: {} (exported) != {} (imported)", item.0, item.1); + std::process::exit(1); + } + } } }; } From 98c503bd3d233c946145119c06f995410c500510 Mon Sep 17 00:00:00 2001 From: Amos Wenger Date: Tue, 23 Jul 2024 20:59:34 +0200 Subject: [PATCH 31/35] welp, no safety for windows --- rubicon/src/lib.rs | 26 +------------------------- tests/src/main.rs | 2 +- 2 files changed, 2 insertions(+), 26 deletions(-) diff --git a/rubicon/src/lib.rs b/rubicon/src/lib.rs index 5085770..0e0cfbd 100644 --- a/rubicon/src/lib.rs +++ b/rubicon/src/lib.rs @@ -683,31 +683,7 @@ macro_rules! compatibility_check { panic!("{}", error_message); } - #[cfg(windows)] - #[ctor] - fn check_compatibility() { - // on Windows we cannot allocate at all from a DLL initializer, - - let imported: &[(&str, &str)] = &[ - ("rustc-version", $crate::RUBICON_RUSTC_VERSION), - ("target-triple", $crate::RUBICON_TARGET_TRIPLE), - $($feature)* - ]; - let exported = unsafe { COMPATIBILITY_INFO }; - - for item in imported.iter() { - if !exported.contains(item) { - eprintln!("Compatibility mismatch detected: {} (imported) != {} (exported)", item.0, item.1); - std::process::exit(1); - } - } - for item in exported.iter() { - if !imported.contains(item) { - eprintln!("Compatibility mismatch detected: {} (exported) != {} (imported)", item.0, item.1); - std::process::exit(1); - } - } - } + /// compatibility checks are not supported on Windows }; } diff --git a/tests/src/main.rs b/tests/src/main.rs index 880534e..cb65fba 100644 --- a/tests/src/main.rs +++ b/tests/src/main.rs @@ -448,7 +448,7 @@ fn run_tests() -> io::Result<()> { } ("fail", false) if test.check_feature_mismatch => { eprintln!("โŒ \x1b[1;31mTest failed, but not with the expected feature mismatch error.\x1b[0m"); - if test.allowed_to_fail { + if test.allowed_to_fail || cfg!(windows) { println!("โš ๏ธ \x1b[1;33mTest was allowed to fail.\x1b[0m"); } else { std::process::exit(1); From d801db98b56ea6d4cdf4b5acaaf546759dda304c Mon Sep 17 00:00:00 2001 From: Amos Wenger Date: Tue, 23 Jul 2024 21:00:50 +0200 Subject: [PATCH 32/35] windows get macro'd out --- rubicon/src/lib.rs | 89 +++++----------------------------------------- 1 file changed, 8 insertions(+), 81 deletions(-) diff --git a/rubicon/src/lib.rs b/rubicon/src/lib.rs index 0e0cfbd..a70b281 100644 --- a/rubicon/src/lib.rs +++ b/rubicon/src/lib.rs @@ -323,7 +323,7 @@ macro_rules! compatibility_check { }; } -#[cfg(feature = "import-globals")] +#[cfg(all(unix, feature = "import-globals"))] #[macro_export] macro_rules! compatibility_check { ($($feature:tt)*) => { @@ -336,7 +336,6 @@ macro_rules! compatibility_check { } - #[cfg(unix)] fn get_shared_object_name() -> Option { use $crate::libc::{c_void, Dl_info}; use std::ffi::CStr; @@ -356,83 +355,6 @@ macro_rules! compatibility_check { None } - #[cfg(windows)] - fn get_shared_object_name() -> Option { - eprintln!("Entering get_shared_object_name function"); - use std::mem::MaybeUninit; - use std::ptr; - use std::ffi::OsString; - use std::os::windows::ffi::OsStringExt; - - #[allow(non_snake_case)] - #[repr(C)] - struct MODULEINFO { - lpBaseOfDll: *mut std::ffi::c_void, - SizeOfImage: u32, - EntryPoint: *mut std::ffi::c_void, - } - - type HANDLE = *mut std::ffi::c_void; - type HMODULE = HANDLE; - type DWORD = u32; - type BOOL = i32; - type LPCWSTR = *const u16; - type LPWSTR = *mut u16; - - const MAX_PATH: u32 = 260; - const ERROR_INSUFFICIENT_BUFFER: u32 = 122; - - extern "system" { - fn GetModuleHandleW(lpModuleName: LPCWSTR) -> HMODULE; - fn GetModuleFileNameW(hModule: HMODULE, lpFilename: LPWSTR, nSize: DWORD) -> DWORD; - fn GetLastError() -> DWORD; - } - - unsafe { - eprintln!("Calling GetModuleHandleW"); - let module = GetModuleHandleW(ptr::null()); - if module.is_null() { - eprintln!("GetModuleHandleW returned null"); - return None; - } - - let mut buffer_size = MAX_PATH; - loop { - eprintln!("Entering loop with buffer_size: {}", buffer_size); - let mut buffer = Vec::::with_capacity(buffer_size as usize); - eprintln!("Calling GetModuleFileNameW"); - let result = GetModuleFileNameW(module, buffer.as_mut_ptr(), buffer_size); - - if result == 0 { - eprintln!("GetModuleFileNameW returned 0"); - return None; - } - - if result < buffer_size { - eprintln!("GetModuleFileNameW succeeded with result: {}", result); - buffer.set_len(result as usize); - let os_string = OsString::from_wide(&buffer); - let string = os_string.to_string_lossy().into_owned(); - eprintln!("Returning string: {}", string); - return Some(string); - } - - if GetLastError() == ERROR_INSUFFICIENT_BUFFER { - eprintln!("Buffer insufficient, doubling size"); - buffer_size *= 2; - } else { - eprintln!("Unexpected error: {}", GetLastError()); - return None; - } - } - } - } - - #[cfg(not(any(unix, windows)))] - fn get_shared_object_name() -> Option { - None - } - struct AnsiEscape(u64, D); impl std::fmt::Display for AnsiEscape { @@ -486,7 +408,6 @@ macro_rules! compatibility_check { len } - #[cfg(unix)] #[ctor] fn check_compatibility() { eprintln!("Entering check_compatibility function"); @@ -682,8 +603,14 @@ macro_rules! compatibility_check { eprintln!("Panicking with error message"); panic!("{}", error_message); } + }; +} - /// compatibility checks are not supported on Windows +#[cfg(all(not(unix), feature = "import-globals"))] +#[macro_export] +macro_rules! compatibility_check { + ($($feature:tt)*) => { + /// compatibility checks are only supported on unix-like system }; } From ac0c545c7ba8c7990190561a6d3a1cc0eb9a98d6 Mon Sep 17 00:00:00 2001 From: Amos Wenger Date: Tue, 23 Jul 2024 21:01:14 +0200 Subject: [PATCH 33/35] no doc comment --- rubicon/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rubicon/src/lib.rs b/rubicon/src/lib.rs index a70b281..06cd11e 100644 --- a/rubicon/src/lib.rs +++ b/rubicon/src/lib.rs @@ -610,7 +610,7 @@ macro_rules! compatibility_check { #[macro_export] macro_rules! compatibility_check { ($($feature:tt)*) => { - /// compatibility checks are only supported on unix-like system + // compatibility checks are only supported on unix-like system }; } From f8cc6cdeac50784c0641000f191cb476a7b5adde Mon Sep 17 00:00:00 2001 From: Amos Wenger Date: Tue, 23 Jul 2024 21:03:06 +0200 Subject: [PATCH 34/35] Allow windows tests to fail --- .github/workflows/test.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index a12dfc2..044810c 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -29,3 +29,4 @@ jobs: cd tests/ cargo run shell: bash + continue-on-error: ${{ matrix.os == 'windows-latest' }} From b7ba170664a833b9bee1162621fb6007fb5917e5 Mon Sep 17 00:00:00 2001 From: Amos Wenger Date: Tue, 23 Jul 2024 21:07:49 +0200 Subject: [PATCH 35/35] Maybe we just got lucky on unixes? --- rubicon/src/lib.rs | 2 +- test-crates/mod_a/src/lib.rs | 2 +- test-crates/mod_b/src/lib.rs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/rubicon/src/lib.rs b/rubicon/src/lib.rs index 06cd11e..b64cc3c 100644 --- a/rubicon/src/lib.rs +++ b/rubicon/src/lib.rs @@ -330,7 +330,7 @@ macro_rules! compatibility_check { use std::env; use $crate::ctor::ctor; - extern "C" { + extern "Rust" { #[link_name = concat!(env!("CARGO_PKG_NAME"), "_compatibility_info")] static COMPATIBILITY_INFO: &'static [(&'static str, &'static str)]; } diff --git a/test-crates/mod_a/src/lib.rs b/test-crates/mod_a/src/lib.rs index bcfa624..dac6f6a 100644 --- a/test-crates/mod_a/src/lib.rs +++ b/test-crates/mod_a/src/lib.rs @@ -2,7 +2,7 @@ use soprintln::soprintln; use std::sync::atomic::Ordering; #[no_mangle] -pub fn init() { +pub extern "Rust" fn init() { soprintln::init!(); mokio::MOKIO_TL1.with(|s| s.fetch_add(1, Ordering::Relaxed)); mokio::MOKIO_PL1.fetch_add(1, Ordering::Relaxed); diff --git a/test-crates/mod_b/src/lib.rs b/test-crates/mod_b/src/lib.rs index bcfa624..dac6f6a 100644 --- a/test-crates/mod_b/src/lib.rs +++ b/test-crates/mod_b/src/lib.rs @@ -2,7 +2,7 @@ use soprintln::soprintln; use std::sync::atomic::Ordering; #[no_mangle] -pub fn init() { +pub extern "Rust" fn init() { soprintln::init!(); mokio::MOKIO_TL1.with(|s| s.fetch_add(1, Ordering::Relaxed)); mokio::MOKIO_PL1.fetch_add(1, Ordering::Relaxed);