github.com/gnolang/gno@v0.0.0-20240520182011-228e9d0192ce/tm2/pkg/iavl/tree_fuzz_test.go (about) 1 package iavl 2 3 import ( 4 "fmt" 5 "math/rand" 6 "testing" 7 8 "github.com/gnolang/gno/tm2/pkg/db/memdb" 9 "github.com/gnolang/gno/tm2/pkg/random" 10 ) 11 12 // This file implement fuzz testing by generating programs and then running 13 // them. If an error occurs, the program that had the error is printed. 14 15 // A program is a list of instructions. 16 type program struct { 17 instructions []instruction 18 } 19 20 func (p *program) Execute(tree *MutableTree) (err error) { 21 var errLine int 22 23 defer func() { 24 if r := recover(); r != nil { 25 var str string 26 27 for i, instr := range p.instructions { 28 prefix := " " 29 if i == errLine { 30 prefix = ">> " 31 } 32 str += prefix + instr.String() + "\n" 33 } 34 err = fmt.Errorf("Program panicked with: %s\n%s", r, str) 35 } 36 }() 37 38 for i, instr := range p.instructions { 39 errLine = i 40 instr.Execute(tree) 41 } 42 return 43 } 44 45 func (p *program) addInstruction(i instruction) { 46 p.instructions = append(p.instructions, i) 47 } 48 49 func (p *program) size() int { 50 return len(p.instructions) 51 } 52 53 type instruction struct { 54 op string 55 k, v []byte 56 version int64 57 } 58 59 func (i instruction) Execute(tree *MutableTree) { 60 switch i.op { 61 case "SET": 62 tree.Set(i.k, i.v) 63 case "REMOVE": 64 tree.Remove(i.k) 65 case "SAVE": 66 tree.SaveVersion() 67 case "DELETE": 68 tree.DeleteVersion(i.version) 69 default: 70 panic("Unrecognized op: " + i.op) 71 } 72 } 73 74 func (i instruction) String() string { 75 if i.version > 0 { 76 return fmt.Sprintf("%-8s %-8s %-8s %-8d", i.op, i.k, i.v, i.version) 77 } 78 return fmt.Sprintf("%-8s %-8s %-8s", i.op, i.k, i.v) 79 } 80 81 // Generate a random program of the given size. 82 func genRandomProgram(size int) *program { 83 p := &program{} 84 nextVersion := 1 85 86 for p.size() < size { 87 k, v := []byte(random.RandStr(1)), []byte(random.RandStr(1)) 88 89 switch rand.Int() % 7 { 90 case 0, 1, 2: 91 p.addInstruction(instruction{op: "SET", k: k, v: v}) 92 case 3, 4: 93 p.addInstruction(instruction{op: "REMOVE", k: k}) 94 case 5: 95 p.addInstruction(instruction{op: "SAVE", version: int64(nextVersion)}) 96 nextVersion++ 97 case 6: 98 if rv := rand.Int() % nextVersion; rv < nextVersion && rv > 0 { 99 p.addInstruction(instruction{op: "DELETE", version: int64(rv)}) 100 } 101 } 102 } 103 return p 104 } 105 106 // Generate many programs and run them. 107 func TestMutableTreeFuzz(t *testing.T) { 108 t.Parallel() 109 110 maxIterations := testFuzzIterations 111 progsPerIteration := 100000 112 iterations := 0 113 114 for size := 5; iterations < maxIterations; size++ { 115 for i := 0; i < progsPerIteration/size; i++ { 116 tree := NewMutableTree(memdb.NewMemDB(), 0) 117 program := genRandomProgram(size) 118 err := program.Execute(tree) 119 if err != nil { 120 t.Fatalf("Error after %d iterations (size %d): %s\n%s", iterations, size, err.Error(), tree.String()) 121 } 122 iterations++ 123 } 124 } 125 }