Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

codegen: rework ABI tests to use a single C program for each test #1050

Merged
merged 2 commits into from
Feb 7, 2021
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
279 changes: 138 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), \
pbor marked this conversation as resolved.
Show resolved Hide resolved
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 @@ -295,6 +317,8 @@ impl Compiler {
pub fn new() -> Result<Compiler, Box<dyn Error>> {
let mut args = get_var("CC", "cc")?;
args.push("-Wno-deprecated-declarations".to_owned());
// For _Generic
args.push("-std=c11".to_owned());
// For %z support in printf when using MinGW.
args.push("-D__USE_MINGW_ANSI_STDIO".to_owned());
args.extend(get_var("CFLAGS", "")?);
Expand All @@ -303,23 +327,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 +385,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 +394,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 +408,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 +524,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