Skip to content

Commit 967e8d7

Browse files
committed
codegen: rework ABI tests to use a single C program for each test
Instead of compiling a C program for each layout and one for each constant to check, generate a single layout.c and a single constant.c which output all the values, one per line. On the rust side, it is easier to compile and run the C program just once and then parse its output one line at a time to compare with the corresponding rust record.
1 parent 7edc5fb commit 967e8d7

File tree

1 file changed

+136
-141
lines changed

1 file changed

+136
-141
lines changed

src/codegen/sys/tests.rs

Lines changed: 136 additions & 141 deletions
Original file line numberDiff line numberDiff line change
@@ -46,12 +46,12 @@ pub fn generate(env: &Env, crate_name: &str) {
4646

4747
let layout_c = tests.join("layout.c");
4848
save_to_file(&layout_c, env.config.make_backup, |w| {
49-
generate_layout_c(env, &layout_c, w)
49+
generate_layout_c(env, &layout_c, w, &ctypes)
5050
});
5151

5252
let constant_c = tests.join("constant.c");
5353
save_to_file(&constant_c, env.config.make_backup, |w| {
54-
generate_constant_c(env, &constant_c, w)
54+
generate_constant_c(env, &constant_c, w, &cconsts)
5555
});
5656

5757
let abi_rs = tests.join("abi.rs");
@@ -202,58 +202,80 @@ fn generate_manual_h(env: &Env, path: &Path, w: &mut dyn Write) -> io::Result<()
202202
}
203203

