//! Multi-file input and `#[cfg]` evaluation. //! //! The differential runner drives one `.rs` per case, so the multi-file path //! and the feature-gating that goes with it are checked here instead. A Rust //! crate splits across files with `mod`; Nim has no equivalent inside a single //! output file, so the items are flattened in argument order. use std::fs; use std::path::{Path, PathBuf}; use std::process::Command; const RUSTNIM: &str = env!("CARGO_BIN_EXE_rustnim"); fn work(name: &str) -> PathBuf { let d = Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/.work").join(name); let _ = fs::remove_dir_all(&d); fs::create_dir_all(&d).unwrap(); d } fn run(dir: &Path, args: &[&str]) -> (bool, String, String) { let out = Command::new(RUSTNIM) .args(args) .env("TMPDIR", dir) .output() .expect("spawn rustnim"); ( out.status.success(), String::from_utf8_lossy(&out.stdout).into_owned(), String::from_utf8_lossy(&out.stderr).into_owned(), ) } #[test] fn files_are_flattened_into_one_module() { let d = work("multifile"); let root = d.join("root.rs"); let a = d.join("a.rs"); fs::write(&a, "pub fn double(x: i32) -> i32 { x * 2 }\n").unwrap(); fs::write( &root, "mod a;\nuse crate::a::double;\nfn main() { println!(\"{}\", double(21)); }\n", ) .unwrap(); // The first input is the crate root; `a.rs` becomes module `a`, and its // items are emitted with that prefix so two modules may define the same // name. let (ok, out, err) = run(&d, &[root.to_str().unwrap(), a.to_str().unwrap()]); assert!(ok, "multi-file transpile failed: {err}"); assert!(out.contains("proc a_double"), "missing `a_double`:\n{out}"); assert!(out.contains("proc main"), "missing `main`:\n{out}"); assert!(out.contains("a_double(21"), "call not qualified:\n{out}"); // The root alone must fail rather than emit a call to something undefined. let (ok, _, err) = run(&d, &[root.to_str().unwrap()]); assert!(!ok, "the root alone should not transpile"); assert!(err.contains("mod a;"), "unexpected: {err}"); } #[test] fn two_modules_may_define_the_same_name() { let d = work("modcollide"); let root = d.join("root.rs"); let x = d.join("x.rs"); let y = d.join("y.rs"); fs::write(&x, "pub fn go() -> i32 { 1 }\n").unwrap(); fs::write(&y, "pub fn go() -> i32 { 2 }\n").unwrap(); fs::write( &root, "mod x;\nmod y;\nfn main() { println!(\"{} {}\", x::go(), y::go()); }\n", ) .unwrap(); let (ok, out, err) = run( &d, &[root.to_str().unwrap(), x.to_str().unwrap(), y.to_str().unwrap()], ); assert!(ok, "{err}"); assert!(out.contains("proc x_go") && out.contains("proc y_go"), "{out}"); assert!(out.contains("x_go()") && out.contains("y_go()"), "{out}"); } #[test] fn cfg_is_evaluated_against_the_feature_set() { let d = work("cfg"); let f = d.join("c.rs"); fs::write( &f, "#[cfg(feature = \"extra\")]\n\ fn extra() -> i32 { 1 }\n\ #[cfg(not(feature = \"extra\"))]\n\ fn plain() -> i32 { 2 }\n\ fn main() {}\n", ) .unwrap(); let (ok, out, err) = run(&d, &[f.to_str().unwrap()]); assert!(ok, "{err}"); assert!(!out.contains("proc extra"), "gated-off item was emitted:\n{out}"); assert!(out.contains("proc plain"), "gated-on item missing:\n{out}"); let (ok, out, err) = run(&d, &[f.to_str().unwrap(), "--cfg", "feature=extra"]); assert!(ok, "{err}"); assert!(out.contains("proc extra"), "feature item missing:\n{out}"); assert!(!out.contains("proc plain"), "`not(feature)` item was emitted:\n{out}"); } #[test] fn an_unevaluable_cfg_predicate_is_reported_not_assumed() { let d = work("cfg-unknown"); let f = d.join("d.rs"); fs::write( &f, // A build-script `cfg`: nothing about the host determines it, so // there is no value we could know. "#[cfg(crossbeam_loom)]\nfn under_loom() -> i32 { 1 }\nfn main() {}\n", ) .unwrap(); let (ok, _, err) = run(&d, &[f.to_str().unwrap()]); assert!(!ok, "an unevaluable cfg must not be silently resolved"); assert!(err.contains("not a predicate rustnim can evaluate"), "unexpected: {err}"); } #[test] fn host_facts_are_evaluated() { // These are determined by the machine the generated Nim is compiled for, // so they are known rather than chosen. let d = work("cfg-host"); let f = d.join("h.rs"); fs::write( &f, "#[cfg(unix)]\nfn on_unix() -> i32 { 1 }\n\ #[cfg(windows)]\nfn on_windows() -> i32 { 2 }\n\ #[cfg(doctest)]\nfn in_doctest() -> i32 { 3 }\n\ fn main() {}\n", ) .unwrap(); let (ok, out, err) = run(&d, &[f.to_str().unwrap()]); assert!(ok, "{err}"); assert!(!out.contains("in_doctest"), "doctest item was emitted:\n{out}"); if cfg!(unix) { assert!(out.contains("proc on_unix"), "{out}"); assert!(!out.contains("on_windows"), "{out}"); } else { assert!(out.contains("proc on_windows"), "{out}"); assert!(!out.contains("on_unix"), "{out}"); } }