nandi/rustnimpublic Fork 0
4e4d09dcdd22d17ba510de5639fc3a952ac73f6e
Commits
Clone
git clone https://git.rickub.com/nandi/rustnim.git
git clone ssh://git@rickub.com/nandi/rustnim.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

Evaluate host cfg predicates, and measure what that actually buys 0e6c394 · on 4e4d09dcdd22d17ba510de5639fc3a952ac73f6e · nandithebull · 9h ago
multifile.rs · 149 lines · 5.1 KBRust Blame HistoryRaw
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
//! 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}");
    }
}