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