Skip to content

Commit 5c2ce61

Browse files
authored
test: replace dvyukov/go-fuzz with native Go fuzzing (#153)
1 parent 1ff0985 commit 5c2ce61

129 files changed

Lines changed: 372 additions & 250 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/dependabot.yml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,5 @@ updates:
33
- package-ecosystem: "gomod"
44
directories:
55
- "/"
6-
- "/fuzz/"
76
schedule:
87
interval: "monthly"

fuzz/.gitignore

Lines changed: 0 additions & 4 deletions
This file was deleted.

fuzz/Makefile

Lines changed: 0 additions & 9 deletions
This file was deleted.

fuzz/README.md

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,15 @@
11
# Fuzzer to validate the AMT
22

3-
To fuzz, run `make`.
3+
Uses native Go fuzzing. Random byte input is decoded as a sequence of AMT
4+
operations (set, get, delete, flush, reload), mirrored into a plain map, and
5+
the AMT is checked against the map by iteration, random gets, and root CID
6+
reconstruction.
7+
8+
To fuzz:
9+
10+
go test -fuzz=FuzzAMTOps .
11+
12+
Seed corpus lives in `testdata/fuzz/FuzzAMTOps/` and is replayed by plain
13+
`go test`; commit minimised failing inputs there as regression tests. To
14+
trace ops while reproducing a failure, set `debug = true` in
15+
`checked_amt_test.go` and run with `-v`.
Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,23 @@ package fuzzer
22

33
import (
44
"context"
5-
"fmt"
65
"math/rand"
6+
"testing"
77

88
cbor "github.com/ipfs/go-ipld-cbor"
99
cbg "github.com/whyrusleeping/cbor-gen"
1010

1111
"github.com/filecoin-project/go-amt-ipld/v4"
1212
)
1313

14+
// Set true to log each op when reproducing a failure with -v.
15+
const debug = false
16+
17+
// checkedAMT mirrors every mutation into a plain map and verifies the AMT
18+
// against it: by iteration, by random get, and by reconstructing from
19+
// scratch to the same root CID.
1420
type checkedAMT struct {
21+
t *testing.T
1522
amt *amt.Root
1623
step uint64
1724
bs cbor.IpldStore
@@ -21,13 +28,14 @@ type checkedAMT struct {
2128
seen map[uint64]struct{}
2229
}
2330

24-
func newCheckedAMT() (*checkedAMT, error) {
31+
func newCheckedAMT(t *testing.T) (*checkedAMT, error) {
2532
bs := cbor.NewCborStore(newMockBlocks())
2633
root, err := amt.NewAMT(bs)
2734
if err != nil {
2835
return nil, err
2936
}
3037
return &checkedAMT{
38+
t: t,
3139
amt: root,
3240
bs: bs,
3341
array: make(map[uint64]cbg.CborInt),
@@ -126,8 +134,8 @@ func (c *checkedAMT) reload() {
126134

127135
func (c *checkedAMT) trace(msg string, args ...interface{}) {
128136
c.step++
129-
if Debug {
130-
fmt.Printf("step %d: "+msg+"\n", append([]interface{}{c.step}, args...)...)
137+
if debug {
138+
c.t.Logf("step %d: "+msg, append([]interface{}{c.step}, args...)...)
131139
}
132140
}
133141

@@ -195,7 +203,7 @@ func (c *checkedAMT) checkByGet(array *amt.Root) {
195203
found, err := array.Get(context.Background(), k, &actual)
196204
c.checkErr(err)
197205
if !found {
198-
c.fail("expected to find key %s", k)
206+
c.fail("expected to find key %d", k)
199207
}
200208
c.checkEq(c.array[k], actual)
201209
}
@@ -228,5 +236,6 @@ func (c *checkedAMT) checkByIter(array *amt.Root) {
228236
}
229237

230238
func (c *checkedAMT) fail(msg string, args ...interface{}) {
231-
panic(fmt.Sprintf("step %d: "+msg, append([]interface{}{c.step}, args...)...))
239+
c.t.Helper()
240+
c.t.Fatalf("step %d: "+msg, append([]interface{}{c.step}, args...)...)
232241
}

fuzz/fuzz_test.go

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
package fuzzer
2+
3+
import (
4+
"encoding/binary"
5+
"testing"
6+
7+
"github.com/filecoin-project/go-amt-ipld/v4"
8+
cbg "github.com/whyrusleeping/cbor-gen"
9+
)
10+
11+
type opCode byte
12+
13+
const (
14+
opSet opCode = iota
15+
opSetSeen
16+
opGet
17+
opGetSeen
18+
opDelete
19+
opDeleteSeen
20+
opFlush
21+
opReload
22+
opMax
23+
)
24+
25+
type op struct {
26+
code opCode
27+
key uint64
28+
value cbg.CborInt
29+
}
30+
31+
func parseOps(data []byte) (ops []op) {
32+
scratch := make([]byte, 17)
33+
34+
for len(data) > 0 {
35+
n := copy(scratch, data)
36+
data = data[n:]
37+
38+
code := opCode(scratch[0] % byte(opMax))
39+
k := binary.LittleEndian.Uint64(scratch[1:]) % amt.MaxIndex
40+
v := binary.LittleEndian.Uint64(scratch[9:])
41+
ops = append(ops, op{code, k, cbg.CborInt(v)})
42+
}
43+
return ops
44+
}
45+
46+
func encodeOp(code opCode, key, value uint64) []byte {
47+
buf := make([]byte, 17)
48+
buf[0] = byte(code)
49+
binary.LittleEndian.PutUint64(buf[1:], key)
50+
binary.LittleEndian.PutUint64(buf[9:], value)
51+
return buf
52+
}
53+
54+
func seed(ops ...[]byte) (data []byte) {
55+
for _, op := range ops {
56+
data = append(data, op...)
57+
}
58+
return data
59+
}
60+
61+
func FuzzAMTOps(f *testing.F) {
62+
f.Add(seed(
63+
encodeOp(opSet, 0, 100),
64+
encodeOp(opSet, 1, 101),
65+
encodeOp(opGet, 0, 0),
66+
encodeOp(opFlush, 0, 0),
67+
encodeOp(opDelete, 1, 0),
68+
))
69+
f.Add(seed(
70+
encodeOp(opSet, amt.MaxIndex-1, 1),
71+
encodeOp(opReload, 0, 0),
72+
encodeOp(opGetSeen, 3, 0),
73+
encodeOp(opDeleteSeen, 7, 0),
74+
encodeOp(opSetSeen, 2, 42),
75+
))
76+
77+
f.Fuzz(func(t *testing.T, data []byte) {
78+
if len(data) < 1 {
79+
return
80+
}
81+
82+
arr, err := newCheckedAMT(t)
83+
if err != nil {
84+
t.Fatal("failed to construct AMT:", err)
85+
}
86+
for _, op := range parseOps(data) {
87+
switch op.code {
88+
case opSet:
89+
arr.set(op.key, op.value)
90+
case opSetSeen:
91+
arr.setSeen(op.key, op.value)
92+
case opGet:
93+
arr.get(op.key)
94+
case opGetSeen:
95+
arr.getSeen(op.key)
96+
case opDelete:
97+
arr.delete(op.key)
98+
case opDeleteSeen:
99+
arr.deleteSeen(op.key)
100+
case opFlush:
101+
arr.flush()
102+
case opReload:
103+
arr.reload()
104+
default:
105+
panic("impossible")
106+
}
107+
}
108+
arr.check()
109+
})
110+
}

fuzz/fuzzer.go

Lines changed: 0 additions & 84 deletions
This file was deleted.

fuzz/go.mod

Lines changed: 0 additions & 38 deletions
This file was deleted.

0 commit comments

Comments
 (0)