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
//! Layer 1: parser corpus.
//!
//! Exercises sui's rnix wrapper against every real `flake.nix` file
//! sitting under `PLEME_IO_ROOT`. Offline — no oracle needed.
//!
//! The test is intentionally lenient about *which* files are in the
//! sample (they're discovered at runtime) but deterministic about
//! *ordering* (lex-sorted, first N), so reruns on the same machine
//! test the same inputs.
//!
//! Failure mode: we collect every broken file into a Vec and, at the
//! end, panic with a summary of the first few. Surfacing the first
//! failure alone would hide real parity regressions.
mod common;
use std::path::PathBuf;
/// How many flake.nix files to pull from the pleme-io workspace.
const FLAKE_SAMPLE_SIZE: usize = 200;
#[test]
fn parse_all_pleme_io_flake_nix_does_not_panic() {
let corpus = common::pleme_io_flake_nix_sample(FLAKE_SAMPLE_SIZE);
if corpus.is_empty() {
eprintln!(
"skip parse_all_pleme_io_flake_nix_does_not_panic: no flake.nix found under {}",
common::pleme_io_root().display()
);
return;
}
let mut failures: Vec<(PathBuf, String)> = Vec::new();
for path in &corpus {
let source = match std::fs::read_to_string(path) {
Ok(s) => s,
Err(e) => {
failures.push((path.clone(), format!("read: {e}")));
continue;
}
};
// Catch panics so one weird file doesn't kill the whole run.
let parsed = std::panic::catch_unwind(|| rnix::Root::parse(&source));
match parsed {
Ok(p) => {
if !p.errors().is_empty() {
let msgs: Vec<String> = p.errors().iter().map(|e| e.to_string()).collect();
failures.push((path.clone(), format!("parse errors: {}", msgs.join("; "))));
}
}
Err(_) => failures.push((path.clone(), "parser panicked".to_string())),
}
}
eprintln!(
"parse_all_pleme_io_flake_nix: parsed {} files, {} failures",
corpus.len(),
failures.len()
);
if !failures.is_empty() {
let summary: String = failures
.iter()
.take(10)
.map(|(p, why)| format!(" {}\n {why}", p.display()))
.collect::<Vec<_>>()
.join("\n");
panic!(
"{} / {} flake.nix files failed to parse. First {} failures:\n{}",
failures.len(),
corpus.len(),
failures.len().min(10),
summary
);
}
}
#[test]
fn parse_roundtrip_preserves_source() {
// For each flake.nix: parse → str(green_node) → reparse → assert
// both string representations are equal. rnix is lossless, so this
// should always hold; a regression here indicates trivia (whitespace,
// comments) got dropped.
let corpus = common::pleme_io_flake_nix_sample(FLAKE_SAMPLE_SIZE);
if corpus.is_empty() {
eprintln!("skip parse_roundtrip_preserves_source: no corpus");
return;
}
let mut failures: Vec<(PathBuf, String)> = Vec::new();
for path in &corpus {
let Ok(source) = std::fs::read_to_string(path) else {
continue;
};
let first = rnix::Root::parse(&source);
if !first.errors().is_empty() {
continue; // handled by the previous test
}
// Round-trip: the green tree's Display impl yields the exact
// source bytes (rnix is a lossless parser).
let round_tripped = first.syntax().to_string();
if round_tripped != source {
failures.push((
path.clone(),
format!(
"round-trip mismatch (orig len {}, rt len {})",
source.len(),
round_tripped.len()
),
));
}
}
if !failures.is_empty() {
let summary: String = failures
.iter()
.take(5)
.map(|(p, why)| format!(" {}\n {why}", p.display()))
.collect::<Vec<_>>()
.join("\n");
panic!(
"{} files failed round-trip. First {}:\n{}",
failures.len(),
failures.len().min(5),
summary
);
}
}
#[test]
fn parse_known_bad_input_returns_error() {
// Sanity: the parser must actually reject obviously broken input.
// If this ever passes, parse_all_pleme_io_flake_nix is worthless.
let garbage = "let x = ; in }";
let parsed = rnix::Root::parse(garbage);
assert!(
!parsed.errors().is_empty(),
"rnix accepted {garbage:?} with zero errors"
);
}
#[test]
fn parse_empty_input_is_handled() {
// Empty input is a common edge case. It should not panic; it may
// or may not produce errors depending on the parser, but sui's
// top-level `eval` should turn it into a clean error.
let _ = rnix::Root::parse("");
let v = common::sui_eval_json("");
assert!(common::is_error_json(&v));
}