github.com/fibonacci-chain/fbc@v0.0.0-20231124064014-c7636198c1e9/libs/iavl/tree_fuzz_test.go (about)

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