github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/kbfs/libkbfs/ops_test.go (about) 1 // Copyright 2016 Keybase Inc. All rights reserved. 2 // Use of this source code is governed by a BSD 3 // license that can be found in the LICENSE file. 4 5 package libkbfs 6 7 import ( 8 "fmt" 9 "math/rand" 10 "reflect" 11 "testing" 12 "time" 13 14 "github.com/keybase/client/go/kbfs/data" 15 "github.com/keybase/client/go/kbfs/kbfsblock" 16 "github.com/keybase/client/go/kbfs/kbfscodec" 17 "github.com/keybase/go-codec/codec" 18 "github.com/stretchr/testify/require" 19 ) 20 21 func TestCreateOpCustomUpdate(t *testing.T) { 22 oldDir := makeRandomBlockPointer(t) 23 co, err := newCreateOp("name", oldDir, data.Exec) 24 require.NoError(t, err) 25 require.Equal(t, blockUpdate{Unref: oldDir}, co.Dir) 26 27 // Update to oldDir should update co.Dir. 28 newDir := oldDir 29 newDir.ID = kbfsblock.FakeID(42) 30 co.AddUpdate(oldDir, newDir) 31 require.Nil(t, co.Updates) 32 require.Equal(t, blockUpdate{Unref: oldDir, Ref: newDir}, co.Dir) 33 } 34 35 func TestRmOpCustomUpdate(t *testing.T) { 36 oldDir := makeRandomBlockPointer(t) 37 ro, err := newRmOp("name", oldDir, data.File) 38 require.NoError(t, err) 39 require.Equal(t, blockUpdate{Unref: oldDir}, ro.Dir) 40 41 // Update to oldDir should update ro.Dir. 42 newDir := oldDir 43 newDir.ID = kbfsblock.FakeID(42) 44 ro.AddUpdate(oldDir, newDir) 45 require.Nil(t, ro.Updates) 46 require.Equal(t, blockUpdate{Unref: oldDir, Ref: newDir}, ro.Dir) 47 } 48 49 func TestRenameOpCustomUpdateWithinDir(t *testing.T) { 50 oldDir := makeRandomBlockPointer(t) 51 renamed := oldDir 52 renamed.ID = kbfsblock.FakeID(42) 53 ro, err := newRenameOp( 54 "old name", oldDir, "new name", oldDir, 55 renamed, data.Exec) 56 require.NoError(t, err) 57 require.Equal(t, blockUpdate{Unref: oldDir}, ro.OldDir) 58 require.Equal(t, data.BlockPointer{}, ro.NewDir.Unref) 59 require.Equal(t, data.BlockPointer{}, ro.NewDir.Ref) 60 61 // Update to oldDir should update ro.OldDir. 62 newDir := oldDir 63 newDir.ID = kbfsblock.FakeID(43) 64 ro.AddUpdate(oldDir, newDir) 65 require.Nil(t, ro.Updates) 66 require.Equal(t, blockUpdate{Unref: oldDir, Ref: newDir}, ro.OldDir) 67 require.Equal(t, blockUpdate{}, ro.NewDir) 68 } 69 70 func TestRenameOpCustomUpdateAcrossDirs(t *testing.T) { 71 oldOldDir := makeRandomBlockPointer(t) 72 oldNewDir := oldOldDir 73 oldNewDir.ID = kbfsblock.FakeID(42) 74 renamed := oldOldDir 75 renamed.ID = kbfsblock.FakeID(43) 76 ro, err := newRenameOp( 77 "old name", oldOldDir, "new name", oldNewDir, 78 renamed, data.Exec) 79 require.NoError(t, err) 80 require.Equal(t, blockUpdate{Unref: oldOldDir}, ro.OldDir) 81 require.Equal(t, blockUpdate{Unref: oldNewDir}, ro.NewDir) 82 83 // Update to oldOldDir should update ro.OldDir. 84 newOldDir := oldOldDir 85 newOldDir.ID = kbfsblock.FakeID(44) 86 ro.AddUpdate(oldOldDir, newOldDir) 87 require.Nil(t, ro.Updates) 88 require.Equal(t, blockUpdate{Unref: oldOldDir, Ref: newOldDir}, ro.OldDir) 89 require.Equal(t, blockUpdate{Unref: oldNewDir}, ro.NewDir) 90 91 // Update to oldNewDir should update ro.OldDir. 92 newNewDir := oldNewDir 93 newNewDir.ID = kbfsblock.FakeID(45) 94 ro.AddUpdate(oldNewDir, newNewDir) 95 require.Nil(t, ro.Updates) 96 require.Equal(t, blockUpdate{Unref: oldOldDir, Ref: newOldDir}, ro.OldDir) 97 require.Equal(t, blockUpdate{Unref: oldNewDir, Ref: newNewDir}, ro.NewDir) 98 } 99 100 func TestSyncOpCustomUpdate(t *testing.T) { 101 oldFile := makeRandomBlockPointer(t) 102 so, err := newSyncOp(oldFile) 103 require.NoError(t, err) 104 require.Equal(t, blockUpdate{Unref: oldFile}, so.File) 105 106 // Update to oldFile should update so.File. 107 newFile := oldFile 108 newFile.ID = kbfsblock.FakeID(42) 109 so.AddUpdate(oldFile, newFile) 110 require.Nil(t, so.Updates) 111 require.Equal(t, blockUpdate{Unref: oldFile, Ref: newFile}, so.File) 112 } 113 114 func TestSetAttrOpCustomUpdate(t *testing.T) { 115 oldDir := makeRandomBlockPointer(t) 116 file := oldDir 117 file.ID = kbfsblock.FakeID(42) 118 sao, err := newSetAttrOp("name", oldDir, mtimeAttr, file) 119 require.NoError(t, err) 120 require.Equal(t, blockUpdate{Unref: oldDir}, sao.Dir) 121 122 // Update to oldDir should update sao.Dir. 123 newDir := oldDir 124 newDir.ID = kbfsblock.FakeID(42) 125 sao.AddUpdate(oldDir, newDir) 126 require.Nil(t, sao.Updates) 127 require.Equal(t, blockUpdate{Unref: oldDir, Ref: newDir}, sao.Dir) 128 } 129 130 type writeRangeFuture struct { 131 WriteRange 132 kbfscodec.Extra 133 } 134 135 func (wrf writeRangeFuture) toCurrent() WriteRange { 136 return wrf.WriteRange 137 } 138 139 func (wrf writeRangeFuture) ToCurrentStruct() kbfscodec.CurrentStruct { 140 return wrf.toCurrent() 141 } 142 143 func makeFakeWriteRangeFuture(t *testing.T) writeRangeFuture { 144 wrf := writeRangeFuture{ 145 WriteRange{ 146 5, 147 10, 148 codec.UnknownFieldSetHandler{}, 149 }, 150 kbfscodec.MakeExtraOrBust("WriteRange", t), 151 } 152 return wrf 153 } 154 155 func TestWriteRangeUnknownFields(t *testing.T) { 156 testStructUnknownFields(t, makeFakeWriteRangeFuture(t)) 157 } 158 159 // opPointerizerFuture and registerOpsFuture are the "future" versions 160 // of opPointerizer and RegisterOps. registerOpsFuture is used by 161 // testStructUnknownFields. 162 163 func opPointerizerFuture(iface interface{}) reflect.Value { 164 switch op := iface.(type) { 165 default: 166 return reflect.ValueOf(iface) 167 case createOpFuture: 168 return reflect.ValueOf(&op) 169 case rmOpFuture: 170 return reflect.ValueOf(&op) 171 case renameOpFuture: 172 return reflect.ValueOf(&op) 173 case syncOpFuture: 174 return reflect.ValueOf(&op) 175 case setAttrOpFuture: 176 return reflect.ValueOf(&op) 177 case resolutionOpFuture: 178 return reflect.ValueOf(&op) 179 case rekeyOpFuture: 180 return reflect.ValueOf(&op) 181 case gcOpFuture: 182 return reflect.ValueOf(&op) 183 } 184 } 185 186 func registerOpsFuture(codec kbfscodec.Codec) { 187 codec.RegisterType(reflect.TypeOf(createOpFuture{}), createOpCode) 188 codec.RegisterType(reflect.TypeOf(rmOpFuture{}), rmOpCode) 189 codec.RegisterType(reflect.TypeOf(renameOpFuture{}), renameOpCode) 190 codec.RegisterType(reflect.TypeOf(syncOpFuture{}), syncOpCode) 191 codec.RegisterType(reflect.TypeOf(setAttrOpFuture{}), setAttrOpCode) 192 codec.RegisterType(reflect.TypeOf(resolutionOpFuture{}), resolutionOpCode) 193 codec.RegisterType(reflect.TypeOf(rekeyOpFuture{}), rekeyOpCode) 194 codec.RegisterType(reflect.TypeOf(gcOpFuture{}), gcOpCode) 195 codec.RegisterIfaceSliceType(reflect.TypeOf(opsList{}), opsListCode, 196 opPointerizerFuture) 197 } 198 199 type createOpFuture struct { 200 createOp 201 kbfscodec.Extra 202 } 203 204 func (cof createOpFuture) toCurrent() createOp { 205 return cof.createOp 206 } 207 208 func (cof createOpFuture) ToCurrentStruct() kbfscodec.CurrentStruct { 209 return cof.toCurrent() 210 } 211 212 func makeFakeBlockUpdate(t *testing.T) blockUpdate { 213 return blockUpdate{ 214 makeFakeBlockPointer(t), 215 makeFakeBlockPointer(t), 216 } 217 } 218 219 func makeFakeOpCommon(t *testing.T, withRefBlocks bool) OpCommon { 220 var refBlocks []data.BlockPointer 221 if withRefBlocks { 222 refBlocks = []data.BlockPointer{makeFakeBlockPointer(t)} 223 } 224 oc := OpCommon{ 225 refBlocks, 226 []data.BlockPointer{makeFakeBlockPointer(t)}, 227 []blockUpdate{makeFakeBlockUpdate(t)}, 228 codec.UnknownFieldSetHandler{}, 229 writerInfo{}, 230 data.Path{}, 231 time.Time{}, 232 } 233 return oc 234 } 235 236 func makeFakeCreateOpFuture(t *testing.T) createOpFuture { 237 cof := createOpFuture{ 238 createOp{ 239 makeFakeOpCommon(t, true), 240 "new name", 241 makeFakeBlockUpdate(t), 242 data.Exec, 243 false, 244 false, 245 "", 246 }, 247 kbfscodec.MakeExtraOrBust("createOp", t), 248 } 249 return cof 250 } 251 252 func TestCreateOpUnknownFields(t *testing.T) { 253 testStructUnknownFields(t, makeFakeCreateOpFuture(t)) 254 } 255 256 type rmOpFuture struct { 257 rmOp 258 kbfscodec.Extra 259 } 260 261 func (rof rmOpFuture) toCurrent() rmOp { 262 return rof.rmOp 263 } 264 265 func (rof rmOpFuture) ToCurrentStruct() kbfscodec.CurrentStruct { 266 return rof.toCurrent() 267 } 268 269 func makeFakeRmOpFuture(t *testing.T) rmOpFuture { 270 rof := rmOpFuture{ 271 rmOp{ 272 makeFakeOpCommon(t, true), 273 "old name", 274 makeFakeBlockUpdate(t), 275 data.File, 276 false, 277 }, 278 kbfscodec.MakeExtraOrBust("rmOp", t), 279 } 280 return rof 281 } 282 283 func TestRmOpUnknownFields(t *testing.T) { 284 testStructUnknownFields(t, makeFakeRmOpFuture(t)) 285 } 286 287 type renameOpFuture struct { 288 renameOp 289 kbfscodec.Extra 290 } 291 292 func (rof renameOpFuture) toCurrent() renameOp { 293 return rof.renameOp 294 } 295 296 func (rof renameOpFuture) ToCurrentStruct() kbfscodec.CurrentStruct { 297 return rof.toCurrent() 298 } 299 300 func makeFakeRenameOpFuture(t *testing.T) renameOpFuture { 301 rof := renameOpFuture{ 302 renameOp{ 303 makeFakeOpCommon(t, true), 304 "old name", 305 makeFakeBlockUpdate(t), 306 "new name", 307 makeFakeBlockUpdate(t), 308 makeFakeBlockPointer(t), 309 data.Exec, 310 data.Path{}, 311 }, 312 kbfscodec.MakeExtraOrBust("renameOp", t), 313 } 314 return rof 315 } 316 317 func TestRenameOpUnknownFields(t *testing.T) { 318 testStructUnknownFields(t, makeFakeRenameOpFuture(t)) 319 } 320 321 type syncOpFuture struct { 322 syncOp 323 // Overrides syncOp.Writes. 324 Writes []writeRangeFuture `codec:"w"` 325 kbfscodec.Extra 326 } 327 328 func (sof syncOpFuture) toCurrent() syncOp { 329 so := sof.syncOp 330 so.Writes = make([]WriteRange, len(sof.Writes)) 331 for i, w := range sof.Writes { 332 so.Writes[i] = w.toCurrent() 333 } 334 return so 335 } 336 337 func (sof syncOpFuture) ToCurrentStruct() kbfscodec.CurrentStruct { 338 return sof.toCurrent() 339 } 340 341 func makeFakeSyncOpFuture(t *testing.T) syncOpFuture { 342 sof := syncOpFuture{ 343 syncOp{ 344 makeFakeOpCommon(t, true), 345 makeFakeBlockUpdate(t), 346 nil, 347 false, 348 }, 349 []writeRangeFuture{ 350 makeFakeWriteRangeFuture(t), 351 makeFakeWriteRangeFuture(t), 352 }, 353 kbfscodec.MakeExtraOrBust("syncOp", t), 354 } 355 return sof 356 } 357 358 func TestSyncOpUnknownFields(t *testing.T) { 359 testStructUnknownFields(t, makeFakeSyncOpFuture(t)) 360 } 361 362 type setAttrOpFuture struct { 363 setAttrOp 364 kbfscodec.Extra 365 } 366 367 func (sof setAttrOpFuture) toCurrent() setAttrOp { 368 return sof.setAttrOp 369 } 370 371 func (sof setAttrOpFuture) ToCurrentStruct() kbfscodec.CurrentStruct { 372 return sof.toCurrent() 373 } 374 375 func makeFakeSetAttrOpFuture(t *testing.T) setAttrOpFuture { 376 sof := setAttrOpFuture{ 377 setAttrOp{ 378 makeFakeOpCommon(t, true), 379 "name", 380 makeFakeBlockUpdate(t), 381 mtimeAttr, 382 makeFakeBlockPointer(t), 383 false, 384 }, 385 kbfscodec.MakeExtraOrBust("setAttrOp", t), 386 } 387 return sof 388 } 389 390 func TestSetAttrOpUnknownFields(t *testing.T) { 391 testStructUnknownFields(t, makeFakeSetAttrOpFuture(t)) 392 } 393 394 type resolutionOpFuture struct { 395 resolutionOp 396 kbfscodec.Extra 397 } 398 399 func (rof resolutionOpFuture) toCurrent() resolutionOp { 400 return rof.resolutionOp 401 } 402 403 func (rof resolutionOpFuture) ToCurrentStruct() kbfscodec.CurrentStruct { 404 return rof.toCurrent() 405 } 406 407 func makeFakeResolutionOpFuture(t *testing.T) resolutionOpFuture { 408 rof := resolutionOpFuture{ 409 resolutionOp{ 410 makeFakeOpCommon(t, true), 411 nil, 412 }, 413 kbfscodec.MakeExtraOrBust("resolutionOp", t), 414 } 415 return rof 416 } 417 418 func TestResolutionOpUnknownFields(t *testing.T) { 419 testStructUnknownFields(t, makeFakeResolutionOpFuture(t)) 420 } 421 422 type rekeyOpFuture struct { 423 rekeyOp 424 kbfscodec.Extra 425 } 426 427 func (rof rekeyOpFuture) toCurrent() rekeyOp { 428 return rof.rekeyOp 429 } 430 431 func (rof rekeyOpFuture) ToCurrentStruct() kbfscodec.CurrentStruct { 432 return rof.toCurrent() 433 } 434 435 func makeFakeRekeyOpFuture(t *testing.T) rekeyOpFuture { 436 rof := rekeyOpFuture{ 437 rekeyOp{ 438 makeFakeOpCommon(t, true), 439 }, 440 kbfscodec.MakeExtraOrBust("rekeyOp", t), 441 } 442 return rof 443 } 444 445 func TestRekeyOpUnknownFields(t *testing.T) { 446 testStructUnknownFields(t, makeFakeRekeyOpFuture(t)) 447 } 448 449 type gcOpFuture struct { 450 GCOp 451 kbfscodec.Extra 452 } 453 454 func (gof gcOpFuture) toCurrent() GCOp { 455 return gof.GCOp 456 } 457 458 func (gof gcOpFuture) ToCurrentStruct() kbfscodec.CurrentStruct { 459 return gof.toCurrent() 460 } 461 462 func makeFakeGcOpFuture(t *testing.T) gcOpFuture { 463 gof := gcOpFuture{ 464 GCOp{ 465 makeFakeOpCommon(t, false), 466 100, 467 }, 468 kbfscodec.MakeExtraOrBust("gcOp", t), 469 } 470 return gof 471 } 472 473 func TestGcOpUnknownFields(t *testing.T) { 474 testStructUnknownFields(t, makeFakeGcOpFuture(t)) 475 } 476 477 type testOps struct { 478 Ops []interface{} 479 } 480 481 // Tests that ops can be serialized and deserialized as extensions. 482 func TestOpSerialization(t *testing.T) { 483 c := kbfscodec.NewMsgpack() 484 RegisterOps(c) 485 486 ops := testOps{} 487 // add a couple ops of different types 488 co, err := newCreateOp("test1", data.BlockPointer{ID: kbfsblock.FakeID(42)}, data.File) 489 require.NoError(t, err) 490 ro, err := newRmOp("test2", data.BlockPointer{ID: kbfsblock.FakeID(43)}, data.File) 491 require.NoError(t, err) 492 ops.Ops = append(ops.Ops, co, ro) 493 494 buf, err := c.Encode(ops) 495 if err != nil { 496 t.Errorf("Couldn't encode ops: %v", err) 497 } 498 499 ops2 := testOps{} 500 err = c.Decode(buf, &ops2) 501 if err != nil { 502 t.Errorf("Couldn't decode ops: %v", err) 503 } 504 505 op1, ok := ops2.Ops[0].(createOp) 506 if !ok { 507 t.Errorf("Couldn't decode createOp: %v", reflect.TypeOf(ops2.Ops[0])) 508 } else if op1.NewName != "test1" { 509 t.Errorf("Wrong name in createOp: %s", op1.NewName) 510 } 511 512 op2, ok := ops2.Ops[1].(rmOp) 513 if !ok { 514 t.Errorf("Couldn't decode rmOp: %v", reflect.TypeOf(ops2.Ops[1])) 515 } else if op2.OldName != "test2" { 516 t.Errorf("Wrong name in rmOp: %s", op2.OldName) 517 } 518 } 519 520 func TestOpInversion(t *testing.T) { 521 oldPtr1 := data.BlockPointer{ID: kbfsblock.FakeID(42)} 522 newPtr1 := data.BlockPointer{ID: kbfsblock.FakeID(82)} 523 oldPtr2 := data.BlockPointer{ID: kbfsblock.FakeID(43)} 524 newPtr2 := data.BlockPointer{ID: kbfsblock.FakeID(83)} 525 filePtr := data.BlockPointer{ID: kbfsblock.FakeID(44)} 526 527 cop, err := newCreateOp("test1", oldPtr1, data.File) 528 require.NoError(t, err) 529 cop.AddUpdate(oldPtr1, newPtr1) 530 cop.AddUpdate(oldPtr2, newPtr2) 531 expectedIOp, err := newRmOp("test1", newPtr1, data.File) 532 require.NoError(t, err) 533 expectedIOp.AddUpdate(newPtr1, oldPtr1) 534 expectedIOp.AddUpdate(newPtr2, oldPtr2) 535 536 iop1, err := invertOpForLocalNotifications(cop) 537 require.NoError(t, err) 538 ro, ok := iop1.(*rmOp) 539 if !ok || !reflect.DeepEqual(*ro, *expectedIOp) { 540 t.Errorf("createOp didn't invert properly, expected %v, got %v", 541 expectedIOp, iop1) 542 } 543 544 // convert it back (works because the inversion picks File as the 545 // type, which is what we use above) 546 iop2, err := invertOpForLocalNotifications(iop1) 547 require.NoError(t, err) 548 co, ok := iop2.(*createOp) 549 if !ok || !reflect.DeepEqual(*co, *cop) { 550 t.Errorf("rmOp didn't invert properly, expected %v, got %v", 551 expectedIOp, iop2) 552 } 553 554 // rename 555 rop, err := newRenameOp("old", oldPtr1, "new", oldPtr2, filePtr, data.File) 556 require.NoError(t, err) 557 rop.AddUpdate(oldPtr1, newPtr1) 558 rop.AddUpdate(oldPtr2, newPtr2) 559 expectedIOp3, err := newRenameOp("new", newPtr2, "old", newPtr1, filePtr, data.File) 560 require.NoError(t, err) 561 expectedIOp3.AddUpdate(newPtr1, oldPtr1) 562 expectedIOp3.AddUpdate(newPtr2, oldPtr2) 563 564 iop3, err := invertOpForLocalNotifications(rop) 565 require.NoError(t, err) 566 iRenameOp, ok := iop3.(*renameOp) 567 if !ok || !reflect.DeepEqual(*iRenameOp, *expectedIOp3) { 568 t.Errorf("renameOp didn't invert properly, expected %v, got %v", 569 expectedIOp3, iop3) 570 } 571 572 // sync (writes should be the same as before) 573 sop, err := newSyncOp(oldPtr1) 574 require.NoError(t, err) 575 sop.AddUpdate(oldPtr1, newPtr1) 576 sop.addWrite(2, 3) 577 sop.addTruncate(100) 578 sop.addWrite(10, 12) 579 expectedIOp4, err := newSyncOp(newPtr1) 580 require.NoError(t, err) 581 expectedIOp4.AddUpdate(newPtr1, oldPtr1) 582 expectedIOp4.Writes = sop.Writes 583 iop4, err := invertOpForLocalNotifications(sop) 584 require.NoError(t, err) 585 so, ok := iop4.(*syncOp) 586 if !ok || !reflect.DeepEqual(*so, *expectedIOp4) { 587 t.Errorf("syncOp didn't invert properly, expected %v, got %v", 588 expectedIOp4, iop4) 589 } 590 591 // setAttr 592 saop, err := newSetAttrOp("name", oldPtr1, mtimeAttr, filePtr) 593 require.NoError(t, err) 594 saop.AddUpdate(oldPtr1, newPtr1) 595 expectedIOp5, err := newSetAttrOp("name", newPtr1, mtimeAttr, filePtr) 596 require.NoError(t, err) 597 expectedIOp5.AddUpdate(newPtr1, oldPtr1) 598 iop5, err := invertOpForLocalNotifications(saop) 599 require.NoError(t, err) 600 sao, ok := iop5.(*setAttrOp) 601 if !ok || !reflect.DeepEqual(*sao, *expectedIOp5) { 602 t.Errorf("setAttrOp didn't invert properly, expected %v, got %v", 603 expectedIOp5, iop5) 604 } 605 606 // rename (same dir) 607 rop, err = newRenameOp("old", oldPtr1, "new", oldPtr1, filePtr, data.File) 608 require.NoError(t, err) 609 rop.AddUpdate(oldPtr1, newPtr1) 610 expectedIOp6, err := newRenameOp("new", newPtr1, "old", newPtr1, filePtr, data.File) 611 require.NoError(t, err) 612 expectedIOp6.AddUpdate(newPtr1, oldPtr1) 613 614 iop6, err := invertOpForLocalNotifications(rop) 615 require.NoError(t, err) 616 iRenameOp, ok = iop6.(*renameOp) 617 if !ok || !reflect.DeepEqual(*iRenameOp, *expectedIOp6) { 618 t.Errorf("renameOp didn't invert properly, expected %v, got %v", 619 expectedIOp6, iop6) 620 } 621 } 622 623 func TestOpsCollapseWriteRange(t *testing.T) { 624 const numAttempts = 1000 625 const fileSize = uint64(1000) 626 const numWrites = 25 627 const maxWriteSize = uint64(50) 628 for i := 0; i < numAttempts; i++ { 629 // Make a "file" where dirty bytes are represented by trues. 630 var file [fileSize]bool 631 var lastByte uint64 632 var lastByteIsTruncate bool 633 var syncOps []*syncOp 634 for j := 0; j < numWrites; j++ { 635 // Start a new syncOp? 636 if len(syncOps) == 0 || rand.Int()%5 == 0 { 637 syncOps = append(syncOps, &syncOp{}) 638 } 639 640 op := syncOps[len(syncOps)-1] 641 // Generate either a random truncate or random write 642 off := uint64(rand.Int()) % fileSize 643 var length uint64 644 if rand.Int()%5 > 0 { 645 // A write, not a truncate 646 maxLen := fileSize - off 647 if maxLen > maxWriteSize { 648 maxLen = maxWriteSize 649 } 650 maxLen-- 651 if maxLen == 0 { 652 maxLen = 1 653 } 654 // Writes must have at least one byte 655 length = uint64(rand.Int())%maxLen + uint64(1) 656 op.addWrite(off, length) 657 // Fill in dirty bytes 658 for k := off; k < off+length; k++ { 659 file[k] = true 660 } 661 if lastByte < off+length { 662 lastByte = off + length 663 } 664 } else { 665 op.addTruncate(off) 666 if lastByteIsTruncate && lastByte < off { 667 for k := lastByte; k < off; k++ { 668 file[k] = true // zero-fill 669 } 670 } 671 for k := off; k < fileSize; k++ { 672 file[k] = false 673 } 674 lastByte = off 675 lastByteIsTruncate = true 676 } 677 } 678 679 var wrComputed []WriteRange 680 for _, op := range syncOps { 681 wrComputed = op.collapseWriteRange(wrComputed) 682 } 683 684 var wrExpected []WriteRange 685 inWrite := false 686 for j := 0; j < int(lastByte); j++ { 687 if !inWrite && file[j] { 688 inWrite = true 689 wrExpected = append(wrExpected, WriteRange{Off: uint64(j)}) 690 } else if inWrite && !file[j] { 691 inWrite = false 692 wrExpected[len(wrExpected)-1].Len = 693 uint64(j) - wrExpected[len(wrExpected)-1].Off 694 } 695 } 696 if inWrite { 697 wrExpected[len(wrExpected)-1].Len = 698 lastByte - wrExpected[len(wrExpected)-1].Off 699 } 700 if lastByteIsTruncate { 701 wrExpected = append(wrExpected, WriteRange{Off: lastByte}) 702 } 703 704 // Verify that the write range represents what's in the file. 705 if g, e := len(wrComputed), len(wrExpected); g != e { 706 t.Errorf("Range lengths differ (%d vs %d)", g, e) 707 continue 708 } 709 for j, wc := range wrComputed { 710 we := wrExpected[j] 711 if wc.Off != we.Off && wc.Len != we.Len { 712 t.Errorf("Writes differ at index %d (%v vs %v)", j, we, wc) 713 } 714 } 715 } 716 } 717 718 func TestCollapseWriteRangeWithLaterTruncate(t *testing.T) { 719 so := &syncOp{} 720 so.Writes = []WriteRange{{Off: 400, Len: 0}} 721 got := so.collapseWriteRange([]WriteRange{{Off: 0, Len: 0}}) 722 expected := []WriteRange{{Off: 0, Len: 400}, {Off: 400, Len: 0}} 723 require.True(t, reflect.DeepEqual(got, expected), 724 "Bad write collapse, got=%v, expected=%v", got, expected) 725 } 726 727 func ExamplecoalesceWrites() { 728 fmt.Println(coalesceWrites( 729 []WriteRange{{Off: 7, Len: 5}, {Off: 18, Len: 10}, 730 {Off: 98, Len: 10}}, WriteRange{Off: 5, Len: 100})) 731 // Output: [{5 103 {{map[]}}}] 732 } 733 734 func ExamplecoalesceWrites_withOldTruncate() { 735 fmt.Println(coalesceWrites( 736 []WriteRange{{Off: 7, Len: 5}, {Off: 18, Len: 10}, 737 {Off: 98, Len: 0}}, WriteRange{Off: 5, Len: 100})) 738 // Output: [{5 100 {{map[]}}} {105 0 {{map[]}}}] 739 }