// hextest — exercises lib/encoding/hex's io-streaming surface. Run with // `out/bin/ww run lib/encoding/hex/hextest.ww`. Mirrors Hare's hex // @test fns (ref/hare/encoding/hex/hex.ha:82,96,194) plus a full-byte // round-trip. Same signalled-then-fail()-with-+10 pattern as the rest of // the 9xx stdlib tests; non-zero exit pinpoints the failing scenario. // // 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; import os; let signalled: i32 = 0; fn fail() void = { os.exit(signalled + 10); }; 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; }; // ---- encodestr ---- ref/hare/encoding/hex/hex.ha:82 @test fn encodestr_basic() void = { let in: [8]u8 = cafebabe(); if (!streq(hex.encodestr(in[0:8]), "cafebabedeadf00d")) { fail(); }; }; // 0x00 → "00" guards a sign-extend / signed-shift on the high nibble. @test fn encodestr_zero() void = { let in: [1]u8; in[0] = 0u8; if (!streq(hex.encodestr(in[0:1]), "00")) { fail(); }; }; // 0xFF → "ff" guards an off-by-one in the digit lookup. @test fn encodestr_ff() void = { let in: [1]u8; in[0] = 0xFFu8; if (!streq(hex.encodestr(in[0:1]), "ff")) { fail(); }; }; @test fn encodestr_empty() void = { let in: [1]u8; if (hex.encodestr(in[0:0]).len != 0) { fail(); }; }; // ---- encode (io.handle sink) ---- ref/hare/encoding/hex/hex.ha:96 @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 => { if (n: i32 != 16) { fail(); }; }; case let e: io.error => fail(); }; if (!streq(memio.string(&out), "cafebabedeadf00d")) { fail(); }; }; // ---- decodestr round-trip ---- ref/hare/encoding/hex/hex.ha:194 @test fn decodestr_lower() void = { match (hex.decodestr("cafebabedeadf00d")) { case let b: []u8 => { let want: [8]u8 = cafebabe(); if (!bytes.equal(b, want[0:8])) { fail(); }; }; case let e: errors.invalid => fail(); }; }; // 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(); if (!bytes.equal(b, want[0:8])) { fail(); }; }; case let e: errors.invalid => fail(); }; }; @test fn decodestr_mixed() void = { match (hex.decodestr("CaFeBaBeDeAdF00d")) { case let b: []u8 => { let want: [8]u8 = cafebabe(); if (!bytes.equal(b, want[0:8])) { fail(); }; }; case let e: errors.invalid => fail(); }; }; @test fn decodestr_empty() void = { match (hex.decodestr("")) { case let b: []u8 => { if (b.len != 0) { fail(); }; }; case let e: errors.invalid => fail(); }; }; // ---- decodestr error cases ---- ref/hare/encoding/hex/hex.ha:154,199 // // Odd length and non-hex chars both return errors.invalid. @test fn decodestr_odd() void = { match (hex.decodestr("abc")) { case let b: []u8 => fail(); case let e: errors.invalid => void; }; }; @test fn decodestr_bad() void = { match (hex.decodestr("zz00")) { // 'z' isn't hex case let b: []u8 => fail(); case let e: errors.invalid => void; }; }; @test fn decodestr_bad_mid() void = { match (hex.decodestr("aabbgg")) { // 'g' isn't hex case let b: []u8 => fail(); case let e: errors.invalid => void; }; }; // ---- round-trip every byte value 0..255 -------------------------------- @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]); if (s.len != 512) { fail(); }; match (hex.decodestr(s)) { case let b: []u8 => { if (b.len != 256) { fail(); }; if (!bytes.equal(b, src[0:256])) { fail(); }; }; case let e: errors.invalid => fail(); }; }; export fn main() i32 = { signalled = 1; encodestr_basic(); signalled = 2; encodestr_zero(); signalled = 3; encodestr_ff(); signalled = 4; encodestr_empty(); signalled = 5; encode_stream(); signalled = 6; decodestr_lower(); signalled = 7; decodestr_upper(); signalled = 8; decodestr_mixed(); signalled = 9; decodestr_empty(); signalled = 10; decodestr_odd(); signalled = 11; decodestr_bad(); signalled = 12; decodestr_bad_mid(); signalled = 13; roundtrip_all_bytes(); return 0; };