Skip to content

Commit

Permalink
codegen: rework ABI tests to use a single C program for each test
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
pbor committed Feb 7, 2021
1 parent 7edc5fb commit 967e8d7
Showing 1 changed file with 136 additions and 141 deletions.
277 changes: 136 additions & 141 deletions src/codegen/sys/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,12 +46,12 @@ pub fn generate(env: &Env, crate_name: &str) {

let layout_c = tests.join("layout.c");
save_to_file(&layout_c, env.config.make_backup, |w| {
generate_layout_c(env, &layout_c, w)
generate_layout_c(env, &layout_c, w, &ctypes)
});

let constant_c = tests.join("constant.c");
save_to_file(&constant_c, env.config.make_backup, |w| {
generate_constant_c(env, &constant_c, w)
generate_constant_c(env, &constant_c, w, &cconsts)
});

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

#[allow(clippy::write_literal)]
fn generate_layout_c(env: &Env, path: &Path, w: &mut dyn Write) -> io::Result<()> {
fn generate_layout_c(
env: &Env,
path: &Path,
w: &mut dyn Write,
ctypes: &[CType],
) -> io::Result<()> {
info!("Generating file {:?}", path);
general::start_comments(w, &env.config)?;
writeln!(w)?;
writeln!(w, "#include \"manual.h\"")?;
writeln!(w, "#include <stdalign.h>")?;
writeln!(w, "#include <stdio.h>")?;
writeln!(w)?;
writeln!(w, "{}", r"int main() {")?;

writeln!(
w,
"{}",
r##"
int main() {
printf("%zu\n%zu", sizeof(ABI_TYPE_NAME), alignof(ABI_TYPE_NAME));
return 0;
}"##
)
for ctype in ctypes {
writeln!(
w,
" printf(\"%s;%zu;%zu\\n\", \"{ctype}\", sizeof({ctype}), alignof({ctype}));",
ctype = ctype.name
)?;
}

writeln!(w, " return 0;")?;
writeln!(w, "{}", r"}")
}

#[allow(clippy::write_literal)]
fn generate_constant_c(env: &Env, path: &Path, w: &mut dyn Write) -> io::Result<()> {
fn generate_constant_c(
env: &Env,
path: &Path,
w: &mut dyn Write,
cconsts: &[CConstant],
) -> io::Result<()> {
info!("Generating file {:?}", path);
general::start_comments(w, &env.config)?;
writeln!(w)?;
writeln!(w, "#include \"manual.h\"")?;
writeln!(w, "#include <stdio.h>")?;

writeln!(
w,
"{}",
r####"
int main() {
printf(_Generic((ABI_CONSTANT_NAME),
char *: "###gir test###%s###gir test###\n",
const char *: "###gir test###%s###gir test###\n",
char: "###gir test###%c###gir test###\n",
signed char: "###gir test###%hhd###gir test###\n",
unsigned char: "###gir test###%hhu###gir test###\n",
short int: "###gir test###%hd###gir test###\n",
unsigned short int: "###gir test###%hu###gir test###\n",
int: "###gir test###%d###gir test###\n",
unsigned int: "###gir test###%u###gir test###\n",
long: "###gir test###%ld###gir test###\n",
unsigned long: "###gir test###%lu###gir test###\n",
long long: "###gir test###%lld###gir test###\n",
unsigned long long: "###gir test###%llu###gir test###\n",
double: "###gir test###%f###gir test###\n",
long double: "###gir test###%ld###gir test###\n"),
ABI_CONSTANT_NAME);
return 0;
}"####
)
#define PRINT_CONSTANT(CONSTANT_NAME) \
printf("%s;", #CONSTANT_NAME); \
printf(_Generic((CONSTANT_NAME), \
char *: "%s", \
const char *: "%s", \
char: "%c", \
signed char: "%hhd", \
unsigned char: "%hhu", \
short int: "%hd", \
unsigned short int: "%hu", \
int: "%d", \
unsigned int: "%u", \
long: "%ld", \
unsigned long: "%lu", \
long long: "%lld", \
unsigned long long: "%llu", \
double: "%f", \
long double: "%ld"), \
CONSTANT_NAME); \
printf("\n");
"####
)?;

writeln!(w, "{}", r"int main() {")?;

for cconst in cconsts {
writeln!(w, " PRINT_CONSTANT({name});", name = cconst.name,)?;
}

writeln!(w, " return 0;")?;
writeln!(w, "{}", r"}")
}

#[allow(clippy::write_literal)]
Expand Down Expand Up @@ -303,23 +325,14 @@ impl Compiler {
Ok(Compiler { args })
}
pub fn define<'a, V: Into<Option<&'a str>>>(&mut self, var: &str, val: V) {
let arg = match val.into() {
None => format!("-D{}", var),
Some(val) => format!("-D{}={}", var, val),
};
self.args.push(arg);
}
pub fn compile(&self, src: &Path, out: &Path) -> Result<(), Box<dyn Error>> {
let mut cmd = self.to_command();
cmd.arg(src);
cmd.arg("-o");
cmd.arg(out);
let status = cmd.spawn()?.wait()?;
if !status.success() {
return Err(format!("compilation command {:?} failed, {}",
&cmd, status).into());
return Err(format!("compilation command {:?} failed, {}", &cmd, status).into());
}
Ok(())
}
Expand Down Expand Up @@ -370,8 +383,6 @@ struct Results {
passed: usize,
/// Total number of failed tests (including those that failed to compile).
failed: usize,
/// Number of tests that failed to compile.
failed_to_compile: usize,
}
impl Results {
Expand All @@ -381,16 +392,8 @@ impl Results {
fn record_failed(&mut self) {
self.failed += 1;
}
fn record_failed_to_compile(&mut self) {
self.failed += 1;
self.failed_to_compile += 1;
}
fn summary(&self) -> String {
format!(
"{} passed; {} failed (compilation errors: {})",
self.passed,
self.failed,
self.failed_to_compile)
format!("{} passed; {} failed", self.passed, self.failed)
}
fn expect_total_success(&self) {
if self.failed == 0 {
Expand All @@ -403,118 +406,110 @@ impl Results {
#[test]
fn cross_validate_constants_with_c() {
let tmpdir = Builder::new().prefix("abi").tempdir().expect("temporary directory");
let cc = Compiler::new().expect("configured compiler");
let mut c_constants: Vec<(String, String)> = Vec::new();
for l in get_c_output("constant").unwrap().lines() {
let mut words = l.trim().split(";");
let name = words.next().expect("Failed to parse name").to_owned();
let value = words
.next()
.and_then(|s| s.parse().ok())
.expect("Failed to parse value");
c_constants.push((name, value));
}
assert_eq!("1",
get_c_value(tmpdir.path(), &cc, "1").expect("C constant"),
"failed to obtain correct constant value for 1");
let mut results : Results = Default::default();
for (i, &(name, rust_value)) in RUST_CONSTANTS.iter().enumerate() {
match get_c_value(tmpdir.path(), &cc, name) {
Err(e) => {
results.record_failed_to_compile();
eprintln!("{}", e);
},
Ok(ref c_value) => {
if rust_value == c_value {
results.record_passed();
} else {
results.record_failed();
eprintln!("Constant value mismatch for {}\nRust: {:?}\nC: {:?}",
name, rust_value, c_value);
}
}
};
if (i + 1) % 25 == 0 {
println!("constants ... {}", results.summary());
let mut results = Results::default();
for ((rust_name, rust_value), (c_name, c_value)) in
RUST_CONSTANTS.iter().zip(c_constants.iter())
{
if rust_name != c_name {
results.record_failed();
eprintln!("Name mismatch:\nRust: {:?}\nC: {:?}", rust_name, c_name,);
continue;
}
if rust_value != c_value {
results.record_failed();
eprintln!(
"Constant value mismatch for {}\nRust: {:?}\nC: {:?}",
rust_name, rust_value, &c_value
);
continue;
}
results.record_passed();
}
results.expect_total_success();
}
#[test]
fn cross_validate_layout_with_c() {
let tmpdir = Builder::new().prefix("abi").tempdir().expect("temporary directory");
let cc = Compiler::new().expect("configured compiler");
let mut c_layouts = Vec::new();
for l in get_c_output("layout").unwrap().lines() {
let mut words = l.trim().split(";");
let name = words.next().expect("Failed to parse name").to_owned();
let size = words
.next()
.and_then(|s| s.parse().ok())
.expect("Failed to parse size");
let alignment = words
.next()
.and_then(|s| s.parse().ok())
.expect("Failed to parse alignment");
c_layouts.push((name, Layout { size, alignment }));
}
assert_eq!(Layout {size: 1, alignment: 1},
get_c_layout(tmpdir.path(), &cc, "char").expect("C layout"),
"failed to obtain correct layout for char type");
let mut results : Results = Default::default();
for (i, &(name, rust_layout)) in RUST_LAYOUTS.iter().enumerate() {
match get_c_layout(tmpdir.path(), &cc, name) {
Err(e) => {
results.record_failed_to_compile();
eprintln!("{}", e);
},
Ok(c_layout) => {
if rust_layout == c_layout {
results.record_passed();
} else {
results.record_failed();
eprintln!("Layout mismatch for {}\nRust: {:?}\nC: {:?}",
name, rust_layout, &c_layout);
}
}
};
if (i + 1) % 25 == 0 {
println!("layout ... {}", results.summary());
let mut results = Results::default();
for ((rust_name, rust_layout), (c_name, c_layout)) in
RUST_LAYOUTS.iter().zip(c_layouts.iter())
{
if rust_name != c_name {
results.record_failed();
eprintln!("Name mismatch:\nRust: {:?}\nC: {:?}", rust_name, c_name,);
continue;
}
}
results.expect_total_success();
}
fn get_c_layout(dir: &Path, cc: &Compiler, name: &str) -> Result<Layout, Box<dyn Error>> {
let exe = dir.join("layout");
let mut cc = cc.clone();
cc.define("ABI_TYPE_NAME", name);
cc.compile(Path::new("tests/layout.c"), &exe)?;
if rust_layout != c_layout {
results.record_failed();
eprintln!(
"Layout mismatch for {}\nRust: {:?}\nC: {:?}",
rust_name, rust_layout, &c_layout
);
continue;
}
let mut abi_cmd = Command::new(exe);
let output = abi_cmd.output()?;
if !output.status.success() {
return Err(format!("command {:?} failed, {:?}",
&abi_cmd, &output).into());
results.record_passed();
}
let stdout = str::from_utf8(&output.stdout)?;
let mut words = stdout.trim().split_whitespace();
let size = words.next().unwrap().parse().unwrap();
let alignment = words.next().unwrap().parse().unwrap();
Ok(Layout {size, alignment})
results.expect_total_success();
}
fn get_c_value(dir: &Path, cc: &Compiler, name: &str) -> Result<String, Box<dyn Error>> {
let exe = dir.join("constant");
let mut cc = cc.clone();
cc.define("ABI_CONSTANT_NAME", name);
cc.compile(Path::new("tests/constant.c"), &exe)?;
fn get_c_output(name: &str) -> Result<String, Box<dyn Error>> {
let tmpdir = Builder::new().prefix("abi").tempdir()?;
let exe = tmpdir.path().join(name);
let c_file = Path::new("tests").join(name).with_extension("c");
let cc = Compiler::new().expect("configured compiler");
cc.compile(&c_file, &exe)?;
let mut abi_cmd = Command::new(exe);
let output = abi_cmd.output()?;
if !output.status.success() {
return Err(format!("command {:?} failed, {:?}",
&abi_cmd, &output).into());
}
let output = str::from_utf8(&output.stdout)?.trim();
if !output.starts_with("###gir test###") ||
!output.ends_with("###gir test###") {
return Err(format!("command {:?} return invalid output, {:?}",
&abi_cmd, &output).into());
return Err(format!("command {:?} failed, {:?}", &abi_cmd, &output).into());
}
Ok(String::from(&output[14..(output.len() - 14)]))
Ok(String::from_utf8(output.stdout)?)
}
const RUST_LAYOUTS: &[(&str, Layout)] = &["####
)?;
for ctype in ctypes {
general::cfg_condition(w, &ctype.cfg_condition, false, 1)?;
writeln!(w, "\t(\"{ctype}\", Layout {{size: size_of::<{ctype}>(), alignment: align_of::<{ctype}>()}}),",
writeln!(w, " (\"{ctype}\", Layout {{size: size_of::<{ctype}>(), alignment: align_of::<{ctype}>()}}),",
ctype=ctype.name)?;
}
writeln!(
Expand All @@ -527,7 +522,7 @@ const RUST_CONSTANTS: &[(&str, &str)] = &["##
for cconst in cconsts {
writeln!(
w,
"\t(\"{name}\", \"{value}\"),",
" (\"{name}\", \"{value}\"),",
name = cconst.name,
value = &general::escape_string(&cconst.value)
)?;
Expand Down

0 comments on commit 967e8d7

Please sign in to comment.