github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/kbfs/libkbfs/cr_chains_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 "reflect" 9 "testing" 10 "time" 11 12 "github.com/keybase/client/go/kbfs/data" 13 idutiltest "github.com/keybase/client/go/kbfs/idutil/test" 14 "github.com/keybase/client/go/kbfs/kbfsblock" 15 "github.com/keybase/client/go/kbfs/kbfscodec" 16 "github.com/keybase/client/go/kbfs/kbfscrypto" 17 "github.com/keybase/client/go/kbfs/kbfsmd" 18 "github.com/keybase/client/go/kbfs/tlf" 19 "github.com/keybase/client/go/kbfs/tlfhandle" 20 "github.com/keybase/client/go/logger" 21 "github.com/keybase/client/go/protocol/keybase1" 22 "github.com/stretchr/testify/require" 23 "golang.org/x/net/context" 24 ) 25 26 func checkExpectedChains(t *testing.T, expected map[data.BlockPointer]data.BlockPointer, 27 expectedRenames map[data.BlockPointer]renameInfo, expectedRoot data.BlockPointer, 28 cc *crChains, checkTailPtr bool) { 29 if g, e := len(cc.byOriginal), len(expected); g != e { 30 t.Errorf("Wrong number of originals, %v vs %v", g, e) 31 } 32 33 if g, e := len(cc.byMostRecent), len(expected); g != e { 34 t.Errorf("Wrong number of most recents, %v vs %v", g, e) 35 } 36 37 if g, e := len(cc.renamedOriginals), len(expectedRenames); g != e { 38 t.Errorf("Wrong number of renames, %v vs %v", g, e) 39 } 40 41 if cc.originalRoot != expectedRoot { 42 t.Fatalf("Root pointer incorrect for multi RMDs, %v vs %v", 43 cc.originalRoot, expectedRoot) 44 } 45 46 for original, mostRecent := range expected { 47 chain, ok := cc.byOriginal[original] 48 if !ok { 49 t.Fatalf("No original for %v", original) 50 } 51 52 if checkTailPtr && chain.mostRecent != mostRecent { 53 t.Fatalf("Chain for %v does not end in %v", original, mostRecent) 54 } 55 56 mrChain, ok := cc.byMostRecent[mostRecent] 57 if !ok { 58 t.Fatalf("No most recent for %v", mostRecent) 59 } 60 61 if chain != mrChain { 62 t.Fatalf("Chain from %v does not end in most recent %v "+ 63 "(%v) vs. (%v)", original, mostRecent, chain, mrChain) 64 } 65 } 66 67 if !reflect.DeepEqual(cc.renamedOriginals, expectedRenames) { 68 t.Errorf("Actual renames don't match the expected renames: %v vs %v", 69 cc.renamedOriginals, expectedRenames) 70 } 71 } 72 73 func testCRInitPtrs(n int) (currPtr byte, ptrs []data.BlockPointer, 74 revPtrs map[data.BlockPointer]data.BlockPointer) { 75 currPtr = byte(42) 76 revPtrs = make(map[data.BlockPointer]data.BlockPointer) 77 for i := 0; i < n; i++ { 78 ptr := data.BlockPointer{ID: kbfsblock.FakeID(currPtr)} 79 currPtr++ 80 ptrs = append(ptrs, ptr) 81 revPtrs[ptr] = ptr 82 } 83 return currPtr, ptrs, revPtrs 84 } 85 86 func testCRFillOpPtrs(currPtr byte, 87 expected map[data.BlockPointer]data.BlockPointer, 88 revPtrs map[data.BlockPointer]data.BlockPointer, 89 affectedPtrs []data.BlockPointer, op op) (nextCurrPtr byte) { 90 for _, ptr := range affectedPtrs { 91 newPtr := data.BlockPointer{ID: kbfsblock.FakeID(currPtr)} 92 currPtr++ 93 op.AddUpdate(ptr, newPtr) 94 expected[revPtrs[ptr]] = newPtr 95 revPtrs[newPtr] = revPtrs[ptr] 96 } 97 return currPtr 98 } 99 100 // If one of the ops is a rename, it doesn't check for exact equality 101 func testCRCheckOps(t *testing.T, cc *crChains, original data.BlockPointer, 102 expectedOps []op) { 103 chain, ok := cc.byOriginal[original] 104 if !ok { 105 t.Fatalf("No chain at %v", original) 106 } 107 108 if g, e := len(chain.ops), len(expectedOps); g != e { 109 t.Fatalf("Wrong number of operations: %d vs %d: %v", g, e, chain.ops) 110 } 111 112 codec := kbfscodec.NewMsgpack() 113 for i, op := range chain.ops { 114 eOp := expectedOps[i] 115 // First check for rename create ops. 116 if co, ok := op.(*createOp); ok && co.renamed { 117 eCOp, ok := eOp.(*createOp) 118 if !ok { 119 t.Errorf("Expected op isn't a create for %v[%d]", original, i) 120 } 121 122 if co.NewName != eCOp.NewName || co.Dir.Unref != eCOp.Dir.Unref || 123 !eCOp.renamed { 124 t.Errorf("Bad create op after rename: %v", co) 125 } 126 } else if ro, ok := op.(*rmOp); ok && 127 // We can tell the rm half of a rename because the updates 128 // aren't initialized. 129 len(ro.Updates) == 0 { 130 eROp, ok := eOp.(*rmOp) 131 if !ok { 132 t.Errorf("Expected op isn't an rm for %v[%d]", original, i) 133 } 134 135 if ro.OldName != eROp.OldName || ro.Dir.Unref != eROp.Dir.Unref || 136 eROp.Dir.Ref.IsInitialized() { 137 t.Errorf("Bad create op after rename: %v", ro) 138 } 139 } else { 140 ok, err := kbfscodec.Equal(codec, op, eOp) 141 if err != nil { 142 t.Fatalf("Couldn't compare ops: %v", err) 143 } 144 if !ok { 145 t.Errorf("Unexpected op %v at %v[%d]; expected %v", op, 146 original, i, eOp) 147 } 148 } 149 150 } 151 } 152 153 func newChainMDForTest(t *testing.T) rootMetadataWithKeyAndTimestamp { 154 tlfID := tlf.FakeID(1, tlf.Private) 155 156 uid := keybase1.MakeTestUID(1) 157 bh, err := tlf.MakeHandle( 158 []keybase1.UserOrTeamID{uid.AsUserOrTeam()}, nil, nil, nil, nil) 159 require.NoError(t, err) 160 161 nug := idutiltest.NormalizedUsernameGetter{ 162 uid.AsUserOrTeam(): "fake_user", 163 } 164 165 ctx := context.Background() 166 h, err := tlfhandle.MakeHandle( 167 ctx, bh, bh.Type(), nil, nug, nil, keybase1.OfflineAvailability_NONE) 168 require.NoError(t, err) 169 170 rmd, err := makeInitialRootMetadata(defaultClientMetadataVer, tlfID, h) 171 require.NoError(t, err) 172 rmd.SetLastModifyingWriter(uid) 173 key := kbfscrypto.MakeFakeVerifyingKeyOrBust("fake key") 174 return rootMetadataWithKeyAndTimestamp{ 175 rmd, key, time.Unix(0, 0), 176 } 177 } 178 179 func makeChainCodec() kbfscodec.Codec { 180 codec := kbfscodec.NewMsgpack() 181 RegisterOps(codec) 182 return codec 183 } 184 185 func TestCRChainsSingleOp(t *testing.T) { 186 chainMD := newChainMDForTest(t) 187 188 currPtr, ptrs, revPtrs := testCRInitPtrs(3) 189 rootPtrUnref := ptrs[0] 190 dir1Unref := ptrs[1] 191 dir2Unref := ptrs[2] 192 expected := make(map[data.BlockPointer]data.BlockPointer) 193 194 co, err := newCreateOp("new", dir2Unref, data.File) 195 require.NoError(t, err) 196 _ = testCRFillOpPtrs(currPtr, expected, revPtrs, 197 []data.BlockPointer{rootPtrUnref, dir1Unref, dir2Unref}, co) 198 chainMD.AddOp(co) 199 chainMD.data.Dir.BlockPointer = expected[rootPtrUnref] 200 201 chainMDs := []chainMetadata{chainMD} 202 cc, err := newCRChains( 203 context.Background(), makeChainCodec(), nil, chainMDs, nil, true) 204 if err != nil { 205 t.Fatalf("Error making chains: %v", err) 206 } 207 checkExpectedChains(t, expected, make(map[data.BlockPointer]renameInfo), 208 rootPtrUnref, cc, true) 209 210 // check for the create op 211 testCRCheckOps(t, cc, dir2Unref, []op{co}) 212 } 213 214 func TestCRChainsRenameOp(t *testing.T) { 215 chainMD := newChainMDForTest(t) 216 217 currPtr, ptrs, revPtrs := testCRInitPtrs(3) 218 rootPtrUnref := ptrs[0] 219 dir1Unref := ptrs[1] 220 dir2Unref := ptrs[2] 221 filePtr := data.BlockPointer{ID: kbfsblock.FakeID(currPtr)} 222 currPtr++ 223 expected := make(map[data.BlockPointer]data.BlockPointer) 224 expectedRenames := make(map[data.BlockPointer]renameInfo) 225 226 oldName, newName := "old", "new" 227 ro, err := newRenameOp(oldName, dir1Unref, newName, dir2Unref, filePtr, data.File) 228 require.NoError(t, err) 229 expectedRenames[filePtr] = renameInfo{dir1Unref, "old", dir2Unref, "new"} 230 _ = testCRFillOpPtrs(currPtr, expected, revPtrs, 231 []data.BlockPointer{rootPtrUnref, dir1Unref, dir2Unref}, ro) 232 chainMD.AddOp(ro) 233 chainMD.data.Dir.BlockPointer = expected[rootPtrUnref] 234 235 chainMDs := []chainMetadata{chainMD} 236 cc, err := newCRChains( 237 context.Background(), makeChainCodec(), nil, chainMDs, nil, true) 238 if err != nil { 239 t.Fatalf("Error making chains: %v", err) 240 } 241 242 checkExpectedChains(t, expected, expectedRenames, rootPtrUnref, cc, true) 243 244 co, err := newCreateOp(newName, dir2Unref, data.File) 245 require.NoError(t, err) 246 co.renamed = true 247 testCRCheckOps(t, cc, dir2Unref, []op{co}) 248 rmo, err := newRmOp(oldName, dir1Unref, data.Dir) 249 require.NoError(t, err) 250 testCRCheckOps(t, cc, dir1Unref, []op{rmo}) 251 } 252 253 func testCRChainsMultiOps(t *testing.T) ([]chainMetadata, data.BlockPointer) { 254 // To start, we have: root/dir1/dir2/file1 and root/dir3/file2 255 // Sequence of operations: 256 // * setex root/dir3/file2 257 // * createfile root/dir1/file3 258 // * rename root/dir3/file2 root/dir1/file4 259 // * write root/dir1/file4 260 // * rm root/dir1/dir2/file1 261 262 f1 := "file1" 263 f2 := "file2" 264 f3 := "file3" 265 f4 := "file4" 266 267 currPtr, ptrs, revPtrs := testCRInitPtrs(5) 268 rootPtrUnref := ptrs[0] 269 dir1Unref := ptrs[1] 270 dir2Unref := ptrs[2] 271 dir3Unref := ptrs[3] 272 file4Unref := ptrs[4] 273 file2Ptr := data.BlockPointer{ID: kbfsblock.FakeID(currPtr)} 274 currPtr++ 275 expected := make(map[data.BlockPointer]data.BlockPointer) 276 expectedRenames := make(map[data.BlockPointer]renameInfo) 277 278 bigChainMD := newChainMDForTest(t) 279 var multiChainMDs []chainMetadata 280 281 // setex root/dir3/file2 282 op1, err := newSetAttrOp(f2, dir3Unref, exAttr, file2Ptr) 283 require.NoError(t, err) 284 currPtr = testCRFillOpPtrs(currPtr, expected, revPtrs, 285 []data.BlockPointer{rootPtrUnref, dir3Unref}, op1) 286 expected[file2Ptr] = file2Ptr // no update to the file ptr 287 bigChainMD.AddOp(op1) 288 newChainMD := newChainMDForTest(t) 289 newChainMD.AddOp(op1) 290 newChainMD.data.Dir.BlockPointer = expected[rootPtrUnref] 291 multiChainMDs = append(multiChainMDs, newChainMD) 292 293 // createfile root/dir1/file3 294 op2, err := newCreateOp(f3, dir1Unref, data.File) 295 require.NoError(t, err) 296 currPtr = testCRFillOpPtrs(currPtr, expected, revPtrs, 297 []data.BlockPointer{expected[rootPtrUnref], dir1Unref}, op2) 298 bigChainMD.AddOp(op2) 299 newChainMD = newChainMDForTest(t) 300 newChainMD.AddOp(op2) 301 newChainMD.data.Dir.BlockPointer = expected[rootPtrUnref] 302 multiChainMDs = append(multiChainMDs, newChainMD) 303 304 // rename root/dir3/file2 root/dir1/file4 305 op3, err := newRenameOp(f2, expected[dir3Unref], f4, 306 expected[dir1Unref], file2Ptr, data.File) 307 require.NoError(t, err) 308 expectedRenames[file2Ptr] = renameInfo{dir3Unref, f2, dir1Unref, f4} 309 currPtr = testCRFillOpPtrs(currPtr, expected, revPtrs, 310 []data.BlockPointer{expected[rootPtrUnref], expected[dir1Unref], 311 expected[dir3Unref]}, op3) 312 bigChainMD.AddOp(op3) 313 newChainMD = newChainMDForTest(t) 314 newChainMD.AddOp(op3) 315 newChainMD.data.Dir.BlockPointer = expected[rootPtrUnref] 316 multiChainMDs = append(multiChainMDs, newChainMD) 317 318 // write root/dir1/file4 319 op4, err := newSyncOp(file4Unref) 320 require.NoError(t, err) 321 currPtr = testCRFillOpPtrs(currPtr, expected, revPtrs, 322 []data.BlockPointer{expected[rootPtrUnref], expected[dir1Unref], file4Unref}, 323 op4) 324 bigChainMD.AddOp(op4) 325 newChainMD = newChainMDForTest(t) 326 newChainMD.AddOp(op4) 327 newChainMD.data.Dir.BlockPointer = expected[rootPtrUnref] 328 multiChainMDs = append(multiChainMDs, newChainMD) 329 330 // rm root/dir1/dir2/file1 331 op5, err := newRmOp(f1, dir2Unref, data.File) 332 require.NoError(t, err) 333 _ = testCRFillOpPtrs(currPtr, expected, revPtrs, 334 []data.BlockPointer{expected[rootPtrUnref], expected[dir1Unref], dir2Unref}, 335 op5) 336 bigChainMD.AddOp(op5) 337 newChainMD = newChainMDForTest(t) 338 newChainMD.AddOp(op5) 339 newChainMD.data.Dir.BlockPointer = expected[rootPtrUnref] 340 multiChainMDs = append(multiChainMDs, newChainMD) 341 342 bigChainMD.data.Dir.BlockPointer = expected[rootPtrUnref] 343 chainMDs := []chainMetadata{bigChainMD} 344 cc, err := newCRChains( 345 context.Background(), makeChainCodec(), nil, chainMDs, nil, true) 346 if err != nil { 347 t.Fatalf("Error making chains for big chainMD: %v", err) 348 } 349 checkExpectedChains(t, expected, expectedRenames, rootPtrUnref, cc, true) 350 351 // root should have no direct ops 352 testCRCheckOps(t, cc, rootPtrUnref, []op{}) 353 354 // dir1 should have two creates (one of which is a rename) 355 co1, err := newCreateOp(f4, op3.NewDir.Unref, data.File) 356 require.NoError(t, err) 357 co1.renamed = true 358 testCRCheckOps(t, cc, dir1Unref, []op{op2, co1}) 359 360 // dir2 should have one rm op 361 testCRCheckOps(t, cc, dir2Unref, []op{op5}) 362 363 // dir3 should have the rm part of a rename 364 ro3, err := newRmOp(f2, op3.OldDir.Unref, data.File) 365 require.NoError(t, err) 366 testCRCheckOps(t, cc, dir3Unref, []op{ro3}) 367 368 // file2 should have the setattr 369 testCRCheckOps(t, cc, file2Ptr, []op{op1}) 370 371 // file4 should have one op 372 testCRCheckOps(t, cc, file4Unref, []op{op4}) 373 374 // now make sure the chain of MDs gets the same answers 375 mcc, err := newCRChains( 376 context.Background(), makeChainCodec(), nil, multiChainMDs, nil, true) 377 if err != nil { 378 t.Fatalf("Error making chains for multi chainMDs: %v", err) 379 } 380 if !reflect.DeepEqual(cc.byOriginal, mcc.byOriginal) { 381 t.Fatalf("Heads for multi chainMDs does not match original for big chainMD: %v", 382 mcc.byOriginal) 383 } 384 if !reflect.DeepEqual(cc.byMostRecent, mcc.byMostRecent) { 385 t.Fatalf("Tails for multi chainMDs does not match most recents for "+ 386 "big chainMD: %v", mcc.byMostRecent) 387 } 388 if mcc.originalRoot != rootPtrUnref { 389 t.Fatalf("Root pointer incorrect for multi chainMDs, %v vs %v", 390 mcc.originalRoot, rootPtrUnref) 391 } 392 return multiChainMDs, file4Unref 393 } 394 395 // Test multiple operations, both in one MD and across multiple MDs 396 func TestCRChainsMultiOps(t *testing.T) { 397 testCRChainsMultiOps(t) 398 } 399 400 // Test that we collapse chains correctly 401 func TestCRChainsCollapse(t *testing.T) { 402 // To start, we have: root/dir1/ and root/dir2/file1 403 // Sequence of operations: 404 // * createfile root/dir1/file2 405 // * setex root/dir2/file1 406 // * createfile root/dir1/file3 407 // * createfile root/dir1/file4 408 // * rm root/dir1/file2 409 // * rename root/dir2/file1 root/dir1/file3 410 // * rm root/dir1/file3 411 // * rename root/dir1/file4 root/dir1/file5 412 // * rename root/dir1/file5 root/dir1/file3 413 414 f1 := "file1" 415 f2 := "file2" 416 f3 := "file3" 417 f4 := "file4" 418 f5 := "file5" 419 420 currPtr, ptrs, revPtrs := testCRInitPtrs(3) 421 rootPtrUnref := ptrs[0] 422 dir1Unref := ptrs[1] 423 dir2Unref := ptrs[2] 424 file1Ptr := data.BlockPointer{ID: kbfsblock.FakeID(currPtr)} 425 currPtr++ 426 file4Ptr := data.BlockPointer{ID: kbfsblock.FakeID(currPtr)} 427 currPtr++ 428 expected := make(map[data.BlockPointer]data.BlockPointer) 429 expectedRenames := make(map[data.BlockPointer]renameInfo) 430 431 chainMD := newChainMDForTest(t) 432 433 // createfile root/dir1/file2 434 op1, err := newCreateOp(f2, dir1Unref, data.File) 435 require.NoError(t, err) 436 currPtr = testCRFillOpPtrs(currPtr, expected, revPtrs, 437 []data.BlockPointer{rootPtrUnref, dir1Unref}, op1) 438 chainMD.AddOp(op1) 439 440 // setex root/dir2/file1 441 op2, err := newSetAttrOp(f1, dir2Unref, exAttr, file1Ptr) 442 require.NoError(t, err) 443 currPtr = testCRFillOpPtrs(currPtr, expected, revPtrs, 444 []data.BlockPointer{expected[rootPtrUnref], dir2Unref}, op2) 445 expected[file1Ptr] = file1Ptr 446 chainMD.AddOp(op2) 447 448 // createfile root/dir1/file3 449 op3, err := newCreateOp(f3, expected[dir1Unref], data.File) 450 require.NoError(t, err) 451 currPtr = testCRFillOpPtrs(currPtr, expected, revPtrs, 452 []data.BlockPointer{expected[rootPtrUnref], expected[dir1Unref]}, op3) 453 chainMD.AddOp(op3) 454 455 // createfile root/dir1/file4 456 op4, err := newCreateOp(f4, expected[dir1Unref], data.File) 457 require.NoError(t, err) 458 currPtr = testCRFillOpPtrs(currPtr, expected, revPtrs, 459 []data.BlockPointer{expected[rootPtrUnref], expected[dir1Unref]}, op4) 460 chainMD.AddOp(op4) 461 462 // rm root/dir1/file2 463 op5, err := newRmOp(f2, expected[dir1Unref], data.File) 464 require.NoError(t, err) 465 currPtr = testCRFillOpPtrs(currPtr, expected, revPtrs, 466 []data.BlockPointer{expected[rootPtrUnref], expected[dir1Unref]}, op5) 467 chainMD.AddOp(op5) 468 469 // rename root/dir2/file1 root/dir1/file3 470 op6, err := newRenameOp(f1, expected[dir2Unref], f3, expected[dir1Unref], 471 file1Ptr, data.File) 472 require.NoError(t, err) 473 expectedRenames[file1Ptr] = renameInfo{dir2Unref, f1, dir1Unref, f3} 474 currPtr = testCRFillOpPtrs(currPtr, expected, revPtrs, 475 []data.BlockPointer{expected[rootPtrUnref], expected[dir1Unref], 476 expected[dir2Unref]}, op6) 477 chainMD.AddOp(op6) 478 479 // rm root/dir1/file3 480 op7, err := newRmOp(f3, expected[dir1Unref], data.File) 481 require.NoError(t, err) 482 currPtr = testCRFillOpPtrs(currPtr, expected, revPtrs, 483 []data.BlockPointer{expected[rootPtrUnref], expected[dir1Unref]}, op7) 484 chainMD.AddOp(op7) 485 486 // rename root/dir1/file4 root/dir1/file5 487 op8, err := newRenameOp(f4, expected[dir1Unref], f5, expected[dir1Unref], 488 file4Ptr, data.File) 489 require.NoError(t, err) 490 currPtr = testCRFillOpPtrs(currPtr, expected, revPtrs, 491 []data.BlockPointer{expected[rootPtrUnref], expected[dir1Unref]}, op8) 492 chainMD.AddOp(op8) 493 494 // rename root/dir1/file5 root/dir1/file3 495 op9, err := newRenameOp(f5, expected[dir1Unref], f3, expected[dir1Unref], 496 file4Ptr, data.File) 497 require.NoError(t, err) 498 // expected the previous old name, not the new one 499 expectedRenames[file4Ptr] = renameInfo{dir1Unref, f4, dir1Unref, f3} 500 _ = testCRFillOpPtrs(currPtr, expected, revPtrs, 501 []data.BlockPointer{expected[rootPtrUnref], expected[dir1Unref]}, op9) 502 chainMD.AddOp(op9) 503 504 chainMD.data.Dir.BlockPointer = expected[rootPtrUnref] 505 chainMDs := []chainMetadata{chainMD} 506 cc, err := newCRChains( 507 context.Background(), makeChainCodec(), nil, chainMDs, nil, true) 508 if err != nil { 509 t.Fatalf("Error making chains: %v", err) 510 } 511 checkExpectedChains(t, expected, expectedRenames, rootPtrUnref, cc, 512 false /*tail ref pointer won't match due to collapsing*/) 513 514 // root should have no direct ops 515 testCRCheckOps(t, cc, rootPtrUnref, []op{}) 516 517 // dir1 should only have one createOp (the final rename) 518 co1, err := newCreateOp(f3, op9.OldDir.Unref, data.File) 519 require.NoError(t, err) 520 co1.renamed = true 521 testCRCheckOps(t, cc, dir1Unref, []op{co1}) 522 523 // dir2 should have the rm part of a rename 524 ro2, err := newRmOp(f1, op6.OldDir.Unref, data.File) 525 require.NoError(t, err) 526 testCRCheckOps(t, cc, dir2Unref, []op{ro2}) 527 528 // file1 should have the setattr 529 testCRCheckOps(t, cc, file1Ptr, []op{op2}) 530 } 531 532 func TestCRChainsRemove(t *testing.T) { 533 chainMDs, writtenFileUnref := testCRChainsMultiOps(t) 534 535 for i := range chainMDs { 536 chainMDs[i].(rootMetadataWithKeyAndTimestamp).RootMetadata.SetRevision( 537 kbfsmd.Revision(i)) 538 } 539 540 ccs, err := newCRChains( 541 context.Background(), makeChainCodec(), nil, chainMDs, nil, true) 542 if err != nil { 543 t.Fatalf("Error making chains: %v", err) 544 } 545 546 // This should remove the write operation. 547 removedChains := ccs.remove(context.Background(), 548 logger.NewTestLogger(t), chainMDs[3].Revision()) 549 require.Len(t, removedChains, 1) 550 require.Equal(t, removedChains[0].original, writtenFileUnref) 551 require.Len(t, removedChains[0].ops, 0) 552 } 553 554 func TestCRChainsCollapsedSyncOps(t *testing.T) { 555 chainMD := newChainMDForTest(t) 556 557 currPtr, ptrs, revPtrs := testCRInitPtrs(3) 558 rootPtrUnref := ptrs[0] 559 file1Unref := ptrs[1] 560 file2Unref := ptrs[2] 561 expected := make(map[data.BlockPointer]data.BlockPointer) 562 563 // Alternate contiguous writes between two files 564 currOff := uint64(0) 565 writeLen := uint64(10) 566 numWrites := uint64(3) 567 expected[rootPtrUnref] = rootPtrUnref 568 expected[file1Unref] = file1Unref 569 expected[file2Unref] = file2Unref 570 571 var so1, so2 *syncOp 572 var err error 573 for i := uint64(0); i < numWrites; i++ { 574 so1, err = newSyncOp(expected[file1Unref]) 575 require.NoError(t, err) 576 so1.Writes = append(so1.Writes, WriteRange{Off: currOff, Len: writeLen}) 577 currPtr = testCRFillOpPtrs(currPtr, expected, revPtrs, 578 []data.BlockPointer{expected[rootPtrUnref], expected[file1Unref]}, so1) 579 chainMD.AddOp(so1) 580 chainMD.data.Dir.BlockPointer = expected[rootPtrUnref] 581 582 so2, err = newSyncOp(expected[file2Unref]) 583 require.NoError(t, err) 584 so2.Writes = append(so2.Writes, WriteRange{Off: currOff, Len: writeLen}) 585 currPtr = testCRFillOpPtrs(currPtr, expected, revPtrs, 586 []data.BlockPointer{expected[rootPtrUnref], expected[file2Unref]}, so2) 587 chainMD.AddOp(so2) 588 chainMD.data.Dir.BlockPointer = expected[rootPtrUnref] 589 590 currOff += writeLen 591 } 592 593 chainMDs := []chainMetadata{chainMD} 594 cc, err := newCRChains( 595 context.Background(), makeChainCodec(), nil, chainMDs, nil, true) 596 if err != nil { 597 t.Fatalf("Error making chains: %v", err) 598 } 599 checkExpectedChains(t, expected, make(map[data.BlockPointer]renameInfo), 600 rootPtrUnref, cc, true) 601 602 // newCRChains copies the ops, so modifying them now is ok. 603 so1.Writes = []WriteRange{{Off: 0, Len: writeLen * numWrites}} 604 so2.Writes = []WriteRange{{Off: 0, Len: writeLen * numWrites}} 605 606 // Check for the collapsed syncOps. 607 testCRCheckOps(t, cc, file1Unref, []op{so1}) 608 testCRCheckOps(t, cc, file2Unref, []op{so2}) 609 }