// Mirrors Hare's hex @test fns (ref/hare/encoding/hex/hex.ha:82,96,194) // plus a full-byte round-trip. A failing row aborts via the assert/abort // builtin (task #5 @test conversion). // // The streaming decoder (newdecoder) is deferred (#247), so the decode // side is exercised through decodestr only. package hex_test; import bytes; import errors; import hex; import io; import memio; fn streq(a: str, b: str) bool = { if (a.len != b.len) { return false; }; let i: i32 = 0; for (i < a.len) { if (a[i] != b[i]) { return false; }; i += 1; }; return true; }; fn cafebabe() [8]u8 = { let r: [8]u8; r[0] = 0xCAu8; r[1] = 0xFEu8; r[2] = 0xBAu8; r[3] = 0xBEu8; r[4] = 0xDEu8; r[5] = 0xADu8; r[6] = 0xF0u8; r[7] = 0x0Du8; return r; }; @test fn encodestr_basic() void = { let in: [8]u8 = cafebabe(); assert(!(!streq(hex.encodestr(in[0:8]), "cafebabedeadf00d"))); }; // 0x00 → "00" guards a sign-extend / signed-shift on the high nibble. @test fn encodestr_zero() void = { let in: [1]u8; in[0] = 0u8; assert(!(!streq(hex.encodestr(in[0:1]), "00"))); }; // 0xFF → "ff" guards an off-by-one in the digit lookup. @test fn encodestr_ff() void = { let in: [1]u8; in[0] = 0xFFu8; assert(!(!streq(hex.encodestr(in[0:1]), "ff"))); }; @test fn encodestr_empty() void = { let in: [1]u8; assert(!(hex.encodestr(in[0:0]).len != 0)); }; @test fn encode_stream() void = { let in: [8]u8 = cafebabe(); let out: memio.stream = memio.dynamic(); match (hex.encode(&out.vt, in[0:8])) { case let n: size => { assert(!(n: i32 != 16)); }; case let e: io.error => abort(); }; assert(!(!streq(memio.string(&out), "cafebabedeadf00d"))); }; @test fn decodestr_lower() void = { match (hex.decodestr("cafebabedeadf00d")) { case let b: []u8 => { let want: [8]u8 = cafebabe(); assert(!(!bytes.equal(b, want[0:8]))); }; case let e: errors.invalid => abort(); }; }; // Mixed/upper case must decode too; the encoder is lowercase-only but // the decoder accepts both per ref/hare/encoding/hex/README:13. @test fn decodestr_upper() void = { match (hex.decodestr("CAFEBABEDEADF00D")) { case let b: []u8 => { let want: [8]u8 = cafebabe(); assert(!(!bytes.equal(b, want[0:8]))); }; case let e: errors.invalid => abort(); }; }; @test fn decodestr_mixed() void = { match (hex.decodestr("CaFeBaBeDeAdF00d")) { case let b: []u8 => { let want: [8]u8 = cafebabe(); assert(!(!bytes.equal(b, want[0:8]))); }; case let e: errors.invalid => abort(); }; }; @test fn decodestr_empty() void = { match (hex.decodestr("")) { case let b: []u8 => { assert(!(b.len != 0)); }; case let e: errors.invalid => abort(); }; }; // Odd length and non-hex chars both return errors.invalid // (ref/hare/encoding/hex/hex.ha:154,199). @test fn decodestr_odd() void = { match (hex.decodestr("abc")) { case let b: []u8 => abort(); case let e: errors.invalid => void; }; }; @test fn decodestr_bad() void = { match (hex.decodestr("zz00")) { // 'z' isn't hex case let b: []u8 => abort(); case let e: errors.invalid => void; }; }; @test fn decodestr_bad_mid() void = { match (hex.decodestr("aabbgg")) { // 'g' isn't hex case let b: []u8 => abort(); case let e: errors.invalid => void; }; }; @test fn roundtrip_all_bytes() void = { let src: [256]u8; let i: i32 = 0; for (i < 256) { src[i] = i: u8; i += 1; }; let s: str = hex.encodestr(src[0:256]); assert(!(s.len != 512)); match (hex.decodestr(s)) { case let b: []u8 => { assert(!(b.len != 256)); assert(!(!bytes.equal(b, src[0:256]))); }; case let e: errors.invalid => abort(); }; };