Skip to content
This repository was archived by the owner on Apr 28, 2025. It is now read-only.

Commit d68e6cd

Browse files
committed
Add traits for testing
These traits give us a more generic way to interface with tuples used for (1) test input, (2) function arguments, and (3) test input.
1 parent 5b32341 commit d68e6cd

File tree

3 files changed

+261
-0
lines changed

3 files changed

+261
-0
lines changed

crates/libm-test/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
mod num_traits;
2+
mod test_traits;
23

34
pub use num_traits::{Float, Hex, Int};
5+
pub use test_traits::{CheckOutput, GenerateInput, TupleCall};
46

57
// List of all files present in libm's source
68
include!(concat!(env!("OUT_DIR"), "/all_files.rs"));

crates/libm-test/src/num_traits.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,20 @@ macro_rules! impl_int {
113113
format!("{self:#0width$x}", width = ((Self::BITS / 8) + 2) as usize)
114114
}
115115
}
116+
117+
impl<Input: Hex + fmt::Debug> $crate::CheckOutput<Input> for $ty {
118+
fn validate(self, expected: Self, input: Input, _allowed_ulp: u32)
119+
{
120+
assert_eq!(
121+
self,
122+
expected,
123+
"expected {expected:?} crate {self:?} ({expbits}, {actbits}) input {input:?} ({ibits})",
124+
expbits = expected.hex(),
125+
actbits = self.hex(),
126+
ibits = input.hex()
127+
);
128+
}
129+
}
116130
}
117131
}
118132

crates/libm-test/src/test_traits.rs

