//! 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 a = d.join("a.rs"); let b = d.join("b.rs"); fs::write(&a, "pub fn double(x: i32) -> i32 { x * 2 }\n").unwrap(); fs::write( &b, "fn main() { println!(\"{}\", double(21)); }\n", ) .unwrap(); // `double` is defined in a.rs and called from b.rs: it resolves only // because both were passed in. let (ok, out, err) = run(&d, &[a.to_str().unwrap(), b.to_str().unwrap()]); assert!(ok, "multi-file transpile failed: {err}"); assert!(out.contains("proc double"), "missing `double`:\n{out}"); assert!(out.contains("proc main"), "missing `main`:\n{out}"); // b.rs alone must fail rather than emit a call to something undefined. let (ok, _, err) = run(&d, &[b.to_str().unwrap()]); assert!(!ok, "b.rs alone should not transpile"); assert!(err.contains("unknown function `double`"), "unexpected: {err}"); } #[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, "#[cfg(target_os = \"linux\")]\nfn only_linux() -> 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}"); }