| Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 19h ago | 1 | //! Multi-file input and `#[cfg]` evaluation. |
| 2 | //! |
| 3 | //! The differential runner drives one `.rs` per case, so the multi-file path |
| 4 | //! and the feature-gating that goes with it are checked here instead. A Rust |
| 5 | //! crate splits across files with `mod`; Nim has no equivalent inside a single |
| 6 | //! output file, so the items are flattened in argument order. |
| 7 | |
| 8 | use std::fs; |
| 9 | use std::path::{Path, PathBuf}; |
| 10 | use std::process::Command; |
| 11 | |
| 12 | const RUSTNIM: &str = env!("CARGO_BIN_EXE_rustnim"); |
| 13 | |
| 14 | fn work(name: &str) -> PathBuf { |
| 15 | let d = Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/.work").join(name); |
| 16 | let _ = fs::remove_dir_all(&d); |
| 17 | fs::create_dir_all(&d).unwrap(); |
| 18 | d |
| 19 | } |
| 20 | |
| 21 | fn run(dir: &Path, args: &[&str]) -> (bool, String, String) { |
| 22 | let out = Command::new(RUSTNIM) |
| 23 | .args(args) |
| 24 | .env("TMPDIR", dir) |
| 25 | .output() |
| 26 | .expect("spawn rustnim"); |
| 27 | ( |
| 28 | out.status.success(), |
| 29 | String::from_utf8_lossy(&out.stdout).into_owned(), |
| 30 | String::from_utf8_lossy(&out.stderr).into_owned(), |
| 31 | ) |
| 32 | } |
| 33 | |
| 34 | #[test] |
| 35 | fn files_are_flattened_into_one_module() { |
| 36 | let d = work("multifile"); |
| Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 18h ago | 37 | let root = d.join("root.rs"); |
| Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 19h ago | 38 | let a = d.join("a.rs"); |
| 39 | fs::write(&a, "pub fn double(x: i32) -> i32 { x * 2 }\n").unwrap(); |
| 40 | fs::write( |
| Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 18h ago | 41 | &root, |
| 42 | "mod a;\nuse crate::a::double;\nfn main() { println!(\"{}\", double(21)); }\n", |
| Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 19h ago | 43 | ) |
| 44 | .unwrap(); |
| 45 | |
| Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 18h ago | 46 | // The first input is the crate root; `a.rs` becomes module `a`, and its |
| 47 | // items are emitted with that prefix so two modules may define the same |
| 48 | // name. |
| 49 | let (ok, out, err) = run(&d, &[root.to_str().unwrap(), a.to_str().unwrap()]); |
| Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 19h ago | 50 | assert!(ok, "multi-file transpile failed: {err}"); |
| Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 18h ago | 51 | assert!(out.contains("proc a_double"), "missing `a_double`:\n{out}"); |
| Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 19h ago | 52 | assert!(out.contains("proc main"), "missing `main`:\n{out}"); |
| Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 18h ago | 53 | assert!(out.contains("a_double(21"), "call not qualified:\n{out}"); |
| Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 19h ago | 54 | |
| Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 18h ago | 55 | // The root alone must fail rather than emit a call to something undefined. |
| 56 | let (ok, _, err) = run(&d, &[root.to_str().unwrap()]); |
| 57 | assert!(!ok, "the root alone should not transpile"); |
| 58 | assert!(err.contains("mod a;"), "unexpected: {err}"); |
| 59 | } |
| 60 | |
| 61 | #[test] |
| 62 | fn two_modules_may_define_the_same_name() { |
| 63 | let d = work("modcollide"); |
| 64 | let root = d.join("root.rs"); |
| 65 | let x = d.join("x.rs"); |
| 66 | let y = d.join("y.rs"); |
| 67 | fs::write(&x, "pub fn go() -> i32 { 1 }\n").unwrap(); |
| 68 | fs::write(&y, "pub fn go() -> i32 { 2 }\n").unwrap(); |
| 69 | fs::write( |
| 70 | &root, |
| 71 | "mod x;\nmod y;\nfn main() { println!(\"{} {}\", x::go(), y::go()); }\n", |
| 72 | ) |
| 73 | .unwrap(); |
| 74 | |
| 75 | let (ok, out, err) = run( |
| 76 | &d, |
| 77 | &[root.to_str().unwrap(), x.to_str().unwrap(), y.to_str().unwrap()], |
| 78 | ); |
| 79 | assert!(ok, "{err}"); |
| 80 | assert!(out.contains("proc x_go") && out.contains("proc y_go"), "{out}"); |
| 81 | assert!(out.contains("x_go()") && out.contains("y_go()"), "{out}"); |
| Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 19h ago | 82 | } |
| 83 | |
| 84 | #[test] |
| 85 | fn cfg_is_evaluated_against_the_feature_set() { |
| 86 | let d = work("cfg"); |
| 87 | let f = d.join("c.rs"); |
| 88 | fs::write( |
| 89 | &f, |
| 90 | "#[cfg(feature = \"extra\")]\n\ |
| 91 | fn extra() -> i32 { 1 }\n\ |
| 92 | #[cfg(not(feature = \"extra\"))]\n\ |
| 93 | fn plain() -> i32 { 2 }\n\ |
| 94 | fn main() {}\n", |
| 95 | ) |
| 96 | .unwrap(); |
| 97 | |
| 98 | let (ok, out, err) = run(&d, &[f.to_str().unwrap()]); |
| 99 | assert!(ok, "{err}"); |
| 100 | assert!(!out.contains("proc extra"), "gated-off item was emitted:\n{out}"); |
| 101 | assert!(out.contains("proc plain"), "gated-on item missing:\n{out}"); |
| 102 | |
| 103 | let (ok, out, err) = run(&d, &[f.to_str().unwrap(), "--cfg", "feature=extra"]); |
| 104 | assert!(ok, "{err}"); |
| 105 | assert!(out.contains("proc extra"), "feature item missing:\n{out}"); |
| 106 | assert!(!out.contains("proc plain"), "`not(feature)` item was emitted:\n{out}"); |
| 107 | } |
| 108 | |
| 109 | #[test] |
| 110 | fn an_unevaluable_cfg_predicate_is_reported_not_assumed() { |
| 111 | let d = work("cfg-unknown"); |
| 112 | let f = d.join("d.rs"); |
| 113 | fs::write( |
| 114 | &f, |
| Evaluate host cfg predicates, and measure what that actually buys 0e6c394 nandithebull 17h ago | 115 | // A build-script `cfg`: nothing about the host determines it, so |
| 116 | // there is no value we could know. |
| 117 | "#[cfg(crossbeam_loom)]\nfn under_loom() -> i32 { 1 }\nfn main() {}\n", |
| Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 19h ago | 118 | ) |
| 119 | .unwrap(); |
| 120 | let (ok, _, err) = run(&d, &[f.to_str().unwrap()]); |
| 121 | assert!(!ok, "an unevaluable cfg must not be silently resolved"); |
| 122 | assert!(err.contains("not a predicate rustnim can evaluate"), "unexpected: {err}"); |
| 123 | } |
| Evaluate host cfg predicates, and measure what that actually buys 0e6c394 nandithebull 17h ago | 124 | |
| 125 | #[test] |
| 126 | fn host_facts_are_evaluated() { |
| 127 | // These are determined by the machine the generated Nim is compiled for, |
| 128 | // so they are known rather than chosen. |
| 129 | let d = work("cfg-host"); |
| 130 | let f = d.join("h.rs"); |
| 131 | fs::write( |
| 132 | &f, |
| 133 | "#[cfg(unix)]\nfn on_unix() -> i32 { 1 }\n\ |
| 134 | #[cfg(windows)]\nfn on_windows() -> i32 { 2 }\n\ |
| 135 | #[cfg(doctest)]\nfn in_doctest() -> i32 { 3 }\n\ |
| 136 | fn main() {}\n", |
| 137 | ) |
| 138 | .unwrap(); |
| 139 | let (ok, out, err) = run(&d, &[f.to_str().unwrap()]); |
| 140 | assert!(ok, "{err}"); |
| 141 | assert!(!out.contains("in_doctest"), "doctest item was emitted:\n{out}"); |
| 142 | if cfg!(unix) { |
| 143 | assert!(out.contains("proc on_unix"), "{out}"); |
| 144 | assert!(!out.contains("on_windows"), "{out}"); |
| 145 | } else { |
| 146 | assert!(out.contains("proc on_windows"), "{out}"); |
| 147 | assert!(!out.contains("on_unix"), "{out}"); |
| 148 | } |
| 149 | } |