Skip to content

Commit d590a5d

Browse files
committed
test(fuzz): add fuzz tests for HexToBytes and BytesToHex functions
1 parent d2331f0 commit d590a5d

File tree

1 file changed

+225
-0
lines changed

1 file changed

+225
-0
lines changed

pkg/utils/helpers_fuzz_test.go

Lines changed: 225 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,225 @@
1+
package utils
2+
3+
import (
4+
"testing"
5+
"unicode/utf8"
6+
)
7+
8+
// FuzzHexToBytes tests the HexToBytes function with random inputs
9+
// to ensure it handles malformed hex strings gracefully without panicking.
10+
func FuzzHexToBytes(f *testing.F) {
11+
// Seed corpus with valid hex strings
12+
f.Add("")
13+
f.Add("ff")
14+
f.Add("0123abcd")
15+
f.Add("ABCD")
16+
f.Add("aBcD")
17+
f.Add("0000")
18+
f.Add("deadbeef")
19+
f.Add("0102030405060708090a0b0c0d0e0f")
20+
21+
// Seed corpus with invalid hex strings
22+
f.Add("xyz")
23+
f.Add("abc") // odd length
24+
f.Add("gg")
25+
f.Add("0x")
26+
f.Add("0xdeadbeef")
27+
f.Add(" ff")
28+
f.Add("ff ")
29+
f.Add("f f")
30+
31+
// Seed corpus with edge cases
32+
f.Add("0")
33+
f.Add("f")
34+
f.Add("00")
35+
f.Add("FF")
36+
f.Add("!@#$")
37+
f.Add("12345") // odd length
38+
f.Add("\x00\x01")
39+
40+
f.Fuzz(func(t *testing.T, hexStr string) {
41+
// Function should not panic on any input
42+
result, err := HexToBytes(hexStr)
43+
44+
// If no error occurred, validate the result
45+
if err == nil {
46+
// Result length should be half the hex string length
47+
expectedLen := len(hexStr) / 2
48+
if len(result) != expectedLen {
49+
t.Errorf("HexToBytes(%q) returned %d bytes, expected %d", hexStr, len(result), expectedLen)
50+
}
51+
52+
// Verify round-trip consistency
53+
hexBack := BytesToHex(result)
54+
// Convert both to lowercase for comparison
55+
if len(hexStr) > 0 {
56+
// Only check if input was valid (even length)
57+
if len(hexStr)%2 == 0 {
58+
lowerInput := ""
59+
for _, ch := range hexStr {
60+
if ch >= 'A' && ch <= 'F' {
61+
lowerInput += string(ch - 'A' + 'a')
62+
} else {
63+
lowerInput += string(ch)
64+
}
65+
}
66+
if hexBack != lowerInput {
67+
// Only report error if both are valid hex
68+
isValidHex := true
69+
for _, ch := range hexStr {
70+
if (ch < '0' || ch > '9') && (ch < 'a' || ch > 'f') && (ch < 'A' || ch > 'F') {
71+
isValidHex = false
72+
break
73+
}
74+
}
75+
if isValidHex {
76+
t.Errorf("Round-trip failed: HexToBytes(%q) -> BytesToHex() = %q", hexStr, hexBack)
77+
}
78+
}
79+
}
80+
}
81+
}
82+
})
83+
}
84+
85+
// FuzzBytesToHex tests the BytesToHex function with random byte slices.
86+
func FuzzBytesToHex(f *testing.F) {
87+
// Seed corpus with various byte sequences
88+
f.Add([]byte{})
89+
f.Add([]byte{0xff})
90+
f.Add([]byte{0x01, 0x23, 0xab, 0xcd})
91+
f.Add([]byte{0x00, 0x00})
92+
f.Add([]byte("hello"))
93+
f.Add([]byte{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f})
94+
95+
// Seed corpus with edge cases
96+
f.Add([]byte{0})
97+
f.Add([]byte{255})
98+
f.Add([]byte{0x80})
99+
f.Add([]byte("\x00"))
100+
f.Add([]byte("\xff\xff\xff"))
101+
102+
f.Fuzz(func(t *testing.T, data []byte) {
103+
// Function should not panic on any input
104+
result := BytesToHex(data)
105+
106+
// Validate result format
107+
if len(result) != len(data)*2 {
108+
t.Errorf("BytesToHex(%v) returned string of length %d, expected %d", data, len(result), len(data)*2)
109+
}
110+
111+
// Result should only contain hex characters (0-9, a-f)
112+
for _, ch := range result {
113+
if (ch < '0' || ch > '9') && (ch < 'a' || ch > 'f') {
114+
t.Errorf("BytesToHex(%v) returned non-hex character: %q", data, ch)
115+
}
116+
}
117+
118+
// Verify round-trip consistency
119+
decoded, err := HexToBytes(result)
120+
if err != nil {
121+
t.Errorf("Round-trip failed: HexToBytes(BytesToHex(%v)) returned error: %v", data, err)
122+
}
123+
if len(decoded) != len(data) {
124+
t.Errorf("Round-trip length mismatch: original %d bytes, decoded %d bytes", len(data), len(decoded))
125+
}
126+
for i := range data {
127+
if i < len(decoded) && decoded[i] != data[i] {
128+
t.Errorf("Round-trip failed at index %d: original %02x, decoded %02x", i, data[i], decoded[i])
129+
break
130+
}
131+
}
132+
})
133+
}
134+
135+
// FuzzUTFBytesToString tests the UTFBytesToString function with random byte sequences,
136+
// including invalid UTF-8 sequences.
137+
func FuzzUTFBytesToString(f *testing.F) {
138+
// Seed corpus with valid UTF-8 strings
139+
f.Add([]byte{})
140+
f.Add([]byte("hello"))
141+
f.Add([]byte("hello world"))
142+
//nolint:gosmopolitan // Test case requires specific UTF-8 characters including Chinese
143+
f.Add([]byte("Hello, 世界"))
144+
f.Add([]byte("Testing 123"))
145+
146+
// Seed corpus with binary data
147+
f.Add([]byte{0x01, 0x02, 0x03})
148+
f.Add([]byte{0x00})
149+
f.Add([]byte{0xff, 0xfe, 0xfd})
150+
151+
// Seed corpus with edge cases
152+
// Invalid UTF-8 continuation byte
153+
f.Add([]byte{0x80})
154+
// Overlong encoding
155+
f.Add([]byte{0xc0, 0x80})
156+
// UTF-16 surrogate
157+
f.Add([]byte{0xed, 0xa0, 0x80})
158+
// Code point out of range
159+
f.Add([]byte{0xf4, 0x90, 0x80, 0x80})
160+
161+
f.Fuzz(func(t *testing.T, data []byte) {
162+
// Function should not panic on any input
163+
result := UTFBytesToString(data)
164+
165+
// Validate that the result is a valid string
166+
// The result is always valid, we just ensure no panic occurs
167+
_ = result
168+
169+
// If input is valid UTF-8, output should match exactly
170+
if utf8.Valid(data) {
171+
if result != string(data) {
172+
t.Errorf("UTFBytesToString(%v) = %q, expected %q", data, result, string(data))
173+
}
174+
}
175+
176+
// Round-trip should preserve the original bytes
177+
roundTrip := []byte(result)
178+
if len(roundTrip) != len(data) {
179+
// This is expected for invalid UTF-8, as Go replaces invalid sequences
180+
// We just ensure no panic occurs
181+
return
182+
}
183+
})
184+
}
185+
186+
// FuzzFlattenFields tests the flattenFields function with random field collections.
187+
func FuzzFlattenFields(f *testing.F) {
188+
// Seed corpus with various field configurations
189+
f.Add([]byte{}, []byte{})
190+
f.Add([]byte("hello"), []byte{})
191+
f.Add([]byte("hello"), []byte("world"))
192+
f.Add([]byte{0x01, 0x02}, []byte{0x03, 0x04})
193+
194+
f.Fuzz(func(t *testing.T, field1, field2 []byte) {
195+
// Create a TokenFields with the fuzzed data
196+
fields := TokenFields{field1, field2}
197+
198+
// Function should not panic on any input
199+
result := flattenFields(fields)
200+
201+
// Validate result length
202+
expectedLen := len(field1) + len(field2)
203+
if len(result) != expectedLen {
204+
t.Errorf("flattenFields(%v) returned %d bytes, expected %d", fields, len(result), expectedLen)
205+
}
206+
207+
// Verify content is concatenated correctly
208+
if len(result) >= len(field1) {
209+
for i := range field1 {
210+
if result[i] != field1[i] {
211+
t.Errorf("flattenFields(%v) first field mismatch at index %d", fields, i)
212+
break
213+
}
214+
}
215+
}
216+
if len(result) >= len(field1)+len(field2) {
217+
for i := range field2 {
218+
if result[len(field1)+i] != field2[i] {
219+
t.Errorf("flattenFields(%v) second field mismatch at index %d", fields, i)
220+
break
221+
}
222+
}
223+
}
224+
})
225+
}

0 commit comments

Comments
 (0)