hanki

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"