fully test text package and fix a few bugs

This commit is contained in:
2025-06-26 20:17:15 +01:00
parent 2d42bdc351
commit 028975da55
8 changed files with 1294 additions and 12 deletions

230
.output.txt Normal file
View File

@@ -0,0 +1,230 @@
=== RUN TestDebugUnmarshalStringArray
debug_test.go:10: arr: [[104 101 108 108 111] [119 111 114 108 100]], rem: "", err: <nil>
--- PASS: TestDebugUnmarshalStringArray (0.00s)
=== RUN TestUnescapeByteString
--- PASS: TestUnescapeByteString (0.00s)
=== RUN TestRandomEscapeByteString
--- PASS: TestRandomEscapeByteString (0.00s)
=== RUN TestUnmarshalHexArray
--- PASS: TestUnmarshalHexArray (0.00s)
=== RUN TestJSONKey
=== RUN TestJSONKey/basic_key
=== RUN TestJSONKey/empty_dst
=== RUN TestJSONKey/empty_key
--- PASS: TestJSONKey (0.00s)
--- PASS: TestJSONKey/basic_key (0.00s)
--- PASS: TestJSONKey/empty_dst (0.00s)
--- PASS: TestJSONKey/empty_key (0.00s)
=== RUN TestUnmarshalHex
=== RUN TestUnmarshalHex/basic_hex
=== RUN TestUnmarshalHex/empty_hex
=== RUN TestUnmarshalHex/no_quotes
=== RUN TestUnmarshalHex/odd_length_hex
=== RUN TestUnmarshalHex/invalid_hex_characters
2025-06-26T20:12:49+01:00.314 ERR encoding/hex: invalid byte: U+0067 'g' /home/david/src/realy.lol/text/helpers.go:51
=== RUN TestUnmarshalHex/empty_input
--- PASS: TestUnmarshalHex (0.00s)
--- PASS: TestUnmarshalHex/basic_hex (0.00s)
--- PASS: TestUnmarshalHex/empty_hex (0.00s)
--- PASS: TestUnmarshalHex/no_quotes (0.00s)
--- PASS: TestUnmarshalHex/odd_length_hex (0.00s)
--- PASS: TestUnmarshalHex/invalid_hex_characters (0.00s)
--- PASS: TestUnmarshalHex/empty_input (0.00s)
=== RUN TestUnmarshalQuoted
=== RUN TestUnmarshalQuoted/basic_quoted_string
=== RUN TestUnmarshalQuoted/empty_quoted_string
=== RUN TestUnmarshalQuoted/escaped_quotes
=== RUN TestUnmarshalQuoted/escaped_backslash
=== RUN TestUnmarshalQuoted/no_opening_quote
=== RUN TestUnmarshalQuoted/no_closing_quote
=== RUN TestUnmarshalQuoted/empty_input
=== RUN TestUnmarshalQuoted/invalid_control_character
--- PASS: TestUnmarshalQuoted (0.00s)
--- PASS: TestUnmarshalQuoted/basic_quoted_string (0.00s)
--- PASS: TestUnmarshalQuoted/empty_quoted_string (0.00s)
--- PASS: TestUnmarshalQuoted/escaped_quotes (0.00s)
--- PASS: TestUnmarshalQuoted/escaped_backslash (0.00s)
--- PASS: TestUnmarshalQuoted/no_opening_quote (0.00s)
--- PASS: TestUnmarshalQuoted/no_closing_quote (0.00s)
--- PASS: TestUnmarshalQuoted/empty_input (0.00s)
--- PASS: TestUnmarshalQuoted/invalid_control_character (0.00s)
=== RUN TestMarshalHexArray
=== RUN TestMarshalHexArray/basic_hex_array
=== RUN TestMarshalHexArray/empty_array
=== RUN TestMarshalHexArray/single_element
--- PASS: TestMarshalHexArray (0.00s)
--- PASS: TestMarshalHexArray/basic_hex_array (0.00s)
--- PASS: TestMarshalHexArray/empty_array (0.00s)
--- PASS: TestMarshalHexArray/single_element (0.00s)
=== RUN TestUnmarshalStringArray
=== RUN TestUnmarshalStringArray/basic_string_array
=== RUN TestUnmarshalStringArray/empty_array
=== RUN TestUnmarshalStringArray/single_element
=== RUN TestUnmarshalStringArray/no_opening_bracket
=== RUN TestUnmarshalStringArray/no_closing_bracket
helpers_test.go:336: UnmarshalStringArray() array length = 2, want 0
--- FAIL: TestUnmarshalStringArray (0.00s)
--- PASS: TestUnmarshalStringArray/basic_string_array (0.00s)
--- PASS: TestUnmarshalStringArray/empty_array (0.00s)
--- PASS: TestUnmarshalStringArray/single_element (0.00s)
--- PASS: TestUnmarshalStringArray/no_opening_bracket (0.00s)
--- FAIL: TestUnmarshalStringArray/no_closing_bracket (0.00s)
=== RUN TestTrue
--- PASS: TestTrue (0.00s)
=== RUN TestFalse
--- PASS: TestFalse (0.00s)
=== RUN TestMarshalBool
=== RUN TestMarshalBool/true_value
=== RUN TestMarshalBool/false_value
=== RUN TestMarshalBool/true_with_empty_src
=== RUN TestMarshalBool/false_with_empty_src
--- PASS: TestMarshalBool (0.00s)
--- PASS: TestMarshalBool/true_value (0.00s)
--- PASS: TestMarshalBool/false_value (0.00s)
--- PASS: TestMarshalBool/true_with_empty_src (0.00s)
--- PASS: TestMarshalBool/false_with_empty_src (0.00s)
=== RUN TestUnmarshalBool
=== RUN TestUnmarshalBool/true_value
=== RUN TestUnmarshalBool/false_value
=== RUN TestUnmarshalBool/true_at_end
=== RUN TestUnmarshalBool/false_at_end
=== RUN TestUnmarshalBool/true_with_prefix
=== RUN TestUnmarshalBool/false_with_prefix
=== RUN TestUnmarshalBool/no_boolean_value
=== RUN TestUnmarshalBool/empty_input
=== RUN TestUnmarshalBool/incomplete_true
=== RUN TestUnmarshalBool/incomplete_false
--- PASS: TestUnmarshalBool (0.00s)
--- PASS: TestUnmarshalBool/true_value (0.00s)
--- PASS: TestUnmarshalBool/false_value (0.00s)
--- PASS: TestUnmarshalBool/true_at_end (0.00s)
--- PASS: TestUnmarshalBool/false_at_end (0.00s)
--- PASS: TestUnmarshalBool/true_with_prefix (0.00s)
--- PASS: TestUnmarshalBool/false_with_prefix (0.00s)
--- PASS: TestUnmarshalBool/no_boolean_value (0.00s)
--- PASS: TestUnmarshalBool/empty_input (0.00s)
--- PASS: TestUnmarshalBool/incomplete_true (0.00s)
--- PASS: TestUnmarshalBool/incomplete_false (0.00s)
=== RUN TestComma
=== RUN TestComma/comma_found
=== RUN TestComma/comma_at_start
=== RUN TestComma/no_comma
helpers_test.go:550: Comma() rem = "nocomma", want ""
=== RUN TestComma/empty_input
--- FAIL: TestComma (0.00s)
--- PASS: TestComma/comma_found (0.00s)
--- PASS: TestComma/comma_at_start (0.00s)
--- FAIL: TestComma/no_comma (0.00s)
--- PASS: TestComma/empty_input (0.00s)
=== RUN TestAppendHexFromBinary
=== RUN TestAppendHexFromBinary/basic_hex_encoding_with_quote
=== RUN TestAppendHexFromBinary/basic_hex_encoding_without_quote
=== RUN TestAppendHexFromBinary/empty_src_with_quote
=== RUN TestAppendHexFromBinary/empty_src_without_quote
=== RUN TestAppendHexFromBinary/single_byte_with_quote
=== RUN TestAppendHexFromBinary/single_byte_without_quote
--- PASS: TestAppendHexFromBinary (0.00s)
--- PASS: TestAppendHexFromBinary/basic_hex_encoding_with_quote (0.00s)
--- PASS: TestAppendHexFromBinary/basic_hex_encoding_without_quote (0.00s)
--- PASS: TestAppendHexFromBinary/empty_src_with_quote (0.00s)
--- PASS: TestAppendHexFromBinary/empty_src_without_quote (0.00s)
--- PASS: TestAppendHexFromBinary/single_byte_with_quote (0.00s)
--- PASS: TestAppendHexFromBinary/single_byte_without_quote (0.00s)
=== RUN TestAppendBinaryFromHex
=== RUN TestAppendBinaryFromHex/basic_hex_decoding_with_unquote
=== RUN TestAppendBinaryFromHex/basic_hex_decoding_without_unquote
=== RUN TestAppendBinaryFromHex/empty_hex_with_unquote
2025-06-26T20:12:49+01:00.315 ERR nothing to decode /home/david/src/realy.lol/text/hex.go:24
hex_test.go:165: AppendBinaryFromHex() error = nothing to decode, wantErr false
=== RUN TestAppendBinaryFromHex/empty_hex_without_unquote
2025-06-26T20:12:49+01:00.315 ERR nothing to decode /home/david/src/realy.lol/text/hex.go:28
hex_test.go:165: AppendBinaryFromHex() error = nothing to decode, wantErr false
=== RUN TestAppendBinaryFromHex/single_byte_hex_with_unquote
=== RUN TestAppendBinaryFromHex/single_byte_hex_without_unquote
=== RUN TestAppendBinaryFromHex/invalid_hex_with_unquote
2025-06-26T20:12:49+01:00.315 ERR encoding/hex: invalid byte: U+0067 'g' /home/david/src/realy.lol/text/hex.go:24
=== RUN TestAppendBinaryFromHex/invalid_hex_without_unquote
2025-06-26T20:12:49+01:00.315 ERR encoding/hex: invalid byte: U+0067 'g' /home/david/src/realy.lol/text/hex.go:28
=== RUN TestAppendBinaryFromHex/odd_length_hex_with_unquote
2025-06-26T20:12:49+01:00.315 ERR encoding/hex: odd length hex string /home/david/src/realy.lol/text/hex.go:24
=== RUN TestAppendBinaryFromHex/odd_length_hex_without_unquote
2025-06-26T20:12:49+01:00.315 ERR encoding/hex: odd length hex string /home/david/src/realy.lol/text/hex.go:28
--- FAIL: TestAppendBinaryFromHex (0.00s)
--- PASS: TestAppendBinaryFromHex/basic_hex_decoding_with_unquote (0.00s)
--- PASS: TestAppendBinaryFromHex/basic_hex_decoding_without_unquote (0.00s)
--- FAIL: TestAppendBinaryFromHex/empty_hex_with_unquote (0.00s)
--- FAIL: TestAppendBinaryFromHex/empty_hex_without_unquote (0.00s)
--- PASS: TestAppendBinaryFromHex/single_byte_hex_with_unquote (0.00s)
--- PASS: TestAppendBinaryFromHex/single_byte_hex_without_unquote (0.00s)
--- PASS: TestAppendBinaryFromHex/invalid_hex_with_unquote (0.00s)
--- PASS: TestAppendBinaryFromHex/invalid_hex_without_unquote (0.00s)
--- PASS: TestAppendBinaryFromHex/odd_length_hex_with_unquote (0.00s)
--- PASS: TestAppendBinaryFromHex/odd_length_hex_without_unquote (0.00s)
=== RUN TestUnquote
=== RUN TestUnquote/basic_quoted_string
=== RUN TestUnquote/single_quotes
=== RUN TestUnquote/empty_quotes
--- PASS: TestUnquote (0.00s)
--- PASS: TestUnquote/basic_quoted_string (0.00s)
--- PASS: TestUnquote/single_quotes (0.00s)
--- PASS: TestUnquote/empty_quotes (0.00s)
=== RUN TestNoop
--- PASS: TestNoop (0.00s)
=== RUN TestAppendQuote
=== RUN TestAppendQuote/basic_append_quote
=== RUN TestAppendQuote/empty_dst
=== RUN TestAppendQuote/empty_src
--- PASS: TestAppendQuote (0.00s)
--- PASS: TestAppendQuote/basic_append_quote (0.00s)
--- PASS: TestAppendQuote/empty_dst (0.00s)
--- PASS: TestAppendQuote/empty_src (0.00s)
=== RUN TestQuote
=== RUN TestQuote/basic_quote
=== RUN TestQuote/empty_dst
--- PASS: TestQuote (0.00s)
--- PASS: TestQuote/basic_quote (0.00s)
--- PASS: TestQuote/empty_dst (0.00s)
=== RUN TestAppendSingleQuote
=== RUN TestAppendSingleQuote/basic_single_quote
=== RUN TestAppendSingleQuote/empty_src
--- PASS: TestAppendSingleQuote (0.00s)
--- PASS: TestAppendSingleQuote/basic_single_quote (0.00s)
--- PASS: TestAppendSingleQuote/empty_src (0.00s)
=== RUN TestAppendBackticks
=== RUN TestAppendBackticks/basic_backticks
=== RUN TestAppendBackticks/empty_src
--- PASS: TestAppendBackticks (0.00s)
--- PASS: TestAppendBackticks/basic_backticks (0.00s)
--- PASS: TestAppendBackticks/empty_src (0.00s)
=== RUN TestAppendBrace
=== RUN TestAppendBrace/basic_braces
=== RUN TestAppendBrace/empty_src
--- PASS: TestAppendBrace (0.00s)
--- PASS: TestAppendBrace/basic_braces (0.00s)
--- PASS: TestAppendBrace/empty_src (0.00s)
=== RUN TestAppendParenthesis
=== RUN TestAppendParenthesis/basic_parenthesis
=== RUN TestAppendParenthesis/empty_src
--- PASS: TestAppendParenthesis (0.00s)
--- PASS: TestAppendParenthesis/basic_parenthesis (0.00s)
--- PASS: TestAppendParenthesis/empty_src (0.00s)
=== RUN TestAppendBracket
=== RUN TestAppendBracket/basic_brackets
=== RUN TestAppendBracket/empty_src
--- PASS: TestAppendBracket (0.00s)
--- PASS: TestAppendBracket/basic_brackets (0.00s)
--- PASS: TestAppendBracket/empty_src (0.00s)
=== RUN TestAppendList
=== RUN TestAppendList/basic_list_with_comma
=== RUN TestAppendList/single_item
=== RUN TestAppendList/empty_list
=== RUN TestAppendList/pipe_separator
--- PASS: TestAppendList (0.00s)
--- PASS: TestAppendList/basic_list_with_comma (0.00s)
--- PASS: TestAppendList/single_item (0.00s)
--- PASS: TestAppendList/empty_list (0.00s)
--- PASS: TestAppendList/pipe_separator (0.00s)
FAIL
coverage: 90.7% of statements
exit status 1
FAIL realy.lol/text 0.008s

