initial commit
This commit is contained in:
@@ -0,0 +1,200 @@
|
||||
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")))
|
||||
}
|
||||
Reference in New Issue
Block a user