Rework how testing is done

Use a build script to generate musl reference outputs and then ensure
that everything gets hooked up to actually run reference tests.
This commit is contained in:
Alex Crichton
2019-05-02 10:48:55 -07:00
parent 575e81ca56
commit 28c69b4197
10 changed files with 351 additions and 974 deletions
-7
View File
@@ -1,7 +0,0 @@
[package]
name = "input-generator"
version = "0.1.0"
authors = ["Jorge Aparicio <jorge@japaric.io>"]
[dependencies]
rand = "0.5.4"
-189
View File
@@ -1,189 +0,0 @@
extern crate rand;
use std::collections::BTreeSet;
use std::error::Error;
use std::fs::{self, File};
use std::io::Write;
use rand::{RngCore, SeedableRng, XorShiftRng};
const NTESTS: usize = 10_000;
fn main() -> Result<(), Box<Error>> {
let mut rng = XorShiftRng::from_rng(&mut rand::thread_rng())?;
fs::remove_dir_all("bin").ok();
fs::create_dir_all("bin/input")?;
fs::create_dir_all("bin/output")?;
f32(&mut rng)?;
f32f32(&mut rng)?;
f32f32f32(&mut rng)?;
f32i16(&mut rng)?;
f64(&mut rng)?;
f64f64(&mut rng)?;
f64f64f64(&mut rng)?;
f64i16(&mut rng)?;
Ok(())
}
fn f32(rng: &mut XorShiftRng) -> Result<(), Box<Error>> {
let mut set = BTreeSet::new();
while set.len() < NTESTS {
let f = f32::from_bits(rng.next_u32());
if f.is_nan() {
continue;
}
set.insert(f.to_bits());
}
let mut f = File::create("bin/input/f32")?;
for i in set {
f.write_all(&i.to_le_bytes())?;
}
Ok(())
}
fn f32f32(rng: &mut XorShiftRng) -> Result<(), Box<Error>> {
let mut f = File::create("bin/input/f32f32")?;
let mut i = 0;
while i < NTESTS {
let x0 = f32::from_bits(rng.next_u32());
let x1 = f32::from_bits(rng.next_u32());
if x0.is_nan() || x1.is_nan() {
continue;
}
i += 1;
f.write_all(&x0.to_bits().to_le_bytes())?;
f.write_all(&x1.to_bits().to_le_bytes())?;
}
Ok(())
}
fn f32i16(rng: &mut XorShiftRng) -> Result<(), Box<Error>> {
let mut f = File::create("bin/input/f32i16")?;
let mut i = 0;
while i < NTESTS {
let x0 = f32::from_bits(rng.next_u32());
let x1 = rng.next_u32() as i16;
if x0.is_nan() {
continue;
}
i += 1;
f.write_all(&x0.to_bits().to_le_bytes())?;
f.write_all(&x1.to_le_bytes())?;
}
Ok(())
}
fn f32f32f32(rng: &mut XorShiftRng) -> Result<(), Box<Error>> {
let mut f = File::create("bin/input/f32f32f32")?;
let mut i = 0;
while i < NTESTS {
let x0 = f32::from_bits(rng.next_u32());
let x1 = f32::from_bits(rng.next_u32());
let x2 = f32::from_bits(rng.next_u32());
if x0.is_nan() || x1.is_nan() || x2.is_nan() {
continue;
}
i += 1;
f.write_all(&x0.to_bits().to_le_bytes())?;
f.write_all(&x1.to_bits().to_le_bytes())?;
f.write_all(&x2.to_bits().to_le_bytes())?;
}
Ok(())
}
fn f64(rng: &mut XorShiftRng) -> Result<(), Box<Error>> {
let mut set = BTreeSet::new();
while set.len() < NTESTS {
let f = f64::from_bits(rng.next_u64());
if f.is_nan() {
continue;
}
set.insert(f.to_bits());
}
let mut f = File::create("bin/input/f64")?;
for i in set {
f.write_all(&i.to_le_bytes())?;
}
Ok(())
}
fn f64f64(rng: &mut XorShiftRng) -> Result<(), Box<Error>> {
let mut f = File::create("bin/input/f64f64")?;
let mut i = 0;
while i < NTESTS {
let x0 = f64::from_bits(rng.next_u64());
let x1 = f64::from_bits(rng.next_u64());
if x0.is_nan() || x1.is_nan() {
continue;
}
i += 1;
f.write_all(&x0.to_bits().to_le_bytes())?;
f.write_all(&x1.to_bits().to_le_bytes())?;
}
Ok(())
}
fn f64f64f64(rng: &mut XorShiftRng) -> Result<(), Box<Error>> {
let mut f = File::create("bin/input/f64f64f64")?;
let mut i = 0;
while i < NTESTS {
let x0 = f64::from_bits(rng.next_u64());
let x1 = f64::from_bits(rng.next_u64());
let x2 = f64::from_bits(rng.next_u64());
if x0.is_nan() || x1.is_nan() || x2.is_nan() {
continue;
}
i += 1;
f.write_all(&x0.to_bits().to_le_bytes())?;
f.write_all(&x1.to_bits().to_le_bytes())?;
f.write_all(&x2.to_bits().to_le_bytes())?;
}
Ok(())
}
fn f64i16(rng: &mut XorShiftRng) -> Result<(), Box<Error>> {
let mut f = File::create("bin/input/f64i16")?;
let mut i = 0;
while i < NTESTS {
let x0 = f64::from_bits(rng.next_u64());
let x1 = rng.next_u32() as i16;
if x0.is_nan() {
continue;
}
i += 1;
f.write_all(&x0.to_bits().to_le_bytes())?;
f.write_all(&x1.to_le_bytes())?;
}
Ok(())
}
-9
View File
@@ -1,9 +0,0 @@
[package]
name = "musl-generator"
version = "0.1.0"
authors = ["Jorge Aparicio <jorge@japaric.io>"]
[dependencies]
lazy_static = "1.0.2"
shared = { path = "../shared" }
libm = { path = ".." }
-191
View File
@@ -1,191 +0,0 @@
macro_rules! f32 {
($($fun:ident,)+) => {{
$(
// check type signature
let _: fn(f32) -> f32 = libm::$fun;
let mut $fun = File::create(concat!("bin/output/musl.", stringify!($fun)))?;
)+
for x in shared::F32.iter() {
$(
let y = unsafe {
extern "C" {
fn $fun(_: f32) -> f32;
}
$fun(*x)
};
$fun.write_all(&y.to_bits().to_le_bytes())?;
)+
}
}};
}
macro_rules! f32f32 {
($($fun:ident,)+) => {{
$(
// check type signature
let _: fn(f32, f32) -> f32 = libm::$fun;
let mut $fun = File::create(concat!("bin/output/musl.", stringify!($fun)))?;
)+
for (x0, x1) in shared::F32F32.iter() {
$(
let y = unsafe {
extern "C" {
fn $fun(_: f32, _: f32) -> f32;
}
$fun(*x0, *x1)
};
$fun.write_all(&y.to_bits().to_le_bytes())?;
)+
}
}};
}
macro_rules! f32f32f32 {
($($fun:ident,)+) => {{
$(
// check type signature
let _: fn(f32, f32, f32) -> f32 = libm::$fun;
let mut $fun = File::create(concat!("bin/output/musl.", stringify!($fun)))?;
)+
for (x0, x1, x2) in shared::F32F32F32.iter() {
$(
let y = unsafe {
extern "C" {
fn $fun(_: f32, _: f32, _: f32) -> f32;
}
$fun(*x0, *x1, *x2)
};
$fun.write_all(&y.to_bits().to_le_bytes())?;
)+
}
}};
}
macro_rules! f32i32 {
($($fun:ident,)+) => {{
$(
// check type signature
let _: fn(f32, i32) -> f32 = libm::$fun;
let mut $fun = File::create(concat!("bin/output/musl.", stringify!($fun)))?;
)+
for (x0, x1) in shared::F32I32.iter() {
$(
let y = unsafe {
extern "C" {
fn $fun(_: f32, _: i32) -> f32;
}
$fun(*x0, *x1 as i32)
};
$fun.write_all(&y.to_bits().to_le_bytes())?;
)+
}
}};
}
macro_rules! f64 {
($($fun:ident,)+) => {{
$(
// check type signature
let _: fn(f64) -> f64 = libm::$fun;
let mut $fun = File::create(concat!("bin/output/musl.", stringify!($fun)))?;
)+
for x in shared::F64.iter() {
$(
let y = unsafe {
extern "C" {
fn $fun(_: f64) -> f64;
}
$fun(*x)
};
$fun.write_all(&y.to_bits().to_le_bytes())?;
)+
}
}};
}
macro_rules! f64f64 {
($($fun:ident,)+) => {{
$(
// check type signature
let _: fn(f64, f64) -> f64 = libm::$fun;
let mut $fun = File::create(concat!("bin/output/musl.", stringify!($fun)))?;
)+
for (x0, x1) in shared::F64F64.iter() {
$(
let y = unsafe {
extern "C" {
fn $fun(_: f64, _: f64) -> f64;
}
$fun(*x0, *x1)
};
$fun.write_all(&y.to_bits().to_le_bytes())?;
)+
}
}};
}
macro_rules! f64f64f64 {
($($fun:ident,)+) => {{
$(
// check type signature
let _: fn(f64, f64, f64) -> f64 = libm::$fun;
let mut $fun = File::create(concat!("bin/output/musl.", stringify!($fun)))?;
)+
for (x0, x1, x2) in shared::F64F64F64.iter() {
$(
let y = unsafe {
extern "C" {
fn $fun(_: f64, _: f64, _: f64) -> f64;
}
$fun(*x0, *x1, *x2)
};
$fun.write_all(&y.to_bits().to_le_bytes())?;
)+
}
}};
}
macro_rules! f64i32 {
($($fun:ident,)+) => {{
$(
// check type signature
let _: fn(f64, i32) -> f64 = libm::$fun;
let mut $fun = File::create(concat!("bin/output/musl.", stringify!($fun)))?;
)+
for (x0, x1) in shared::F64I32.iter() {
$(
let y = unsafe {
extern "C" {
fn $fun(_: f64, _: i32) -> f64;
}
$fun(*x0, *x1 as i32)
};
$fun.write_all(&y.to_bits().to_le_bytes())?;
)+
}
}};
}
-97
View File
@@ -1,97 +0,0 @@
extern crate libm;
extern crate shared;
use std::error::Error;
use std::fs::File;
use std::io::Write;
#[macro_use]
mod macros;
fn main() -> Result<(), Box<Error>> {
f32! {
acosf,
asinf,
atanf,
cbrtf,
ceilf,
cosf,
coshf,
exp2f,
expf,
expm1f,
fabsf,
floorf,
log10f,
log1pf,
log2f,
logf,
roundf,
sinf,
sinhf,
sqrtf,
tanf,
tanhf,
truncf,
}
f32f32! {
atan2f,
fdimf,
fmodf,
hypotf,
powf,
}
f32i32! {
scalbnf,
}
f32f32f32! {
fmaf,
}
f64! {
acos,
asin,
atan,
cbrt,
ceil,
cos,
cosh,
exp,
exp2,
expm1,
fabs,
floor,
log,
log10,
log1p,
log2,
round,
sin,
sinh,
sqrt,
tan,
tanh,
trunc,
}
f64f64! {
atan2,
fdim,
fmod,
hypot,
pow,
}
f64i32! {
scalbn,
}
f64f64f64! {
fma,
}
Ok(())
}
-7
View File
@@ -1,7 +0,0 @@
[package]
name = "shared"
version = "0.1.0"
authors = ["Jorge Aparicio <jorge@japaric.io>"]
[dependencies]
lazy_static = "1.0.2"
-469
View File
@@ -1,469 +0,0 @@
#[macro_use]
extern crate lazy_static;
lazy_static! {
pub static ref F32: Vec<f32> = {
let bytes = include_bytes!("../../bin/input/f32");
bytes
.chunks_exact(4)
.map(|chunk| {
let mut buf = [0; 4];
buf.copy_from_slice(chunk);
f32::from_bits(u32::from_le(u32::from_le_bytes(buf)))
})
.collect()
};
pub static ref F32F32: Vec<(f32, f32)> = {
let bytes = include_bytes!("../../bin/input/f32f32");
bytes
.chunks_exact(8)
.map(|chunk| {
let mut x0 = [0; 4];
let mut x1 = [0; 4];
x0.copy_from_slice(&chunk[..4]);
x1.copy_from_slice(&chunk[4..]);
(
f32::from_bits(u32::from_le(u32::from_le_bytes(x0))),
f32::from_bits(u32::from_le(u32::from_le_bytes(x1))),
)
})
.collect()
};
pub static ref F32F32F32: Vec<(f32, f32, f32)> = {
let bytes = include_bytes!("../../bin/input/f32f32f32");
bytes
.chunks_exact(12)
.map(|chunk| {
let mut x0 = [0; 4];
let mut x1 = [0; 4];
let mut x2 = [0; 4];
x0.copy_from_slice(&chunk[..4]);
x1.copy_from_slice(&chunk[4..8]);
x2.copy_from_slice(&chunk[8..]);
(
f32::from_bits(u32::from_le(u32::from_le_bytes(x0))),
f32::from_bits(u32::from_le(u32::from_le_bytes(x1))),
f32::from_bits(u32::from_le(u32::from_le_bytes(x2))),
)
})
.collect()
};
pub static ref F32I32: Vec<(f32, i32)> = {
let bytes = include_bytes!("../../bin/input/f32i16");
bytes
.chunks_exact(6)
.map(|chunk| {
let mut x0 = [0; 4];
let mut x1 = [0; 2];
x0.copy_from_slice(&chunk[..4]);
x1.copy_from_slice(&chunk[4..]);
(
f32::from_bits(u32::from_le(u32::from_le_bytes(x0))),
i16::from_le(i16::from_le_bytes(x1)) as i32,
)
})
.collect()
};
pub static ref F64: Vec<f64> = {
let bytes = include_bytes!("../../bin/input/f64");
bytes
.chunks_exact(8)
.map(|chunk| {
let mut buf = [0; 8];
buf.copy_from_slice(chunk);
f64::from_bits(u64::from_le(u64::from_le_bytes(buf)))
})
.collect()
};
pub static ref F64F64: Vec<(f64, f64)> = {
let bytes = include_bytes!("../../bin/input/f64f64");
bytes
.chunks_exact(16)
.map(|chunk| {
let mut x0 = [0; 8];
let mut x1 = [0; 8];
x0.copy_from_slice(&chunk[..8]);
x1.copy_from_slice(&chunk[8..]);
(
f64::from_bits(u64::from_le(u64::from_le_bytes(x0))),
f64::from_bits(u64::from_le(u64::from_le_bytes(x1))),
)
})
.collect()
};
pub static ref F64F64F64: Vec<(f64, f64, f64)> = {
let bytes = include_bytes!("../../bin/input/f64f64f64");
bytes
.chunks_exact(24)
.map(|chunk| {
let mut x0 = [0; 8];
let mut x1 = [0; 8];
let mut x2 = [0; 8];
x0.copy_from_slice(&chunk[..8]);
x1.copy_from_slice(&chunk[8..16]);
x2.copy_from_slice(&chunk[16..]);
(
f64::from_bits(u64::from_le(u64::from_le_bytes(x0))),
f64::from_bits(u64::from_le(u64::from_le_bytes(x1))),
f64::from_bits(u64::from_le(u64::from_le_bytes(x2))),
)
})
.collect()
};
pub static ref F64I32: Vec<(f64, i32)> = {
let bytes = include_bytes!("../../bin/input/f64i16");
bytes
.chunks_exact(10)
.map(|chunk| {
let mut x0 = [0; 8];
let mut x1 = [0; 2];
x0.copy_from_slice(&chunk[..8]);
x1.copy_from_slice(&chunk[8..]);
(
f64::from_bits(u64::from_le(u64::from_le_bytes(x0))),
i16::from_le(i16::from_le_bytes(x1)) as i32,
)
})
.collect()
};
}
#[macro_export]
macro_rules! f32 {
($lib:expr, $($fun:ident),+) => {
$(
#[test]
fn $fun() {
let expected = include_bytes!(concat!("../bin/output/", $lib, ".", stringify!($fun)))
.chunks_exact(4)
.map(|chunk| {
let mut buf = [0; 4];
buf.copy_from_slice(chunk);
f32::from_bits(u32::from_le(u32::from_le_bytes(buf)))
})
.collect::<Vec<_>>();
for (input, expected) in $crate::F32.iter().zip(&expected) {
if let Ok(output) = panic::catch_unwind(|| libm::$fun(*input)) {
if let Err(error) = libm::_eqf(output, *expected) {
panic!(
"INPUT: {:#x}, OUTPUT: {:#x}, EXPECTED: {:#x}, ERROR: {}",
input.to_bits(),
output.to_bits(),
expected.to_bits(),
error
);
}
} else {
panic!(
"INPUT: {:#x}, OUTPUT: PANIC!, EXPECTED: {:#x}",
input.to_bits(),
expected.to_bits()
);
}
}
}
)+
}
}
#[macro_export]
macro_rules! f32f32 {
($lib:expr, $($fun:ident),+) => {
$(
#[test]
fn $fun() {
let expected = include_bytes!(concat!("../bin/output/", $lib, ".", stringify!($fun)))
.chunks_exact(4)
.map(|chunk| {
let mut buf = [0; 4];
buf.copy_from_slice(chunk);
f32::from_bits(u32::from_le(u32::from_le_bytes(buf)))
})
.collect::<Vec<_>>();
for ((i0, i1), expected) in $crate::F32F32.iter().zip(&expected) {
if let Ok(output) = panic::catch_unwind(|| libm::$fun(*i0, *i1)) {
if let Err(error) = libm::_eqf(output, *expected) {
panic!(
"INPUT: ({:#x}, {:#x}), OUTPUT: {:#x}, EXPECTED: {:#x}, ERROR: {}",
i0.to_bits(),
i1.to_bits(),
output.to_bits(),
expected.to_bits(),
error
);
}
} else {
panic!(
"INPUT: ({:#x}, {:#x}), OUTPUT: PANIC!, EXPECTED: {:#x}",
i0.to_bits(),
i1.to_bits(),
expected.to_bits()
);
}
}
}
)+
}
}
#[macro_export]
macro_rules! f32f32f32 {
($lib:expr, $($fun:ident),+) => {
$(
#[test]
fn $fun() {
let expected = include_bytes!(concat!("../bin/output/", $lib, ".", stringify!($fun)))
.chunks_exact(4)
.map(|chunk| {
let mut buf = [0; 4];
buf.copy_from_slice(chunk);
f32::from_bits(u32::from_le(u32::from_le_bytes(buf)))
})
.collect::<Vec<_>>();
for ((i0, i1, i2), expected) in $crate::F32F32F32.iter().zip(&expected) {
if let Ok(output) = panic::catch_unwind(|| libm::$fun(*i0, *i1, *i2)) {
if let Err(error) = libm::_eqf(output, *expected) {
panic!(
"INPUT: ({:#x}, {:#x}, {:#x}), OUTPUT: {:#x}, EXPECTED: {:#x}, ERROR: {}",
i0.to_bits(),
i1.to_bits(),
i2.to_bits(),
output.to_bits(),
expected.to_bits(),
error
);
}
} else {
panic!(
"INPUT: ({:#x}, {:#x}), OUTPUT: PANIC!, EXPECTED: {:#x}",
i0.to_bits(),
i1.to_bits(),
expected.to_bits()
);
}
}
}
)+
}
}
#[macro_export]
macro_rules! f32i32 {
($lib:expr, $($fun:ident),+) => {
$(
#[test]
fn $fun() {
let expected = include_bytes!(concat!("../bin/output/", $lib, ".", stringify!($fun)))
.chunks_exact(4)
.map(|chunk| {
let mut buf = [0; 4];
buf.copy_from_slice(chunk);
f32::from_bits(u32::from_le(u32::from_le_bytes(buf)))
})
.collect::<Vec<_>>();
for ((i0, i1), expected) in $crate::F32I32.iter().zip(&expected) {
if let Ok(output) = panic::catch_unwind(|| libm::$fun(*i0, *i1)) {
if let Err(error) = libm::_eqf(output, *expected) {
panic!(
"INPUT: ({:#x}, {:#x}), OUTPUT: {:#x}, EXPECTED: {:#x}, ERROR: {}",
i0.to_bits(),
i1,
output.to_bits(),
expected.to_bits(),
error
);
}
} else {
panic!(
"INPUT: ({:#x}, {:#x}), OUTPUT: PANIC!, EXPECTED: {:#x}",
i0.to_bits(),
i1,
expected.to_bits()
);
}
}
}
)+
}
}
#[macro_export]
macro_rules! f64 {
($lib:expr, $($fun:ident),+) => {
$(
#[test]
fn $fun() {
let expected = include_bytes!(concat!("../bin/output/", $lib, ".", stringify!($fun)))
.chunks_exact(8)
.map(|chunk| {
let mut buf = [0; 8];
buf.copy_from_slice(chunk);
f64::from_bits(u64::from_le(u64::from_le_bytes(buf)))
})
.collect::<Vec<_>>();
for (input, expected) in shared::F64.iter().zip(&expected) {
if let Ok(output) = panic::catch_unwind(|| libm::$fun(*input)) {
if let Err(error) = libm::_eq(output, *expected) {
panic!(
"INPUT: {:#x}, OUTPUT: {:#x}, EXPECTED: {:#x}, ERROR: {}",
input.to_bits(),
output.to_bits(),
expected.to_bits(),
error
);
}
} else {
panic!(
"INPUT: {:#x}, OUTPUT: PANIC!, EXPECTED: {:#x}",
input.to_bits(),
expected.to_bits()
);
}
}
}
)+
}
}
#[macro_export]
macro_rules! f64f64 {
($lib:expr, $($fun:ident),+) => {
$(
#[test]
fn $fun() {
let expected = include_bytes!(concat!("../bin/output/", $lib, ".", stringify!($fun)))
.chunks_exact(8)
.map(|chunk| {
let mut buf = [0; 8];
buf.copy_from_slice(chunk);
f64::from_bits(u64::from_le(u64::from_le_bytes(buf)))
})
.collect::<Vec<_>>();
for ((i0, i1), expected) in shared::F64F64.iter().zip(&expected) {
if let Ok(output) = panic::catch_unwind(|| libm::$fun(*i0, *i1)) {
if let Err(error) = libm::_eq(output, *expected) {
panic!(
"INPUT: ({:#x}, {:#x}), OUTPUT: {:#x}, EXPECTED: {:#x}, ERROR: {}",
i0.to_bits(),
i1.to_bits(),
output.to_bits(),
expected.to_bits(),
error
);
}
} else {
panic!(
"INPUT: ({:#x}, {:#x}), OUTPUT: PANIC!, EXPECTED: {:#x}",
i0.to_bits(),
i1.to_bits(),
expected.to_bits()
);
}
}
}
)+
}
}
#[macro_export]
macro_rules! f64f64f64 {
($lib:expr, $($fun:ident),+) => {
$(
#[test]
fn $fun() {
let expected = include_bytes!(concat!("../bin/output/", $lib, ".", stringify!($fun)))
.chunks_exact(8)
.map(|chunk| {
let mut buf = [0; 8];
buf.copy_from_slice(chunk);
f64::from_bits(u64::from_le(u64::from_le_bytes(buf)))
})
.collect::<Vec<_>>();
for ((i0, i1, i2), expected) in shared::F64F64F64.iter().zip(&expected) {
if let Ok(output) = panic::catch_unwind(|| libm::$fun(*i0, *i1, *i2)) {
if let Err(error) = libm::_eq(output, *expected) {
panic!(
"INPUT: ({:#x}, {:#x}, {:#x}), OUTPUT: {:#x}, EXPECTED: {:#x}, ERROR: {}",
i0.to_bits(),
i1.to_bits(),
i2.to_bits(),
output.to_bits(),
expected.to_bits(),
error
);
}
} else {
panic!(
"INPUT: ({:#x}, {:#x}), OUTPUT: PANIC!, EXPECTED: {:#x}",
i0.to_bits(),
i1.to_bits(),
expected.to_bits()
);
}
}
}
)+
}
}
#[macro_export]
macro_rules! f64i32 {
($lib:expr, $($fun:ident),+) => {
$(
#[test]
fn $fun() {
let expected = include_bytes!(concat!("../bin/output/", $lib, ".", stringify!($fun)))
.chunks_exact(8)
.map(|chunk| {
let mut buf = [0; 8];
buf.copy_from_slice(chunk);
f64::from_bits(u64::from_le(u64::from_le_bytes(buf)))
})
.collect::<Vec<_>>();
for ((i0, i1), expected) in shared::F64I32.iter().zip(&expected) {
if let Ok(output) = panic::catch_unwind(|| libm::$fun(*i0, *i1)) {
if let Err(error) = libm::_eq(output, *expected) {
panic!(
"INPUT: ({:#x}, {:#x}), OUTPUT: {:#x}, EXPECTED: {:#x}, ERROR: {}",
i0.to_bits(),
i1,
output.to_bits(),
expected.to_bits(),
error
);
}
} else {
panic!(
"INPUT: ({:#x}, {:#x}), OUTPUT: PANIC!, EXPECTED: {:#x}",
i0.to_bits(),
i1,
expected.to_bits()
);
}
}
}
)+
}
}