Skip to content

Commit d05a293

Browse files
committed
add fuzz testing
1 parent b40ca14 commit d05a293

3 files changed

Lines changed: 248 additions & 0 deletions

File tree

fuzz_test.go

Lines changed: 240 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,240 @@
1+
package wrap_test
2+
3+
import (
4+
"strings"
5+
"testing"
6+
"unicode/utf8"
7+
8+
"github.com/bbrks/wrap/v2"
9+
)
10+
11+
func FuzzWrap(f *testing.F) {
12+
// Seed corpus with interesting cases
13+
f.Add("hello world", 10)
14+
f.Add("hello-world-foo-bar", 8)
15+
f.Add("", 5)
16+
f.Add("a", 1)
17+
f.Add("日本語テスト", 3)
18+
f.Add(" leading spaces", 10)
19+
f.Add("trailing spaces ", 10)
20+
f.Add("multiple spaces", 5)
21+
f.Add("line1\nline2\nline3", 5)
22+
f.Add("no-break-points-here", 5)
23+
f.Add("😀😀😀😀", 2)
24+
25+
// Edge cases
26+
f.Add("", 0)
27+
f.Add("", -1)
28+
f.Add("a", 0)
29+
f.Add("a", -1)
30+
f.Add("a", 1000000)
31+
f.Add(strings.Repeat("a", 10000), 10)
32+
f.Add(strings.Repeat("a ", 1000), 5)
33+
f.Add(strings.Repeat("日本語 ", 500), 3)
34+
35+
// Pathological cases
36+
f.Add("\n\n\n", 1)
37+
f.Add(" \n \n ", 2)
38+
f.Add("-", 1)
39+
f.Add("---", 1)
40+
f.Add("- - - -", 1)
41+
f.Add("\t\t\t", 2)
42+
f.Add("a\nb\nc\nd\ne", 1)
43+
f.Add(strings.Repeat("\n", 100), 5)
44+
f.Add(strings.Repeat(" ", 100), 5)
45+
f.Add(strings.Repeat("-", 100), 5)
46+
47+
// Mixed multibyte
48+
f.Add("aあbいcうdえeお", 2)
49+
f.Add("🇺🇸🇬🇧🇯🇵", 2) // Flag emojis (multi-codepoint)
50+
f.Add("👨‍👩‍👧‍👦", 1) // Family emoji (ZWJ sequence)
51+
f.Add("e\u0301", 1) // e + combining acute accent
52+
f.Add("한글테스트", 3) // Korean
53+
f.Add("مرحبا", 2) // Arabic (RTL)
54+
f.Add("שלום", 2) // Hebrew (RTL)
55+
56+
// Breakpoint edge cases
57+
f.Add("a-b-c-d-e", 1)
58+
f.Add("a - b - c", 2)
59+
f.Add("----", 2)
60+
f.Add(" ", 2)
61+
f.Add("a--b", 2)
62+
f.Add("a b", 2)
63+
f.Add("-a-", 2)
64+
f.Add(" a ", 2)
65+
66+
f.Fuzz(func(t *testing.T, input string, limit int) {
67+
// Skip invalid UTF-8 inputs
68+
if !utf8.ValidString(input) {
69+
t.Skip()
70+
}
71+
72+
w := wrap.NewWrapper()
73+
result := w.Wrap(input, limit)
74+
75+
// Result must be valid UTF-8
76+
if !utf8.ValidString(result) {
77+
t.Errorf("result is not valid UTF-8: %q", result)
78+
}
79+
80+
// Test with StripTrailingNewline
81+
w.StripTrailingNewline = true
82+
result2 := w.Wrap(input, limit)
83+
if !utf8.ValidString(result2) {
84+
t.Errorf("result with StripTrailingNewline is not valid UTF-8: %q", result2)
85+
}
86+
87+
// Test with CutLongWords
88+
w.CutLongWords = true
89+
result3 := w.Wrap(input, limit)
90+
if !utf8.ValidString(result3) {
91+
t.Errorf("result with CutLongWords is not valid UTF-8: %q", result3)
92+
}
93+
94+
// With CutLongWords and positive limit, lines should not exceed limit by more than 1
95+
// (the +1 accounts for preserved hyphens at line breaks)
96+
if limit > 0 {
97+
for _, line := range strings.Split(strings.TrimSuffix(result3, "\n"), "\n") {
98+
lineLen := utf8.RuneCountInString(line)
99+
// Allow +1 for hyphen preservation at end of line
100+
if lineLen > limit+1 {
101+
t.Errorf("line exceeds limit %d by more than 1: %q (len=%d)", limit, line, lineLen)
102+
}
103+
}
104+
}
105+
})
106+
}
107+
108+
func FuzzWrapWithOptions(f *testing.F) {
109+
// Seed with combinations of input, limit, prefix, suffix
110+
f.Add("hello world", 10, "// ", "", " -")
111+
f.Add("hello world", 15, "/* ", " */", " -")
112+
f.Add("test input", 5, "> ", "", " ")
113+
f.Add("日本語", 4, "- ", "", " -")
114+
f.Add("a-b-c", 3, "", "", "-")
115+
f.Add("a|b|c", 3, "", "", "|")
116+
f.Add("test", 10, ">>>", "<<<", " ")
117+
f.Add("", 5, "prefix", "suffix", " -")
118+
f.Add("no breaks here", 5, "", "", "")
119+
f.Add(strings.Repeat("word ", 100), 20, "# ", "", " ")
120+
121+
// Extreme prefix/suffix
122+
f.Add("x", 100, strings.Repeat("p", 50), strings.Repeat("s", 50), " ")
123+
f.Add("x", 10, strings.Repeat("p", 100), "", " ")
124+
125+
// Custom breakpoints
126+
f.Add("a,b,c,d,e", 3, "", "", ",")
127+
f.Add("a::b::c", 4, "", "", ":")
128+
f.Add("path/to/file", 5, "", "", "/")
129+
f.Add("CamelCaseText", 5, "", "", "ABCDEFGHIJKLMNOPQRSTUVWXYZ")
130+
131+
f.Fuzz(func(t *testing.T, input string, limit int, prefix, suffix, breakpoints string) {
132+
// Skip invalid UTF-8 inputs
133+
if !utf8.ValidString(input) || !utf8.ValidString(prefix) || !utf8.ValidString(suffix) || !utf8.ValidString(breakpoints) {
134+
t.Skip()
135+
}
136+
137+
w := wrap.NewWrapper()
138+
w.OutputLinePrefix = prefix
139+
w.OutputLineSuffix = suffix
140+
w.Breakpoints = breakpoints
141+
142+
result := w.Wrap(input, limit)
143+
144+
// Result must be valid UTF-8
145+
if !utf8.ValidString(result) {
146+
t.Errorf("result is not valid UTF-8: %q", result)
147+
}
148+
149+
// Test all option combinations
150+
for _, strip := range []bool{false, true} {
151+
for _, cut := range []bool{false, true} {
152+
for _, includeLimit := range []bool{false, true} {
153+
w.StripTrailingNewline = strip
154+
w.CutLongWords = cut
155+
w.LimitIncludesPrefixSuffix = includeLimit
156+
157+
result := w.Wrap(input, limit)
158+
if !utf8.ValidString(result) {
159+
t.Errorf("result is not valid UTF-8 with strip=%v cut=%v includeLimit=%v: %q",
160+
strip, cut, includeLimit, result)
161+
}
162+
}
163+
}
164+
}
165+
})
166+
}
167+
168+
func FuzzWrapCustomNewline(f *testing.F) {
169+
f.Add("hello world", 5, "\n")
170+
f.Add("hello world", 5, "\r\n")
171+
f.Add("hello world", 5, "<br>")
172+
f.Add("line1\nline2", 10, "\n")
173+
f.Add("line1\r\nline2", 10, "\r\n")
174+
f.Add("line1<br>line2", 10, "<br>")
175+
f.Add(strings.Repeat("word ", 50), 10, "|||")
176+
f.Add("日本語テスト", 3, "→")
177+
178+
f.Fuzz(func(t *testing.T, input string, limit int, newline string) {
179+
if !utf8.ValidString(input) || !utf8.ValidString(newline) {
180+
t.Skip()
181+
}
182+
183+
w := wrap.NewWrapper()
184+
w.Newline = newline
185+
186+
result := w.Wrap(input, limit)
187+
188+
if !utf8.ValidString(result) {
189+
t.Errorf("result is not valid UTF-8: %q", result)
190+
}
191+
192+
// Test with various option combinations
193+
for _, strip := range []bool{false, true} {
194+
for _, cut := range []bool{false, true} {
195+
w.StripTrailingNewline = strip
196+
w.CutLongWords = cut
197+
198+
result := w.Wrap(input, limit)
199+
if !utf8.ValidString(result) {
200+
t.Errorf("result is not valid UTF-8 with strip=%v cut=%v: %q", strip, cut, result)
201+
}
202+
}
203+
}
204+
})
205+
}
206+
207+
func FuzzWrapTrimOptions(f *testing.F) {
208+
f.Add("// hello world", 10, "// ", "")
209+
f.Add("/* comment */", 20, "/* ", " */")
210+
f.Add("# heading", 15, "# ", "")
211+
f.Add("> quote", 10, "> ", "")
212+
f.Add(" indented ", 10, " ", " ")
213+
f.Add("prefix text suffix", 10, "prefix ", " suffix")
214+
f.Add("// line1\n// line2", 20, "// ", "")
215+
216+
f.Fuzz(func(t *testing.T, input string, limit int, trimPrefix, trimSuffix string) {
217+
if !utf8.ValidString(input) || !utf8.ValidString(trimPrefix) || !utf8.ValidString(trimSuffix) {
218+
t.Skip()
219+
}
220+
221+
w := wrap.NewWrapper()
222+
w.TrimInputPrefix = trimPrefix
223+
w.TrimInputSuffix = trimSuffix
224+
225+
result := w.Wrap(input, limit)
226+
227+
if !utf8.ValidString(result) {
228+
t.Errorf("result is not valid UTF-8: %q", result)
229+
}
230+
231+
// Combined with output prefix/suffix
232+
w.OutputLinePrefix = trimPrefix
233+
w.OutputLineSuffix = trimSuffix
234+
result2 := w.Wrap(input, limit)
235+
236+
if !utf8.ValidString(result2) {
237+
t.Errorf("result with output prefix/suffix is not valid UTF-8: %q", result2)
238+
}
239+
})
240+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
go test fuzz v1
2+
string("0000000000")
3+
int(8)
4+
string("00000000000")
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
go test fuzz v1
2+
string("01")
3+
int(1)
4+
string("00")

0 commit comments

Comments
 (0)