| // 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 |
| } |
| } |
| }) |
| } |