1
// Skipped under Miri: spawns the built nms binary (which runs wasm via
2
// wasmtime); subprocess execution and Cranelift can't run under Miri.
3
#![cfg(not(miri))]
4

            
5
//! Smoke tests for the `nms --test PATH` subcommand. Invokes the
6
//! built binary so the runner's argv/exit-code contract is what CI
7
//! actually checks: a non-zero exit code is the only signal an
8
//! external harness sees when a deftest fails.
9

            
10
use std::path::PathBuf;
11
use std::process::Command;
12

            
13
7
fn nms_bin() -> PathBuf {
14
    // CARGO_BIN_EXE_<name> is set by cargo when building integration
15
    // tests for a crate with a `[[bin]]` of that name. Reliable across
16
    // workspace + isolated layouts; no manual target-dir parsing.
17
7
    PathBuf::from(env!("CARGO_BIN_EXE_nms"))
18
7
}
19

            
20
3
fn samples_dir() -> PathBuf {
21
3
    PathBuf::from(env!("CARGO_MANIFEST_DIR"))
22
3
        .parent()
23
3
        .unwrap()
24
3
        .join("nomiscript/tests/nms_runner_samples")
25
3
}
26

            
27
#[test]
28
1
fn test_subcommand_runs_all_files_in_dir() {
29
1
    let out = Command::new(nms_bin())
30
1
        .arg("--test")
31
1
        .arg(samples_dir())
32
1
        .output()
33
1
        .expect("invoke nms");
34
1
    let stdout = String::from_utf8_lossy(&out.stdout);
35
1
    let stderr = String::from_utf8_lossy(&out.stderr);
36
1
    assert!(
37
1
        out.status.success(),
38
        "expected success, got {:?}\nstdout: {stdout}\nstderr: {stderr}",
39
        out.status
40
    );
41
1
    assert!(stdout.contains("0 failed"), "stdout: {stdout}");
42
1
}
43

            
44
#[test]
45
1
fn test_subcommand_accepts_single_file() {
46
1
    let path = samples_dir().join("arithmetic.nms");
47
1
    let out = Command::new(nms_bin())
48
1
        .arg("--test")
49
1
        .arg(&path)
50
1
        .output()
51
1
        .expect("invoke nms");
52
1
    assert!(out.status.success(), "stderr: {:?}", out.stderr);
53
1
    let stdout = String::from_utf8_lossy(&out.stdout);
54
1
    assert!(stdout.contains("5 passed"), "stdout: {stdout}");
55
1
}
56

            
57
#[test]
58
1
fn test_subcommand_nonzero_exit_on_failure() {
59
1
    let dir = tempfile_dir("nms_runner_failure_");
60
1
    std::fs::write(
61
1
        dir.join("a.nms"),
62
        "(deftest will-fail (assert-equal 1 2))\n",
63
    )
64
1
    .unwrap();
65
1
    let out = Command::new(nms_bin())
66
1
        .arg("--test")
67
1
        .arg(&dir)
68
1
        .output()
69
1
        .expect("invoke nms");
70
1
    assert!(!out.status.success(), "expected non-zero exit on failure");
71
1
    let stdout = String::from_utf8_lossy(&out.stdout);
72
1
    assert!(stdout.contains("1 failed"), "stdout: {stdout}");
73
1
}
74

            
75
#[test]
76
1
fn coverage_flag_appends_coverage_section() {
77
1
    let out = Command::new(nms_bin())
78
1
        .arg("--test")
79
1
        .arg(samples_dir())
80
1
        .arg("--coverage")
81
1
        .output()
82
1
        .expect("invoke nms");
83
1
    assert!(out.status.success(), "stderr: {:?}", out.stderr);
84
1
    let stdout = String::from_utf8_lossy(&out.stdout);
85
1
    assert!(stdout.contains("--- coverage ---"), "stdout: {stdout}");
86
    // sandbox nms has no host fns registered, so the dump is the
87
    // explanatory placeholder rather than a native-fn list. The rpc
88
    // crate's unit tests assert the populated path.
89
1
    assert!(
90
1
        stdout.contains("(no native fns referenced)"),
91
        "stdout: {stdout}"
92
    );
93
1
}
94

            
95
#[test]
96
1
fn invalid_profile_strategy_errors_with_clear_message() {
97
1
    let out = Command::new(nms_bin())
98
1
        .arg("--profile=bogus")
99
1
        .arg("-e")
100
1
        .arg("(+ 1 2)")
101
1
        .output()
102
1
        .expect("invoke nms");
103
1
    assert!(!out.status.success(), "expected non-zero exit");
104
1
    let stderr = String::from_utf8_lossy(&out.stderr);
105
1
    assert!(
106
1
        stderr.contains("unknown --profile strategy"),
107
        "stderr: {stderr}"
108
    );
109
1
}
110

            
111
#[test]
112
1
fn perfmap_profile_strategy_accepted() {
113
    // PerfMap is the lighter Linux profiler agent. We don't validate
114
    // the output file (path depends on PID); just confirm the flag is
115
    // accepted and the eval completes.
116
1
    let out = Command::new(nms_bin())
117
1
        .arg("--profile=perfmap")
118
1
        .arg("-e")
119
1
        .arg("(+ 2 2)")
120
1
        .output()
121
1
        .expect("invoke nms");
122
1
    assert!(out.status.success(), "stderr: {:?}", out.stderr);
123
1
    let stdout = String::from_utf8_lossy(&out.stdout);
124
1
    assert!(stdout.contains('4'), "stdout: {stdout}");
125
1
}
126

            
127
#[test]
128
1
fn test_subcommand_errors_on_empty_dir() {
129
1
    let dir = tempfile_dir("nms_runner_empty_");
130
1
    let out = Command::new(nms_bin())
131
1
        .arg("--test")
132
1
        .arg(&dir)
133
1
        .output()
134
1
        .expect("invoke nms");
135
1
    assert!(!out.status.success(), "expected non-zero exit on empty dir");
136
1
}
137

            
138
2
fn tempfile_dir(prefix: &str) -> PathBuf {
139
2
    let dir = std::env::temp_dir().join(format!(
140
2
        "{prefix}{}",
141
2
        std::time::SystemTime::now()
142
2
            .duration_since(std::time::UNIX_EPOCH)
143
2
            .unwrap()
144
2
            .as_nanos()
145
2
    ));
146
2
    std::fs::create_dir_all(&dir).unwrap();
147
2
    dir
148
2
}