package pdf import "core:mem/virtual" import "core:slice" import "core:testing" @(private = "file") Rl_Case :: struct { name: string, in_: []byte, want: []byte, } @(private = "file") check_rl :: proc(t: ^testing.T, c: Rl_Case) { out, err := run_length_decode(c.in_) defer delete(out) testing.expectf(t, err == .None, "%s: err = %v", c.name, err) testing.expectf(t, slice.equal(out, c.want), "%s: got %v, want %v", c.name, out, c.want) } @(test) test_run_length_literals :: proc(t: ^testing.T) { cases := []Rl_Case{ {"one byte", {0, 'A', 128}, {'A'}}, {"three bytes", {2, 'a', 'b', 'c', 128}, {'a', 'b', 'c'}}, {"two runs", {1, 'a', 'b', 0, 'c', 128}, {'a', 'b', 'c'}}, {"nul literal", {1, 0x00, 0x80, 128}, {0x00, 0x80}}, // 128 only means EOD in a length position; as data it is just a byte. {"128 as data", {0, 128, 128}, {128}}, } for c in cases { check_rl(t, c) } } @(test) test_run_length_repeats :: proc(t: ^testing.T) { cases := []Rl_Case{ // 257-255 = 2 is the shortest repeat the encoding can express. {"two", {255, 'x', 128}, {'x', 'x'}}, {"three", {254, 'x', 128}, {'x', 'x', 'x'}}, {"four", {253, 0xFF, 128}, {0xFF, 0xFF, 0xFF, 0xFF}}, {"mixed", {1, 'a', 'b', 254, 'c', 0, 'd', 128}, {'a', 'b', 'c', 'c', 'c', 'd'}}, } for c in cases { check_rl(t, c) } } @(test) test_run_length_boundaries :: proc(t: ^testing.T) { // The longest literal run (127 -> 128 bytes) and the longest repeat // (129 -> 128 copies), the two lengths an off-by-one lands on. enc := make([dynamic]byte) defer delete(enc) want := make([dynamic]byte) defer delete(want) append(&enc, 127) for i in 0 ..< 128 { append(&enc, byte(i)) append(&want, byte(i)) } append(&enc, 129, 'Z') for _ in 0 ..< 128 { append(&want, 'Z') } append(&enc, 128) check_rl(t, {"max runs", enc[:], want[:]}) } @(test) test_run_length_degenerate :: proc(t: ^testing.T) { cases := []Rl_Case{ {"empty", {}, {}}, {"eod immediately", {128}, {}}, {"eod then junk", {128, 5, 'a', 'b'}, {}}, // No EOD marker: the data simply ends. {"missing eod", {1, 'a', 'b'}, {'a', 'b'}}, {"repeat, no eod", {254, 'q'}, {'q', 'q', 'q'}}, } for c in cases { check_rl(t, c) } } @(test) test_run_length_truncated :: proc(t: ^testing.T) { cases := []Rl_Case{ // A literal run promising more bytes than remain keeps what is there. {"short literal", {5, 'a', 'b'}, {'a', 'b'}}, {"literal, none", {5}, {}}, // A repeat with no byte to repeat produces nothing and ends. {"repeat, no byte", {250}, {}}, {"tail after good", {1, 'a', 'b', 9, 'c'}, {'a', 'b', 'c'}}, } for c in cases { check_rl(t, c) } } @(test) test_run_length_truncated_everywhere :: proc(t: ^testing.T) { // Every prefix of a well-formed stream must decode a prefix of the // plaintext without crashing. enc := []byte{2, 'a', 'b', 'c', 254, 'z', 0, 'Q', 129, '!', 128} full := "abczzzQ" for cut in 0 ..= len(enc) { out, err := run_length_decode(enc[:cut]) testing.expectf(t, err == .None, "cut %d: err = %v", cut, err) n := min(len(out), len(full)) testing.expectf(t, slice.equal(out[:n], transmute([]byte)full[:n]), "cut %d: got %v", cut, out) // Nothing past the '!' run can appear, so the length is bounded. testing.expectf(t, len(out) <= len(full) + 128, "cut %d: %d bytes", cut, len(out)) delete(out) } } @(test) test_run_length_all_lengths :: proc(t: ^testing.T) { // Sweep every length byte as the first byte of a stream, with one byte of // payload behind it, and assert the decoded length is exactly what the // encoding promises. This is the table the filter is. for length in 0 ..= 255 { buf := [3]byte{byte(length), 'p', 128} out, err := run_length_decode(buf[:]) want := 0 switch { case length == 128: want = 0 case length < 128: // "length+1 literal bytes", of which only 2 are present here // ('p' and the 128 that would otherwise have been EOD). want = min(length + 1, 2) case: want = 257 - length } testing.expectf(t, err == .None, "length %d: err = %v", length, err) testing.expectf(t, len(out) == want, "length %d: got %d bytes, want %d", length, len(out), want) delete(out) } } @(test) test_run_length_cross_check :: proc(t: ^testing.T) { // Vectors from an independent RunLength encoder over random bytes drawn // from a small alphabet, so literal and repeat runs interleave. cases := []struct{name, in_, want: string}{ {"n=1", "\x00\xFF\x80", "\xFF"}, {"n=3", "\xFF\x00\x00a\x80", "\x00\x00a"}, {"n=9", "\x05\x00\xFFb\x00\xFF\x00\xFFc\x00a\x80", "\x00\xFFb\x00\xFF\x00cca"}, {"n=40", "\x00b\xFF\x00\x02a\xFFa\xFF\x00\x09cbcab\xFFcbc\x00\xFFa\x07\x00bacab\xFFa\xFFc\x09\x00a\xFFca\x00ba\x00b\x80", "b\x00\x00a\xFFa\x00\x00cbcab\xFFcbc\x00aa\x00bacab\xFFacc\x00a\xFFca\x00ba\x00b"}, } for c in cases { check_rl(t, {c.name, transmute([]byte)c.in_, transmute([]byte)c.want}) } } @(private = "file") rl_fuzz_next :: proc(state: ^u64) -> u32 { state^ = state^ * 6364136223846793005 + 1442695040888963407 return u32(state^ >> 33) } @(test) test_run_length_fuzz :: proc(t: ^testing.T) { // Random bytes decode to at most 128 bytes per input byte and must never // crash or hang, whatever the length bytes happen to say. state: u64 = 0xBADF00D buf: [256]byte for round in 0 ..< 400 { n := int(rl_fuzz_next(&state) % u32(len(buf) + 1)) for i in 0 ..< n { buf[i] = byte(rl_fuzz_next(&state)) } out, err := run_length_decode(buf[:n]) testing.expectf(t, err == .None, "round %d: err %v", round, err) testing.expectf(t, len(out) <= 128 * n, "round %d: grew to %d from %d", round, len(out), n) delete(out) } } @(test) test_run_length_honours_allocator :: proc(t: ^testing.T) { arena: virtual.Arena testing.expect(t, virtual.arena_init_growing(&arena) == nil) defer virtual.arena_destroy(&arena) out, err := run_length_decode({2, 'a', 'b', 'c', 128}, virtual.arena_allocator(&arena)) testing.expect(t, err == .None) testing.expect(t, slice.equal(out, transmute([]byte)string("abc"))) }