aboutsummaryrefslogtreecommitdiff
path: root/src/internal/runtime/maps/fuzz_test.go
diff options
context:
space:
mode:
Diffstat (limited to 'src/internal/runtime/maps/fuzz_test.go')
-rw-r--r--src/internal/runtime/maps/fuzz_test.go212
1 files changed, 212 insertions, 0 deletions
diff --git a/src/internal/runtime/maps/fuzz_test.go b/src/internal/runtime/maps/fuzz_test.go
new file mode 100644
index 0000000000..40a50107aa
--- /dev/null
+++ b/src/internal/runtime/maps/fuzz_test.go
@@ -0,0 +1,212 @@
+// Copyright 2024 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package maps implements Go's builtin map type.
+package maps_test
+
+import (
+ "bytes"
+ "encoding/binary"
+ "fmt"
+ "internal/runtime/maps"
+ "reflect"
+ "testing"
+ "unsafe"
+)
+
+// The input to FuzzTable is a binary-encoded array of fuzzCommand structs.
+//
+// Each fuzz call begins with an empty table[uint16, uint32].
+//
+// Each command is then executed on the table in sequence. Operations with
+// output (e.g., Get) are verified against a reference map.
+type fuzzCommand struct {
+ Op fuzzOp
+
+ // Used for Get, Put, Delete.
+ Key uint16
+
+ // Used for Put.
+ Elem uint32
+}
+
+// Encoded size of fuzzCommand.
+var fuzzCommandSize = binary.Size(fuzzCommand{})
+
+type fuzzOp uint8
+
+const (
+ fuzzOpGet fuzzOp = iota
+ fuzzOpPut
+ fuzzOpDelete
+)
+
+func encode(fc []fuzzCommand) []byte {
+ var buf bytes.Buffer
+ if err := binary.Write(&buf, binary.LittleEndian, fc); err != nil {
+ panic(fmt.Sprintf("error writing %v: %v", fc, err))
+ }
+ return buf.Bytes()
+}
+
+func decode(b []byte) []fuzzCommand {
+ // Round b down to a multiple of fuzzCommand size. i.e., ignore extra
+ // bytes of input.
+ entries := len(b) / fuzzCommandSize
+ usefulSize := entries * fuzzCommandSize
+ b = b[:usefulSize]
+
+ fc := make([]fuzzCommand, entries)
+ buf := bytes.NewReader(b)
+ if err := binary.Read(buf, binary.LittleEndian, &fc); err != nil {
+ panic(fmt.Sprintf("error reading %v: %v", b, err))
+ }
+
+ return fc
+}
+
+func TestEncodeDecode(t *testing.T) {
+ fc := []fuzzCommand{
+ {
+ Op: fuzzOpPut,
+ Key: 123,
+ Elem: 456,
+ },
+ {
+ Op: fuzzOpGet,
+ Key: 123,
+ },
+ }
+
+ b := encode(fc)
+ got := decode(b)
+ if !reflect.DeepEqual(fc, got) {
+ t.Errorf("encode-decode roundtrip got %+v want %+v", got, fc)
+ }
+
+ // Extra trailing bytes ignored.
+ b = append(b, 42)
+ got = decode(b)
+ if !reflect.DeepEqual(fc, got) {
+ t.Errorf("encode-decode (extra byte) roundtrip got %+v want %+v", got, fc)
+ }
+}
+
+func FuzzTable(f *testing.F) {
+ // All of the ops.
+ f.Add(encode([]fuzzCommand{
+ {
+ Op: fuzzOpPut,
+ Key: 123,
+ Elem: 456,
+ },
+ {
+ Op: fuzzOpDelete,
+ Key: 123,
+ },
+ {
+ Op: fuzzOpGet,
+ Key: 123,
+ },
+ }))
+
+ // Add enough times to trigger grow.
+ f.Add(encode([]fuzzCommand{
+ {
+ Op: fuzzOpPut,
+ Key: 1,
+ Elem: 101,
+ },
+ {
+ Op: fuzzOpPut,
+ Key: 2,
+ Elem: 102,
+ },
+ {
+ Op: fuzzOpPut,
+ Key: 3,
+ Elem: 103,
+ },
+ {
+ Op: fuzzOpPut,
+ Key: 4,
+ Elem: 104,
+ },
+ {
+ Op: fuzzOpPut,
+ Key: 5,
+ Elem: 105,
+ },
+ {
+ Op: fuzzOpPut,
+ Key: 6,
+ Elem: 106,
+ },
+ {
+ Op: fuzzOpPut,
+ Key: 7,
+ Elem: 107,
+ },
+ {
+ Op: fuzzOpPut,
+ Key: 8,
+ Elem: 108,
+ },
+ {
+ Op: fuzzOpGet,
+ Key: 1,
+ },
+ {
+ Op: fuzzOpDelete,
+ Key: 2,
+ },
+ {
+ Op: fuzzOpPut,
+ Key: 2,
+ Elem: 42,
+ },
+ {
+ Op: fuzzOpGet,
+ Key: 2,
+ },
+ }))
+
+ f.Fuzz(func(t *testing.T, in []byte) {
+ fc := decode(in)
+ if len(fc) == 0 {
+ return
+ }
+
+ tab := maps.NewTestTable[uint16, uint32](8)
+ ref := make(map[uint16]uint32)
+ for _, c := range fc {
+ switch c.Op {
+ case fuzzOpGet:
+ elemPtr, ok := tab.Get(unsafe.Pointer(&c.Key))
+ refElem, refOK := ref[c.Key]
+
+ if ok != refOK {
+ t.Errorf("Get(%d) got ok %v want ok %v", c.Key, ok, refOK)
+ }
+ if !ok {
+ continue
+ }
+ gotElem := *(*uint32)(elemPtr)
+ if gotElem != refElem {
+ t.Errorf("Get(%d) got %d want %d", c.Key, gotElem, refElem)
+ }
+ case fuzzOpPut:
+ tab.Put(unsafe.Pointer(&c.Key), unsafe.Pointer(&c.Elem))
+ ref[c.Key] = c.Elem
+ case fuzzOpDelete:
+ tab.Delete(unsafe.Pointer(&c.Key))
+ delete(ref, c.Key)
+ default:
+ // Just skip this command to keep the fuzzer
+ // less constrained.
+ continue
+ }
+ }
+ })
+}