Repository navigation
Expand file tree
/
Copy pathfuzz_test.go
More file actions
264 lines (236 loc) · 8.9 KB
/
Copy pathfuzz_test.go
File metadata and controls
264 lines (236 loc) · 8.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
package dotenv
import (
"strings"
"testing"
)
// FuzzParseRender asserts the package's central invariant against arbitrary
// input: parsing and re-rendering an untouched file returns it byte for byte.
//
// Example-based tests only cover the shapes someone thought of. Any input at all
// is valid here, since an unrecognised line is meant to survive verbatim rather
// than be normalised.
func FuzzParseRender(f *testing.F) {
for _, s := range []string{
"", "\n", "A=1\n", "A=1", "# c\n\nA=1\n",
"A=\"multi\nline\"\n", "A='lit'\n", "A=`tick`\n",
"A=1\r\nB=2\r\n", "export A=1 # note\n", "not a pair\n",
"A=${B}\n", "A=$$\n", "A=\"unterminated\n", "=bad\n",
"A=a=b\n", " \n\t\n", "A=héllo\n", "A=\\\n",
} {
f.Add(s)
}
f.Fuzz(func(t *testing.T, in string) {
if got := Parse(in).Render(); got != in {
t.Errorf("round trip changed the input:\n got: %q\nwant: %q", got, in)
}
})
}
// FuzzParseIsIdempotent asserts re-parsing rendered output yields the same
// result, so a file cannot drift by being processed repeatedly.
func FuzzParseIsIdempotent(f *testing.F) {
for _, s := range []string{"A=1\n", "A=\"x\ny\"\n", "# c\nA=1\n", "A=`t`\n"} {
f.Add(s)
}
f.Fuzz(func(t *testing.T, in string) {
once := Parse(in).Render()
if twice := Parse(once).Render(); once != twice {
t.Errorf("parsing is not idempotent:\n once: %q\ntwice: %q", once, twice)
}
})
}
// FuzzSetRoundTrip is the editor's guarantee: whatever value Set writes, the
// parser reads back identically, for ANY value — both the raw value and its
// expanded meaning.
//
// This is the property that caught the backtick-quoting bug — the renderer had
// not learned about a delimiter the parser had.
func FuzzSetRoundTrip(f *testing.F) {
for _, s := range []string{
"", "simple", "with space", "multi\nline", `"quoted"`, "'single'",
"`backtick`", `back\slash`, "#hash", "$VAR", "a=b", "héllo", "\t",
} {
f.Add(s)
}
f.Fuzz(func(t *testing.T, value string) {
// A carriage return cannot survive: the format is line-based, so the
// parser reads it as a terminator. Skipping documents the one genuine
// limit rather than hiding it.
if strings.ContainsRune(value, '\r') {
t.Skip("carriage returns are line terminators, not value content")
}
// Each source style the edited entry may start from: bare, a literal
// quote with a spaced comment, and a double quote with a tight one.
for _, src := range []string{"K=placeholder\n", "A=x\nK='p' # c\n", "A=x\nK=\"p\"#c\n"} {
file := Parse(src)
file.Set("K", value)
rendered := file.Render()
reread := Parse(rendered)
back, ok := reread.Get("K")
if !ok {
t.Fatalf("Set(%q) produced output with no K:\n%q", value, rendered)
}
if back != value {
t.Errorf("Set(%q) → Get = %q\nrendered: %q", value, back, rendered)
}
// The meaning must survive too: what GetExpanded reports in this
// session is what it reports after the file is read again.
mem, _, memErr := file.GetExpanded("K")
disk, _, diskErr := reread.GetExpanded("K")
if mem != disk || (memErr == nil) != (diskErr == nil) {
t.Errorf("Set(%q): GetExpanded in memory = %q (%v), re-read = %q (%v)\nrendered: %q", value, mem, memErr, disk, diskErr, rendered)
}
}
})
}
// FuzzSetIsIdempotent asserts a repeated edit produces no churn, which is what
// makes running a tool twice safe in version control.
func FuzzSetIsIdempotent(f *testing.F) {
for _, s := range []string{"", "x", "with space", "multi\nline"} {
f.Add(s)
}
f.Fuzz(func(t *testing.T, value string) {
if strings.ContainsRune(value, '\r') {
t.Skip("carriage returns are line terminators, not value content")
}
file := Parse("K=start\n# keep me\n")
file.Set("K", value)
once := file.Render()
file.Set("K", value)
if twice := file.Render(); twice != once {
t.Errorf("Set(%q) twice differs:\n once: %q\ntwice: %q", value, once, twice)
}
if !strings.Contains(once, "# keep me") {
t.Errorf("Set(%q) destroyed the comment:\n%q", value, once)
}
})
}
// FuzzExpandTerminates asserts expansion always halts and never panics, whatever
// tangle of references the input contains.
func FuzzExpandTerminates(f *testing.F) {
for _, s := range []string{
"A=${B}\nB=${A}\n", "A=${A}\n", "A=${B:-${C:-${A}}}\n",
"A=$$\n", "A=${\n", "A=$(\n", "A=${:-}\n", "A=$\n",
"A=${B:?}\n", "A=${B:+${C}}\n", "A=$$$$$\n",
} {
f.Add(s)
}
f.Fuzz(func(t *testing.T, in string) {
file := Parse(in)
// A panic or a hang is the failure; the values are unchecked because
// arbitrary input has no expected output.
for _, k := range file.Keys() {
_, _, _ = file.GetExpanded(k)
}
_, _ = file.ExpandedMap()
})
}
// FuzzAppendRoundTrip extends the editor's guarantee to literal placement:
// whatever Append writes, the parser reads back identically.
func FuzzAppendRoundTrip(f *testing.F) {
for _, s := range []string{"", "x", "with space", "multi\nline", "#hash", "`tick`"} {
f.Add("A=1\n", s)
}
f.Add("", "v")
f.Add("A=1\r\n", "v")
f.Fuzz(func(t *testing.T, src, value string) {
if strings.ContainsRune(value, '\r') {
t.Skip("carriage returns are line terminators, not value content")
}
// A source ending inside an unterminated quote swallows whatever is
// appended, because the open quote keeps consuming lines. The file was
// already malformed; nothing appended to it can be found again. Probe
// for that rather than guessing from the text — see
// TestAppend_UnterminatedQuoteSwallowsWhatFollows.
probe := Parse(src)
probe.Append(NewPair("VOLT_PROBE", "1"))
if !Parse(probe.Render()).Has("VOLT_PROBE") {
t.Skip("source ends inside an unterminated quote")
}
file := Parse(src)
file.Append(NewComment("note"), NewPair("APPENDED", value), NewBlank())
rendered := file.Render()
back, ok := Parse(rendered).Get("APPENDED")
if !ok {
t.Fatalf("Append produced output with no APPENDED:\n%q", rendered)
}
if back != value {
t.Errorf("Append(%q) → Get = %q\nrendered: %q", value, back, rendered)
}
// Everything that was already there must survive untouched.
if !strings.HasPrefix(rendered, Parse(src).Render()) {
t.Errorf("Append disturbed the existing content:\n%q", rendered)
}
})
}
// FuzzDisableEnableIsReversible asserts Unset followed by Restore returns the
// file to its exact original bytes — the property that makes commenting a key
// out a safe, undoable operation rather than a lossy one.
func FuzzDisableEnableIsReversible(f *testing.F) {
for _, s := range []string{
"A=1\n", " A = 1 \n", "export A=1 # note\n", "A=\"one\ntwo\"\n",
"A=1\r\n", "A=\n", "A='lit'\n", "A=`tick`\n", "#A=1\n", " # A=1\n",
} {
f.Add(s)
}
f.Fuzz(func(t *testing.T, src string) {
file := Parse(src)
if !file.Has("A") {
t.Skip("nothing to disable")
}
// Restore targets the LAST disabled entry for the key, so a disabled
// entry sitting after the one Unset just created is what comes back.
// The round trip is exact only when there is no such entry — see
// TestRestore_TargetsTheLastDisabledEntry.
for _, d := range file.Disabled() {
if d.Key == "A" {
t.Skip("an existing disabled A would be restored instead")
}
}
before := file.Render()
if !file.Unset("A", false) {
t.Fatal("Unset reported not found for an active key")
}
if !file.Restore("A") {
t.Fatalf("Restore could not reverse Unset:\n%q", file.Render())
}
if after := file.Render(); after != before {
t.Errorf("disable → enable was lossy:\n before: %q\n after: %q", before, after)
}
})
}
// FuzzPluginsNeverAffectBytes is the firewall as a property: for ANY input, a
// file parsed with every value-changing hook installed must render to the same
// bytes as one parsed with none.
//
// The example-based version covers one crafted file. This covers the ones nobody
// thought of, which is where a future hook would breach the rule unnoticed.
func FuzzPluginsNeverAffectBytes(f *testing.F) {
for _, s := range []string{
"A=1\n", "A=${B}\n", "A=\"$(cmd)\"\n", "# A=disabled\n",
"A='literal'\n", "A=`tick`\n", "A=\"multi\nline\"\n", "", "\n",
"A=1\r\nB=${A}\r\n", "not a pair\n", "A=$$\n",
} {
f.Add(s)
}
f.Fuzz(func(t *testing.T, src string) {
plain := Parse(src)
hooked := Parse(src,
WithLookup(func(string) (string, bool) { return "SUPPLIED", true }),
WithValueTransform(func(_, v string) (string, error) { return "MANGLED-" + v, nil }),
WithCommandRunner(func(string) (string, error) { return "RAN", nil }),
)
if plain.Render() != hooked.Render() {
t.Errorf("plugins changed the bytes:\n plain: %q\nhooked: %q", plain.Render(), hooked.Render())
}
// Reading is allowed to differ, and must terminate rather than hang.
for _, k := range hooked.Keys() {
_, _, _ = hooked.GetExpanded(k)
}
// An edit with hooks installed must also match one without.
plain.Set("PROBE", "v")
hooked.Set("PROBE", "v")
if plain.Render() != hooked.Render() {
t.Errorf("plugins changed the bytes after an edit:\n plain: %q\nhooked: %q", plain.Render(), hooked.Render())
}
})
}