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