Skip to content

Commit a4ecb69

Browse files
committed
feat: bless test
1 parent db427c7 commit a4ecb69

File tree

8 files changed

+142
-42
lines changed

8 files changed

+142
-42
lines changed

bootstrap/Cargo.lock

Lines changed: 14 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

bootstrap/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,6 @@ color-print = "0.3.6"
1010
env_logger = "0.11.5"
1111
glob = "0.3.1"
1212
log = "0.4.22"
13+
regex = "1.11.1"
14+
similar = "2.6.0"
1315
which = "6.0.1"

bootstrap/src/test.rs

Lines changed: 98 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,19 @@ use anstream::{eprint as print, eprintln as println};
55
use clap::Args;
66
use color_print::{cprint, cprintln};
77
use glob::glob;
8+
use similar::{ChangeTag, TextDiff};
89
use which::which;
910

1011
use crate::manifest::Manifest;
1112
use crate::Run;
1213

1314
/// Run tests
1415
#[derive(Args, Debug)]
15-
pub struct TestCommand {}
16+
pub struct TestCommand {
17+
/// Update the blessed output
18+
#[clap(long)]
19+
pub bless: bool,
20+
}
1621

1722
impl Run for TestCommand {
1823
fn run(&self, manifest: &Manifest) {
@@ -37,7 +42,12 @@ impl Run for TestCommand {
3742
TestType::FileCheck => {
3843
cprint!("File checking {}...", testcase.name);
3944
testcase.build(manifest);
40-
filechecker.run(&testcase.source, &testcase.output_file);
45+
filechecker.run(&testcase);
46+
}
47+
TestType::Bless => {
48+
cprint!("Blessing {}...", testcase.name);
49+
testcase.build(manifest);
50+
bless(self.bless, &testcase);
4151
}
4252
TestType::Compile => {
4353
cprint!("Compiling {}...", testcase.name);
@@ -55,24 +65,15 @@ impl Run for TestCommand {
5565

5666
impl TestCommand {
5767
pub fn collect_testcases(&self, manifest: &Manifest) -> Vec<TestCase> {
58-
let mut result = vec![];
59-
60-
// Test auxiliary (should compile first)
61-
for case in glob("tests/auxiliary/*.rs").unwrap() {
62-
let case = case.unwrap();
63-
let filename = case.file_stem().unwrap();
64-
let name = format!("auxiliary/{}", filename.to_string_lossy());
65-
let output_file = manifest.out_dir.join(filename);
66-
result.push(TestCase { name, source: case, output_file, test: TestType::CompileLib })
67-
}
68+
let mut tests = vec![];
6869

6970
// Examples
7071
for case in glob("examples/*.rs").unwrap() {
7172
let case = case.unwrap();
7273
let filename = case.file_stem().unwrap();
7374
let name = format!("examples/{}", filename.to_string_lossy());
7475
let output_file = manifest.out_dir.join("examples").join(filename);
75-
result.push(TestCase { name, source: case, output_file, test: TestType::Compile })
76+
tests.push(TestCase { name, source: case, output_file, test: TestType::Compile })
7677
}
7778

7879
// Codegen tests
@@ -81,18 +82,49 @@ impl TestCommand {
8182
let filename = case.file_stem().unwrap();
8283
let name = format!("codegen/{}", filename.to_string_lossy());
8384
let output_file = manifest.out_dir.join("tests/codegen").join(filename);
84-
result.push(TestCase { name, source: case, output_file, test: TestType::FileCheck })
85+
tests.push(TestCase { name, source: case, output_file, test: TestType::FileCheck })
86+
}
87+
88+
// Bless tests - the output should be the same as the last run
89+
for case in glob("tests/bless/*.rs").unwrap() {
90+
let case = case.unwrap();
91+
let filename = case.file_stem().unwrap();
92+
let name = format!("bless/{}", filename.to_string_lossy());
93+
let output_file = manifest.out_dir.join("tests/bless").join(filename);
94+
tests.push(TestCase { name, source: case, output_file, test: TestType::Bless })
8595
}
8696

87-
result
97+
// Collect test-auxiliary
98+
let aux_use = regex::Regex::new(r"^//@\s*aux-build:(?P<fname>.*)").unwrap();
99+
let mut auxiliary = vec![];
100+
for case in tests.iter() {
101+
let source = std::fs::read_to_string(&case.source).unwrap();
102+
for cap in aux_use.captures_iter(&source) {
103+
let fname = cap.name("fname").unwrap().as_str();
104+
let source = Path::new("tests/auxiliary").join(fname);
105+
let filename = source.file_stem().unwrap();
106+
let name = format!("auxiliary/{}", filename.to_string_lossy());
107+
let output_file = manifest.out_dir.join(filename); // aux files are output to the base directory
108+
auxiliary.push(TestCase { name, source, output_file, test: TestType::CompileLib })
109+
}
110+
}
111+
112+
// Compile auxiliary before the tests
113+
let mut cases = auxiliary;
114+
cases.extend(tests);
115+
cases
88116
}
89117
}
90118

91119
pub enum TestType {
92120
/// Test an executable can be compiled
93121
Compile,
122+
/// Test a library can be compiled
94123
CompileLib,
124+
/// Run LLVM FileCheck on the generated code
95125
FileCheck,
126+
/// Bless test - the output should be the same as the last run
127+
Bless,
96128
}
97129

98130
pub struct TestCase {
@@ -125,11 +157,27 @@ impl TestCase {
125157
.args(["--crate-type", "lib"])
126158
.arg("-O")
127159
.arg(&self.source)
128-
.arg("--out-dir")
129-
.arg(self.output_file.parent().unwrap());
160+
.arg("--out-dir") // we use `--out-dir` to integrate with the default name convention
161+
.arg(output_dir); // so here we ignore the filename and just use the directory
130162
log::debug!("running {:?}", command);
131163
command.status().unwrap();
132164
}
165+
166+
/// Get the generated C file f
167+
pub fn generated(&self) -> PathBuf {
168+
let case = self.source.file_stem().unwrap().to_string_lossy();
169+
let generated = std::fs::read_dir(self.output_file.parent().unwrap())
170+
.unwrap()
171+
.filter_map(|entry| entry.ok())
172+
.find(|entry| {
173+
let filename = entry.file_name();
174+
let filename = filename.to_string_lossy();
175+
filename.ends_with(".c") && filename.starts_with(case.as_ref())
176+
});
177+
178+
assert!(generated.is_some(), "could not find {case}'s generated file");
179+
generated.unwrap().path()
180+
}
133181
}
134182

135183
struct FileChecker {
@@ -153,25 +201,41 @@ impl FileChecker {
153201
Self { filecheck }
154202
}
155203

156-
fn run(&self, source: &Path, output: &Path) {
157-
let case = source.file_stem().unwrap().to_string_lossy();
158-
let generated = std::fs::read_dir(output.parent().unwrap())
159-
.unwrap()
160-
.filter_map(|entry| entry.ok())
161-
.find(|entry| {
162-
let filename = entry.file_name();
163-
let filename = filename.to_string_lossy();
164-
filename.ends_with(".c") && filename.starts_with(case.as_ref())
165-
});
166-
167-
assert!(generated.is_some(), "could not find {case}'s generated file");
168-
let generated = generated.unwrap();
169-
170-
let generated = File::open(generated.path()).unwrap();
204+
fn run(&self, case: &TestCase) {
205+
let generated = File::open(case.generated()).unwrap();
171206
let mut command = std::process::Command::new(&self.filecheck);
172-
command.arg(source).stdin(generated);
207+
command.arg(&case.source).stdin(generated);
173208
log::debug!("running {:?}", command);
174209
let output = command.output().unwrap();
175-
assert!(output.status.success(), "failed to run FileCheck on {case}");
210+
assert!(
211+
output.status.success(),
212+
"failed to run FileCheck on {}",
213+
case.source.file_stem().unwrap().to_string_lossy()
214+
);
215+
}
216+
}
217+
218+
fn bless(update: bool, case: &TestCase) {
219+
let output = case.generated();
220+
let blessed = case.source.with_extension("c");
221+
if update {
222+
std::fs::copy(output, blessed).unwrap();
223+
} else {
224+
let output = std::fs::read_to_string(output).unwrap();
225+
let blessed = std::fs::read_to_string(blessed).unwrap();
226+
227+
let diff = TextDiff::from_lines(&blessed, &output);
228+
if diff.ratio() < 1.0 {
229+
cprintln!("<r,s>output does not match blessed output</r,s>");
230+
for change in diff.iter_all_changes() {
231+
let lineno = change.old_index().unwrap_or(change.new_index().unwrap_or(0));
232+
match change.tag() {
233+
ChangeTag::Equal => print!(" {:4}| {}", lineno, change),
234+
ChangeTag::Insert => cprint!("<g>+{:4}| {}</g>", lineno, change),
235+
ChangeTag::Delete => cprint!("<r>-{:4}| {}</r>", lineno, change),
236+
}
237+
}
238+
std::process::exit(1);
239+
}
176240
}
177241
}

examples/basic_math.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
//@ aux-build:mini_core.rs
2+
13
#![feature(no_core)]
24
#![no_core]
35
#![no_main]

tests/bless/basic_math.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
//@ aux-build:mini_core.rs
2+
3+
#![feature(no_core)]
4+
#![no_core]
5+
#![no_main]
6+
7+
extern crate mini_core;
8+
9+
#[no_mangle]
10+
pub fn main() -> i32 {
11+
0
12+
}
13+
14+
#[no_mangle]
15+
pub fn foo(x: u8, _y: u16, _z: u32) -> i64 {
16+
x as i64
17+
}

tests/codegen/filename.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
//! Test that the generated code has the filename and function name in it
22
3+
//@ aux-build:mini_core.rs
4+
35
// CHECK: filename
46

57
#![feature(no_core)]

tests/codegen/params_count.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
//! Test that the generated code has the right number of parameters
22
3+
//@ aux-build:mini_core.rs
4+
35
#![feature(no_core)]
46
#![no_core]
57
#![no_main]
@@ -11,7 +13,7 @@ extern crate mini_core;
1113

1214
// expect three int params
1315
// CHECK-LABEL: foo
14-
// CHECK: (int32_t {{[a-zA-Z_][a-zA-Z0-9_]*}}, int32_t {{[a-zA-Z_][a-zA-Z0-9_]*}}, int32_t {{[a-zA-Z_][a-zA-Z0-9_]*}})
16+
// CHECK: (int32_t {{[[:alnum:]_]*}}, int32_t {{[[:alnum:]_]*}}, int32_t {{[[:alnum:]_]*}})
1517
// CHECK: return 0;
1618
#[no_mangle]
1719
pub fn foo(_x: i32, _y: i32, _z: i32) -> i32 {

tests/codegen/ret_value.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
//! Test that we can return a value from a function
2+
3+
//@ aux-build:mini_core.rs
4+
25
#![feature(no_core)]
36
#![no_core]
47
#![no_main]
58

69
extern crate mini_core;
710

811
// CHECK-LABEL: main
9-
// CHECK: 42
12+
// CHECK: return 42;
1013
#[no_mangle]
1114
pub fn main() -> i32 {
1215
42

0 commit comments

Comments
 (0)