Skip to content

Commit 1f0c194

Browse files
test(flux-shard-operator): add fuzz targets for shard config parsers
Native Go fuzzing over the flag parsers in config.go, asserting the invariants that matter rather than just no-panic: - ParseShardIndex round-trips with ShardName (accepted input is the canonical name of the returned index; index is non-negative). - ShardName round-trips back through ParseShardIndex for every valid index. - ParseShardCount: a non-auto success yields count >= 1; auto yields 0. - ParsePinnedTenants: every entry in a success has a non-empty key mapped to a valid shard name. Adds an OSS-Fuzz-detectable fuzzing signal for the OpenSSF Scorecard Fuzzing check while covering parsers that sit on the sharding hot path. Signed-off-by: Myasnikov Daniil <[email protected]> Assisted-By: Claude <[email protected]> Co-Authored-By: Claude Opus 4.8 <[email protected]>
1 parent a0bd53f commit 1f0c194

1 file changed

Lines changed: 91 additions & 0 deletions

File tree

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
package fluxshardoperator
2+
3+
import "testing"
4+
5+
// FuzzParseShardIndex checks that parsing an arbitrary shard-key value never
6+
// panics and that a successful parse round-trips: if ParseShardIndex accepts a
7+
// string it must be the canonical name of the index it returns.
8+
func FuzzParseShardIndex(f *testing.F) {
9+
for _, seed := range []string{"", "shard0", "shard10", "shard-1", "shard00", "tenants", "shard", "shardx", "shard 1"} {
10+
f.Add(seed)
11+
}
12+
f.Fuzz(func(t *testing.T, s string) {
13+
i, ok := ParseShardIndex(s)
14+
if !ok {
15+
return
16+
}
17+
if i < 0 {
18+
t.Fatalf("ParseShardIndex(%q) returned ok with negative index %d", s, i)
19+
}
20+
if got := ShardName(i); got != s {
21+
t.Fatalf("ParseShardIndex(%q)=%d but ShardName(%d)=%q, not a round-trip", s, i, i, got)
22+
}
23+
})
24+
}
25+
26+
// FuzzShardNameRoundTrip checks the inverse direction: every canonical name
27+
// produced by ShardName must parse back to the same non-negative index.
28+
func FuzzShardNameRoundTrip(f *testing.F) {
29+
for _, seed := range []int{0, 1, 9, 10, 128, 1000} {
30+
f.Add(seed)
31+
}
32+
f.Fuzz(func(t *testing.T, i int) {
33+
if i < 0 {
34+
return // negative indices are not valid shards; ShardName is only defined for i >= 0
35+
}
36+
got, ok := ParseShardIndex(ShardName(i))
37+
if !ok || got != i {
38+
t.Fatalf("ShardName(%d) did not round-trip: ParseShardIndex=%d ok=%v", i, got, ok)
39+
}
40+
})
41+
}
42+
43+
// FuzzParseShardCount checks that the --shard-count flag parser never panics and
44+
// upholds its contract: a successful non-auto parse yields a positive count, and
45+
// the auto sentinel yields count 0.
46+
func FuzzParseShardCount(f *testing.F) {
47+
for _, seed := range []string{"", "auto", "AUTO", " auto ", "0", "1", "-1", "3", " 5 ", "abc", "9999999999999999999"} {
48+
f.Add(seed)
49+
}
50+
f.Fuzz(func(t *testing.T, s string) {
51+
count, auto, err := ParseShardCount(s)
52+
if err != nil {
53+
return
54+
}
55+
if auto {
56+
if count != 0 {
57+
t.Fatalf("ParseShardCount(%q): auto=true must yield count 0, got %d", s, count)
58+
}
59+
return
60+
}
61+
if count < 1 {
62+
t.Fatalf("ParseShardCount(%q): non-auto success must yield count >= 1, got %d", s, count)
63+
}
64+
})
65+
}
66+
67+
// FuzzParsePinnedTenants checks that the --pinned-tenants flag parser never
68+
// panics and that every entry in a successful parse is well-formed: a non-empty
69+
// tenant key mapped to a value that is itself a valid shard name.
70+
func FuzzParsePinnedTenants(f *testing.F) {
71+
for _, seed := range []string{
72+
"", "a=shard0", "a=shard0,b=shard1", " a = shard0 ", "a=", "=shard0",
73+
"a=shardx", "a", "a=shard0,", ",", "a=shard0,b",
74+
} {
75+
f.Add(seed)
76+
}
77+
f.Fuzz(func(t *testing.T, s string) {
78+
pinned, err := ParsePinnedTenants(s)
79+
if err != nil {
80+
return
81+
}
82+
for k, v := range pinned {
83+
if k == "" {
84+
t.Fatalf("ParsePinnedTenants(%q) produced an empty tenant key", s)
85+
}
86+
if _, ok := ParseShardIndex(v); !ok {
87+
t.Fatalf("ParsePinnedTenants(%q) mapped %q to invalid shard %q", s, k, v)
88+
}
89+
}
90+
})
91+
}

0 commit comments

Comments
 (0)