204204
#[allow(clippy::write_literal)]
205-
fn generate_layout_c(env: &Env, path: &Path, w: &mut dyn Write) -> io::Result<()> {
205+
fn generate_layout_c(
206+
env: &Env,
207+
path: &Path,
208+
w: &mut dyn Write,
209+
ctypes: &[CType],
210+
) -> io::Result<()> {
206211
info!("Generating file {:?}", path);
207212
general::start_comments(w, &env.config)?;
208213
writeln!(w)?;
209214
writeln!(w, "#include \"manual.h\"")?;
210215
writeln!(w, "#include <stdalign.h>")?;
211216
writeln!(w, "#include <stdio.h>")?;
217+
writeln!(w)?;
218+
writeln!(w, "{}", r"int main() {")?;
212219

213-
writeln!(
214-
w,
215-
"{}",
216-
r##"
217-
int main() {
218-
printf("%zu\n%zu", sizeof(ABI_TYPE_NAME), alignof(ABI_TYPE_NAME));
219-
return 0;
220-
}"##
221-
)
220+
for ctype in ctypes {
221+
writeln!(
222+
w,
223+
" printf(\"%s;%zu;%zu\\n\", \"{ctype}\", sizeof({ctype}), alignof({ctype}));",
224+
ctype = ctype.name
225+
)?;
226+
}
227+
228+
writeln!(w, " return 0;")?;
229+
writeln!(w, "{}", r"}")
222230
}
223231

224232
#[allow(clippy::write_literal)]
225-
fn generate_constant_c(env: &Env, path: &Path, w: &mut dyn Write) -> io::Result<()> {
233+
fn generate_constant_c(
234+
env: &Env,
235+
path: &Path,
236+
w: &mut dyn Write,
237+
cconsts: &[CConstant],
238+
) -> io::Result<()> {
226239
info!("Generating file {:?}", path);
227240
general::start_comments(w, &env.config)?;
228241
writeln!(w)?;
229242
writeln!(w, "#include \"manual.h\"")?;
230243
writeln!(w, "#include <stdio.h>")?;
231-
232244
writeln!(
233245
w,
234246
"{}",
235247
r####"
236-
int main() {
237-
printf(_Generic((ABI_CONSTANT_NAME),
238-
char *: "###gir test###%s###gir test###\n",
239-
const char *: "###gir test###%s###gir test###\n",
240-
char: "###gir test###%c###gir test###\n",
241-
signed char: "###gir test###%hhd###gir test###\n",
242-
unsigned char: "###gir test###%hhu###gir test###\n",
243-
short int: "###gir test###%hd###gir test###\n",
244-
unsigned short int: "###gir test###%hu###gir test###\n",
245-
int: "###gir test###%d###gir test###\n",
246-
unsigned int: "###gir test###%u###gir test###\n",
247-
long: "###gir test###%ld###gir test###\n",
248-
unsigned long: "###gir test###%lu###gir test###\n",
249-
long long: "###gir test###%lld###gir test###\n",
250-
unsigned long long: "###gir test###%llu###gir test###\n",
251-
double: "###gir test###%f###gir test###\n",
252-
long double: "###gir test###%ld###gir test###\n"),
253-
ABI_CONSTANT_NAME);
254-
return 0;
255-
}"####
256-
)
248+
#define PRINT_CONSTANT(CONSTANT_NAME) \
249+
printf("%s;", #CONSTANT_NAME); \
250+
printf(_Generic((CONSTANT_NAME), \
251+
char *: "%s", \
252+
const char *: "%s", \
253+
char: "%c", \
254+
signed char: "%hhd", \
255+
unsigned char: "%hhu", \
256+
short int: "%hd", \
257+
unsigned short int: "%hu", \
258+
int: "%d", \
259+
unsigned int: "%u", \
260+
long: "%ld", \
261+
unsigned long: "%lu", \
262+
long long: "%lld", \
263+
unsigned long long: "%llu", \
264+
double: "%f", \
265+
long double: "%ld"), \
266+
CONSTANT_NAME); \
267+
printf("\n");
268+
"####
269+
)?;
270+
271+
writeln!(w, "{}", r"int main() {")?;
272+
273+
for cconst in cconsts {
274+
writeln!(w, " PRINT_CONSTANT({name});", name = cconst.name,)?;
275+
}
276+
277+
writeln!(w, " return 0;")?;
278+
writeln!(w, "{}", r"}")
257279
}
258280

259281
#[allow(clippy::write_literal)]
@@ -303,23 +325,14 @@ impl Compiler {
303325
Ok(Compiler { args })
304326
}
305327
306-
pub fn define<'a, V: Into<Option<&'a str>>>(&mut self, var: &str, val: V) {
307-
let arg = match val.into() {
308-
None => format!("-D{}", var),
309-
Some(val) => format!("-D{}={}", var, val),
310-
};
311-
self.args.push(arg);
312-
}
313-
314328
pub fn compile(&self, src: &Path, out: &Path) -> Result<(), Box<dyn Error>> {
315329
let mut cmd = self.to_command();
316330
cmd.arg(src);
317331
cmd.arg("-o");
318332
cmd.arg(out);
319333
let status = cmd.spawn()?.wait()?;
320334
if !status.success() {
321-
return Err(format!("compilation command {:?} failed, {}",
322-
&cmd, status).into());
335+
return Err(format!("compilation command {:?} failed, {}", &cmd, status).into());
323336
}
324337
Ok(())
325338
}
@@ -370,8 +383,6 @@ struct Results {
370383
passed: usize,
371384
/// Total number of failed tests (including those that failed to compile).
372385
failed: usize,
373-
/// Number of tests that failed to compile.
374-
failed_to_compile: usize,
375386
}
376387
377388
impl Results {
@@ -381,16 +392,8 @@ impl Results {
381392
fn record_failed(&mut self) {
382393
self.failed += 1;
383394
}
384-
fn record_failed_to_compile(&mut self) {
385-
self.failed += 1;
386-
self.failed_to_compile += 1;
387-
}
388395
fn summary(&self) -> String {
389-
format!(
390-
"{} passed; {} failed (compilation errors: {})",
391-
self.passed,
392-
self.failed,
393-
self.failed_to_compile)
396+
format!("{} passed; {} failed", self.passed, self.failed)
394397
}
395398
fn expect_total_success(&self) {
396399
if self.failed == 0 {
@@ -403,118 +406,110 @@ impl Results {
403406
404407
#[test]
405408
fn cross_validate_constants_with_c() {
406-
let tmpdir = Builder::new().prefix("abi").tempdir().expect("temporary directory");
407-
let cc = Compiler::new().expect("configured compiler");
409+
let mut c_constants: Vec<(String, String)> = Vec::new();
410+
411+
for l in get_c_output("constant").unwrap().lines() {
412+
let mut words = l.trim().split(";");
413+
let name = words.next().expect("Failed to parse name").to_owned();
414+
let value = words
415+
.next()
416+
.and_then(|s| s.parse().ok())
417+
.expect("Failed to parse value");
418+
c_constants.push((name, value));
419+
}
408420
409-
assert_eq!("1",
410-
get_c_value(tmpdir.path(), &cc, "1").expect("C constant"),
411-
"failed to obtain correct constant value for 1");
412-
413-
let mut results : Results = Default::default();
414-
for (i, &(name, rust_value)) in RUST_CONSTANTS.iter().enumerate() {
415-
match get_c_value(tmpdir.path(), &cc, name) {
416-
Err(e) => {
417-
results.record_failed_to_compile();
418-
eprintln!("{}", e);
419-
},
420-
Ok(ref c_value) => {
421-
if rust_value == c_value {
422-
results.record_passed();
423-
} else {
424-
results.record_failed();
425-
eprintln!("Constant value mismatch for {}\nRust: {:?}\nC: {:?}",
426-
name, rust_value, c_value);
427-
}
428-
}
429-
};
430-
if (i + 1) % 25 == 0 {
431-
println!("constants ... {}", results.summary());
421+
let mut results = Results::default();
422+
423+
for ((rust_name, rust_value), (c_name, c_value)) in
424+
RUST_CONSTANTS.iter().zip(c_constants.iter())
425+
{
426+
if rust_name != c_name {
427+
results.record_failed();
428+
eprintln!("Name mismatch:\nRust: {:?}\nC: {:?}", rust_name, c_name,);
429+
continue;
432430
}
431+
432+
if rust_value != c_value {
433+
results.record_failed();
434+
eprintln!(
435+
"Constant value mismatch for {}\nRust: {:?}\nC: {:?}",
436+
rust_name, rust_value, &c_value
437+
);
438+
continue;
439+
}
440+
441+
results.record_passed();
433442
}
443+
434444
results.expect_total_success();
435445
}
436446
437447
#[test]
438448
fn cross_validate_layout_with_c() {
439-
let tmpdir = Builder::new().prefix("abi").tempdir().expect("temporary directory");
440-
let cc = Compiler::new().expect("configured compiler");
449+
let mut c_layouts = Vec::new();
450+
451+
for l in get_c_output("layout").unwrap().lines() {
452+
let mut words = l.trim().split(";");
453+
let name = words.next().expect("Failed to parse name").to_owned();
454+
let size = words
455+
.next()
456+
.and_then(|s| s.parse().ok())
457+
.expect("Failed to parse size");
458+
let alignment = words
459+
.next()
460+
.and_then(|s| s.parse().ok())
461+
.expect("Failed to parse alignment");
462+
c_layouts.push((name, Layout { size, alignment }));
463+
}
441464
442-
assert_eq!(Layout {size: 1, alignment: 1},
443-
get_c_layout(tmpdir.path(), &cc, "char").expect("C layout"),
444-
"failed to obtain correct layout for char type");
445-
446-
let mut results : Results = Default::default();
447-
for (i, &(name, rust_layout)) in RUST_LAYOUTS.iter().enumerate() {
448-
match get_c_layout(tmpdir.path(), &cc, name) {
449-
Err(e) => {
450-
results.record_failed_to_compile();
451-
eprintln!("{}", e);
452-
},
453-
Ok(c_layout) => {
454-
if rust_layout == c_layout {
455-
results.record_passed();
456-
} else {
457-
results.record_failed();
458-
eprintln!("Layout mismatch for {}\nRust: {:?}\nC: {:?}",
459-
name, rust_layout, &c_layout);
460-
}
461-
}
462-
};
463-
if (i + 1) % 25 == 0 {
464-
println!("layout ... {}", results.summary());
465+
let mut results = Results::default();
466+
467+
for ((rust_name, rust_layout), (c_name, c_layout)) in
468+
RUST_LAYOUTS.iter().zip(c_layouts.iter())
469+
{
470+
if rust_name != c_name {
471+
results.record_failed();
472+
eprintln!("Name mismatch:\nRust: {:?}\nC: {:?}", rust_name, c_name,);
473+
continue;
465474
}
466-
}
467-
results.expect_total_success();
468-
}
469475
470-
fn get_c_layout(dir: &Path, cc: &Compiler, name: &str) -> Result<Layout, Box<dyn Error>> {
471-
let exe = dir.join("layout");
472-
let mut cc = cc.clone();
473-
cc.define("ABI_TYPE_NAME", name);
474-
cc.compile(Path::new("tests/layout.c"), &exe)?;
476+
if rust_layout != c_layout {
477+
results.record_failed();
478+
eprintln!(
479+
"Layout mismatch for {}\nRust: {:?}\nC: {:?}",
480+
rust_name, rust_layout, &c_layout
481+
);
482+
continue;
483+
}
475484
476-
let mut abi_cmd = Command::new(exe);
477-
let output = abi_cmd.output()?;
478-
if !output.status.success() {
479-
return Err(format!("command {:?} failed, {:?}",
480-
&abi_cmd, &output).into());
485+
results.record_passed();
481486
}
482487
483-
let stdout = str::from_utf8(&output.stdout)?;
484-
let mut words = stdout.trim().split_whitespace();
485-
let size = words.next().unwrap().parse().unwrap();
486-
let alignment = words.next().unwrap().parse().unwrap();
487-
Ok(Layout {size, alignment})
488+
results.expect_total_success();
488489
}
489490
490-
fn get_c_value(dir: &Path, cc: &Compiler, name: &str) -> Result<String, Box<dyn Error>> {
491-
let exe = dir.join("constant");
492-
let mut cc = cc.clone();
493-
cc.define("ABI_CONSTANT_NAME", name);
494-
cc.compile(Path::new("tests/constant.c"), &exe)?;
491+
fn get_c_output(name: &str) -> Result<String, Box<dyn Error>> {
492+
let tmpdir = Builder::new().prefix("abi").tempdir()?;
493+
let exe = tmpdir.path().join(name);
494+
let c_file = Path::new("tests").join(name).with_extension("c");
495+
496+
let cc = Compiler::new().expect("configured compiler");
497+
cc.compile(&c_file, &exe)?;
495498
496499
let mut abi_cmd = Command::new(exe);
497500
let output = abi_cmd.output()?;
498501
if !output.status.success() {
499-
return Err(format!("command {:?} failed, {:?}",
500-
&abi_cmd, &output).into());
501-
}
502-
503-
let output = str::from_utf8(&output.stdout)?.trim();
504-
if !output.starts_with("###gir test###") ||
505-
!output.ends_with("###gir test###") {
506-
return Err(format!("command {:?} return invalid output, {:?}",
507-
&abi_cmd, &output).into());
502+
return Err(format!("command {:?} failed, {:?}", &abi_cmd, &output).into());
508503
}
509504
510-
Ok(String::from(&output[14..(output.len() - 14)]))
505+
Ok(String::from_utf8(output.stdout)?)
511506
}
512507
513508
const RUST_LAYOUTS: &[(&str, Layout)] = &["####
514509
)?;
515510
for ctype in ctypes {
516511
general::cfg_condition(w, &ctype.cfg_condition, false, 1)?;
517-
writeln!(w, "\t(\"{ctype}\", Layout {{size: size_of::<{ctype}>(), alignment: align_of::<{ctype}>()}}),",
512+
writeln!(w, " (\"{ctype}\", Layout {{size: size_of::<{ctype}>(), alignment: align_of::<{ctype}>()}}),",
518513
ctype=ctype.name)?;
519514
}
520515
writeln!(
@@ -527,7 +522,7 @@ const RUST_CONSTANTS: &[(&str, &str)] = &["##
527522
for cconst in cconsts {
528523
writeln!(
529524
w,
530-
"\t(\"{name}\", \"{value}\"),",
525+
" (\"{name}\", \"{value}\"),",
531526
name = cconst.name,
532527
value = &general::escape_string(&cconst.value)
533528
)?;

0 commit comments

Comments
 (0)