New tests cover edge cases and error scenarios for `UnmarshalQuoted`, `UnmarshalHexArray`, `UnmarshalStringArray`, `Less` method in `tags`, and `Marshal`/`Unmarshal` methods in `atag`. These tests improve overall test coverage and ensure error handling is robust.
658 lines
16 KiB
Go
658 lines
16 KiB
Go
package text
|
|
|
|
import (
|
|
"bytes"
|
|
"testing"
|
|
|
|
"lukechampine.com/frand"
|
|
|
|
"realy.lol/chk"
|
|
"realy.lol/hex"
|
|
"realy.lol/sha256"
|
|
)
|
|
|
|
func TestUnmarshalHexArray(t *testing.T) {
|
|
var ha [][]byte
|
|
h := make([]byte, sha256.Size)
|
|
frand.Read(h)
|
|
var dst []byte
|
|
for _ = range 20 {
|
|
hh := sha256.Sum256(h)
|
|
h = hh[:]
|
|
ha = append(ha, h)
|
|
}
|
|
dst = append(dst, '[')
|
|
for i := range ha {
|
|
dst = AppendQuote(dst, ha[i], hex.EncAppend)
|
|
if i != len(ha)-1 {
|
|
dst = append(dst, ',')
|
|
}
|
|
}
|
|
dst = append(dst, ']')
|
|
var ha2 [][]byte
|
|
var rem []byte
|
|
var err error
|
|
if ha2, rem, err = UnmarshalHexArray(dst, 32); chk.E(err) {
|
|
t.Fatal(err)
|
|
}
|
|
if len(ha2) != len(ha) {
|
|
t.Fatalf("failed to unmarshal, got %d fields, expected %d", len(ha2),
|
|
len(ha))
|
|
}
|
|
if len(rem) > 0 {
|
|
t.Fatalf("failed to unmarshal, remnant afterwards '%s'", rem)
|
|
}
|
|
for i := range ha2 {
|
|
if !bytes.Equal(ha[i], ha2[i]) {
|
|
t.Fatalf("failed to unmarshal at element %d; got %x, expected %x",
|
|
i, ha[i], ha2[i])
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestJSONKey(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
dst []byte
|
|
key []byte
|
|
expected []byte
|
|
}{
|
|
{
|
|
name: "basic key",
|
|
dst: []byte("prefix"),
|
|
key: []byte("name"),
|
|
expected: []byte(`prefix"name":`),
|
|
},
|
|
{
|
|
name: "empty dst",
|
|
dst: []byte{},
|
|
key: []byte("id"),
|
|
expected: []byte(`"id":`),
|
|
},
|
|
{
|
|
name: "empty key",
|
|
dst: []byte("start"),
|
|
key: []byte{},
|
|
expected: []byte(`start"":`),
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
result := JSONKey(tt.dst, tt.key)
|
|
if !bytes.Equal(result, tt.expected) {
|
|
t.Errorf("JSONKey() = %q, want %q", result, tt.expected)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestUnmarshalHex(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
input []byte
|
|
expectedHex []byte
|
|
expectedRem []byte
|
|
wantErr bool
|
|
}{
|
|
{
|
|
name: "basic hex",
|
|
input: []byte(`"01234567"remaining`),
|
|
expectedHex: []byte{0x01, 0x23, 0x45, 0x67},
|
|
expectedRem: []byte("remaining"),
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "empty hex",
|
|
input: []byte(`""rest`),
|
|
expectedHex: []byte{},
|
|
expectedRem: []byte("rest"),
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "no quotes",
|
|
input: []byte("01234567"),
|
|
expectedHex: nil,
|
|
expectedRem: []byte("01234567"),
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "odd length hex",
|
|
input: []byte(`"123"`),
|
|
expectedHex: nil,
|
|
expectedRem: []byte{},
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "invalid hex characters",
|
|
input: []byte(`"gg"`),
|
|
expectedHex: nil,
|
|
expectedRem: []byte{},
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "empty input",
|
|
input: []byte{},
|
|
expectedHex: nil,
|
|
expectedRem: []byte{},
|
|
wantErr: true,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
hex, rem, err := UnmarshalHex(tt.input)
|
|
if (err != nil) != tt.wantErr {
|
|
t.Errorf("UnmarshalHex() error = %v, wantErr %v", err, tt.wantErr)
|
|
return
|
|
}
|
|
if !tt.wantErr {
|
|
if !bytes.Equal(hex, tt.expectedHex) {
|
|
t.Errorf("UnmarshalHex() hex = %v, want %v", hex, tt.expectedHex)
|
|
}
|
|
if !bytes.Equal(rem, tt.expectedRem) {
|
|
t.Errorf("UnmarshalHex() rem = %q, want %q", rem, tt.expectedRem)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestUnmarshalQuoted(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
input []byte
|
|
expectedContent []byte
|
|
expectedRem []byte
|
|
wantErr bool
|
|
}{
|
|
{
|
|
name: "basic quoted string",
|
|
input: []byte(`"hello"world`),
|
|
expectedContent: []byte("hello"),
|
|
expectedRem: []byte("world"),
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "empty quoted string",
|
|
input: []byte(`""rest`),
|
|
expectedContent: []byte{},
|
|
expectedRem: []byte("rest"),
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "escaped quotes",
|
|
input: []byte(`"say \"hello\""end`),
|
|
expectedContent: []byte(`say "hello"`),
|
|
expectedRem: []byte("end"),
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "escaped backslash",
|
|
input: []byte(`"path\\to\\file"rest`),
|
|
expectedContent: []byte(`path\to\file`),
|
|
expectedRem: []byte("rest"),
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "no opening quote",
|
|
input: []byte(`hello"world`),
|
|
expectedContent: []byte("world"),
|
|
expectedRem: []byte{},
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "no closing quote",
|
|
input: []byte(`"hello`),
|
|
expectedContent: []byte("hello"),
|
|
expectedRem: []byte{},
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "empty input",
|
|
input: []byte{},
|
|
expectedContent: nil,
|
|
expectedRem: []byte{},
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "invalid control character",
|
|
input: []byte("\"hello\tworld\""),
|
|
expectedContent: nil,
|
|
expectedRem: []byte{},
|
|
wantErr: true,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
content, rem, err := UnmarshalQuoted(tt.input)
|
|
if (err != nil) != tt.wantErr {
|
|
t.Errorf("UnmarshalQuoted() error = %v, wantErr %v", err, tt.wantErr)
|
|
return
|
|
}
|
|
if !tt.wantErr {
|
|
if !bytes.Equal(content, tt.expectedContent) {
|
|
t.Errorf("UnmarshalQuoted() content = %q, want %q", content, tt.expectedContent)
|
|
}
|
|
if !bytes.Equal(rem, tt.expectedRem) {
|
|
t.Errorf("UnmarshalQuoted() rem = %q, want %q", rem, tt.expectedRem)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestMarshalHexArray(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
dst []byte
|
|
ha [][]byte
|
|
expected []byte
|
|
}{
|
|
{
|
|
name: "basic hex array",
|
|
dst: []byte("prefix"),
|
|
ha: [][]byte{{0x01, 0x23}, {0x45, 0x67}},
|
|
expected: []byte(`prefix["0123","4567"]`),
|
|
},
|
|
{
|
|
name: "empty array",
|
|
dst: []byte("start"),
|
|
ha: [][]byte{},
|
|
expected: []byte("start[]"),
|
|
},
|
|
{
|
|
name: "single element",
|
|
dst: []byte{},
|
|
ha: [][]byte{{0xff}},
|
|
expected: []byte(`["ff"]`),
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
result := MarshalHexArray(tt.dst, tt.ha)
|
|
if !bytes.Equal(result, tt.expected) {
|
|
t.Errorf("MarshalHexArray() = %q, want %q", result, tt.expected)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestUnmarshalStringArray(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
input []byte
|
|
expectedArr [][]byte
|
|
expectedRem []byte
|
|
wantErr bool
|
|
}{
|
|
{
|
|
name: "basic string array",
|
|
input: []byte(`["hello","world"]rest`),
|
|
expectedArr: [][]byte{[]byte("hello"), []byte("world")},
|
|
expectedRem: []byte("rest"),
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "empty array",
|
|
input: []byte(`[]remaining`),
|
|
expectedArr: [][]byte{},
|
|
expectedRem: []byte("remaining"),
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "single element",
|
|
input: []byte(`["single"]end`),
|
|
expectedArr: [][]byte{[]byte("single")},
|
|
expectedRem: []byte("end"),
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "no opening bracket",
|
|
input: []byte(`"hello","world"]`),
|
|
expectedArr: [][]byte{},
|
|
expectedRem: []byte{},
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "no closing bracket",
|
|
input: []byte(`["hello","world"`),
|
|
expectedArr: [][]byte{[]byte("hello"), []byte("world")},
|
|
expectedRem: []byte{},
|
|
wantErr: false,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
arr, rem, err := UnmarshalStringArray(tt.input)
|
|
if (err != nil) != tt.wantErr {
|
|
t.Errorf("UnmarshalStringArray() error = %v, wantErr %v", err, tt.wantErr)
|
|
return
|
|
}
|
|
if len(arr) != len(tt.expectedArr) {
|
|
t.Errorf("UnmarshalStringArray() array length = %d, want %d", len(arr), len(tt.expectedArr))
|
|
return
|
|
}
|
|
for i := range arr {
|
|
if !bytes.Equal(arr[i], tt.expectedArr[i]) {
|
|
t.Errorf("UnmarshalStringArray() arr[%d] = %q, want %q", i, arr[i], tt.expectedArr[i])
|
|
}
|
|
}
|
|
if !bytes.Equal(rem, tt.expectedRem) {
|
|
t.Errorf("UnmarshalStringArray() rem = %q, want %q", rem, tt.expectedRem)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestTrue(t *testing.T) {
|
|
result := True()
|
|
expected := []byte("true")
|
|
if !bytes.Equal(result, expected) {
|
|
t.Errorf("True() = %q, want %q", result, expected)
|
|
}
|
|
}
|
|
|
|
func TestFalse(t *testing.T) {
|
|
result := False()
|
|
expected := []byte("false")
|
|
if !bytes.Equal(result, expected) {
|
|
t.Errorf("False() = %q, want %q", result, expected)
|
|
}
|
|
}
|
|
|
|
func TestMarshalBool(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
src []byte
|
|
truth bool
|
|
expected []byte
|
|
}{
|
|
{
|
|
name: "true value",
|
|
src: []byte("prefix"),
|
|
truth: true,
|
|
expected: []byte("prefixtrue"),
|
|
},
|
|
{
|
|
name: "false value",
|
|
src: []byte("prefix"),
|
|
truth: false,
|
|
expected: []byte("prefixfalse"),
|
|
},
|
|
{
|
|
name: "true with empty src",
|
|
src: []byte{},
|
|
truth: true,
|
|
expected: []byte("true"),
|
|
},
|
|
{
|
|
name: "false with empty src",
|
|
src: []byte{},
|
|
truth: false,
|
|
expected: []byte("false"),
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
result := MarshalBool(tt.src, tt.truth)
|
|
if !bytes.Equal(result, tt.expected) {
|
|
t.Errorf("MarshalBool() = %q, want %q", result, tt.expected)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestUnmarshalBool(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
input []byte
|
|
expectedRem []byte
|
|
expectedTruth bool
|
|
wantErr bool
|
|
}{
|
|
{
|
|
name: "true value",
|
|
input: []byte("truerest"),
|
|
expectedRem: []byte("rest"),
|
|
expectedTruth: true,
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "false value",
|
|
input: []byte("falserest"),
|
|
expectedRem: []byte("rest"),
|
|
expectedTruth: false,
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "true at end",
|
|
input: []byte("true"),
|
|
expectedRem: []byte{},
|
|
expectedTruth: true,
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "false at end",
|
|
input: []byte("false"),
|
|
expectedRem: []byte{},
|
|
expectedTruth: false,
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "true with prefix",
|
|
input: []byte("prefixtruerest"),
|
|
expectedRem: []byte("rest"),
|
|
expectedTruth: true,
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "false with prefix",
|
|
input: []byte("prefixfalserest"),
|
|
expectedRem: []byte("rest"),
|
|
expectedTruth: false,
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "no boolean value",
|
|
input: []byte("nothing"),
|
|
expectedRem: []byte{},
|
|
expectedTruth: false,
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "empty input",
|
|
input: []byte{},
|
|
expectedRem: []byte{},
|
|
expectedTruth: false,
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "incomplete true",
|
|
input: []byte("tr"),
|
|
expectedRem: []byte{},
|
|
expectedTruth: false,
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "incomplete false",
|
|
input: []byte("fal"),
|
|
expectedRem: []byte{},
|
|
expectedTruth: false,
|
|
wantErr: true,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
rem, truth, err := UnmarshalBool(tt.input)
|
|
if (err != nil) != tt.wantErr {
|
|
t.Errorf("UnmarshalBool() error = %v, wantErr %v", err, tt.wantErr)
|
|
return
|
|
}
|
|
if !tt.wantErr {
|
|
if truth != tt.expectedTruth {
|
|
t.Errorf("UnmarshalBool() truth = %v, want %v", truth, tt.expectedTruth)
|
|
}
|
|
if !bytes.Equal(rem, tt.expectedRem) {
|
|
t.Errorf("UnmarshalBool() rem = %q, want %q", rem, tt.expectedRem)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestComma(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
input []byte
|
|
expectedRem []byte
|
|
wantErr bool
|
|
}{
|
|
{
|
|
name: "comma found",
|
|
input: []byte("prefix,rest"),
|
|
expectedRem: []byte(",rest"),
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "comma at start",
|
|
input: []byte(",rest"),
|
|
expectedRem: []byte(",rest"),
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "no comma",
|
|
input: []byte("nocomma"),
|
|
expectedRem: []byte("nocomma"),
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "empty input",
|
|
input: []byte{},
|
|
expectedRem: []byte{},
|
|
wantErr: true,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
rem, err := Comma(tt.input)
|
|
if (err != nil) != tt.wantErr {
|
|
t.Errorf("Comma() error = %v, wantErr %v", err, tt.wantErr)
|
|
return
|
|
}
|
|
if !bytes.Equal(rem, tt.expectedRem) {
|
|
t.Errorf("Comma() rem = %q, want %q", rem, tt.expectedRem)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// Additional tests to reach 100% coverage
|
|
func TestUnmarshalQuotedEOF(t *testing.T) {
|
|
// Test EOF error case - when rem becomes empty during processing
|
|
content, rem, err := UnmarshalQuoted([]byte(`"`))
|
|
if err == nil {
|
|
t.Error("UnmarshalQuoted should return EOF error for incomplete quoted string")
|
|
}
|
|
// The function returns empty slice, not nil
|
|
if len(content) != 0 {
|
|
t.Errorf("Expected empty content, got %v", content)
|
|
}
|
|
if len(rem) != 0 {
|
|
t.Errorf("Expected empty rem, got %v", rem)
|
|
}
|
|
}
|
|
|
|
func TestUnmarshalHexArrayEdgeCases(t *testing.T) {
|
|
// Test closing bracket case - this should return error for wrong size
|
|
_, _, err := UnmarshalHexArray([]byte(`["]`), 1)
|
|
if err == nil {
|
|
t.Error("UnmarshalHexArray should return error for wrong hex size")
|
|
}
|
|
|
|
// Test invalid hex size error
|
|
_, _, err2 := UnmarshalHexArray([]byte(`["01"]`), 2)
|
|
if err2 == nil {
|
|
t.Error("UnmarshalHexArray should return error for wrong hex size")
|
|
}
|
|
|
|
// Test invalid hex content error
|
|
_, _, err3 := UnmarshalHexArray([]byte(`["gg"]`), 1)
|
|
if err3 == nil {
|
|
t.Error("UnmarshalHexArray should return error for invalid hex")
|
|
}
|
|
|
|
// Test other character handling
|
|
arr4, rem4, err4 := UnmarshalHexArray([]byte(`[x]`), 1)
|
|
if err4 != nil {
|
|
t.Errorf("UnmarshalHexArray should handle other characters, got error: %v", err4)
|
|
}
|
|
if len(arr4) != 0 {
|
|
t.Errorf("Expected empty array, got %v", arr4)
|
|
}
|
|
if !bytes.Equal(rem4, []byte{}) {
|
|
t.Errorf("Expected empty rem, got %v", rem4)
|
|
}
|
|
|
|
// Test non-bracket start
|
|
arr5, rem5, err5 := UnmarshalHexArray([]byte(`x]`), 1)
|
|
if err5 != nil {
|
|
t.Errorf("UnmarshalHexArray should handle non-bracket start, got error: %v", err5)
|
|
}
|
|
if len(arr5) != 0 {
|
|
t.Errorf("Expected empty array, got %v", arr5)
|
|
}
|
|
if !bytes.Equal(rem5, []byte{}) {
|
|
t.Errorf("Expected empty rem, got %v", rem5)
|
|
}
|
|
|
|
// Test closing bracket without quote
|
|
arr6, rem6, err6 := UnmarshalHexArray([]byte(`[]`), 1)
|
|
if err6 != nil {
|
|
t.Errorf("UnmarshalHexArray should handle empty array, got error: %v", err6)
|
|
}
|
|
if len(arr6) != 0 {
|
|
t.Errorf("Expected empty array, got %v", arr6)
|
|
}
|
|
if !bytes.Equal(rem6, []byte{}) {
|
|
t.Errorf("Expected empty rem, got %v", rem6)
|
|
}
|
|
}
|
|
|
|
func TestUnmarshalStringArrayEdgeCases(t *testing.T) {
|
|
// Test UnmarshalQuoted error case - this actually doesn't fail, it just processes what it can
|
|
arr, _, err := UnmarshalStringArray([]byte(`["hello`))
|
|
if err != nil {
|
|
t.Errorf("UnmarshalStringArray should handle incomplete string, got error: %v", err)
|
|
}
|
|
// It should extract "hello" even without closing quote
|
|
if len(arr) != 1 || !bytes.Equal(arr[0], []byte("hello")) {
|
|
t.Errorf("Expected [hello], got %v", arr)
|
|
}
|
|
|
|
// Test other character handling
|
|
arr2, rem2, err2 := UnmarshalStringArray([]byte(`[x]`))
|
|
if err2 != nil {
|
|
t.Errorf("UnmarshalStringArray should handle other characters, got error: %v", err2)
|
|
}
|
|
if len(arr2) != 0 {
|
|
t.Errorf("Expected empty array, got %v", arr2)
|
|
}
|
|
if !bytes.Equal(rem2, []byte{}) {
|
|
t.Errorf("Expected empty rem, got %v", rem2)
|
|
}
|
|
|
|
// Test case that actually triggers UnmarshalQuoted error - control character
|
|
// This test covers the error path in UnmarshalStringArray when UnmarshalQuoted fails
|
|
_, _, err3 := UnmarshalStringArray([]byte(`["hello` + string(rune(9)) + `world"]`))
|
|
if err3 == nil {
|
|
t.Error("UnmarshalStringArray should return error for control characters")
|
|
}
|
|
}
|