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

Commit e661694

Browse files
committed
Test xfail
1 parent 15c8a05 commit e661694

File tree

4 files changed

+206
-17
lines changed

4 files changed

+206
-17
lines changed

crates/libm-test/src/lib.rs

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,11 @@ pub mod gen;
33
pub mod mpfloat;
44
mod num_traits;
55
mod test_traits;
6+
mod xfail;
67

78
pub use num_traits::{Float, Hex, Int};
89
pub use test_traits::{CheckBasis, CheckCtx, CheckOutput, GenerateInput, TupleCall};
10+
pub use xfail::{IgnoreCase, XFail};
911

1012
// List of all files present in libm's source
1113
include!(concat!(env!("OUT_DIR"), "/all_files.rs"));
@@ -52,13 +54,3 @@ pub fn multiprec_allowed_ulp(name: &str) -> u32 {
5254
_ => MULTIPREC_DEFAULT_ULP,
5355
}
5456
}
55-
56-
/// If only a few checks are incorrect, xfail them here rather than skipping the entire test.
57-
pub fn xfail<F: Float>(actual: F, expected: F, ctx: &CheckCtx) -> bool {
58-
match (&ctx.basis, ctx.fname) {
59-
// FIXME(correctness): for large negative inputs (e.g.
60-
// -1.7976931348623157e308), we return -NaN but musl says +NaN
61-
(CheckBasis::Musl, "tgamma" | "tgammaf") if actual.is_nan() && expected.is_nan() => true,
62-
_ => false,
63-
}
64-
}

crates/libm-test/src/num_traits.rs

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
1+
use crate::{IgnoreCase, XFail};
12
use std::fmt;
23

