github.com/thepudds/swisstable@v0.0.0-20221011152303-9c77dc657777/vmap_test.go (about)

     1  package swisstable
     2  
     3  // VMap is a self validating map. It wraps a swisstable.Map and validates
     4  // varies aspects of its operation, including during iteration where
     5  // it validates whether or not a key is allowed to be seen zero times,
     6  // exactly once, or multiple times due to add/deletes during the iteration.
     7  //
     8  // It is intended to work well with fuzzing. See autogenfuzzchain_test.go for an example.
     9  //
    10  // It was extracted from TestMap_RangeAddDelete, and currently overlaps with it.
    11  // TODO: use a vMap in TestMap_RangeAddDelete.
    12  // TODO: maybe move to internal. (currently reaching into implementation to set hashFunc)
    13  
    14  import (
    15  	"fmt"
    16  	"sort"
    17  	"testing"
    18  )
    19  
    20  type OpType byte
    21  
    22  const (
    23  	GetOp OpType = iota
    24  	SetOp
    25  	DeleteOp
    26  	LenOp
    27  	RangeOp
    28  
    29  	BulkGetOp // must be first bulk op, after non-bulk ops
    30  	BulkSetOp
    31  	BulkDeleteOp
    32  
    33  	OpTypeCount
    34  )
    35  
    36  // func (op OpType) isBulkOp() bool {
    37  // 	return op%OpTypeCount >= GetBulkOp
    38  // }
    39  
    40  type Op struct {
    41  	OpType OpType
    42  
    43  	// used only if Op is not bulk Op
    44  	Key Key
    45  
    46  	// used only if Op is bulk op
    47  	Keys Keys
    48  
    49  	// used during a Range to specify when to do this op,
    50  	// not used if this Op is not used in a Range
    51  	RangeIndex uint16
    52  }
    53  
    54  func (o Op) String() string {
    55  	t := o.OpType % OpTypeCount
    56  	switch {
    57  	case t < BulkGetOp:
    58  		return fmt.Sprintf("{Op: %v Key: %v}", t, o.Key)
    59  	case t < OpTypeCount:
    60  		return fmt.Sprintf("{Op: %v Keys: %v RangeIndex: %v}", t, o.Keys, o.RangeIndex)
    61  	default:
    62  		return fmt.Sprintf("{Op: unknown %v}", o.OpType)
    63  	}
    64  }
    65  
    66  type Keys struct {
    67  	Start, End, Stride uint8 // [Start, End) - start inclusive, end exclusive
    68  }
    69  
    70  // Vmap is a self-validating wrapper around Map
    71  type Vmap struct {
    72  	// swisstable.Map under test
    73  	m *Map
    74  
    75  	// repeat any operations on our Map to a mirrored runtime map
    76  	mirror map[Key]Value
    77  }
    78  
    79  // TODO: add testing.T
    80  func NewVmap(capacity byte, start []Key) *Vmap {
    81  	vm := &Vmap{}
    82  	vm.m = New(int(capacity))
    83  
    84  	// override the seed to make repeatable and consistent with an earlier value
    85  	vm.m.seed = 42
    86  
    87  	// make more reproducible, and also lumpier with a worse hash
    88  	vm.m.hashFunc = identityHash
    89  
    90  	vm.mirror = make(map[Key]Value)
    91  
    92  	return vm
    93  }
    94  
    95  // TODO: maybe add testing.T
    96  // TODO: don't think I need return values?
    97  func (vm *Vmap) Get(k Key) (v Value, ok bool) {
    98  	// TODO: consolidate or remove the debugVmap printlns
    99  	if debugVmap {
   100  		println("Get key:", k)
   101  	}
   102  	got, gotOk := vm.m.Get(k)
   103  	want, wantOk := vm.mirror[k]
   104  	if want != got || gotOk != wantOk {
   105  		panic(fmt.Sprintf("Map.Get(%v) = %v, %v. want = %v, %v", k, got, gotOk, want, wantOk))
   106  	}
   107  	return
   108  }
   109  
   110  func (vm *Vmap) Set(k Key, v Value) {
   111  	// TODO: could validate presence/absence vs. mirror in Set and Delete,
   112  	// but eventually Get hopefully will evacuate, so probably better not to call here.
   113  	if debugVmap {
   114  		println("Set key:", k)
   115  	}
   116  	vm.m.Set(k, v)
   117  	vm.mirror[k] = v
   118  }
   119  
   120  func (vm *Vmap) Delete(k Key) {
   121  	if debugVmap {
   122  		println("Delete key:", k)
   123  	}
   124  	vm.m.Delete(k)
   125  	delete(vm.mirror, k)
   126  }
   127  
   128  // TODO: maybe add testing.T
   129  func (vm *Vmap) Len() int {
   130  	got := vm.m.Len()
   131  	want := len(vm.mirror)
   132  	if want != got {
   133  		panic(fmt.Sprintf("Map.Len() = %v, want %v", got, want))
   134  	}
   135  
   136  	return vm.m.Len()
   137  }
   138  
   139  // Bulk operations
   140  
   141  func (vm *Vmap) GetBulk(list Keys) (values []Value, oks []bool) {
   142  	for _, key := range keySlice(list) {
   143  		vm.Get(key)
   144  	}
   145  	return nil, nil
   146  }
   147  
   148  func (vm *Vmap) SetBulk(list Keys) {
   149  	for _, key := range keySlice(list) {
   150  		vm.Set(key, Value(key))
   151  	}
   152  }
   153  
   154  func (vm *Vmap) DeleteBulk(list Keys) {
   155  	for _, key := range keySlice(list) {
   156  		vm.Delete(key)
   157  	}
   158  }
   159  
   160  func (vm *Vmap) Range(ops []Op) {
   161  	// we fix up RangeIndex to make the values useful more often
   162  	for i := range ops {
   163  		if ops[i].RangeIndex > 5001 {
   164  			ops[i].RangeIndex = 0
   165  		}
   166  	}
   167  
   168  	sort.SliceStable(ops, func(i, j int) bool {
   169  		return ops[i].RangeIndex < ops[j].RangeIndex
   170  	})
   171  
   172  	// Create somes sets to dynamically track validity of keys that appear in a range.
   173  	// allowed tracks start + added - deleted; these keys allowed but not required.
   174  	allowed := newKeySet(nil)
   175  	// mustSee tracks start - deleted; these are keys we are required to see at some point.
   176  	mustSee := newKeySet(nil)
   177  	// add the starting keys
   178  	for k := range vm.mirror {
   179  		allowed.add(k)
   180  		mustSee.add(k)
   181  	}
   182  
   183  	// seen is used to verify no unexpected dups, and at end, to verify mustSee.
   184  	seen := newKeySet(nil)
   185  
   186  	// Also dynamically track if key X is added, deleted, and then re-added during iteration,
   187  	// which means it is legal per Go spec to be seen again in the iteration.
   188  	// Example with stdlib map repeating keys during iter: https://go.dev/play/p/RN-v8rmQmeE
   189  	deleted := newKeySet(nil)
   190  	addedAfterDeleted := newKeySet(nil)
   191  
   192  	trackSet := func(k Key) {
   193  		// update our trackers for a Set op during the range.
   194  		allowed.add(k)
   195  		if deleted.contains(k) {
   196  			addedAfterDeleted.add(k)
   197  			deleted.remove(k)
   198  		}
   199  	}
   200  
   201  	trackDelete := func(k Key) {
   202  		// update our trackers for a Delete op during the range.
   203  		allowed.remove(k)
   204  		mustSee.remove(k) // we are no longer required to see this. Fine if we saw it earlier.
   205  		deleted.add(k)
   206  		if addedAfterDeleted.contains(k) {
   207  			addedAfterDeleted.remove(k)
   208  		}
   209  	}
   210  
   211  	var rangeIndex uint16
   212  	vm.m.Range(func(key Key, value Value) bool {
   213  		// TODO: maybe add env var for equiv of:
   214  		// println("iteration:", rangeIndex, "key:", key)
   215  
   216  		seen.add(key)
   217  
   218  		for len(ops) > 0 {
   219  			op := ops[0]
   220  			if op.RangeIndex != rangeIndex {
   221  				break
   222  			}
   223  
   224  			switch op.OpType % OpTypeCount {
   225  			case GetOp:
   226  				if debugVmap {
   227  					println("range case GetOp key:", op.Key)
   228  				}
   229  				vm.Get(op.Key)
   230  			case SetOp:
   231  				if debugVmap {
   232  					println("range case SetOp key:", op.Key)
   233  				}
   234  				vm.Set(op.Key, Value(op.Key))
   235  				trackSet(op.Key)
   236  			case DeleteOp:
   237  				if debugVmap {
   238  					println("range case DeleteOp key:", op.Key)
   239  				}
   240  				vm.Delete(op.Key)
   241  				trackDelete(op.Key)
   242  			case LenOp:
   243  				vm.Len()
   244  			case RangeOp:
   245  				// Ignore.
   246  				// We could allow this, but naive approach might allow O(n^2) or worse behavior
   247  			case BulkGetOp:
   248  				for _, key := range keySlice(op.Keys) {
   249  					if debugVmap {
   250  						println("range case BulkGetOp key:", key)
   251  					}
   252  					vm.Get(key)
   253  				}
   254  			case BulkSetOp:
   255  				for _, key := range keySlice(op.Keys) {
   256  					if debugVmap {
   257  						println("range case BulkSetOp key:", key)
   258  					}
   259  					vm.Set(key, Value(key))
   260  					trackSet(key)
   261  				}
   262  			case BulkDeleteOp:
   263  				for _, key := range keySlice(op.Keys) {
   264  					if debugVmap {
   265  						println("range case BulkDeleteOp key:", key)
   266  					}
   267  					vm.Delete(key)
   268  					trackDelete(key)
   269  				}
   270  			default:
   271  				panic("unexpected OpType")
   272  			}
   273  
   274  			ops = ops[1:]
   275  		}
   276  		rangeIndex++
   277  		return true // keep iterating
   278  	})
   279  
   280  	for _, key := range mustSee.elems() {
   281  		if !seen.contains(key) {
   282  			panic(fmt.Sprintf("Map.Range() expected key %v not seen", key))
   283  		}
   284  	}
   285  }
   286  
   287  // keySlice converts from start/end/stride to a []Key
   288  func keySlice(list Keys) []Key {
   289  	// we fix up start/end to make the values useful more often
   290  	start, end := int(list.Start), int(list.End)
   291  	switch {
   292  	case start > end:
   293  		start, end = end, start
   294  	case start == end:
   295  		return nil
   296  	}
   297  
   298  	var stride int
   299  	switch {
   300  	case list.Stride < 128:
   301  		// prefer stride of 1
   302  		stride = 1
   303  	default:
   304  		stride = int(list.Stride%8) + 1
   305  	}
   306  
   307  	var res []Key
   308  	for i := start; i < end; i += stride {
   309  		res = append(res, Key(i))
   310  	}
   311  	return res
   312  }
   313  
   314  // some simple test functions
   315  
   316  func TestValidatingMap_Range(t *testing.T) {
   317  	tests := []struct {
   318  		name string
   319  		ops  []Op
   320  	}{
   321  		{
   322  			name: "",
   323  			ops: []Op{
   324  				{
   325  					OpType:     GetOp,
   326  					Key:        1,
   327  					Keys:       Keys{},
   328  					RangeIndex: 0,
   329  				},
   330  				{
   331  					OpType:     GetOp,
   332  					Key:        2,
   333  					Keys:       Keys{},
   334  					RangeIndex: 0,
   335  				},
   336  				{
   337  					OpType:     SetOp,
   338  					Key:        3,
   339  					Keys:       Keys{},
   340  					RangeIndex: 2, // should happen last
   341  				},
   342  				{
   343  					OpType:     55,
   344  					Key:        4,
   345  					Keys:       Keys{},
   346  					RangeIndex: 0,
   347  				},
   348  			},
   349  		},
   350  	}
   351  	for _, tt := range tests {
   352  		t.Run(tt.name, func(t *testing.T) {
   353  			t.Logf("ops: %v", tt.ops)
   354  			vm := NewVmap(100, nil)
   355  			vm.m.Set(100, 100)
   356  			vm.m.Set(101, 101)
   357  			vm.m.Set(102, 102)
   358  			vm.Range(tt.ops)
   359  			// TODO: maybe delete this test, or add a want here
   360  		})
   361  	}
   362  }
   363  
   364  const debugVmap = false