mirror of
https://github.com/rust-lang/rust.git
synced 2025-06-21 03:57:38 +00:00

The plan is to add more test related features that could be considered "reference tests". Rename the feature here to avoid future confusion.
457 lines
15 KiB
Rust
457 lines
15 KiB
Rust
fn main() {
|
|
#[cfg(feature = "musl-bitwise-tests")]
|
|
musl_reference_tests::generate();
|
|
}
|
|
|
|
#[cfg(feature = "musl-bitwise-tests")]
|
|
mod musl_reference_tests {
|
|
use rand::seq::SliceRandom;
|
|
use rand::Rng;
|
|
use std::env;
|
|
use std::fs;
|
|
use std::path::PathBuf;
|
|
use std::process::Command;
|
|
|
|
// Number of tests to generate for each function
|
|
const NTESTS: usize = 500;
|
|
|
|
// These files are all internal functions or otherwise miscellaneous, not
|
|
// defining a function we want to test.
|
|
const IGNORED_FILES: &[&str] = &[
|
|
"fenv.rs",
|
|
// These are giving slightly different results compared to musl
|
|
"lgamma.rs",
|
|
"lgammaf.rs",
|
|
"tgamma.rs",
|
|
"j0.rs",
|
|
"j0f.rs",
|
|
"jn.rs",
|
|
"jnf.rs",
|
|
"j1.rs",
|
|
"j1f.rs",
|
|
];
|
|
|
|
struct Function {
|
|
name: String,
|
|
args: Vec<Ty>,
|
|
ret: Vec<Ty>,
|
|
tests: Vec<Test>,
|
|
}
|
|
|
|
enum Ty {
|
|
F32,
|
|
F64,
|
|
I32,
|
|
Bool,
|
|
}
|
|
|
|
struct Test {
|
|
inputs: Vec<i64>,
|
|
outputs: Vec<i64>,
|
|
}
|
|
|
|
pub fn generate() {
|
|
// PowerPC tests are failing on LLVM 13: https://github.com/rust-lang/rust/issues/88520
|
|
let target_arch = env::var("CARGO_CFG_TARGET_ARCH").unwrap();
|
|
let libm_test = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap());
|
|
let math_src = libm_test.join("../../src/math");
|
|
|
|
if target_arch == "powerpc64" {
|
|
return;
|
|
}
|
|
|
|
let files = fs::read_dir(math_src)
|
|
.unwrap()
|
|
.map(|f| f.unwrap().path())
|
|
.collect::<Vec<_>>();
|
|
|
|
let mut math = Vec::new();
|
|
for file in files {
|
|
if IGNORED_FILES.iter().any(|f| file.ends_with(f)) {
|
|
continue;
|
|
}
|
|
|
|
println!("generating musl reference tests in {:?}", file);
|
|
|
|
let contents = fs::read_to_string(file).unwrap();
|
|
let mut functions = contents.lines().filter(|f| f.starts_with("pub fn"));
|
|
while let Some(function_to_test) = functions.next() {
|
|
math.push(parse(function_to_test));
|
|
}
|
|
}
|
|
|
|
// Generate a bunch of random inputs for each function. This will
|
|
// attempt to generate a good set of uniform test cases for exercising
|
|
// all the various functionality.
|
|
generate_random_tests(&mut math, &mut rand::thread_rng());
|
|
|
|
// After we have all our inputs, use the x86_64-unknown-linux-musl
|
|
// target to generate the expected output.
|
|
generate_test_outputs(&mut math);
|
|
//panic!("Boo");
|
|
// ... and now that we have both inputs and expected outputs, do a bunch
|
|
// of codegen to create the unit tests which we'll actually execute.
|
|
generate_unit_tests(&math);
|
|
}
|
|
|
|
/// A "poor man's" parser for the signature of a function
|
|
fn parse(s: &str) -> Function {
|
|
let s = eat(s, "pub fn ");
|
|
let pos = s.find('(').unwrap();
|
|
let name = &s[..pos];
|
|
let s = &s[pos + 1..];
|
|
let end = s.find(')').unwrap();
|
|
let args = s[..end]
|
|
.split(',')
|
|
.map(|arg| {
|
|
let colon = arg.find(':').unwrap();
|
|
parse_ty(arg[colon + 1..].trim())
|
|
})
|
|
.collect::<Vec<_>>();
|
|
let tail = &s[end + 1..];
|
|
let tail = eat(tail, " -> ");
|
|
let ret = parse_retty(tail.replace("{", "").trim());
|
|
|
|
return Function {
|
|
name: name.to_string(),
|
|
args,
|
|
ret,
|
|
tests: Vec::new(),
|
|
};
|
|
|
|
fn parse_ty(s: &str) -> Ty {
|
|
match s {
|
|
"f32" => Ty::F32,
|
|
"f64" => Ty::F64,
|
|
"i32" => Ty::I32,
|
|
"bool" => Ty::Bool,
|
|
other => panic!("unknown type `{}`", other),
|
|
}
|
|
}
|
|
|
|
fn parse_retty(s: &str) -> Vec<Ty> {
|
|
match s {
|
|
"(f32, f32)" => vec![Ty::F32, Ty::F32],
|
|
"(f32, i32)" => vec![Ty::F32, Ty::I32],
|
|
"(f64, f64)" => vec![Ty::F64, Ty::F64],
|
|
"(f64, i32)" => vec![Ty::F64, Ty::I32],
|
|
other => vec![parse_ty(other)],
|
|
}
|
|
}
|
|
|
|
fn eat<'a>(s: &'a str, prefix: &str) -> &'a str {
|
|
if s.starts_with(prefix) {
|
|
&s[prefix.len()..]
|
|
} else {
|
|
panic!("{:?} didn't start with {:?}", s, prefix)
|
|
}
|
|
}
|
|
}
|
|
|
|
fn generate_random_tests<R: Rng>(functions: &mut [Function], rng: &mut R) {
|
|
for function in functions {
|
|
for _ in 0..NTESTS {
|
|
function.tests.push(generate_test(function, rng));
|
|
}
|
|
}
|
|
|
|
fn generate_test<R: Rng>(function: &Function, rng: &mut R) -> Test {
|
|
let mut inputs = function
|
|
.args
|
|
.iter()
|
|
.map(|ty| ty.gen_i64(rng))
|
|
.collect::<Vec<_>>();
|
|
|
|
// First argument to this function appears to be a number of
|
|
// iterations, so passing in massive random numbers causes it to
|
|
// take forever to execute, so make sure we're not running random
|
|
// math code until the heat death of the universe.
|
|
if function.name == "jn" || function.name == "jnf" {
|
|
inputs[0] &= 0xffff;
|
|
}
|
|
|
|
Test {
|
|
inputs,
|
|
// zero output for now since we'll generate it later
|
|
outputs: vec![],
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Ty {
|
|
fn gen_i64<R: Rng>(&self, r: &mut R) -> i64 {
|
|
use std::f32;
|
|
use std::f64;
|
|
|
|
return match self {
|
|
Ty::F32 => {
|
|
if r.gen_range(0, 20) < 1 {
|
|
let i = *[f32::NAN, f32::INFINITY, f32::NEG_INFINITY]
|
|
.choose(r)
|
|
.unwrap();
|
|
i.to_bits().into()
|
|
} else {
|
|
r.gen::<f32>().to_bits().into()
|
|
}
|
|
}
|
|
Ty::F64 => {
|
|
if r.gen_range(0, 20) < 1 {
|
|
let i = *[f64::NAN, f64::INFINITY, f64::NEG_INFINITY]
|
|
.choose(r)
|
|
.unwrap();
|
|
i.to_bits() as i64
|
|
} else {
|
|
r.gen::<f64>().to_bits() as i64
|
|
}
|
|
}
|
|
Ty::I32 => {
|
|
if r.gen_range(0, 10) < 1 {
|
|
let i = *[i32::max_value(), 0, i32::min_value()].choose(r).unwrap();
|
|
i.into()
|
|
} else {
|
|
r.gen::<i32>().into()
|
|
}
|
|
}
|
|
Ty::Bool => r.gen::<bool>() as i64,
|
|
};
|
|
}
|
|
|
|
fn libc_ty(&self) -> &'static str {
|
|
match self {
|
|
Ty::F32 => "f32",
|
|
Ty::F64 => "f64",
|
|
Ty::I32 => "i32",
|
|
Ty::Bool => "i32",
|
|
}
|
|
}
|
|
|
|
fn libc_pty(&self) -> &'static str {
|
|
match self {
|
|
Ty::F32 => "*mut f32",
|
|
Ty::F64 => "*mut f64",
|
|
Ty::I32 => "*mut i32",
|
|
Ty::Bool => "*mut i32",
|
|
}
|
|
}
|
|
|
|
fn default(&self) -> &'static str {
|
|
match self {
|
|
Ty::F32 => "0_f32",
|
|
Ty::F64 => "0_f64",
|
|
Ty::I32 => "0_i32",
|
|
Ty::Bool => "false",
|
|
}
|
|
}
|
|
|
|
fn to_i64(&self) -> &'static str {
|
|
match self {
|
|
Ty::F32 => ".to_bits() as i64",
|
|
Ty::F64 => ".to_bits() as i64",
|
|
Ty::I32 => " as i64",
|
|
Ty::Bool => " as i64",
|
|
}
|
|
}
|
|
}
|
|
|
|
fn generate_test_outputs(functions: &mut [Function]) {
|
|
let mut src = String::new();
|
|
let dst = std::env::var("OUT_DIR").unwrap();
|
|
|
|
// Generate a program which will run all tests with all inputs in
|
|
// `functions`. This program will write all outputs to stdout (in a
|
|
// binary format).
|
|
src.push_str("use std::io::Write;");
|
|
src.push_str("fn main() {");
|
|
src.push_str("let mut result = Vec::new();");
|
|
for function in functions.iter_mut() {
|
|
src.push_str("unsafe {");
|
|
src.push_str("extern { fn ");
|
|
src.push_str(&function.name);
|
|
src.push_str("(");
|
|
|
|
let (ret, retptr) = match function.name.as_str() {
|
|
"sincos" | "sincosf" => (None, &function.ret[..]),
|
|
_ => (Some(&function.ret[0]), &function.ret[1..]),
|
|
};
|
|
for (i, arg) in function.args.iter().enumerate() {
|
|
src.push_str(&format!("arg{}: {},", i, arg.libc_ty()));
|
|
}
|
|
for (i, ret) in retptr.iter().enumerate() {
|
|
src.push_str(&format!("argret{}: {},", i, ret.libc_pty()));
|
|
}
|
|
src.push_str(")");
|
|
if let Some(ty) = ret {
|
|
src.push_str(" -> ");
|
|
src.push_str(ty.libc_ty());
|
|
}
|
|
src.push_str("; }");
|
|
|
|
src.push_str(&format!("static TESTS: &[[i64; {}]]", function.args.len()));
|
|
src.push_str(" = &[");
|
|
for test in function.tests.iter() {
|
|
src.push_str("[");
|
|
for val in test.inputs.iter() {
|
|
src.push_str(&val.to_string());
|
|
src.push_str(",");
|
|
}
|
|
src.push_str("],");
|
|
}
|
|
src.push_str("];");
|
|
|
|
src.push_str("for test in TESTS {");
|
|
for (i, arg) in retptr.iter().enumerate() {
|
|
src.push_str(&format!("let mut argret{} = {};", i, arg.default()));
|
|
}
|
|
src.push_str("let output = ");
|
|
src.push_str(&function.name);
|
|
src.push_str("(");
|
|
for (i, arg) in function.args.iter().enumerate() {
|
|
src.push_str(&match arg {
|
|
Ty::F32 => format!("f32::from_bits(test[{}] as u32)", i),
|
|
Ty::F64 => format!("f64::from_bits(test[{}] as u64)", i),
|
|
Ty::I32 => format!("test[{}] as i32", i),
|
|
Ty::Bool => format!("test[{}] as i32", i),
|
|
});
|
|
src.push_str(",");
|
|
}
|
|
for (i, _) in retptr.iter().enumerate() {
|
|
src.push_str(&format!("&mut argret{},", i));
|
|
}
|
|
src.push_str(");");
|
|
if let Some(ty) = &ret {
|
|
src.push_str(&format!("let output = output{};", ty.to_i64()));
|
|
src.push_str("result.extend_from_slice(&output.to_le_bytes());");
|
|
}
|
|
|
|
for (i, ret) in retptr.iter().enumerate() {
|
|
src.push_str(&format!(
|
|
"result.extend_from_slice(&(argret{}{}).to_le_bytes());",
|
|
i,
|
|
ret.to_i64(),
|
|
));
|
|
}
|
|
src.push_str("}");
|
|
|
|
src.push_str("}");
|
|
}
|
|
|
|
src.push_str("std::io::stdout().write_all(&result).unwrap();");
|
|
|
|
src.push_str("}");
|
|
|
|
let path = format!("{}/gen.rs", dst);
|
|
fs::write(&path, src).unwrap();
|
|
|
|
// Make it somewhat pretty if something goes wrong
|
|
drop(Command::new("rustfmt").arg(&path).status());
|
|
|
|
// Compile and execute this tests for the musl target, assuming we're an
|
|
// x86_64 host effectively.
|
|
let status = Command::new("rustc")
|
|
.current_dir(&dst)
|
|
.arg(&path)
|
|
.arg("--target=x86_64-unknown-linux-musl")
|
|
.status()
|
|
.unwrap();
|
|
assert!(status.success());
|
|
let output = Command::new("./gen").current_dir(&dst).output().unwrap();
|
|
assert!(output.status.success());
|
|
assert!(output.stderr.is_empty());
|
|
|
|
// Map all the output bytes back to an `i64` and then shove it all into
|
|
// the expected results.
|
|
let mut results = output.stdout.chunks_exact(8).map(|buf| {
|
|
let mut exact = [0; 8];
|
|
exact.copy_from_slice(buf);
|
|
i64::from_le_bytes(exact)
|
|
});
|
|
|
|
for f in functions.iter_mut() {
|
|
for test in f.tests.iter_mut() {
|
|
test.outputs = (0..f.ret.len()).map(|_| results.next().unwrap()).collect();
|
|
}
|
|
}
|
|
assert!(results.next().is_none());
|
|
}
|
|
|
|
/// Codegens a file which has a ton of `#[test]` annotations for all the
|
|
/// tests that we generated above.
|
|
fn generate_unit_tests(functions: &[Function]) {
|
|
let mut src = String::new();
|
|
let dst = std::env::var("OUT_DIR").unwrap();
|
|
|
|
for function in functions {
|
|
src.push_str("#[test]");
|
|
src.push_str("fn ");
|
|
src.push_str(&function.name);
|
|
src.push_str("_matches_musl() {");
|
|
src.push_str(&format!(
|
|
"static TESTS: &[([i64; {}], [i64; {}])]",
|
|
function.args.len(),
|
|
function.ret.len(),
|
|
));
|
|
src.push_str(" = &[");
|
|
for test in function.tests.iter() {
|
|
src.push_str("([");
|
|
for val in test.inputs.iter() {
|
|
src.push_str(&val.to_string());
|
|
src.push_str(",");
|
|
}
|
|
src.push_str("],");
|
|
src.push_str("[");
|
|
for val in test.outputs.iter() {
|
|
src.push_str(&val.to_string());
|
|
src.push_str(",");
|
|
}
|
|
src.push_str("],");
|
|
src.push_str("),");
|
|
}
|
|
src.push_str("];");
|
|
|
|
src.push_str("for (test, expected) in TESTS {");
|
|
src.push_str("let output = libm::");
|
|
src.push_str(&function.name);
|
|
src.push_str("(");
|
|
for (i, arg) in function.args.iter().enumerate() {
|
|
src.push_str(&match arg {
|
|
Ty::F32 => format!("f32::from_bits(test[{}] as u32)", i),
|
|
Ty::F64 => format!("f64::from_bits(test[{}] as u64)", i),
|
|
Ty::I32 => format!("test[{}] as i32", i),
|
|
Ty::Bool => format!("test[{}] as i32", i),
|
|
});
|
|
src.push_str(",");
|
|
}
|
|
src.push_str(");");
|
|
|
|
for (i, ret) in function.ret.iter().enumerate() {
|
|
let get = if function.ret.len() == 1 {
|
|
String::new()
|
|
} else {
|
|
format!(".{}", i)
|
|
};
|
|
src.push_str(&(match ret {
|
|
Ty::F32 => format!("if libm::_eqf(output{}, f32::from_bits(expected[{}] as u32)).is_ok() {{ continue }}", get, i),
|
|
Ty::F64 => format!("if libm::_eq(output{}, f64::from_bits(expected[{}] as u64)).is_ok() {{ continue }}", get, i),
|
|
Ty::I32 => format!("if output{} as i64 == expected[{}] {{ continue }}", get, i),
|
|
Ty::Bool => unreachable!(),
|
|
}));
|
|
}
|
|
|
|
src.push_str(
|
|
r#"
|
|
panic!("INPUT: {:?} EXPECTED: {:?} ACTUAL {:?}", test, expected, output);
|
|
"#,
|
|
);
|
|
src.push_str("}");
|
|
|
|
src.push_str("}");
|
|
}
|
|
|
|
let path = format!("{}/musl-tests.rs", dst);
|
|
fs::write(&path, src).unwrap();
|
|
|
|
// Try to make it somewhat pretty
|
|
drop(Command::new("rustfmt").arg(&path).status());
|
|
}
|
|
}
|