34
/// Common types and methods for floating point numbers.
45
pub trait Float: Copy + fmt::Display + fmt::Debug + PartialEq<Self> {
56
type Int: Int<OtherSign = Self::SignedInt, Unsigned = Self::Int>;
67
type SignedInt: Int + Int<OtherSign = Self::Int, Unsigned = Self::Int>;
78

9+
const ZERO: Self;
10+
811
/// The bitwidth of the float type
912
const BITS: u32;
1013

@@ -27,6 +30,7 @@ macro_rules! impl_float {
2730
type Int = $ui;
2831
type SignedInt = $si;
2932

33+
const ZERO: Self = 0.0;
3034
const BITS: u32 = <$ui>::BITS;
3135
const SIGNIFICAND_BITS: u32 = $significand_bits;
3236

@@ -125,13 +129,21 @@ macro_rules! impl_int {
125129
}
126130
}
127131

128-
impl<Input: Hex + fmt::Debug> $crate::CheckOutput<Input> for $ty {
132+
impl<Input> $crate::CheckOutput<Input> for $ty
133+
where
134+
Input: Hex + fmt::Debug,
135+
XFail: IgnoreCase<Input>,
136+
{
129137
fn validate<'a>(
130138
self,
131139
expected: Self,
132140
input: Input,
133-
_ctx: &$crate::CheckCtx,
141+
ctx: &$crate::CheckCtx,
134142
) -> anyhow::Result<()> {
143+
if XFail::xfail_int(input, self, expected, ctx) {
144+
return Ok(());
145+
}
146+
135147
anyhow::ensure!(
136148
self == expected,
137149
"\

crates/libm-test/src/test_traits.rs

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
//! - `CheckOutput`: implemented on anything that is an output type for validation against an
88
//! expected value.
99
10-
use crate::{Float, Hex, Int};
10+
use crate::{Float, Hex, IgnoreCase, Int, XFail};
1111
use anyhow::{bail, ensure, Context};
1212
use std::fmt;
1313

@@ -138,11 +138,12 @@ where
138138
F: Float + Hex,
139139
Input: Hex + fmt::Debug,
140140
u32: TryFrom<F::SignedInt, Error: fmt::Debug>,
141+
XFail: IgnoreCase<Input>,
141142
{
142143
fn validate<'a>(self, expected: Self, input: Input, ctx: &CheckCtx) -> anyhow::Result<()> {
143144
// Create a wrapper function so we only need to `.with_context` once.
144145
let inner = || -> anyhow::Result<()> {
145-
if crate::xfail(self, expected, ctx) {
146+
if XFail::xfail_float(input, self, expected, ctx) {
146147
return Ok(());
147148
}
148149

@@ -200,17 +201,28 @@ where
200201
macro_rules! impl_tuples {
201202
($(($a:ty, $b:ty);)*) => {
202203
$(
203-
impl<Input: Hex + fmt::Debug> CheckOutput<Input> for ($a, $b) {
204+
impl<Input> CheckOutput<Input> for ($a, $b)
205+
where
206+
Input: Hex + fmt::Debug,
207+
XFail: IgnoreCase<Input>,
208+
{
204209
fn validate<'a>(
205210
self,
206211
expected: Self,
207212
input: Input,
208213
ctx: &CheckCtx,
209214
) -> anyhow::Result<()> {
210-
self.0.validate(expected.0, input, ctx,)
215+
self.0.validate(expected.0, input, ctx)
211216
.and_then(|()| self.1.validate(expected.1, input, ctx))
212217
.with_context(|| format!(
213-
"full input {input:?} full actual {self:?} expected {expected:?}"
218+
"full context:\
219+
\n input: {input:?} {ibits}\
220+
\n expected: {expected:?} {expbits}\
221+
\n actual: {self:?} {actbits}\
222+
",
223+
actbits = self.hex(),
224+
expbits = expected.hex(),
225+
ibits = input.hex(),
214226
))
215227
}
216228
}

crates/libm-test/src/xfail.rs

Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
//! Configuration for skipping individual test cases (inputs) rather than ignoring entire tests.
2+
//!
3+
//! One common case here is that our NaNs usually have the sign bit set (platform dependent),
4+
//! while MPFR seems to put meaning on the signedness of NaNs and zeros.
5+
6+
#![allow(unused)]
7+
use crate::{CheckBasis, CheckCtx, Float, Int};
8+
9+
/// Type implementing [`IgnoreCase`].
10+
pub struct XFail;
11+
12+
/// If the relevant function returns true, the input has a mismatch but should still be skipped.
13+
///
14+
/// This gets implemented once per input type, then the functions provide further filtering
15+
/// based on function name and values.
16+
pub trait IgnoreCase<Input> {
17+
fn xfail_float<F: Float>(_input: Input, _actual: F, _expected: F, _ctx: &CheckCtx) -> bool {
18+
false
19+
}
20+
21+
fn xfail_int<I: Int>(_input: Input, _actual: I, _expected: I, _ctx: &CheckCtx) -> bool {
22+
false
23+
}
24+
}
25+
26+
impl IgnoreCase<(f32,)> for XFail {
27+
fn xfail_float<F: Float>(input: (f32,), actual: F, expected: F, ctx: &CheckCtx) -> bool {
28+
match &ctx.basis {
29+
CheckBasis::Musl => match ctx.fname {
30+
// We return +NaN, Musl returns -NaN
31+
"tgammaf" => input.0 < 0.0,
32+
_ => false,
33+
},
34+
CheckBasis::MultiPrecision => match ctx.fname {
35+
// For almost everything we return -NaN but MPFR does +NaN
36+
_ if input.0.is_nan() && all_nan(&[actual, expected]) => true,
37+
// Out of domain we return +NaN, MPFR returns -NaN
38+
"atanhf" => input.0 < -1.0 && all_nan(&[actual, expected]),
39+
// We return -NaN, MPFR says +NaN
40+
"tgammaf" => input.0 < 0.0 && all_nan(&[actual, expected]),
41+
_ => false,
42+
},
43+
}
44+
}
45+
46+
fn xfail_int<I: Int>(input: (f32,), actual: I, expected: I, ctx: &CheckCtx) -> bool {
47+
match &ctx.basis {
48+
CheckBasis::Musl => false,
49+
CheckBasis::MultiPrecision => match ctx.fname {
50+
// We set -1, MPFR sets +1
51+
"lgammaf_r" => input.0 == f32::NEG_INFINITY && actual.abs() == expected.abs(),
52+
_ => false,
53+
},
54+
}
55+
}
56+
}
57+
58+
impl IgnoreCase<(f64,)> for XFail {
59+
fn xfail_float<F: Float>(input: (f64,), actual: F, expected: F, ctx: &CheckCtx) -> bool {
60+
// See the `f32` version for notes about what is skipped
61+
match &ctx.basis {
62+
CheckBasis::Musl => match ctx.fname {
63+
"tgamma" => input.0 < 0.0,
64+
_ => false,
65+
},
66+
CheckBasis::MultiPrecision => match ctx.fname {
67+
_ if input.0.is_nan() && all_nan(&[actual, expected]) => true,
68+
"atanh" => input.0 < -1.0 && all_nan(&[actual, expected]),
69+
"tgamma" => input.0 < 0.0 && all_nan(&[actual, expected]),
70+
_ => false,
71+
},
72+
}
73+
}
74+
75+
fn xfail_int<I: Int>(input: (f64,), actual: I, expected: I, ctx: &CheckCtx) -> bool {
76+
// See the `f32` version for notes about what is skipped
77+
match &ctx.basis {
78+
CheckBasis::Musl => false,
79+
CheckBasis::MultiPrecision => match ctx.fname {
80+
"lgamma_r" => input.0 == f64::NEG_INFINITY && actual.abs() == expected.abs(),
81+
_ => false,
82+
},
83+
}
84+
}
85+
}
86+
87+
impl IgnoreCase<(f32, f32)> for XFail {
88+
fn xfail_float<F: Float>(input: (f32, f32), actual: F, expected: F, ctx: &CheckCtx) -> bool {
89+
match &ctx.basis {
90+
CheckBasis::Musl => false,
91+
CheckBasis::MultiPrecision => {
92+
all_nan(&[input.0, input.1]) && all_nan(&[actual, expected])
93+
}
94+
}
95+
}
96+
}
97+
98+
impl IgnoreCase<(f64, f64)> for XFail {
99+
fn xfail_float<F: Float>(input: (f64, f64), actual: F, expected: F, ctx: &CheckCtx) -> bool {
100+
match &ctx.basis {
101+
CheckBasis::Musl => false,
102+
CheckBasis::MultiPrecision => {
103+
all_nan(&[input.0, input.1]) && all_nan(&[actual, expected])
104+
}
105+
}
106+
}
107+
}
108+
109+
impl IgnoreCase<(f32, f32, f32)> for XFail {
110+
fn xfail_float<F: Float>(
111+
input: (f32, f32, f32),
112+
actual: F,
113+
expected: F,
114+
ctx: &CheckCtx,
115+
) -> bool {
116+
match &ctx.basis {
117+
CheckBasis::Musl => false,
118+
CheckBasis::MultiPrecision => {
119+
all_nan(&[input.0, input.1, input.2]) && all_nan(&[actual, expected])
120+
}
121+
}
122+
}
123+
}
124+
impl IgnoreCase<(f64, f64, f64)> for XFail {
125+
fn xfail_float<F: Float>(
126+
input: (f64, f64, f64),
127+
actual: F,
128+
expected: F,
129+
ctx: &CheckCtx,
130+
) -> bool {
131+
match &ctx.basis {
132+
CheckBasis::Musl => false,
133+
CheckBasis::MultiPrecision => {
134+
all_nan(&[input.0, input.1, input.2]) && all_nan(&[actual, expected])
135+
}
136+
}
137+
}
138+
}
139+
140+
impl IgnoreCase<(i32, f32)> for XFail {
141+
fn xfail_float<F: Float>(input: (i32, f32), actual: F, expected: F, ctx: &CheckCtx) -> bool {
142+
match &ctx.basis {
143+
CheckBasis::Musl => false,
144+
CheckBasis::MultiPrecision => match ctx.fname {
145+
_ if input.1.is_nan() && all_nan(&[actual, expected]) => true,
146+
// We return +0.0, MPFR returns -0.0
147+
"jnf" => input.1 == f32::NEG_INFINITY && actual == F::ZERO && expected == F::ZERO,
148+
_ => false,
149+
},
150+
}
151+
}
152+
}
153+
154+
impl IgnoreCase<(i32, f64)> for XFail {
155+
fn xfail_float<F: Float>(input: (i32, f64), actual: F, expected: F, ctx: &CheckCtx) -> bool {
156+
match &ctx.basis {
157+
CheckBasis::Musl => false,
158+
CheckBasis::MultiPrecision => match ctx.fname {
159+
_ if input.1.is_nan() && all_nan(&[actual, expected]) => true,
160+
"jn" => input.1 == f64::NEG_INFINITY && actual == F::ZERO && expected == F::ZERO,
161+
_ => false,
162+
},
163+
}
164+
}
165+
}
166+
167+
impl IgnoreCase<(f32, i32)> for XFail {}
168+
impl IgnoreCase<(f64, i32)> for XFail {}
169+
170+
/// Convenience to check if all values are NaN
171+
fn all_nan<F: Float>(v1: &[F]) -> bool {
172+
v1.iter().all(|v| v.is_nan())
173+
}

0 commit comments

Comments
 (0)