[varint] Add LEB128 decoding + tests

Also make tests in general less spammy: Don't print [PASS] for each successful test, only report failures and progress.
This commit is contained in:
Jeroen van Rijn
2022-03-08 15:40:00 +01:00
parent 29e660b16f
commit 6d7217f37a
13 changed files with 1138 additions and 1002 deletions
+67
View File
@@ -0,0 +1,67 @@
/*
Copyright 2022 Jeroen van Rijn <nom@duclavier.com>.
Made available under Odin's BSD-3 license.
List of contributors:
Jeroen van Rijn: Initial implementation.
*/
// package varint implements variable length integer encoding and decoding
// using the LEB128 format as used by DWARF debug and other file formats
package varint
// Decode a slice of bytes encoding an unsigned LEB128 integer into value and number of bytes used.
// Returns `size` == 0 for an invalid value, empty slice, or a varint > 16 bytes.
// In theory we should use the bigint package. In practice, varints bigger than this indicate a corrupted file.
decode_uleb128 :: proc(buf: []u8) -> (val: u128, size: int) {
more := true
for v, i in buf {
size = i + 1
if size > size_of(u128) {
return
}
val |= u128(v & 0x7f) << uint(i * 7)
if v < 128 {
more = false
break
}
}
// If the buffer runs out before the number ends, return an error.
if more {
return 0, 0
}
return
}
// Decode a slice of bytes encoding a signed LEB128 integer into value and number of bytes used.
// Returns `size` == 0 for an invalid value, empty slice, or a varint > 16 bytes.
// In theory we should use the bigint package. In practice, varints bigger than this indicate a corrupted file.
decode_ileb128 :: proc(buf: []u8) -> (val: i128, size: int) {
shift: uint
if len(buf) == 0 {
return
}
for v in buf {
size += 1
if size > size_of(i128) {
return
}
val |= i128(v & 0x7f) << shift
shift += 7
if v < 128 { break }
}
if buf[size - 1] & 0x40 == 0x40 {
val |= max(i128) << shift
}
return
}
+5 -1
View File
@@ -1,7 +1,7 @@
ODIN=../../odin ODIN=../../odin
PYTHON=$(shell which python3) PYTHON=$(shell which python3)
all: download_test_assets image_test compress_test strings_test hash_test crypto_test noise_test all: download_test_assets image_test compress_test strings_test hash_test crypto_test noise_test encoding_test
download_test_assets: download_test_assets:
$(PYTHON) download_assets.py $(PYTHON) download_assets.py
@@ -23,3 +23,7 @@ crypto_test:
noise_test: noise_test:
$(ODIN) run math/noise -out=test_noise $(ODIN) run math/noise -out=test_noise
encoding_test:
$(ODIN) run encoding/json -out=test_json
$(ODIN) run encoding/varint -out=test_varint
+2 -1
View File
@@ -35,7 +35,8 @@ echo ---
echo --- echo ---
echo Running core:encoding tests echo Running core:encoding tests
echo --- echo ---
%PATH_TO_ODIN% run encoding %COMMON% %PATH_TO_ODIN% run encoding/json %COMMON%
%PATH_TO_ODIN% run encoding/varint %COMMON%
echo --- echo ---
echo Running core:math/noise tests echo Running core:math/noise tests
+1 -3
View File
@@ -30,14 +30,12 @@ when ODIN_TEST {
log :: testing.log log :: testing.log
} else { } else {
expect :: proc(t: ^testing.T, condition: bool, message: string, loc := #caller_location) { expect :: proc(t: ^testing.T, condition: bool, message: string, loc := #caller_location) {
fmt.printf("[%v] ", loc)
TEST_count += 1 TEST_count += 1
if !condition { if !condition {
TEST_fail += 1 TEST_fail += 1
fmt.println(message) fmt.printf("[%v] %v\n", loc, message)
return return
} }
fmt.println(" PASS")
} }
log :: proc(t: ^testing.T, v: any, loc := #caller_location) { log :: proc(t: ^testing.T, v: any, loc := #caller_location) {
fmt.printf("[%v] ", loc) fmt.printf("[%v] ", loc)
+1 -3
View File
@@ -47,14 +47,12 @@ when ODIN_TEST {
log :: testing.log log :: testing.log
} else { } else {
expect :: proc(t: ^testing.T, condition: bool, message: string, loc := #caller_location) { expect :: proc(t: ^testing.T, condition: bool, message: string, loc := #caller_location) {
fmt.printf("[%v] ", loc)
TEST_count += 1 TEST_count += 1
if !condition { if !condition {
TEST_fail += 1 TEST_fail += 1
fmt.println(message) fmt.printf("[%v] %v\n", loc, message)
return return
} }
fmt.println(" PASS")
} }
log :: proc(t: ^testing.T, v: any, loc := #caller_location) { log :: proc(t: ^testing.T, v: any, loc := #caller_location) {
fmt.printf("[%v] ", loc) fmt.printf("[%v] ", loc)
Binary file not shown.
@@ -13,14 +13,12 @@ when ODIN_TEST {
log :: testing.log log :: testing.log
} else { } else {
expect :: proc(t: ^testing.T, condition: bool, message: string, loc := #caller_location) { expect :: proc(t: ^testing.T, condition: bool, message: string, loc := #caller_location) {
fmt.printf("[%v] ", loc)
TEST_count += 1 TEST_count += 1
if !condition { if !condition {
TEST_fail += 1 TEST_fail += 1
fmt.println(message) fmt.printf("[%v] %v\n", loc, message)
return return
} }
fmt.println(" PASS")
} }
log :: proc(t: ^testing.T, v: any, loc := #caller_location) { log :: proc(t: ^testing.T, v: any, loc := #caller_location) {
fmt.printf("[%v] ", loc) fmt.printf("[%v] ", loc)
@@ -0,0 +1,81 @@
package test_core_varint
import "core:encoding/varint"
import "core:testing"
import "core:fmt"
import "core:os"
TEST_count := 0
TEST_fail := 0
when ODIN_TEST {
expect :: testing.expect
log :: testing.log
} else {
expect :: proc(t: ^testing.T, condition: bool, message: string, loc := #caller_location) {
TEST_count += 1
if !condition {
TEST_fail += 1
fmt.printf("[%v] %v\n", loc, message)
return
}
}
log :: proc(t: ^testing.T, v: any, loc := #caller_location) {
fmt.printf("[%v] ", loc)
fmt.printf("log: %v\n", v)
}
}
main :: proc() {
t := testing.T{}
test_dwarf(&t)
fmt.printf("%v/%v tests successful.\n", TEST_count - TEST_fail, TEST_count)
if TEST_fail > 0 {
os.exit(1)
}
}
@(test)
test_dwarf :: proc(t: ^testing.T) {
for vector in ULEB_Vectors {
val, size := varint.decode_uleb128(vector.encoded)
msg := fmt.tprintf("Expected %02x to decode to %v consuming %v bytes, got %v and %v", vector.encoded, vector.value, vector.size, val, size)
expect(t, size == vector.size && val == vector.value, msg)
}
for vector in ILEB_Vectors {
val, size := varint.decode_ileb128(vector.encoded)
msg := fmt.tprintf("Expected %02x to decode to %v consuming %v bytes, got %v and %v", vector.encoded, vector.value, vector.size, val, size)
expect(t, size == vector.size && val == vector.value, msg)
}
}
ULEB_Test_Vector :: struct {
encoded: []u8,
value: u128,
size: int,
}
ULEB_Vectors :: []ULEB_Test_Vector{
{ []u8{0x00}, 0, 1 },
{ []u8{0x7f}, 127, 1 },
{ []u8{0xE5, 0x8E, 0x26}, 624485, 3 },
{ []u8{0x80}, 0, 0 },
{ []u8{}, 0, 0 },
}
ILEB_Test_Vector :: struct {
encoded: []u8,
value: i128,
size: int,
}
ILEB_Vectors :: []ILEB_Test_Vector{
{ []u8{0x00}, 0, 1 },
{ []u8{0xC0, 0xBB, 0x78}, -123456, 3 },
{ []u8{}, 0, 0 },
}
+1 -3
View File
@@ -15,14 +15,12 @@ when ODIN_TEST {
log :: testing.log log :: testing.log
} else { } else {
expect :: proc(t: ^testing.T, condition: bool, message: string, loc := #caller_location) { expect :: proc(t: ^testing.T, condition: bool, message: string, loc := #caller_location) {
fmt.printf("[%v] ", loc)
TEST_count += 1 TEST_count += 1
if !condition { if !condition {
TEST_fail += 1 TEST_fail += 1
fmt.println(" FAIL:", message) fmt.printf("[%v] %v\n", loc, message)
return return
} }
fmt.println(" PASS")
} }
log :: proc(t: ^testing.T, v: any, loc := #caller_location) { log :: proc(t: ^testing.T, v: any, loc := #caller_location) {
fmt.printf("[%v] ", loc) fmt.printf("[%v] ", loc)
+1 -3
View File
@@ -36,14 +36,12 @@ when ODIN_TEST {
log :: testing.log log :: testing.log
} else { } else {
expect :: proc(t: ^testing.T, condition: bool, message: string, loc := #caller_location) { expect :: proc(t: ^testing.T, condition: bool, message: string, loc := #caller_location) {
fmt.printf("[%v] ", loc)
TEST_count += 1 TEST_count += 1
if !condition { if !condition {
TEST_fail += 1 TEST_fail += 1
fmt.println(message) fmt.printf("[%v] %v\n", loc, message)
return return
} }
fmt.println(" PASS")
} }
log :: proc(t: ^testing.T, v: any, loc := #caller_location) { log :: proc(t: ^testing.T, v: any, loc := #caller_location) {
fmt.printf("[%v] ", loc) fmt.printf("[%v] ", loc)
@@ -17,14 +17,12 @@ when ODIN_TEST {
log :: testing.log log :: testing.log
} else { } else {
expect :: proc(t: ^testing.T, condition: bool, message: string, loc := #caller_location) { expect :: proc(t: ^testing.T, condition: bool, message: string, loc := #caller_location) {
fmt.printf("[%v] ", loc)
TEST_count += 1 TEST_count += 1
if !condition { if !condition {
TEST_fail += 1 TEST_fail += 1
fmt.println(message) fmt.printf("[%v] %v\n", loc, message)
return return
} }
fmt.println(" PASS")
} }
log :: proc(t: ^testing.T, v: any, loc := #caller_location) { log :: proc(t: ^testing.T, v: any, loc := #caller_location) {
fmt.printf("[%v] ", loc) fmt.printf("[%v] ", loc)
+1 -4
View File
@@ -14,14 +14,12 @@ when ODIN_TEST {
log :: testing.log log :: testing.log
} else { } else {
expect :: proc(t: ^testing.T, condition: bool, message: string, loc := #caller_location) { expect :: proc(t: ^testing.T, condition: bool, message: string, loc := #caller_location) {
fmt.printf("[%v] ", loc)
TEST_count += 1 TEST_count += 1
if !condition { if !condition {
TEST_fail += 1 TEST_fail += 1
fmt.println(message) fmt.printf("[%v] %v\n", loc, message)
return return
} }
fmt.println(" PASS")
} }
log :: proc(t: ^testing.T, v: any, loc := #caller_location) { log :: proc(t: ^testing.T, v: any, loc := #caller_location) {
fmt.printf("[%v] ", loc) fmt.printf("[%v] ", loc)
@@ -29,7 +27,6 @@ when ODIN_TEST {
} }
} }
main :: proc() { main :: proc() {
t := testing.T{} t := testing.T{}
test_parse_demo(&t) test_parse_demo(&t)
+1 -3
View File
@@ -13,14 +13,12 @@ when ODIN_TEST {
log :: testing.log log :: testing.log
} else { } else {
expect :: proc(t: ^testing.T, condition: bool, message: string, loc := #caller_location) { expect :: proc(t: ^testing.T, condition: bool, message: string, loc := #caller_location) {
fmt.printf("[%v] ", loc)
TEST_count += 1 TEST_count += 1
if !condition { if !condition {
TEST_fail += 1 TEST_fail += 1
fmt.println(message) fmt.printf("[%v] %v\n", loc, message)
return return
} }
fmt.println(" PASS")
} }
log :: proc(t: ^testing.T, v: any, loc := #caller_location) { log :: proc(t: ^testing.T, v: any, loc := #caller_location) {
fmt.printf("[%v] ", loc) fmt.printf("[%v] ", loc)