Files
realy/text/helpers_test.go
mleku 03f44c1f59 Add comprehensive edge case tests for unmarshaling functions
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.
2025-06-26 20:45:10 +01:00

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")
}
}