test
stdlib/core/test.hk: assertion conveniences for test blocks.
test is a keyword and a module head at once, the actor precedent: test.within_epsilon? resolves like any core-qualified name while the keyword goes on introducing test blocks.
The module exists because f64 has no Eq (IEEE equality is not an equivalence relation), and assert!(a == b) on floats is a compile error. The proximity predicates below are the missing spelling: a float assertion states its tolerance, either as an absolute distance or as a count of representable steps.
within_epsilon?
def within_epsilon?(a: f64, b: f64, epsilon: f64) -> bool
Whether a and b differ by no more than epsilon, the absolute-distance float assertion. Non-finite operands answer false - a proximity claim needs a finite distance, and NaN is near nothing, itself included.
test.within_epsilon?(0.1f64 + 0.2f64, 0.3f64, 0.0000001f64) => true
test.within_epsilon?(1.0f64, 1.1f64, 0.01f64) => false
test.within_epsilon?(0.0f64 / 0.0f64, 0.0f64, 1.0f64) => false
within_epsilon32?
def within_epsilon32?(a: f32, b: f32, epsilon: f32) -> bool
The f32 twin of within_epsilon?.
test.within_epsilon32?(f32.from_bits(1065353216u32), f32.from_bits(1065353217u32), f32.from_bits(1008981770u32)) => true
within_ulps?
def within_ulps?(a: f64, b: f64, ulps: u64) -> bool
Whether a and b are at most ulps representable doubles apart, the relative float assertion, and the one whose tolerance needs no unit: one ULP is the distance to the next representable value at that magnitude. Exact bit arithmetic over f64.to_bits, which makes both tiers agree by construction. Non-finite operands answer false; 0.0 and -0.0 are zero ULPs apart.
test.within_ulps?(1.0f64, 1.0f64, 0u64) => true
test.within_ulps?(0.1f64 + 0.2f64, 0.3f64, 1u64) => true
test.within_ulps?(1.0f64, 2.0f64, 1000u64) => false
_finite?
def _finite?(bits: u64) -> bool
True unless the IEEE 754 exponent (bits 52..62) is all ones (inf / NaN).
_finite32?
def _finite32?(bits: u32) -> bool
The f32 half: exponent bits 23..30 all ones is inf / NaN.
ulporder
def _ulp_order(bits: u64) -> u64
Map float bits onto a single monotone unsigned line, which makes ULP distance plain subtraction: a negative float (sign bit set) reflects below the midpoint, a non-negative one shifts above it. -0.0 and 0.0 both land on the midpoint, which is what makes them zero apart.
diff
def diff<T: Display>(expected: T, actual: T) -> string
A readable delta for a failed comparison, for assert!-adjacent interpolation: both values rendered through Display, plus the byte offset where their renderings first part. It is no matcher DSL: it renders, it points, and the assertion remains an ordinary assert!.
test.diff(12, 13) => "expected 12, got 13 (renders differ from byte 1)"
test.diff("same", "same") => "(both render as \"same\")"
firstgap
def _first_gap(e: bytes, a: bytes, i: int) -> int
The first byte offset at which the two renderings disagree; one being a prefix of the other parts at the shorter length.
Recorded
struct Recorded
calls: List<string>
end
The capturing side of a provide-based test double for user effects: a pure value accumulating what the double saw, threaded through the test the way any accumulator is. Built-in capabilities (io/net/fs) are not provider-based and cannot be intercepted, which is the intent: a test that needs to observe them is an integration test and should say so.
test.Recorded.empty().record("get /a").record("get /b").calls.length => 2
impl Recorded
empty
def empty() -> Recorded
The double before anything is seen.
test.Recorded.empty().calls.length => 0
record
def record(self, call: string) -> Recorded
self with one more observed call appended; render arguments into the string at the call site, and the record therefore reads like a transcript.
test.Recorded.empty().record("query 7").calls.get_or(0, "") => "query 7"