| // 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_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 Map[uint16, uint32]. | |
| // | |
| // Each command is then executed on the map 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 | |
| } | |
| m, typ := maps.NewTestMap[uint16, uint32](8) | |
| ref := make(map[uint16]uint32) | |
| for _, c := range fc { | |
| switch c.Op { | |
| case fuzzOpGet: | |
| elemPtr, ok := m.Get(typ, 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: | |
| m.Put(typ, unsafe.Pointer(&c.Key), unsafe.Pointer(&c.Elem)) | |
| ref[c.Key] = c.Elem | |
| case fuzzOpDelete: | |
| m.Delete(typ, unsafe.Pointer(&c.Key)) | |
| delete(ref, c.Key) | |
| default: | |
| // Just skip this command to keep the fuzzer | |
| // less constrained. | |
| continue | |
| } | |
| } | |
| }) | |
| } |