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  }