Lines changed: 245 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,245 @@
1+
//! Traits related to testing.
2+
//!
3+
//! There are three main traits in this module:
4+
//!
5+
//! - `GenerateInput`: implemented on any types that create test cases.
6+
//! - `TupleCall`: implemented on tuples to allow calling them as function arguments.
7+
//! - `CheckOutput`: implemented on anything that is an output type for validation against an
8+
//! expected value.
9+
10+
use crate::{Float, Hex, Int};
11+
use std::ffi::c_int;
12+
use std::fmt;
13+
14+
/// Implement this on types that can generate a sequence of tuples for test input.
15+
pub trait GenerateInput<TupleArgs> {
16+
fn get_cases(&self) -> impl ExactSizeIterator<Item = TupleArgs>;
17+
}
18+
19+
/// Trait for calling a function with a tuple as arguments.
20+
///
21+
/// Implemented on the tuple with the function signature as the generic (so we can use the same
22+
/// tuple for multiple signatures).
23+
pub trait TupleCall<Func>: fmt::Debug {
24+
type Output;
25+
fn call(self, f: Func) -> Self::Output;
26+
}
27+
28+
/// A trait to implement on any output type so we can verify it in a generic way.
29+
pub trait CheckOutput<Input>: Sized {
30+
/// Assert that `self` and `expected` are the same.
31+
///
32+
/// `input` is only used here for error messages.
33+
fn validate(self, expected: Self, input: Input, allowed_ulp: u32);
34+
}
35+
36+
/// Implement `TupleCall` for signatures with no `&mut`.
37+
macro_rules! impl_tupl_call {
38+
($( ($($argty:ty),*) -> $ret:ty; )+) => {
39+
$(
40+
impl TupleCall<fn( $($argty),* ) -> $ret> for ( $($argty,)* ) {
41+
type Output = $ret;
42+
43+
fn call(self, f: fn($($argty),*) -> $ret) -> Self::Output {
44+
impl_tupl_call!(@call f, self, $($argty),*)
45+
}
46+
}
47+
)*
48+
};
49+
50+
(@call $f:ident, $this:ident, $a1:ty, $a2:ty, $a3:ty) => {
51+
$f($this.0, $this.1, $this.2)
52+
};
53+
(@call $f:ident, $this:ident, $a1:ty, $a2:ty) => {
54+
$f($this.0, $this.1)
55+
};
56+
(@call $f:ident, $this:ident, $a1:ty) => {
57+
$f($this.0)
58+
};
59+
}
60+
61+
impl_tupl_call! {
62+
(f32) -> f32;
63+
(f64) -> f64;
64+
(f32) -> i32;
65+
(f64) -> i32;
66+
(f32, f32) -> f32;
67+
(f64, f64) -> f64;
68+
(f32, i32) -> f32;
69+
(f64, i32) -> f64;
70+
(i32, f32) -> f32;
71+
(i32, f64) -> f64;
72+
(f32, f32, f32) -> f32;
73+
(f64, f64, f64) -> f64;
74+
(f32) -> (f32, f32);
75+
(f64) -> (f64, f64);
76+
(f32) -> (f32, c_int);
77+
(f64) -> (f64, c_int);
78+
(f32, f32) -> (f32, c_int);
79+
(f64, f64) -> (f64, c_int);
80+
}
81+
82+
/* Implement `TupleCall` for signatures that use `&mut` (i.e. system symbols that return
83+
* more than one value) */
84+
85+
impl TupleCall<fn(f32, &mut c_int) -> f32> for (f32,) {
86+
type Output = (f32, c_int);
87+
88+
fn call(self, f: fn(f32, &mut c_int) -> f32) -> Self::Output {
89+
let mut iret = 0;
90+
let fret = f(self.0, &mut iret);
91+
(fret, iret)
92+
}
93+
}
94+
95+
impl TupleCall<fn(f64, &mut c_int) -> f64> for (f64,) {
96+
type Output = (f64, c_int);
97+
98+
fn call(self, f: fn(f64, &mut c_int) -> f64) -> Self::Output {
99+
let mut iret = 0;
100+
let fret = f(self.0, &mut iret);
101+
(fret, iret)
102+
}
103+
}
104+
105+
impl TupleCall<fn(f32, &mut f32) -> f32> for (f32,) {
106+
type Output = (f32, f32);
107+
108+
fn call(self, f: fn(f32, &mut f32) -> f32) -> Self::Output {
109+
let mut ret2 = 0.0;
110+
let ret1 = f(self.0, &mut ret2);
111+
(ret1, ret2)
112+
}
113+
}
114+
115+
impl TupleCall<fn(f64, &mut f64) -> f64> for (f64,) {
116+
type Output = (f64, f64);
117+
118+
fn call(self, f: fn(f64, &mut f64) -> f64) -> Self::Output {
119+
let mut ret2 = 0.0;
120+
let ret1 = f(self.0, &mut ret2);
121+
(ret1, ret2)
122+
}
123+
}
124+
125+
impl TupleCall<fn(f32, f32, &mut c_int) -> f32> for (f32, f32) {
126+
type Output = (f32, c_int);
127+
128+
fn call(self, f: fn(f32, f32, &mut c_int) -> f32) -> Self::Output {
129+
let mut iret = 0;
130+
let fret = f(self.0, self.1, &mut iret);
131+
(fret, iret)
132+
}
133+
}
134+
135+
impl TupleCall<fn(f64, f64, &mut c_int) -> f64> for (f64, f64) {
136+
type Output = (f64, c_int);
137+
138+
fn call(self, f: fn(f64, f64, &mut c_int) -> f64) -> Self::Output {
139+
let mut iret = 0;
140+
let fret = f(self.0, self.1, &mut iret);
141+
(fret, iret)
142+
}
143+
}
144+
145+
impl TupleCall<fn(f32, &mut f32, &mut f32)> for (f32,) {
146+
type Output = (f32, f32);
147+
148+
fn call(self, f: fn(f32, &mut f32, &mut f32)) -> Self::Output {
149+
let mut ret1 = 0.0;
150+
let mut ret2 = 0.0;
151+
f(self.0, &mut ret1, &mut ret2);
152+
(ret1, ret2)
153+
}
154+
}
155+
156+
impl TupleCall<fn(f64, &mut f64, &mut f64)> for (f64,) {
157+
type Output = (f64, f64);
158+
159+
fn call(self, f: fn(f64, &mut f64, &mut f64)) -> Self::Output {
160+
let mut ret1 = 0.0;
161+
let mut ret2 = 0.0;
162+
f(self.0, &mut ret1, &mut ret2);
163+
(ret1, ret2)
164+
}
165+
}
166+
167+
// Implement for floats
168+
impl<F, Input> CheckOutput<Input> for F
169+
where
170+
F: Float + Hex,
171+
Input: Hex + fmt::Debug,
172+
u32: TryFrom<F::SignedInt, Error: fmt::Debug>,
173+
{
174+
fn validate(self, expected: Self, input: Input, allowed_ulp: u32) {
175+
let make_msg = || {
176+
format!(
177+
"expected {expected:?} crate {self:?} ({expbits}, {actbits}) input {input:?} ({ibits})",
178+
expbits = expected.hex(),
179+
actbits = self.hex(),
180+
ibits = input.hex()
181+
)
182+
};
183+
184+
// Check when both are NaN
185+
if self.is_nan() && expected.is_nan() {
186+
assert_eq!(
187+
self.to_bits(),
188+
expected.to_bits(),
189+
"NaN have different bitpatterns: {}",
190+
make_msg()
191+
);
192+
// Nothing else to check
193+
return;
194+
} else if self.is_nan() || expected.is_nan() {
195+
panic!("mismatched NaN: {}", make_msg());
196+
}
197+
198+
// Make sure that the signs are the same before checing ULP
199+
assert_eq!(
200+
self.signum(),
201+
expected.signum(),
202+
"mismatched signs: {}",
203+
make_msg()
204+
);
205+
206+
let ulp_diff = self
207+
.to_bits()
208+
.signed()
209+
.checked_sub(expected.to_bits().signed())
210+
.unwrap()
211+
.abs();
212+
213+
let ulp_u32 = u32::try_from(ulp_diff).unwrap_or_else(|e| {
214+
panic!("{e:?}: ulp of {ulp_diff} exceeds u32::MAX: {}", make_msg())
215+
});
216+
217+
assert!(
218+
ulp_u32 <= allowed_ulp,
219+
"ulp {ulp_diff} > {allowed_ulp}: {}",
220+
make_msg()
221+
);
222+
}
223+
}
224+
225+
/// Implement `CheckOutput` for combinations of types.
226+
macro_rules! impl_tuples {
227+
($(($a:ty, $b:ty);)*) => {
228+
$(
229+
impl<Input: Hex + fmt::Debug> CheckOutput<Input> for ($a, $b) {
230+
fn validate(self, expected: Self, input: Input, allowed_ulp: u32)
231+
{
232+
self.0.validate(expected.0, input, allowed_ulp);
233+
self.1.validate(expected.1, input, allowed_ulp);
234+
}
235+
}
236+
)*
237+
};
238+
}
239+
240+
impl_tuples!(
241+
(f32, i32);
242+
(f64, i32);
243+
(f32, f32);
244+
(f64, f64);
245+
);

0 commit comments

Comments
 (0)