View File

@@ -112,7 +112,7 @@ func NostrUnescape(dst []byte) (b []byte) {
// anything else after a reverse solidus just preserve it.
default:
dst[w] = dst[r]
dst[w] = '\\'
w++
dst[w] = c
w++

View File

@@ -401,3 +401,46 @@ func BenchmarkNostrEscapeNostrUnescape(b *testing.B) {
}
})
}
func TestNostrUnescapeEdgeCases(t *testing.T) {
tests := []struct {
name string
input []byte
expected []byte
}{
{
name: "unicode escape",
input: []byte(`\u0041`),
expected: []byte(`\u0041`),
},
{
name: "forward slash escape",
input: []byte(`\/`),
expected: []byte(`\/`),
},
{
name: "octal escape",
input: []byte(`\123`),
expected: []byte(`\123`),
},
{
name: "unknown escape",
input: []byte(`\x`),
expected: []byte(`\x`),
},
{
name: "multiple escapes",
input: []byte(`\u0041\n\/\123\x`),
expected: []byte("\\u0041\n\\/\\123\\x"),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := NostrUnescape(tt.input)
if !bytes.Equal(result, tt.expected) {
t.Errorf("NostrUnescape() = %q, want %q", result, tt.expected)
}
})
}
}

View File

@@ -62,7 +62,7 @@ func UnmarshalQuoted(b []byte) (content, rem []byte, err error) {
return
}
rem = b[:]
for ; len(rem) >= 0; rem = rem[1:] {
for ; len(rem) > 0; rem = rem[1:] {
// advance to open quotes
if rem[0] == '"' {
rem = rem[1:]
@@ -133,11 +133,13 @@ func MarshalHexArray(dst []byte, ha [][]byte) (b []byte) {
func UnmarshalHexArray(b []byte, size int) (t [][]byte, rem []byte, err error) {
rem = b
var openBracket bool
for ; len(rem) > 0; rem = rem[1:] {
for len(rem) > 0 {
if rem[0] == '[' {
openBracket = true
rem = rem[1:]
} else if openBracket {
if rem[0] == ',' {
rem = rem[1:]
continue
} else if rem[0] == ']' {
rem = rem[1:]
@@ -153,12 +155,16 @@ func UnmarshalHexArray(b []byte, size int) (t [][]byte, rem []byte, err error) {
return
}
t = append(t, h)
if rem[0] == ']' {
if len(rem) > 0 && rem[0] == ']' {
rem = rem[1:]
// done
return
}
} else {
rem = rem[1:]
}
} else {
rem = rem[1:]
}
}
return
@@ -168,11 +174,13 @@ func UnmarshalHexArray(b []byte, size int) (t [][]byte, rem []byte, err error) {
func UnmarshalStringArray(b []byte) (t [][]byte, rem []byte, err error) {
rem = b
var openBracket bool
for ; len(rem) > 0; rem = rem[1:] {
for len(rem) > 0 {
if rem[0] == '[' {
openBracket = true
rem = rem[1:]
} else if openBracket {
if rem[0] == ',' {
rem = rem[1:]
continue
} else if rem[0] == ']' {
rem = rem[1:]
@@ -183,12 +191,16 @@ func UnmarshalStringArray(b []byte) (t [][]byte, rem []byte, err error) {
return
}
t = append(t, h)
if rem[0] == ']' {
if len(rem) > 0 && rem[0] == ']' {
rem = rem[1:]
// done
return
}
} else {
rem = rem[1:]
}
} else {
rem = rem[1:]
}
}
return

View File

@@ -49,3 +49,506 @@ func TestUnmarshalHexArray(t *testing.T) {
}
}
}
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)
}
})
}
}

173
text/hex_test.go Normal file
View File

@@ -0,0 +1,173 @@
package text
import (
"bytes"
"testing"
)
func TestAppendHexFromBinary(t *testing.T) {
tests := []struct {
name string
dst []byte
src []byte
quote bool
expected []byte
}{
{
name: "basic hex encoding with quote",
dst: []byte("prefix"),
src: []byte{0x01, 0x23, 0x45, 0x67},
quote: true,
expected: []byte(`prefix"01234567"`),
},
{
name: "basic hex encoding without quote",
dst: []byte("prefix"),
src: []byte{0x01, 0x23, 0x45, 0x67},
quote: false,
expected: []byte("prefix01234567"),
},
{
name: "empty src with quote",
dst: []byte("start"),
src: []byte{},
quote: true,
expected: []byte(`start""`),
},
{
name: "empty src without quote",
dst: []byte("start"),
src: []byte{},
quote: false,
expected: []byte("start"),
},
{
name: "single byte with quote",
dst: []byte{},
src: []byte{0xff},
quote: true,
expected: []byte(`"ff"`),
},
{
name: "single byte without quote",
dst: []byte{},
src: []byte{0xff},
quote: false,
expected: []byte("ff"),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := AppendHexFromBinary(tt.dst, tt.src, tt.quote)
if !bytes.Equal(result, tt.expected) {
t.Errorf("AppendHexFromBinary() = %q, want %q", result, tt.expected)
}
})
}
}
func TestAppendBinaryFromHex(t *testing.T) {
tests := []struct {
name string
dst []byte
src []byte
unquote bool
expected []byte
wantErr bool
}{
{
name: "basic hex decoding with unquote",
dst: []byte("prefix"),
src: []byte(`"01234567"`),
unquote: true,
expected: []byte("prefix\x01\x23\x45\x67"),
wantErr: false,
},
{
name: "basic hex decoding without unquote",
dst: []byte("prefix"),
src: []byte("01234567"),
unquote: false,
expected: []byte("prefix\x01\x23\x45\x67"),
wantErr: false,
},
{
name: "empty hex with unquote",
dst: []byte("start"),
src: []byte(`""`),
unquote: true,
expected: []byte("start"),
wantErr: true,
},
{
name: "empty hex without unquote",
dst: []byte("start"),
src: []byte(""),
unquote: false,
expected: []byte("start"),
wantErr: true,
},
{
name: "single byte hex with unquote",
dst: []byte{},
src: []byte(`"ff"`),
unquote: true,
expected: []byte{0xff},
wantErr: false,
},
{
name: "single byte hex without unquote",
dst: []byte{},
src: []byte("ff"),
unquote: false,
expected: []byte{0xff},
wantErr: false,
},
{
name: "invalid hex with unquote",
dst: []byte{},
src: []byte(`"gg"`),
unquote: true,
expected: []byte{},
wantErr: true,
},
{
name: "invalid hex without unquote",
dst: []byte{},
src: []byte("gg"),
unquote: false,
expected: []byte{},
wantErr: true,
},
{
name: "odd length hex with unquote",
dst: []byte{},
src: []byte(`"123"`),
unquote: true,
expected: []byte{},
wantErr: true,
},
{
name: "odd length hex without unquote",
dst: []byte{},
src: []byte("123"),
unquote: false,
expected: []byte{},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result, err := AppendBinaryFromHex(tt.dst, tt.src, tt.unquote)
if (err != nil) != tt.wantErr {
t.Errorf("AppendBinaryFromHex() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !tt.wantErr && !bytes.Equal(result, tt.expected) {
t.Errorf("AppendBinaryFromHex() = %q, want %q", result, tt.expected)
}
})
}
}

View File

@@ -77,7 +77,7 @@ func AppendList(dst []byte, src [][]byte, separator byte,
ac AppendBytesClosure) []byte {
last := len(src) - 1
for i := range src {
dst = append(dst, ac(dst, src[i])...)
dst = ac(dst, src[i])
if i < last {
dst = append(dst, separator)
}

321
text/wrap_test.go Normal file
View File

@@ -0,0 +1,321 @@
package text
import (
"bytes"
"testing"
)
func TestUnquote(t *testing.T) {
tests := []struct {
name string
input []byte
expected []byte
}{
{
name: "basic quoted string",
input: []byte(`"hello"`),
expected: []byte("hello"),
},
{
name: "single quotes",
input: []byte(`'world'`),
expected: []byte("world"),
},
{
name: "empty quotes",
input: []byte(`""`),
expected: []byte(""),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := Unquote(tt.input)
if !bytes.Equal(result, tt.expected) {
t.Errorf("Unquote() = %q, want %q", result, tt.expected)
}
})
}
}
func TestNoop(t *testing.T) {
dst := []byte("hello")
src := []byte("world")
expected := []byte("helloworld")
result := Noop(dst, src)
if !bytes.Equal(result, expected) {
t.Errorf("Noop() = %q, want %q", result, expected)
}
}
func TestAppendQuote(t *testing.T) {
tests := []struct {
name string
dst []byte
src []byte
expected []byte
}{
{
name: "basic append quote",
dst: []byte("prefix"),
src: []byte("content"),
expected: []byte(`prefix"content"`),
},
{
name: "empty dst",
dst: []byte{},
src: []byte("test"),
expected: []byte(`"test"`),
},
{
name: "empty src",
dst: []byte("start"),
src: []byte{},
expected: []byte(`start""`),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := AppendQuote(tt.dst, tt.src, Noop)
if !bytes.Equal(result, tt.expected) {
t.Errorf("AppendQuote() = %q, want %q", result, tt.expected)
}
})
}
}
func TestQuote(t *testing.T) {
tests := []struct {
name string
dst []byte
src []byte
expected []byte
}{
{
name: "basic quote",
dst: []byte("prefix"),
src: []byte("content"),
expected: []byte(`prefix"content"`),
},
{
name: "empty dst",
dst: []byte{},
src: []byte("test"),
expected: []byte(`"test"`),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := Quote(tt.dst, tt.src)
if !bytes.Equal(result, tt.expected) {
t.Errorf("Quote() = %q, want %q", result, tt.expected)
}
})
}
}
func TestAppendSingleQuote(t *testing.T) {
tests := []struct {
name string
dst []byte
src []byte
expected []byte
}{
{
name: "basic single quote",
dst: []byte("prefix"),
src: []byte("content"),
expected: []byte("prefix'content'"),
},
{
name: "empty src",
dst: []byte("start"),
src: []byte{},
expected: []byte("start''"),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := AppendSingleQuote(tt.dst, tt.src, Noop)
if !bytes.Equal(result, tt.expected) {
t.Errorf("AppendSingleQuote() = %q, want %q", result, tt.expected)
}
})
}
}
func TestAppendBackticks(t *testing.T) {
tests := []struct {
name string
dst []byte
src []byte
expected []byte
}{
{
name: "basic backticks",
dst: []byte("prefix"),
src: []byte("content"),
expected: []byte("prefix`content`"),
},
{
name: "empty src",
dst: []byte("start"),
src: []byte{},
expected: []byte("start``"),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := AppendBackticks(tt.dst, tt.src, Noop)
if !bytes.Equal(result, tt.expected) {
t.Errorf("AppendBackticks() = %q, want %q", result, tt.expected)
}
})
}
}
func TestAppendBrace(t *testing.T) {
tests := []struct {
name string
dst []byte
src []byte
expected []byte
}{
{
name: "basic braces",
dst: []byte("prefix"),
src: []byte("content"),
expected: []byte("prefix(content)"),
},
{
name: "empty src",
dst: []byte("start"),
src: []byte{},
expected: []byte("start()"),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := AppendBrace(tt.dst, tt.src, Noop)
if !bytes.Equal(result, tt.expected) {
t.Errorf("AppendBrace() = %q, want %q", result, tt.expected)
}
})
}
}
func TestAppendParenthesis(t *testing.T) {
tests := []struct {
name string
dst []byte
src []byte
expected []byte
}{
{
name: "basic parenthesis",
dst: []byte("prefix"),
src: []byte("content"),
expected: []byte("prefix{content}"),
},
{
name: "empty src",
dst: []byte("start"),
src: []byte{},
expected: []byte("start{}"),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := AppendParenthesis(tt.dst, tt.src, Noop)
if !bytes.Equal(result, tt.expected) {
t.Errorf("AppendParenthesis() = %q, want %q", result, tt.expected)
}
})
}
}
func TestAppendBracket(t *testing.T) {
tests := []struct {
name string
dst []byte
src []byte
expected []byte
}{
{
name: "basic brackets",
dst: []byte("prefix"),
src: []byte("content"),
expected: []byte("prefix[content]"),
},
{
name: "empty src",
dst: []byte("start"),
src: []byte{},
expected: []byte("start[]"),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := AppendBracket(tt.dst, tt.src, Noop)
if !bytes.Equal(result, tt.expected) {
t.Errorf("AppendBracket() = %q, want %q", result, tt.expected)
}
})
}
}
func TestAppendList(t *testing.T) {
tests := []struct {
name string
dst []byte
src [][]byte
separator byte
expected []byte
}{
{
name: "basic list with comma",
dst: []byte("list:"),
src: [][]byte{[]byte("a"), []byte("b"), []byte("c")},
separator: ',',
expected: []byte("list:a,b,c"),
},
{
name: "single item",
dst: []byte("item:"),
src: [][]byte{[]byte("single")},
separator: ',',
expected: []byte("item:single"),
},
{
name: "empty list",
dst: []byte("empty:"),
src: [][]byte{},
separator: ',',
expected: []byte("empty:"),
},
{
name: "pipe separator",
dst: []byte{},
src: [][]byte{[]byte("x"), []byte("y")},
separator: '|',
expected: []byte("x|y"),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := AppendList(tt.dst, tt.src, tt.separator, Noop)
if !bytes.Equal(result, tt.expected) {
t.Errorf("AppendList() = %q, want %q", result, tt.expected)
}
})
}
}