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(): {&copyUnmergedEntryAction{
   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(): {&copyUnmergedEntryAction{
   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: {&copyUnmergedEntryAction{
   662  			dirC2.ChildName("file2"), dirC2.ChildName("file2"),
   663  			dirC2.ChildName(""), false, false, data.DirEntry{}, nil}},
   664  		dirBPtr: {&copyUnmergedEntryAction{
   665  			dirB1.ChildName("dirC"), dirB1.ChildName("dirC"),
   666  			dirB1.ChildName(""), false, false,
   667  			data.DirEntry{}, nil}},
   668  		dirAPtr1: {&copyUnmergedEntryAction{
   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(): {&copyUnmergedEntryAction{
   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(): {&copyUnmergedEntryAction{
   962  			dirA2.ChildName("dirJ"), dirA2.ChildName("dirJ"),
   963  			dirA2.ChildName(""), false, false, data.DirEntry{}, nil}},
   964  		mergedPathE.TailPointer(): {&copyUnmergedEntryAction{
   965  			dirE1.ChildName("dirF"), dirE1.ChildName("dirF"),
   966  			dirE1.ChildName(""), false, false, data.DirEntry{}, nil}},
   967  		mergedPathF.TailPointer(): {&copyUnmergedEntryAction{
   968  			dirF2.ChildName("file3"), dirF2.ChildName("file3"),
   969  			dirF2.ChildName(""), false, false, data.DirEntry{}, nil}},
   970  		mergedPathH.TailPointer(): {&copyUnmergedEntryAction{
   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(): {&copyUnmergedEntryAction{
  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  }