github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/kbfs/libkbfs/conflict_resolver_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 "errors" 9 "reflect" 10 "testing" 11 "time" 12 13 "github.com/golang/mock/gomock" 14 "github.com/keybase/client/go/kbfs/data" 15 "github.com/keybase/client/go/kbfs/env" 16 "github.com/keybase/client/go/kbfs/idutil" 17 "github.com/keybase/client/go/kbfs/kbfscodec" 18 "github.com/keybase/client/go/kbfs/kbfscrypto" 19 "github.com/keybase/client/go/kbfs/kbfsmd" 20 "github.com/keybase/client/go/kbfs/libcontext" 21 "github.com/keybase/client/go/kbfs/test/clocktest" 22 "github.com/keybase/client/go/kbfs/tlf" 23 "github.com/keybase/client/go/kbfs/tlfhandle" 24 kbname "github.com/keybase/client/go/kbun" 25 "github.com/keybase/client/go/protocol/keybase1" 26 "github.com/stretchr/testify/require" 27 "golang.org/x/net/context" 28 ) 29 30 func crTestInit(t *testing.T) (ctx context.Context, cancel context.CancelFunc, 31 mockCtrl *gomock.Controller, config *ConfigMock, cr *ConflictResolver) { 32 ctr := NewSafeTestReporter(t) 33 mockCtrl = gomock.NewController(ctr) 34 config = NewConfigMock(mockCtrl, ctr) 35 config.SetCodec(kbfscodec.NewMsgpack()) 36 config.SetClock(data.WallClock{}) 37 id := tlf.FakeID(1, tlf.Private) 38 fbo := newFolderBranchOps(ctx, env.EmptyAppStateUpdater{}, config, 39 data.FolderBranch{Tlf: id, Branch: data.MasterBranch}, standard, 40 nil, nil, nil) 41 // usernames don't matter for these tests 42 config.mockKbpki.EXPECT().GetNormalizedUsername( 43 gomock.Any(), gomock.Any(), gomock.Any()). 44 AnyTimes().Return(kbname.NormalizedUsername("mockUser"), nil) 45 46 config.mockMdserv.EXPECT().CancelRegistration( 47 gomock.Any(), gomock.Any()).AnyTimes().Return() 48 49 mockDaemon := NewMockKeybaseService(mockCtrl) 50 mockDaemon.EXPECT().LoadUserPlusKeys( 51 gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). 52 AnyTimes().Return(idutil.UserInfo{Name: "mockUser"}, nil) 53 config.SetKeybaseService(mockDaemon) 54 55 timeoutCtx, cancel := context.WithTimeout( 56 context.Background(), individualTestTimeout) 57 initSuccess := false 58 defer func() { 59 if !initSuccess { 60 cancel() 61 } 62 }() 63 64 ctx, err := libcontext.NewContextWithCancellationDelayer(libcontext.NewContextReplayable( 65 timeoutCtx, func(c context.Context) context.Context { 66 return c 67 })) 68 if err != nil { 69 t.Fatal(err) 70 } 71 72 initSuccess = true 73 return ctx, cancel, mockCtrl, config, fbo.cr 74 } 75 76 func crTestShutdown( 77 ctx context.Context, t *testing.T, cancel context.CancelFunc, 78 mockCtrl *gomock.Controller, config *ConfigMock, cr *ConflictResolver) { 79 err := libcontext.CleanupCancellationDelayer(ctx) 80 require.NoError(t, err) 81 config.ctr.CheckForFailures() 82 err = cr.fbo.Shutdown(ctx) 83 require.NoError(t, err) 84 cancel() 85 mockCtrl.Finish() 86 } 87 88 type failingCodec struct { 89 kbfscodec.Codec 90 } 91 92 func (fc failingCodec) Encode(interface{}) ([]byte, error) { 93 return nil, errors.New("Stopping resolution process early") 94 } 95 96 func crMakeFakeRMD(rev kbfsmd.Revision, bid kbfsmd.BranchID) ImmutableRootMetadata { 97 var writerFlags kbfsmd.WriterFlags 98 if bid != kbfsmd.NullBranchID { 99 writerFlags = kbfsmd.MetadataFlagUnmerged 100 } 101 key := kbfscrypto.MakeFakeVerifyingKeyOrBust("fake key") 102 h := &tlfhandle.Handle{} 103 h.SetName("fake") 104 return MakeImmutableRootMetadata(&RootMetadata{ 105 bareMd: &kbfsmd.RootMetadataV2{ 106 WriterMetadataV2: kbfsmd.WriterMetadataV2{ 107 ID: tlf.FakeID(0x1, tlf.Private), 108 WFlags: writerFlags, 109 BID: bid, 110 }, 111 WriterMetadataSigInfo: kbfscrypto.SignatureInfo{ 112 VerifyingKey: key, 113 }, 114 Revision: rev, 115 PrevRoot: kbfsmd.FakeID(byte(rev - 1)), 116 }, 117 tlfHandle: h, 118 data: PrivateMetadata{ 119 Changes: BlockChanges{ 120 Ops: []op{newGCOp(0)}, // arbitrary op to fool unembed checks 121 }, 122 }, 123 }, key, kbfsmd.FakeID(byte(rev)), time.Now(), true) 124 } 125 126 func TestCRInput(t *testing.T) { 127 ctx, cancel, mockCtrl, config, cr := crTestInit(t) 128 defer crTestShutdown(ctx, t, cancel, mockCtrl, config, cr) 129 130 // First try a completely unknown revision 131 cr.Resolve( 132 ctx, kbfsmd.RevisionUninitialized, kbfsmd.RevisionUninitialized) 133 // This should return without doing anything (i.e., without 134 // calling any mock methods) 135 err := cr.Wait(ctx) 136 require.NoError(t, err) 137 138 // Next, try resolving a few items 139 branchPoint := kbfsmd.Revision(2) 140 unmergedHead := kbfsmd.Revision(5) 141 mergedHead := kbfsmd.Revision(15) 142 143 crypto := MakeCryptoCommon(config.Codec(), makeBlockCryptV1()) 144 bid, err := crypto.MakeRandomBranchID() 145 if err != nil { 146 t.Fatalf("Branch id err: %+v", bid) 147 } 148 cr.fbo.unmergedBID = bid 149 cr.fbo.head = crMakeFakeRMD(unmergedHead, bid) 150 cr.fbo.headStatus = headTrusted 151 // serve all the MDs from the cache 152 config.mockMdcache.EXPECT().Put(gomock.Any()).AnyTimes().Return(nil) 153 for i := unmergedHead; i >= branchPoint+1; i-- { 154 config.mockMdcache.EXPECT().Get(cr.fbo.id(), i, bid).Return( 155 crMakeFakeRMD(i, bid), nil) 156 } 157 for i := kbfsmd.RevisionInitial; i <= branchPoint; i++ { 158 config.mockMdcache.EXPECT().Get(cr.fbo.id(), i, bid).Return( 159 ImmutableRootMetadata{}, NoSuchMDError{cr.fbo.id(), branchPoint, bid}) 160 } 161 config.mockMdops.EXPECT().GetUnmergedRange(gomock.Any(), cr.fbo.id(), 162 bid, kbfsmd.RevisionInitial, branchPoint).Return(nil, nil) 163 164 for i := branchPoint; i <= mergedHead; i++ { 165 config.mockMdcache.EXPECT().Get(cr.fbo.id(), i, kbfsmd.NullBranchID).Return( 166 crMakeFakeRMD(i, kbfsmd.NullBranchID), nil) 167 } 168 for i := mergedHead + 1; i <= branchPoint-1+2*maxMDsAtATime; i++ { 169 config.mockMdcache.EXPECT().Get(cr.fbo.id(), i, kbfsmd.NullBranchID).Return( 170 ImmutableRootMetadata{}, NoSuchMDError{cr.fbo.id(), i, kbfsmd.NullBranchID}) 171 } 172 config.mockMdops.EXPECT().GetRange(gomock.Any(), cr.fbo.id(), mergedHead+1, 173 gomock.Any(), nil).Return(nil, nil) 174 175 // CR doesn't see any operations and so it does resolution early. 176 // Just cause an error so it doesn't bother the mocks too much. 177 config.SetCodec(failingCodec{config.Codec()}) 178 config.mockRep.EXPECT().ReportErr(gomock.Any(), gomock.Any(), 179 gomock.Any(), gomock.Any(), gomock.Any()) 180 181 // First try a completely unknown revision 182 cr.Resolve(ctx, unmergedHead, kbfsmd.RevisionUninitialized) 183 err = cr.Wait(ctx) 184 require.NoError(t, err) 185 // Make sure sure the input is up-to-date 186 if cr.currInput.merged != mergedHead { 187 t.Fatalf("Unexpected merged input: %d", cr.currInput.merged) 188 } 189 190 // Now make sure we ignore future inputs with lesser MDs 191 cr.Resolve(ctx, kbfsmd.RevisionUninitialized, mergedHead-1) 192 // This should return without doing anything (i.e., without 193 // calling any mock methods) 194 err = cr.Wait(ctx) 195 require.NoError(t, err) 196 } 197 198 func TestCRInputFracturedRange(t *testing.T) { 199 ctx, cancel, mockCtrl, config, cr := crTestInit(t) 200 defer crTestShutdown(ctx, t, cancel, mockCtrl, config, cr) 201 202 // Next, try resolving a few items 203 branchPoint := kbfsmd.Revision(2) 204 unmergedHead := kbfsmd.Revision(5) 205 mergedHead := kbfsmd.Revision(15) 206 207 crypto := MakeCryptoCommon(config.Codec(), makeBlockCryptV1()) 208 bid, err := crypto.MakeRandomBranchID() 209 if err != nil { 210 t.Fatalf("Branch id err: %+v", bid) 211 } 212 cr.fbo.unmergedBID = bid 213 cr.fbo.head = crMakeFakeRMD(unmergedHead, bid) 214 cr.fbo.headStatus = headTrusted 215 // serve all the MDs from the cache 216 config.mockMdcache.EXPECT().Put(gomock.Any()).AnyTimes().Return(nil) 217 for i := unmergedHead; i >= branchPoint+1; i-- { 218 config.mockMdcache.EXPECT().Get(cr.fbo.id(), i, bid).Return(crMakeFakeRMD(i, bid), nil) 219 } 220 for i := kbfsmd.RevisionInitial; i <= branchPoint; i++ { 221 config.mockMdcache.EXPECT().Get(cr.fbo.id(), i, bid).Return( 222 ImmutableRootMetadata{}, NoSuchMDError{cr.fbo.id(), branchPoint, bid}) 223 } 224 config.mockMdops.EXPECT().GetUnmergedRange(gomock.Any(), cr.fbo.id(), 225 bid, kbfsmd.RevisionInitial, branchPoint).Return(nil, nil) 226 227 skipCacheRevision := kbfsmd.Revision(10) 228 for i := branchPoint; i <= mergedHead; i++ { 229 // Pretend that revision 10 isn't in the cache, and needs to 230 // be fetched from the server. 231 if i != skipCacheRevision { 232 config.mockMdcache.EXPECT().Get(cr.fbo.id(), i, 233 kbfsmd.NullBranchID).Return(crMakeFakeRMD(i, kbfsmd.NullBranchID), nil) 234 } else { 235 config.mockMdcache.EXPECT().Get(cr.fbo.id(), i, 236 kbfsmd.NullBranchID).Return( 237 ImmutableRootMetadata{}, NoSuchMDError{cr.fbo.id(), i, kbfsmd.NullBranchID}) 238 } 239 } 240 config.mockMdops.EXPECT().GetRange(gomock.Any(), cr.fbo.id(), 241 skipCacheRevision, skipCacheRevision, gomock.Any()).Return( 242 []ImmutableRootMetadata{crMakeFakeRMD(skipCacheRevision, kbfsmd.NullBranchID)}, nil) 243 for i := mergedHead + 1; i <= branchPoint-1+2*maxMDsAtATime; i++ { 244 config.mockMdcache.EXPECT().Get(cr.fbo.id(), i, kbfsmd.NullBranchID).Return( 245 ImmutableRootMetadata{}, NoSuchMDError{cr.fbo.id(), i, kbfsmd.NullBranchID}) 246 } 247 config.mockMdops.EXPECT().GetRange(gomock.Any(), cr.fbo.id(), mergedHead+1, 248 gomock.Any(), gomock.Any()).Return(nil, nil) 249 250 // CR doesn't see any operations and so it does resolution early. 251 // Just cause an error so it doesn't bother the mocks too much. 252 config.SetCodec(failingCodec{config.Codec()}) 253 config.mockRep.EXPECT().ReportErr(gomock.Any(), gomock.Any(), 254 gomock.Any(), gomock.Any(), gomock.Any()) 255 256 // Resolve the fractured revision list 257 cr.Resolve(ctx, unmergedHead, kbfsmd.RevisionUninitialized) 258 err = cr.Wait(ctx) 259 require.NoError(t, err) 260 // Make sure sure the input is up-to-date 261 if cr.currInput.merged != mergedHead { 262 t.Fatalf("Unexpected merged input: %d", cr.currInput.merged) 263 } 264 } 265 266 func testCRSharedFolderForUsers( 267 ctx context.Context, t *testing.T, name string, createAs keybase1.UID, 268 configs map[keybase1.UID]Config, dirs []string) map[keybase1.UID]Node { 269 nodes := make(map[keybase1.UID]Node) 270 271 // create by the first user 272 kbfsOps := configs[createAs].KBFSOps() 273 rootNode := GetRootNodeOrBust(ctx, t, configs[createAs], name, tlf.Private) 274 dir := rootNode 275 for _, d := range dirs { 276 dirNext, _, err := kbfsOps.CreateDir(ctx, dir, dir.ChildName(d)) 277 if _, ok := err.(data.NameExistsError); ok { 278 dirNext, _, err = kbfsOps.Lookup(ctx, dir, dir.ChildName(d)) 279 if err != nil { 280 t.Fatalf("Couldn't lookup dir: %v", err) 281 } 282 } else if err != nil { 283 t.Fatalf("Couldn't create dir: %v", err) 284 } 285 dir = dirNext 286 } 287 err := kbfsOps.SyncAll(ctx, rootNode.GetFolderBranch()) 288 if err != nil { 289 t.Fatalf("Couldn't sync all: %v", err) 290 } 291 nodes[createAs] = dir 292 293 for u, config := range configs { 294 if u == createAs { 295 continue 296 } 297 298 kbfsOps := config.KBFSOps() 299 err = kbfsOps.SyncFromServer(ctx, rootNode.GetFolderBranch(), nil) 300 require.NoError(t, err) 301 rootNode := GetRootNodeOrBust(ctx, t, config, name, tlf.Private) 302 dir := rootNode 303 for _, d := range dirs { 304 var err error 305 dir, _, err = kbfsOps.Lookup(ctx, dir, dir.ChildName(d)) 306 if err != nil { 307 t.Fatalf("Couldn't lookup dir: %v", err) 308 } 309 } 310 nodes[u] = dir 311 } 312 return nodes 313 } 314 315 func testCRCheckPathsAndActions(t *testing.T, cr *ConflictResolver, 316 expectedUnmergedPaths []data.Path, expectedMergedPaths map[data.BlockPointer]data.Path, 317 expectedRecreateOps []*createOp, 318 expectedActions map[data.BlockPointer]crActionList) { 319 ctx := libcontext.BackgroundContextWithCancellationDelayer() 320 defer func() { 321 err := libcontext.CleanupCancellationDelayer(ctx) 322 require.NoError(t, err) 323 }() 324 lState := makeFBOLockState() 325 326 // Step 1 -- check the chains and paths 327 unmergedChains, mergedChains, unmergedPaths, mergedPaths, 328 recreateOps, _, _, err := cr.buildChainsAndPaths(ctx, lState, false) 329 if err != nil { 330 t.Fatalf("Couldn't build chains and paths: %v", err) 331 } 332 333 // we don't care about the order of the unmerged paths, so put 334 // them into maps for comparison 335 eUPathMap := make(map[data.BlockPointer]data.Path) 336 for _, p := range expectedUnmergedPaths { 337 eUPathMap[p.TailPointer()] = p 338 } 339 uPathMap := make(map[data.BlockPointer]data.Path) 340 for _, p := range unmergedPaths { 341 uPathMap[p.TailPointer()] = p 342 } 343 344 if !reflect.DeepEqual(eUPathMap, uPathMap) { 345 t.Fatalf("Unmerged paths aren't right. Expected %v, got %v", 346 expectedUnmergedPaths, unmergedPaths) 347 } 348 349 if !reflect.DeepEqual(expectedMergedPaths, mergedPaths) { 350 for k, v := range expectedMergedPaths { 351 t.Logf("Expected: %v -> %v", k, v.Path) 352 t.Logf("Got: %v -> %v", k, mergedPaths[k].Path) 353 } 354 t.Fatalf("Merged paths aren't right. Expected %v, got %v", 355 expectedMergedPaths, mergedPaths) 356 } 357 358 if g, e := len(recreateOps), len(expectedRecreateOps); g != e { 359 t.Fatalf("Different number of recreate ops: %d vs %d", g, e) 360 } 361 362 // Can't use reflect.DeepEqual on the array since these contain 363 // pointers which will always differ. 364 for i, op := range expectedRecreateOps { 365 if g, e := *recreateOps[i], *op; g.Dir.Unref != e.Dir.Unref || 366 g.NewName != e.NewName || g.Type != e.Type { 367 t.Fatalf("Unexpected op at index %d: %v vs %v", i, g, e) 368 } 369 } 370 371 // Now for step 2 -- check the actions 372 actionMap, _, err := cr.computeActions( 373 ctx, unmergedChains, mergedChains, 374 unmergedPaths, mergedPaths, recreateOps, writerInfo{}) 375 if err != nil { 376 t.Fatalf("Couldn't compute actions: %v", err) 377 } 378 if expectedActions == nil { 379 return 380 } 381 382 // Set writer infos to match so DeepEqual will succeed. 383 for k, v := range expectedActions { 384 v2, ok := actionMap[k] 385 if !ok || (len(v) != len(v2)) { 386 break 387 } 388 for i := 0; i < len(v); i++ { 389 if x, ok := v[i].(*dropUnmergedAction); ok { 390 y := v2[i].(*dropUnmergedAction) 391 y.op.setWriterInfo(x.op.getWriterInfo()) 392 } 393 } 394 } 395 396 if !reflect.DeepEqual(expectedActions, actionMap) { 397 for k, v := range expectedActions { 398 t.Logf("Sub %v eq=%v", k, reflect.DeepEqual(v, actionMap[k])) 399 t.Logf("Expected: %v", v) 400 t.Logf("Got: %v", actionMap[k]) 401 } 402 t.Fatalf("Actions aren't right. Expected %v, got %v", 403 expectedActions, actionMap) 404 } 405 } 406 407 func testCRGetCROrBust(t *testing.T, config Config, 408 fb data.FolderBranch) *ConflictResolver { 409 kbfsOpsCast, ok := config.KBFSOps().(*KBFSOpsStandard) 410 if !ok { 411 t.Fatalf("Unexpected KBFSOps type") 412 } 413 ops := kbfsOpsCast.getOpsNoAdd(context.TODO(), fb) 414 return ops.cr 415 } 416 417 // Make two users, u1 and u2, with some common directories in a shared 418 // folder. Pause updates on u2, and have both users make different 419 // updates in a shared subdirectory. Then run through the first few 420 // conflict resolution steps directly through u2's conflict resolver 421 // and make sure the resulting unmerged path maps correctly to the 422 // merged path. 423 func TestCRMergedChainsSimple(t *testing.T) { 424 var userName1, userName2 kbname.NormalizedUsername = "u1", "u2" 425 config1, uid1, ctx, cancel := kbfsOpsConcurInit(t, userName1, userName2) 426 defer kbfsConcurTestShutdown(ctx, t, config1, cancel) 427 428 config2 := ConfigAsUser(config1, userName2) 429 defer CheckConfigAndShutdown(ctx, t, config2) 430 session2, err := config2.KBPKI().GetCurrentSession(ctx) 431 if err != nil { 432 t.Fatal(err) 433 } 434 uid2 := session2.UID 435 436 name := userName1.String() + "," + userName2.String() 437 438 configs := make(map[keybase1.UID]Config) 439 configs[uid1] = config1 440 configs[uid2] = config2 441 nodes := testCRSharedFolderForUsers(ctx, t, name, uid1, configs, []string{"dir"}) 442 dir1 := nodes[uid1] 443 dir2 := nodes[uid2] 444 fb := dir1.GetFolderBranch() 445 446 // pause user 2 447 _, err = DisableUpdatesForTesting(config2, fb) 448 if err != nil { 449 t.Fatalf("Can't disable updates for user 2: %v", err) 450 } 451 452 // user1 makes a file 453 _, _, err = config1.KBFSOps().CreateFile( 454 ctx, dir1, dir1.ChildName("file1"), false, NoExcl) 455 if err != nil { 456 t.Fatalf("Couldn't create file: %v", err) 457 } 458 err = config1.KBFSOps().SyncAll(ctx, dir1.GetFolderBranch()) 459 if err != nil { 460 t.Fatalf("Couldn't sync all: %v", err) 461 } 462 463 cr1 := testCRGetCROrBust(t, config1, fb) 464 cr2 := testCRGetCROrBust(t, config2, fb) 465 cr2.Shutdown() 466 467 // user2 makes a file (causes a conflict, and goes unstaged) 468 _, _, err = config2.KBFSOps().CreateFile( 469 ctx, dir2, dir2.ChildName("file2"), false, NoExcl) 470 if err != nil { 471 t.Fatalf("Couldn't create file: %v", err) 472 } 473 err = config2.KBFSOps().SyncAll(ctx, dir2.GetFolderBranch()) 474 if err != nil { 475 t.Fatalf("Couldn't sync all: %v", err) 476 } 477 478 // Now step through conflict resolution manually for user 2 479 mergedPaths := make(map[data.BlockPointer]data.Path) 480 expectedUnmergedPath := cr2.fbo.nodeCache.PathFromNode(dir2) 481 mergedPath := cr1.fbo.nodeCache.PathFromNode(dir1) 482 mergedPaths[expectedUnmergedPath.TailPointer()] = mergedPath 483 expectedActions := map[data.BlockPointer]crActionList{ 484 mergedPath.TailPointer(): {©UnmergedEntryAction{ 485 dir2.ChildName("file2"), dir2.ChildName("file2"), 486 dir2.ChildName(""), false, false, data.DirEntry{}, nil}}, 487 } 488 testCRCheckPathsAndActions(t, cr2, []data.Path{expectedUnmergedPath}, 489 mergedPaths, nil, expectedActions) 490 } 491 492 // Same as TestCRMergedChainsSimple, but the two users make changes in 493 // different, unrelated subdirectories, forcing the resolver to use 494 // mostly original block pointers when constructing the merged path. 495 func TestCRMergedChainsDifferentDirectories(t *testing.T) { 496 var userName1, userName2 kbname.NormalizedUsername = "u1", "u2" 497 config1, uid1, ctx, cancel := kbfsOpsConcurInit(t, userName1, userName2) 498 defer kbfsConcurTestShutdown(ctx, t, config1, cancel) 499 500 config2 := ConfigAsUser(config1, userName2) 501 defer CheckConfigAndShutdown(ctx, t, config2) 502 session2, err := config2.KBPKI().GetCurrentSession(ctx) 503 if err != nil { 504 t.Fatal(err) 505 } 506 uid2 := session2.UID 507 508 name := userName1.String() + "," + userName2.String() 509 510 configs := make(map[keybase1.UID]Config) 511 configs[uid1] = config1 512 configs[uid2] = config2 513 nodesA := testCRSharedFolderForUsers(ctx, t, name, uid1, configs, []string{"dirA"}) 514 dirA1 := nodesA[uid1] 515 nodesB := testCRSharedFolderForUsers(ctx, t, name, uid1, configs, []string{"dirB"}) 516 dirB1 := nodesB[uid1] 517 dirB2 := nodesB[uid2] 518 fb := dirA1.GetFolderBranch() 519 520 // pause user 2 521 _, err = DisableUpdatesForTesting(config2, fb) 522 if err != nil { 523 t.Fatalf("Can't disable updates for user 2: %v", err) 524 } 525 526 // user1 makes a file in dir A 527 _, _, err = config1.KBFSOps().CreateFile( 528 ctx, dirA1, dirA1.ChildName("file1"), false, NoExcl) 529 if err != nil { 530 t.Fatalf("Couldn't create file: %v", err) 531 } 532 err = config1.KBFSOps().SyncAll(ctx, dirA1.GetFolderBranch()) 533 if err != nil { 534 t.Fatalf("Couldn't sync all: %v", err) 535 } 536 537 cr1 := testCRGetCROrBust(t, config1, fb) 538 cr2 := testCRGetCROrBust(t, config2, fb) 539 cr2.Shutdown() 540 541 // user2 makes a file in dir B 542 _, _, err = config2.KBFSOps().CreateFile( 543 ctx, dirB2, dirB2.ChildName("file2"), false, NoExcl) 544 if err != nil { 545 t.Fatalf("Couldn't create file: %v", err) 546 } 547 err = config2.KBFSOps().SyncAll(ctx, dirB2.GetFolderBranch()) 548 if err != nil { 549 t.Fatalf("Couldn't sync all: %v", err) 550 } 551 552 // Now step through conflict resolution manually for user 2 553 mergedPaths := make(map[data.BlockPointer]data.Path) 554 expectedUnmergedPath := cr2.fbo.nodeCache.PathFromNode(dirB2) 555 mergedPath := cr1.fbo.nodeCache.PathFromNode(dirB1) 556 mergedPaths[expectedUnmergedPath.TailPointer()] = mergedPath 557 expectedActions := map[data.BlockPointer]crActionList{ 558 mergedPath.TailPointer(): {©UnmergedEntryAction{ 559 dirB2.ChildName("file2"), dirB2.ChildName("file2"), 560 dirB2.ChildName(""), false, false, data.DirEntry{}, nil}}, 561 } 562 testCRCheckPathsAndActions(t, cr2, []data.Path{expectedUnmergedPath}, 563 mergedPaths, nil, expectedActions) 564 } 565 566 // Same as TestCRMergedChainsSimple, but u1 actually deletes some of 567 // the subdirectories used by u2, forcing the resolver to generate 568 // some recreateOps. 569 func TestCRMergedChainsDeletedDirectories(t *testing.T) { 570 var userName1, userName2 kbname.NormalizedUsername = "u1", "u2" 571 config1, uid1, ctx, cancel := kbfsOpsConcurInit(t, userName1, userName2) 572 defer kbfsConcurTestShutdown(ctx, t, config1, cancel) 573 574 config2 := ConfigAsUser(config1, userName2) 575 defer CheckConfigAndShutdown(ctx, t, config2) 576 session2, err := config2.KBPKI().GetCurrentSession(ctx) 577 if err != nil { 578 t.Fatal(err) 579 } 580 uid2 := session2.UID 581 582 name := userName1.String() + "," + userName2.String() 583 584 configs := make(map[keybase1.UID]Config) 585 configs[uid1] = config1 586 configs[uid2] = config2 587 nodesA := testCRSharedFolderForUsers(ctx, t, name, uid1, configs, []string{"dirA"}) 588 dirA1 := nodesA[uid1] 589 fb := dirA1.GetFolderBranch() 590 591 cr1 := testCRGetCROrBust(t, config1, fb) 592 cr2 := testCRGetCROrBust(t, config2, fb) 593 cr2.Shutdown() 594 595 nodesB := testCRSharedFolderForUsers(ctx, t, name, uid1, configs, 596 []string{"dirA", "dirB"}) 597 dirB1 := nodesB[uid1] 598 nodesC := testCRSharedFolderForUsers(ctx, t, name, uid1, configs, 599 []string{"dirA", "dirB", "dirC"}) 600 dirC2 := nodesC[uid2] 601 dirAPtr := cr1.fbo.nodeCache.PathFromNode(dirA1).TailPointer() 602 dirBPtr := cr1.fbo.nodeCache.PathFromNode(dirB1).TailPointer() 603 dirCPtr := cr2.fbo.nodeCache.PathFromNode(dirC2).TailPointer() 604 605 // pause user 2 606 _, err = DisableUpdatesForTesting(config2, fb) 607 if err != nil { 608 t.Fatalf("Can't disable updates for user 2: %v", err) 609 } 610 611 // user1 deletes dirB and dirC 612 err = config1.KBFSOps().RemoveDir(ctx, dirB1, dirB1.ChildName("dirC")) 613 if err != nil { 614 t.Fatalf("Couldn't remove dir: %v", err) 615 } 616 err = config1.KBFSOps().RemoveDir(ctx, dirA1, dirA1.ChildName("dirB")) 617 if err != nil { 618 t.Fatalf("Couldn't remove dir: %v", err) 619 } 620 err = config1.KBFSOps().SyncAll(ctx, dirB1.GetFolderBranch()) 621 if err != nil { 622 t.Fatalf("Couldn't sync all: %v", err) 623 } 624 625 // user2 makes a file in dir C 626 _, _, err = config2.KBFSOps().CreateFile( 627 ctx, dirC2, dirC2.ChildName("file2"), false, NoExcl) 628 if err != nil { 629 t.Fatalf("Couldn't create file: %v", err) 630 } 631 err = config2.KBFSOps().SyncAll(ctx, dirC2.GetFolderBranch()) 632 if err != nil { 633 t.Fatalf("Couldn't sync all: %v", err) 634 } 635 636 // Now step through conflict resolution manually for user 2 637 638 expectedUnmergedPath := cr2.fbo.nodeCache.PathFromNode(dirC2) 639 // The merged path will consist of the latest root and dirA 640 // components, plus the original blockpointers of the deleted 641 // nodes. 642 mergedPaths := make(map[data.BlockPointer]data.Path) 643 mergedPath := cr1.fbo.nodeCache.PathFromNode(dirA1) 644 mergedPath.Path = append(mergedPath.Path, data.PathNode{ 645 BlockPointer: dirBPtr, 646 Name: dirA1.ChildName("dirB"), 647 }) 648 mergedPath.Path = append(mergedPath.Path, data.PathNode{ 649 BlockPointer: dirCPtr, 650 Name: dirB1.ChildName("dirC"), 651 }) 652 mergedPaths[expectedUnmergedPath.TailPointer()] = mergedPath 653 654 coB, err := newCreateOp("dirB", dirAPtr, data.Dir) 655 require.NoError(t, err) 656 coC, err := newCreateOp("dirC", dirBPtr, data.Dir) 657 require.NoError(t, err) 658 659 dirAPtr1 := cr1.fbo.nodeCache.PathFromNode(dirA1).TailPointer() 660 expectedActions := map[data.BlockPointer]crActionList{ 661 dirCPtr: {©UnmergedEntryAction{ 662 dirC2.ChildName("file2"), dirC2.ChildName("file2"), 663 dirC2.ChildName(""), false, false, data.DirEntry{}, nil}}, 664 dirBPtr: {©UnmergedEntryAction{ 665 dirB1.ChildName("dirC"), dirB1.ChildName("dirC"), 666 dirB1.ChildName(""), false, false, 667 data.DirEntry{}, nil}}, 668 dirAPtr1: {©UnmergedEntryAction{ 669 dirA1.ChildName("dirB"), dirA1.ChildName("dirB"), 670 dirA1.ChildName(""), false, false, data.DirEntry{}, nil}}, 671 } 672 673 testCRCheckPathsAndActions(t, cr2, []data.Path{expectedUnmergedPath}, 674 mergedPaths, []*createOp{coB, coC}, expectedActions) 675 } 676 677 // Same as TestCRMergedChainsSimple, but u1 actually renames one of 678 // the subdirectories used by u2, forcing the resolver to follow the 679 // path across the rename. 680 func TestCRMergedChainsRenamedDirectory(t *testing.T) { 681 var userName1, userName2 kbname.NormalizedUsername = "u1", "u2" 682 config1, uid1, ctx, cancel := kbfsOpsConcurInit(t, userName1, userName2) 683 defer kbfsConcurTestShutdown(ctx, t, config1, cancel) 684 685 config2 := ConfigAsUser(config1, userName2) 686 defer CheckConfigAndShutdown(ctx, t, config2) 687 session2, err := config2.KBPKI().GetCurrentSession(ctx) 688 if err != nil { 689 t.Fatal(err) 690 } 691 uid2 := session2.UID 692 693 name := userName1.String() + "," + userName2.String() 694 695 configs := make(map[keybase1.UID]Config) 696 configs[uid1] = config1 697 configs[uid2] = config2 698 nodesA := testCRSharedFolderForUsers(ctx, t, name, uid1, configs, []string{"dirA"}) 699 dirA1 := nodesA[uid1] 700 fb := dirA1.GetFolderBranch() 701 702 cr1 := testCRGetCROrBust(t, config1, fb) 703 cr2 := testCRGetCROrBust(t, config2, fb) 704 cr2.Shutdown() 705 706 nodesB := testCRSharedFolderForUsers(ctx, t, name, uid1, configs, 707 []string{"dirA", "dirB"}) 708 dirB1 := nodesB[uid1] 709 nodesC := testCRSharedFolderForUsers(ctx, t, name, uid1, configs, 710 []string{"dirA", "dirB", "dirC"}) 711 dirC1 := nodesC[uid1] 712 dirC2 := nodesC[uid2] 713 dirCPtr := cr1.fbo.nodeCache.PathFromNode(dirC1).TailPointer() 714 715 // pause user 2 716 _, err = DisableUpdatesForTesting(config2, fb) 717 if err != nil { 718 t.Fatalf("Can't disable updates for user 2: %v", err) 719 } 720 721 // user1 makes /dirA/dirD and renames dirC into it 722 dirD1, _, err := config1.KBFSOps().CreateDir( 723 ctx, dirA1, dirA1.ChildName("dirD")) 724 if err != nil { 725 t.Fatalf("Couldn't make dir: %v", err) 726 } 727 err = config1.KBFSOps().Rename( 728 ctx, dirB1, dirB1.ChildName("dirC"), dirD1, dirD1.ChildName("dirC")) 729 if err != nil { 730 t.Fatalf("Couldn't remove dir: %v", err) 731 } 732 err = config1.KBFSOps().SyncAll(ctx, dirA1.GetFolderBranch()) 733 if err != nil { 734 t.Fatalf("Couldn't sync all: %v", err) 735 } 736 737 // user2 makes a file in dir C 738 _, _, err = config2.KBFSOps().CreateFile( 739 ctx, dirC2, dirC2.ChildName("file2"), false, NoExcl) 740 if err != nil { 741 t.Fatalf("Couldn't create file: %v", err) 742 } 743 err = config2.KBFSOps().SyncAll(ctx, dirC2.GetFolderBranch()) 744 if err != nil { 745 t.Fatalf("Couldn't sync all: %v", err) 746 } 747 748 // Now step through conflict resolution manually for user 2 749 750 expectedUnmergedPath := cr2.fbo.nodeCache.PathFromNode(dirC2) 751 // The merged path will be the dirD/dirC path 752 mergedPaths := make(map[data.BlockPointer]data.Path) 753 mergedPath := cr1.fbo.nodeCache.PathFromNode(dirD1) 754 mergedPath.Path = append(mergedPath.Path, data.PathNode{ 755 BlockPointer: dirCPtr, 756 Name: dirB1.ChildName("dirC"), 757 }) 758 mergedPaths[expectedUnmergedPath.TailPointer()] = mergedPath 759 760 expectedActions := map[data.BlockPointer]crActionList{ 761 mergedPath.TailPointer(): {©UnmergedEntryAction{ 762 dirC2.ChildName("file2"), dirC2.ChildName("file2"), 763 dirC2.ChildName(""), false, false, data.DirEntry{}, nil}}, 764 } 765 766 testCRCheckPathsAndActions(t, cr2, []data.Path{expectedUnmergedPath}, 767 mergedPaths, nil, expectedActions) 768 } 769 770 // A mix of the above TestCRMergedChains* tests, with various other 771 // types of operations thrown in the mix (like u2 deleting unrelated 772 // directories, etc). 773 func TestCRMergedChainsComplex(t *testing.T) { 774 var userName1, userName2 kbname.NormalizedUsername = "u1", "u2" 775 config1, uid1, ctx, cancel := kbfsOpsConcurInit(t, userName1, userName2) 776 defer kbfsConcurTestShutdown(ctx, t, config1, cancel) 777 778 config2 := ConfigAsUser(config1, userName2) 779 defer CheckConfigAndShutdown(ctx, t, config2) 780 session2, err := config2.KBPKI().GetCurrentSession(ctx) 781 if err != nil { 782 t.Fatal(err) 783 } 784 uid2 := session2.UID 785 786 // Setup: 787 // /dirA/dirB/dirC 788 // /dirA/dirB/dirD/file5 789 // /dirE/dirF 790 // /dirG/dirH 791 792 name := userName1.String() + "," + userName2.String() 793 794 configs := make(map[keybase1.UID]Config) 795 configs[uid1] = config1 796 configs[uid2] = config2 797 nodesA := testCRSharedFolderForUsers(ctx, t, name, uid1, configs, []string{"dirA"}) 798 dirA1 := nodesA[uid1] 799 dirA2 := nodesA[uid2] 800 fb := dirA1.GetFolderBranch() 801 802 cr1 := testCRGetCROrBust(t, config1, fb) 803 cr2 := testCRGetCROrBust(t, config2, fb) 804 cr2.Shutdown() 805 806 nodesB := testCRSharedFolderForUsers(ctx, t, name, uid1, configs, 807 []string{"dirA", "dirB"}) 808 dirB1 := nodesB[uid1] 809 dirB2 := nodesB[uid2] 810 nodesC := testCRSharedFolderForUsers(ctx, t, name, uid1, configs, 811 []string{"dirA", "dirB", "dirC"}) 812 dirC2 := nodesC[uid2] 813 nodesD := testCRSharedFolderForUsers(ctx, t, name, uid1, configs, 814 []string{"dirA", "dirB", "dirD"}) 815 dirD1 := nodesD[uid1] 816 dirD2 := nodesD[uid2] 817 nodesE := testCRSharedFolderForUsers(ctx, t, name, uid1, configs, []string{"dirE"}) 818 dirE1 := nodesE[uid1] 819 nodesF := testCRSharedFolderForUsers(ctx, t, name, uid1, configs, 820 []string{"dirE", "dirF"}) 821 dirF2 := nodesF[uid2] 822 dirEPtr := cr2.fbo.nodeCache.PathFromNode(dirE1).TailPointer() 823 dirFPtr := cr2.fbo.nodeCache.PathFromNode(dirF2).TailPointer() 824 nodesG := testCRSharedFolderForUsers(ctx, t, name, uid1, configs, []string{"dirG"}) 825 dirG1 := nodesG[uid1] 826 nodesH := testCRSharedFolderForUsers(ctx, t, name, uid1, configs, 827 []string{"dirG", "dirH"}) 828 dirH1 := nodesH[uid1] 829 dirH2 := nodesH[uid2] 830 dirHPtr := cr1.fbo.nodeCache.PathFromNode(dirH1).TailPointer() 831 832 _, _, err = config1.KBFSOps().CreateFile( 833 ctx, dirD1, dirD1.ChildName("file5"), false, NoExcl) 834 if err != nil { 835 t.Fatalf("Couldn't create file: %v", err) 836 } 837 err = config1.KBFSOps().SyncAll(ctx, dirD1.GetFolderBranch()) 838 if err != nil { 839 t.Fatalf("Couldn't sync all: %v", err) 840 } 841 842 err = config2.KBFSOps().SyncFromServer(ctx, fb, nil) 843 require.NoError(t, err) 844 845 // pause user 2 846 _, err = DisableUpdatesForTesting(config2, fb) 847 if err != nil { 848 t.Fatalf("Can't disable updates for user 2: %v", err) 849 } 850 851 // user 1: 852 // touch /dirA/file1 853 // rm -rf /dirE/dirF 854 // mv /dirG/dirH /dirA/dirI 855 // 856 // user 2: 857 // mkdir /dirA/dirJ 858 // touch /dirA/dirJ/file2 859 // touch /dirE/dirF/file3 860 // touch /dirA/dirB/dirC/file4 861 // mv /dirA/dirB/dirC/file4 /dirG/dirH/file4 862 // rm /dirA/dirB/dirD/file5 863 // rm -rf /dirA/dirB/dirD 864 865 // user 1: 866 _, _, err = config1.KBFSOps().CreateFile( 867 ctx, dirA1, dirA1.ChildName("file1"), false, NoExcl) 868 if err != nil { 869 t.Fatalf("Couldn't create file: %v", err) 870 } 871 err = config1.KBFSOps().RemoveDir( 872 ctx, dirE1, dirE1.ChildName("dirF")) 873 if err != nil { 874 t.Fatalf("Couldn't remove dir: %v", err) 875 } 876 err = config1.KBFSOps().Rename( 877 ctx, dirG1, dirG1.ChildName("dirH"), dirA1, dirA1.ChildName("dirI")) 878 if err != nil { 879 t.Fatalf("Couldn't remove dir: %v", err) 880 } 881 err = config1.KBFSOps().SyncAll(ctx, dirA1.GetFolderBranch()) 882 if err != nil { 883 t.Fatalf("Couldn't sync all: %v", err) 884 } 885 886 // user2 887 dirJ2, _, err := config2.KBFSOps().CreateDir( 888 ctx, dirA2, dirA2.ChildName("dirJ")) 889 if err != nil { 890 t.Fatalf("Couldn't create file: %v", err) 891 } 892 _, _, err = config2.KBFSOps().CreateFile( 893 ctx, dirJ2, dirJ2.ChildName("file2"), false, NoExcl) 894 if err != nil { 895 t.Fatalf("Couldn't create file: %v", err) 896 } 897 _, _, err = config2.KBFSOps().CreateFile( 898 ctx, dirF2, dirF2.ChildName("file3"), false, NoExcl) 899 if err != nil { 900 t.Fatalf("Couldn't create file: %v", err) 901 } 902 _, _, err = config2.KBFSOps().CreateFile( 903 ctx, dirC2, dirC2.ChildName("file4"), false, NoExcl) 904 if err != nil { 905 t.Fatalf("Couldn't create file: %v", err) 906 } 907 err = config2.KBFSOps().Rename( 908 ctx, dirC2, dirC2.ChildName("file4"), dirH2, dirH2.ChildName("file4")) 909 if err != nil { 910 t.Fatalf("Couldn't remove dir: %v", err) 911 } 912 err = config2.KBFSOps().RemoveEntry(ctx, dirD2, dirD2.ChildName("file5")) 913 if err != nil { 914 t.Fatalf("Couldn't remove dir: %v", err) 915 } 916 err = config2.KBFSOps().RemoveDir(ctx, dirB2, dirB2.ChildName("dirD")) 917 if err != nil { 918 t.Fatalf("Couldn't remove dir: %v", err) 919 } 920 err = config2.KBFSOps().SyncAll(ctx, dirB2.GetFolderBranch()) 921 if err != nil { 922 t.Fatalf("Couldn't sync all: %v", err) 923 } 924 925 // Now step through conflict resolution manually for user 2 926 927 uPathA2 := cr2.fbo.nodeCache.PathFromNode(dirA2) 928 uPathF2 := cr2.fbo.nodeCache.PathFromNode(dirF2) 929 // no expected unmerged path for dirJ or dirC 930 uPathH2 := cr2.fbo.nodeCache.PathFromNode(dirH2) 931 // no expected unmerged path for dirD 932 uPathB2 := cr2.fbo.nodeCache.PathFromNode(dirB2) 933 934 mergedPaths := make(map[data.BlockPointer]data.Path) 935 // Both users updated A 936 mergedPathA := cr1.fbo.nodeCache.PathFromNode(dirA1) 937 mergedPaths[uPathA2.TailPointer()] = mergedPathA 938 // user 1 deleted dirF, so reconstruct 939 mergedPathF := cr1.fbo.nodeCache.PathFromNode(dirE1) 940 mergedPathF.Path = append(mergedPathF.Path, data.PathNode{ 941 BlockPointer: dirFPtr, 942 Name: dirE1.ChildName("dirF"), 943 }) 944 mergedPaths[uPathF2.TailPointer()] = mergedPathF 945 // dirH from user 2 is /dirA/dirI for user 1 946 mergedPathH := cr1.fbo.nodeCache.PathFromNode(dirA1) 947 mergedPathH.Path = append(mergedPathH.Path, data.PathNode{ 948 BlockPointer: dirHPtr, 949 Name: dirA1.ChildName("dirI"), 950 }) 951 mergedPaths[uPathH2.TailPointer()] = mergedPathH 952 // dirB wasn't touched by user 1 953 mergedPathB := cr1.fbo.nodeCache.PathFromNode(dirB1) 954 mergedPaths[uPathB2.TailPointer()] = mergedPathB 955 956 coF, err := newCreateOp("dirF", dirEPtr, data.Dir) 957 require.NoError(t, err) 958 959 mergedPathE := cr1.fbo.nodeCache.PathFromNode(dirE1) 960 expectedActions := map[data.BlockPointer]crActionList{ 961 mergedPathA.TailPointer(): {©UnmergedEntryAction{ 962 dirA2.ChildName("dirJ"), dirA2.ChildName("dirJ"), 963 dirA2.ChildName(""), false, false, data.DirEntry{}, nil}}, 964 mergedPathE.TailPointer(): {©UnmergedEntryAction{ 965 dirE1.ChildName("dirF"), dirE1.ChildName("dirF"), 966 dirE1.ChildName(""), false, false, data.DirEntry{}, nil}}, 967 mergedPathF.TailPointer(): {©UnmergedEntryAction{ 968 dirF2.ChildName("file3"), dirF2.ChildName("file3"), 969 dirF2.ChildName(""), false, false, data.DirEntry{}, nil}}, 970 mergedPathH.TailPointer(): {©UnmergedEntryAction{ 971 dirH2.ChildName("file4"), dirH2.ChildName("file4"), 972 dirH2.ChildName(""), false, false, data.DirEntry{}, nil}}, 973 mergedPathB.TailPointer(): {&rmMergedEntryAction{ 974 dirB2.ChildName("dirD")}}, 975 } 976 // `rm file5` doesn't get an action because the parent directory 977 // was deleted in the unmerged branch. 978 979 testCRCheckPathsAndActions(t, cr2, []data.Path{uPathA2, uPathF2, uPathH2, 980 uPathB2}, mergedPaths, []*createOp{coF}, expectedActions) 981 } 982 983 // Tests that conflict resolution detects and can fix rename cycles. 984 func TestCRMergedChainsRenameCycleSimple(t *testing.T) { 985 var userName1, userName2 kbname.NormalizedUsername = "u1", "u2" 986 config1, uid1, ctx, cancel := kbfsOpsConcurInit(t, userName1, userName2) 987 defer kbfsConcurTestShutdown(ctx, t, config1, cancel) 988 989 clock, now := clocktest.NewTestClockAndTimeNow() 990 config1.SetClock(clock) 991 992 config2 := ConfigAsUser(config1, userName2) 993 defer CheckConfigAndShutdown(ctx, t, config2) 994 session2, err := config2.KBPKI().GetCurrentSession(ctx) 995 if err != nil { 996 t.Fatal(err) 997 } 998 uid2 := session2.UID 999 1000 name := userName1.String() + "," + userName2.String() 1001 1002 configs := make(map[keybase1.UID]Config) 1003 configs[uid1] = config1 1004 configs[uid2] = config2 1005 nodesRoot := testCRSharedFolderForUsers(ctx, t, name, uid1, configs, []string{"root"}) 1006 dirRoot1 := nodesRoot[uid1] 1007 dirRoot2 := nodesRoot[uid2] 1008 fb := dirRoot1.GetFolderBranch() 1009 1010 nodesA := testCRSharedFolderForUsers(ctx, t, name, uid1, configs, 1011 []string{"root", "dirA"}) 1012 dirA1 := nodesA[uid1] 1013 1014 nodesB := testCRSharedFolderForUsers(ctx, t, name, uid1, configs, 1015 []string{"root", "dirB"}) 1016 dirB1 := nodesB[uid1] 1017 dirB2 := nodesB[uid2] 1018 1019 cr1 := testCRGetCROrBust(t, config1, fb) 1020 cr2 := testCRGetCROrBust(t, config2, fb) 1021 cr2.Shutdown() 1022 1023 // pause user 2 1024 _, err = DisableUpdatesForTesting(config2, fb) 1025 if err != nil { 1026 t.Fatalf("Can't disable updates for user 2: %v", err) 1027 } 1028 1029 // user1 moves dirB into dirA 1030 err = config1.KBFSOps().Rename( 1031 ctx, dirRoot1, dirRoot1.ChildName("dirB"), dirA1, 1032 dirA1.ChildName("dirB")) 1033 if err != nil { 1034 t.Fatalf("Couldn't make dir: %v", err) 1035 } 1036 err = config1.KBFSOps().SyncAll(ctx, dirRoot1.GetFolderBranch()) 1037 if err != nil { 1038 t.Fatalf("Couldn't sync all: %v", err) 1039 } 1040 1041 // user2 moves dirA into dirB 1042 err = config2.KBFSOps().Rename( 1043 ctx, dirRoot2, dirRoot2.ChildName("dirA"), dirB2, 1044 dirB2.ChildName("dirA")) 1045 if err != nil { 1046 t.Fatalf("Couldn't make dir: %v", err) 1047 } 1048 err = config2.KBFSOps().SyncAll(ctx, dirRoot2.GetFolderBranch()) 1049 if err != nil { 1050 t.Fatalf("Couldn't sync all: %v", err) 1051 } 1052 1053 // Now step through conflict resolution manually for user 2 1054 1055 mergedPaths := make(map[data.BlockPointer]data.Path) 1056 1057 // root 1058 unmergedPathRoot := cr2.fbo.nodeCache.PathFromNode(dirRoot2) 1059 mergedPathRoot := cr1.fbo.nodeCache.PathFromNode(dirRoot1) 1060 mergedPaths[unmergedPathRoot.TailPointer()] = mergedPathRoot 1061 unmergedPathB := cr2.fbo.nodeCache.PathFromNode(dirB2) 1062 mergedPathB := cr1.fbo.nodeCache.PathFromNode(dirB1) 1063 mergedPaths[unmergedPathB.TailPointer()] = mergedPathB 1064 1065 ro, err := newRmOp("dirA", unmergedPathRoot.TailPointer(), data.Dir) 1066 require.NoError(t, err) 1067 err = ro.Dir.setRef(unmergedPathRoot.TailPointer()) 1068 require.NoError(t, err) 1069 ro.dropThis = true 1070 ro.setWriterInfo(writerInfo{}) 1071 ro.setFinalPath(unmergedPathRoot) 1072 ro.setLocalTimestamp(now) 1073 expectedActions := map[data.BlockPointer]crActionList{ 1074 mergedPathRoot.TailPointer(): {&dropUnmergedAction{ro}}, 1075 mergedPathB.TailPointer(): {©UnmergedEntryAction{ 1076 dirB2.ChildName("dirA"), dirB2.ChildName("dirA"), 1077 dirB2.ChildName("./../"), false, false, data.DirEntry{}, nil}}, 1078 } 1079 1080 testCRCheckPathsAndActions(t, cr2, []data.Path{unmergedPathRoot, unmergedPathB}, 1081 mergedPaths, nil, expectedActions) 1082 } 1083 1084 // Tests that conflict resolution detects and renames conflicts. 1085 func TestCRMergedChainsConflictSimple(t *testing.T) { 1086 var userName1, userName2 kbname.NormalizedUsername = "u1", "u2" 1087 config1, uid1, ctx, cancel := kbfsOpsConcurInit(t, userName1, userName2) 1088 defer kbfsConcurTestShutdown(ctx, t, config1, cancel) 1089 1090 config2 := ConfigAsUser(config1, userName2) 1091 defer CheckConfigAndShutdown(ctx, t, config2) 1092 session2, err := config2.KBPKI().GetCurrentSession(ctx) 1093 if err != nil { 1094 t.Fatal(err) 1095 } 1096 uid2 := session2.UID 1097 1098 clock, now := clocktest.NewTestClockAndTimeNow() 1099 config2.SetClock(clock) 1100 1101 name := userName1.String() + "," + userName2.String() 1102 1103 configs := make(map[keybase1.UID]Config) 1104 configs[uid1] = config1 1105 configs[uid2] = config2 1106 nodesRoot := testCRSharedFolderForUsers(ctx, t, name, uid1, configs, []string{"root"}) 1107 dirRoot1 := nodesRoot[uid1] 1108 dirRoot2 := nodesRoot[uid2] 1109 fb := dirRoot1.GetFolderBranch() 1110 1111 cr1 := testCRGetCROrBust(t, config1, fb) 1112 cr2 := testCRGetCROrBust(t, config2, fb) 1113 cr2.Shutdown() 1114 1115 // pause user 2 1116 _, err = DisableUpdatesForTesting(config2, fb) 1117 if err != nil { 1118 t.Fatalf("Can't disable updates for user 2: %v", err) 1119 } 1120 1121 // user1 creates file1 1122 _, _, err = config1.KBFSOps().CreateFile( 1123 ctx, dirRoot1, dirRoot1.ChildName("file1"), false, NoExcl) 1124 if err != nil { 1125 t.Fatalf("Couldn't make file: %v", err) 1126 } 1127 err = config1.KBFSOps().SyncAll(ctx, dirRoot1.GetFolderBranch()) 1128 if err != nil { 1129 t.Fatalf("Couldn't sync all: %v", err) 1130 } 1131 1132 // user2 also create file1, but makes it executable 1133 _, _, err = config2.KBFSOps().CreateFile( 1134 ctx, dirRoot2, dirRoot2.ChildName("file1"), true, NoExcl) 1135 if err != nil { 1136 t.Fatalf("Couldn't make dir: %v", err) 1137 } 1138 err = config2.KBFSOps().SyncAll(ctx, dirRoot2.GetFolderBranch()) 1139 if err != nil { 1140 t.Fatalf("Couldn't sync all: %v", err) 1141 } 1142 1143 // Now step through conflict resolution manually for user 2 1144 mergedPaths := make(map[data.BlockPointer]data.Path) 1145 1146 // root 1147 unmergedPathRoot := cr2.fbo.nodeCache.PathFromNode(dirRoot2) 1148 mergedPathRoot := cr1.fbo.nodeCache.PathFromNode(dirRoot1) 1149 mergedPaths[unmergedPathRoot.TailPointer()] = mergedPathRoot 1150 1151 cre := WriterDeviceDateConflictRenamer{} 1152 expectedActions := map[data.BlockPointer]crActionList{ 1153 mergedPathRoot.TailPointer(): {&renameUnmergedAction{ 1154 dirRoot1.ChildName("file1"), 1155 dirRoot1.ChildName(cre.ConflictRenameHelper( 1156 now, "u2", "dev1", "file1")), 1157 dirRoot1.ChildName(""), 0, false, data.ZeroPtr, data.ZeroPtr}}, 1158 } 1159 1160 testCRCheckPathsAndActions(t, cr2, []data.Path{unmergedPathRoot}, 1161 mergedPaths, nil, expectedActions) 1162 } 1163 1164 // Tests that conflict resolution detects and renames conflicts. 1165 func TestCRMergedChainsConflictFileCollapse(t *testing.T) { 1166 var userName1, userName2 kbname.NormalizedUsername = "u1", "u2" 1167 config1, uid1, ctx, cancel := kbfsOpsConcurInit(t, userName1, userName2) 1168 defer kbfsConcurTestShutdown(ctx, t, config1, cancel) 1169 1170 config2 := ConfigAsUser(config1, userName2) 1171 defer CheckConfigAndShutdown(ctx, t, config2) 1172 session2, err := config2.KBPKI().GetCurrentSession(ctx) 1173 if err != nil { 1174 t.Fatal(err) 1175 } 1176 uid2 := session2.UID 1177 1178 clock, now := clocktest.NewTestClockAndTimeNow() 1179 config2.SetClock(clock) 1180 1181 name := userName1.String() + "," + userName2.String() 1182 1183 configs := make(map[keybase1.UID]Config) 1184 configs[uid1] = config1 1185 configs[uid2] = config2 1186 nodesRoot := testCRSharedFolderForUsers(ctx, t, name, uid1, configs, []string{"root"}) 1187 dirRoot1 := nodesRoot[uid1] 1188 dirRoot2 := nodesRoot[uid2] 1189 fb := dirRoot1.GetFolderBranch() 1190 1191 cr1 := testCRGetCROrBust(t, config1, fb) 1192 cr2 := testCRGetCROrBust(t, config2, fb) 1193 cr2.Shutdown() 1194 1195 // user1 creates file 1196 _, _, err = config1.KBFSOps().CreateFile( 1197 ctx, dirRoot1, dirRoot1.ChildName("file"), false, NoExcl) 1198 if err != nil { 1199 t.Fatalf("Couldn't make file: %v", err) 1200 } 1201 err = config1.KBFSOps().SyncAll(ctx, dirRoot1.GetFolderBranch()) 1202 if err != nil { 1203 t.Fatalf("Couldn't sync all: %v", err) 1204 } 1205 1206 // user2 lookup 1207 err = config2.KBFSOps().SyncFromServer(ctx, fb, nil) 1208 if err != nil { 1209 t.Fatalf("Couldn't sync user 2") 1210 } 1211 file2, _, err := config2.KBFSOps().Lookup( 1212 ctx, dirRoot2, dirRoot2.ChildName("file")) 1213 if err != nil { 1214 t.Fatalf("Couldn't lookup file: %v", err) 1215 } 1216 1217 filePtr := cr2.fbo.nodeCache.PathFromNode(file2).TailPointer() 1218 dirRootPtr := cr2.fbo.nodeCache.PathFromNode(dirRoot2).TailPointer() 1219 1220 // pause user 2 1221 _, err = DisableUpdatesForTesting(config2, fb) 1222 if err != nil { 1223 t.Fatalf("Can't disable updates for user 2: %v", err) 1224 } 1225 1226 // user1 deletes the file and creates another 1227 err = config1.KBFSOps().RemoveEntry( 1228 ctx, dirRoot1, dirRoot1.ChildName("file")) 1229 if err != nil { 1230 t.Fatalf("Couldn't remove file: %v", err) 1231 } 1232 _, _, err = config1.KBFSOps().CreateFile( 1233 ctx, dirRoot1, dirRoot1.ChildName("file"), false, NoExcl) 1234 if err != nil { 1235 t.Fatalf("Couldn't re-make file: %v", err) 1236 } 1237 err = config1.KBFSOps().SyncAll(ctx, dirRoot1.GetFolderBranch()) 1238 if err != nil { 1239 t.Fatalf("Couldn't sync all: %v", err) 1240 } 1241 1242 // user2 updates the file attribute and writes too. 1243 err = config2.KBFSOps().SetEx(ctx, file2, true) 1244 if err != nil { 1245 t.Fatalf("Couldn't set ex: %v", err) 1246 } 1247 err = config2.KBFSOps().Write(ctx, file2, []byte{1, 2, 3}, 0) 1248 if err != nil { 1249 t.Fatalf("Couldn't write: %v", err) 1250 } 1251 err = config2.KBFSOps().SyncAll(ctx, file2.GetFolderBranch()) 1252 if err != nil { 1253 t.Fatalf("Couldn't sync: %v", err) 1254 } 1255 1256 // Now step through conflict resolution manually for user 2 1257 mergedPaths := make(map[data.BlockPointer]data.Path) 1258 1259 // file (needs to be recreated) 1260 unmergedPathFile := cr2.fbo.nodeCache.PathFromNode(file2) 1261 mergedPathFile := cr1.fbo.nodeCache.PathFromNode(dirRoot1) 1262 mergedPathFile.Path = append(mergedPathFile.Path, data.PathNode{ 1263 BlockPointer: filePtr, 1264 Name: dirRoot1.ChildName("file"), 1265 }) 1266 mergedPaths[unmergedPathFile.TailPointer()] = mergedPathFile 1267 1268 coFile, err := newCreateOp("file", dirRootPtr, data.Exec) 1269 require.NoError(t, err) 1270 1271 cre := WriterDeviceDateConflictRenamer{} 1272 mergedPathRoot := cr1.fbo.nodeCache.PathFromNode(dirRoot1) 1273 // Both unmerged actions should collapse into just one rename operation 1274 expectedActions := map[data.BlockPointer]crActionList{ 1275 mergedPathRoot.TailPointer(): {&renameUnmergedAction{ 1276 dirRoot1.ChildName("file"), 1277 dirRoot2.ChildName(cre.ConflictRenameHelper( 1278 now, "u2", "dev1", "file")), 1279 dirRoot1.ChildName(""), 0, false, data.ZeroPtr, data.ZeroPtr}}, 1280 } 1281 1282 testCRCheckPathsAndActions(t, cr2, []data.Path{unmergedPathFile}, 1283 mergedPaths, []*createOp{coFile}, expectedActions) 1284 } 1285 1286 // Test that actions get executed properly in the simple case of two 1287 // files being created simultaneously in the same directory. 1288 func TestCRDoActionsSimple(t *testing.T) { 1289 var userName1, userName2 kbname.NormalizedUsername = "u1", "u2" 1290 config1, uid1, ctx, cancel := kbfsOpsConcurInit(t, userName1, userName2) 1291 defer kbfsConcurTestShutdown(ctx, t, config1, cancel) 1292 1293 config2 := ConfigAsUser(config1, userName2) 1294 defer CheckConfigAndShutdown(ctx, t, config2) 1295 session2, err := config2.KBPKI().GetCurrentSession(ctx) 1296 if err != nil { 1297 t.Fatal(err) 1298 } 1299 uid2 := session2.UID 1300 1301 name := userName1.String() + "," + userName2.String() 1302 1303 configs := make(map[keybase1.UID]Config) 1304 configs[uid1] = config1 1305 configs[uid2] = config2 1306 nodes := testCRSharedFolderForUsers(ctx, t, name, uid1, configs, []string{"dir"}) 1307 dir1 := nodes[uid1] 1308 dir2 := nodes[uid2] 1309 fb := dir1.GetFolderBranch() 1310 1311 // pause user 2 1312 _, err = DisableUpdatesForTesting(config2, fb) 1313 if err != nil { 1314 t.Fatalf("Can't disable updates for user 2: %v", err) 1315 } 1316 1317 // user1 makes a file 1318 _, _, err = config1.KBFSOps().CreateFile( 1319 ctx, dir1, dir1.ChildName("file1"), false, NoExcl) 1320 if err != nil { 1321 t.Fatalf("Couldn't create file: %v", err) 1322 } 1323 err = config1.KBFSOps().SyncAll(ctx, dir1.GetFolderBranch()) 1324 if err != nil { 1325 t.Fatalf("Couldn't sync all: %v", err) 1326 } 1327 1328 cr1 := testCRGetCROrBust(t, config1, fb) 1329 cr2 := testCRGetCROrBust(t, config2, fb) 1330 cr2.Shutdown() 1331 1332 // user2 makes a file (causes a conflict, and goes unstaged) 1333 _, _, err = config2.KBFSOps().CreateFile( 1334 ctx, dir2, dir2.ChildName("file2"), false, NoExcl) 1335 if err != nil { 1336 t.Fatalf("Couldn't create file: %v", err) 1337 } 1338 err = config2.KBFSOps().SyncAll(ctx, dir2.GetFolderBranch()) 1339 if err != nil { 1340 t.Fatalf("Couldn't sync all: %v", err) 1341 } 1342 1343 lState := makeFBOLockState() 1344 1345 // Now run through conflict resolution manually for user2. 1346 unmergedChains, mergedChains, unmergedPaths, mergedPaths, 1347 recreateOps, _, _, err := cr2.buildChainsAndPaths(ctx, lState, false) 1348 if err != nil { 1349 t.Fatalf("Couldn't build chains and paths: %v", err) 1350 } 1351 1352 actionMap, _, err := cr2.computeActions(ctx, unmergedChains, mergedChains, 1353 unmergedPaths, mergedPaths, recreateOps, writerInfo{}) 1354 if err != nil { 1355 t.Fatalf("Couldn't compute actions: %v", err) 1356 } 1357 1358 dbm := newDirBlockMapMemory() 1359 newFileBlocks := newFileBlockMapMemory() 1360 dirtyBcache := data.SimpleDirtyBlockCacheStandard() 1361 err = cr2.doActions(ctx, lState, unmergedChains, mergedChains, 1362 unmergedPaths, mergedPaths, actionMap, dbm, newFileBlocks, dirtyBcache) 1363 if err != nil { 1364 t.Fatalf("Couldn't do actions: %v", err) 1365 } 1366 1367 // Does the merged block contain both entries? 1368 mergedRootPath := cr1.fbo.nodeCache.PathFromNode(dir1) 1369 block1, ok := dbm.blocks[mergedRootPath.TailPointer()] 1370 if !ok { 1371 t.Fatalf("Couldn't find merged block at path %s", mergedRootPath) 1372 } 1373 if g, e := len(block1.Children), 2; g != e { 1374 t.Errorf("Unexpected number of children: %d vs %d", g, e) 1375 } 1376 for _, file := range []string{"file1", "file2"} { 1377 if _, ok := block1.Children[file]; !ok { 1378 t.Errorf("Couldn't find entry in merged children: %s", file) 1379 } 1380 } 1381 if len(newFileBlocks.blocks) != 0 { 1382 t.Errorf("Unexpected new file blocks!") 1383 } 1384 } 1385 1386 // Test that actions get executed properly in the case of two 1387 // simultaneous writes to the same file. 1388 func TestCRDoActionsWriteConflict(t *testing.T) { 1389 var userName1, userName2 kbname.NormalizedUsername = "u1", "u2" 1390 config1, uid1, ctx, cancel := kbfsOpsConcurInit(t, userName1, userName2) 1391 defer kbfsConcurTestShutdown(ctx, t, config1, cancel) 1392 1393 config2 := ConfigAsUser(config1, userName2) 1394 defer CheckConfigAndShutdown(ctx, t, config2) 1395 session2, err := config2.KBPKI().GetCurrentSession(ctx) 1396 if err != nil { 1397 t.Fatal(err) 1398 } 1399 uid2 := session2.UID 1400 1401 clock, now := clocktest.NewTestClockAndTimeNow() 1402 config2.SetClock(clock) 1403 1404 name := userName1.String() + "," + userName2.String() 1405 1406 configs := make(map[keybase1.UID]Config) 1407 configs[uid1] = config1 1408 configs[uid2] = config2 1409 nodes := testCRSharedFolderForUsers(ctx, t, name, uid1, configs, []string{"dir"}) 1410 dir1 := nodes[uid1] 1411 dir2 := nodes[uid2] 1412 fb := dir1.GetFolderBranch() 1413 1414 // user1 makes a file 1415 file1, _, err := config1.KBFSOps().CreateFile( 1416 ctx, dir1, dir1.ChildName("file"), false, NoExcl) 1417 if err != nil { 1418 t.Fatalf("Couldn't create file: %v", err) 1419 } 1420 err = config1.KBFSOps().SyncAll(ctx, dir1.GetFolderBranch()) 1421 if err != nil { 1422 t.Fatalf("Couldn't sync all: %v", err) 1423 } 1424 1425 // user2 lookup 1426 err = config2.KBFSOps().SyncFromServer(ctx, fb, nil) 1427 if err != nil { 1428 t.Fatalf("Couldn't sync user 2") 1429 } 1430 file2, _, err := config2.KBFSOps().Lookup(ctx, dir2, dir2.ChildName("file")) 1431 if err != nil { 1432 t.Fatalf("Couldn't lookup file: %v", err) 1433 } 1434 1435 // pause user 2 1436 _, err = DisableUpdatesForTesting(config2, fb) 1437 if err != nil { 1438 t.Fatalf("Can't disable updates for user 2: %v", err) 1439 } 1440 1441 cr1 := testCRGetCROrBust(t, config1, fb) 1442 cr2 := testCRGetCROrBust(t, config2, fb) 1443 cr2.Shutdown() 1444 1445 // user1 writes the file 1446 err = config1.KBFSOps().Write(ctx, file1, []byte{1, 2, 3}, 0) 1447 if err != nil { 1448 t.Fatalf("Couldn't write file: %v", err) 1449 } 1450 err = config1.KBFSOps().SyncAll(ctx, file1.GetFolderBranch()) 1451 if err != nil { 1452 t.Fatalf("Couldn't sync file: %v", err) 1453 } 1454 1455 // user2 writes the file 1456 unmergedData := []byte{4, 5, 6} 1457 err = config2.KBFSOps().Write(ctx, file2, unmergedData, 0) 1458 if err != nil { 1459 t.Fatalf("Couldn't write file: %v", err) 1460 } 1461 err = config2.KBFSOps().SyncAll(ctx, file2.GetFolderBranch()) 1462 if err != nil { 1463 t.Fatalf("Couldn't sync file: %v", err) 1464 } 1465 1466 lState := makeFBOLockState() 1467 1468 // Now run through conflict resolution manually for user2. 1469 unmergedChains, mergedChains, unmergedPaths, mergedPaths, 1470 recreateOps, _, _, err := cr2.buildChainsAndPaths(ctx, lState, false) 1471 if err != nil { 1472 t.Fatalf("Couldn't build chains and paths: %v", err) 1473 } 1474 1475 actionMap, _, err := cr2.computeActions(ctx, unmergedChains, mergedChains, 1476 unmergedPaths, mergedPaths, recreateOps, writerInfo{}) 1477 if err != nil { 1478 t.Fatalf("Couldn't compute actions: %v", err) 1479 } 1480 1481 dbm := newDirBlockMapMemory() 1482 newFileBlocks := newFileBlockMapMemory() 1483 dirtyBcache := data.SimpleDirtyBlockCacheStandard() 1484 err = cr2.doActions(ctx, lState, unmergedChains, mergedChains, 1485 unmergedPaths, mergedPaths, actionMap, dbm, newFileBlocks, dirtyBcache) 1486 if err != nil { 1487 t.Fatalf("Couldn't do actions: %v", err) 1488 } 1489 1490 // Does the merged block contain the two files? 1491 mergedRootPath := cr1.fbo.nodeCache.PathFromNode(dir1) 1492 cre := WriterDeviceDateConflictRenamer{} 1493 mergedName := cre.ConflictRenameHelper(now, "u2", "dev1", "file") 1494 if len(newFileBlocks.blocks) != 1 { 1495 t.Errorf("Unexpected new file blocks!") 1496 } 1497 if blocks, ok := newFileBlocks.blocks[mergedRootPath.TailPointer()]; !ok { 1498 t.Errorf("No blocks for dir merged ptr: %v", 1499 mergedRootPath.TailPointer()) 1500 } else if len(blocks) != 1 { 1501 t.Errorf("Unexpected number of blocks") 1502 } else if info, ok := blocks[mergedName]; !ok { 1503 t.Errorf("No block for name %s", mergedName) 1504 } else if info.block.IsInd { 1505 t.Errorf("Unexpected indirect block") 1506 } else if g, e := info.block.Contents, unmergedData; !reflect.DeepEqual(g, e) { 1507 t.Errorf("Unexpected block contents: %v vs %v", g, e) 1508 } 1509 1510 // NOTE: the action doesn't actually create the entry, so this 1511 // test can only check that newFileBlocks looks correct. 1512 }