github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/kbfs/test/cr_simple_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  // These tests all do one conflict-free operation while a user is unstaged.
     6  
     7  package test
     8  
     9  import (
    10  	"testing"
    11  	"time"
    12  )
    13  
    14  // bob writes a non-conflicting file while unstaged
    15  func TestCrUnmergedFile(t *testing.T) {
    16  	test(t,
    17  		users("alice", "bob"),
    18  		as(alice,
    19  			mkfile("a/b", "hello"),
    20  		),
    21  		as(bob,
    22  			disableUpdates(),
    23  		),
    24  		as(alice,
    25  			write("a/c", "world"),
    26  		),
    27  		as(bob, noSync(),
    28  			write("a/d", "uh oh"),
    29  			reenableUpdates(),
    30  			lsdir("a/", m{"b": "FILE", "c": "FILE", "d": "FILE"}),
    31  			read("a/b", "hello"),
    32  			read("a/c", "world"),
    33  			read("a/d", "uh oh"),
    34  		),
    35  		as(alice,
    36  			lsdir("a/", m{"b": "FILE", "c": "FILE", "d": "FILE"}),
    37  			read("a/b", "hello"),
    38  			read("a/c", "world"),
    39  			read("a/d", "uh oh"),
    40  		),
    41  	)
    42  }
    43  
    44  // bob writes a non-conflicting dir (containing a file) while unstaged
    45  func TestCrUnmergedDir(t *testing.T) {
    46  	test(t,
    47  		users("alice", "bob"),
    48  		as(alice,
    49  			mkfile("a/b", "hello"),
    50  		),
    51  		as(bob,
    52  			disableUpdates(),
    53  		),
    54  		as(alice,
    55  			write("a/c", "world"),
    56  		),
    57  		as(bob, noSync(),
    58  			write("a/d/e", "uh oh"),
    59  			reenableUpdates(),
    60  			lsdir("a/", m{"b": "FILE", "c": "FILE", "d": "DIR"}),
    61  			read("a/b", "hello"),
    62  			read("a/c", "world"),
    63  			lsdir("a/d", m{"e": "FILE"}),
    64  			read("a/d/e", "uh oh"),
    65  		),
    66  		as(alice,
    67  			lsdir("a/", m{"b": "FILE", "c": "FILE", "d": "DIR"}),
    68  			read("a/b", "hello"),
    69  			read("a/c", "world"),
    70  			lsdir("a/d", m{"e": "FILE"}),
    71  			read("a/d/e", "uh oh"),
    72  		),
    73  	)
    74  }
    75  
    76  // bob creates a non-conflicting symlink(while unstaged),
    77  func TestCrUnmergedSymlink(t *testing.T) {
    78  	test(t,
    79  		skip("dokan", "Does not work with Dokan."),
    80  		users("alice", "bob"),
    81  		as(alice,
    82  			mkfile("a/b", "hello"),
    83  		),
    84  		as(bob,
    85  			disableUpdates(),
    86  		),
    87  		as(alice,
    88  			write("a/c", "world"),
    89  		),
    90  		as(bob, noSync(),
    91  			link("a/d", "b"),
    92  			reenableUpdates(),
    93  			lsdir("a/", m{"b": "FILE", "c": "FILE", "d": "SYM"}),
    94  			read("a/b", "hello"),
    95  			read("a/c", "world"),
    96  			read("a/d", "hello"),
    97  		),
    98  		as(alice,
    99  			lsdir("a/", m{"b": "FILE", "c": "FILE", "d": "SYM"}),
   100  			read("a/b", "hello"),
   101  			read("a/c", "world"),
   102  			read("a/d", "hello"),
   103  		),
   104  	)
   105  }
   106  
   107  // bob makes a non-conflicting file executable while unstaged
   108  func TestCrUnmergedSetex(t *testing.T) {
   109  	test(t,
   110  		users("alice", "bob"),
   111  		as(alice,
   112  			mkfile("a/b", "hello"),
   113  		),
   114  		as(bob,
   115  			disableUpdates(),
   116  		),
   117  		as(alice,
   118  			write("a/c", "world"),
   119  		),
   120  		as(bob, noSync(),
   121  			setex("a/b", true),
   122  			reenableUpdates(),
   123  			lsdir("a/", m{"b": "EXEC", "c": "FILE"}),
   124  			read("a/c", "world"),
   125  		),
   126  		as(alice,
   127  			lsdir("a/", m{"b": "EXEC", "c": "FILE"}),
   128  			read("a/c", "world"),
   129  		),
   130  	)
   131  }
   132  
   133  // bob sets the mtime on a file while unstaged
   134  func TestCrUnmergedSetMtime(t *testing.T) {
   135  	targetMtime := time.Now().Add(1 * time.Minute)
   136  	test(t,
   137  		users("alice", "bob"),
   138  		as(alice,
   139  			mkfile("a/b", "hello"),
   140  		),
   141  		as(bob,
   142  			disableUpdates(),
   143  		),
   144  		as(alice,
   145  			write("a/c", "world"),
   146  		),
   147  		as(bob, noSync(),
   148  			setmtime("a/b", targetMtime),
   149  			reenableUpdates(),
   150  			lsdir("a/", m{"b": "FILE", "c": "FILE"}),
   151  			read("a/c", "world"),
   152  			mtime("a/b", targetMtime),
   153  		),
   154  		as(alice,
   155  			lsdir("a/", m{"b": "FILE", "c": "FILE"}),
   156  			read("a/c", "world"),
   157  			mtime("a/b", targetMtime),
   158  		),
   159  	)
   160  }
   161  
   162  // bob sets the mtime on a file in a newly-created directory while
   163  // unstaged.  Regression test for KBFS-2162.
   164  func TestCrUnmergedSetMtimeInNewDir(t *testing.T) {
   165  	targetMtime := time.Now().Add(1 * time.Minute)
   166  	test(t,
   167  		users("alice", "bob"),
   168  		as(alice,
   169  			mkfile("a/b", "hello"),
   170  		),
   171  		as(bob,
   172  			disableUpdates(),
   173  		),
   174  		as(alice,
   175  			write("a/c", "world"),
   176  		),
   177  		as(bob, noSync(),
   178  			mkdir("d"),
   179  			mkdir("d/e"),
   180  			setmtime("d/e", targetMtime),
   181  			reenableUpdates(),
   182  			lsdir("a/", m{"b": "FILE", "c": "FILE"}),
   183  			read("a/c", "world"),
   184  			lsdir("d/", m{"e": "DIR"}),
   185  			mtime("d/e", targetMtime),
   186  		),
   187  		as(alice,
   188  			lsdir("a/", m{"b": "FILE", "c": "FILE"}),
   189  			read("a/c", "world"),
   190  			lsdir("d/", m{"e": "DIR"}),
   191  			mtime("d/e", targetMtime),
   192  		),
   193  	)
   194  }
   195  
   196  // bob sets the mtime on a moved file while unstaged
   197  func TestCrUnmergedSetMtimeOnMovedFile(t *testing.T) {
   198  	targetMtime := time.Now().Add(1 * time.Minute)
   199  	test(t,
   200  		users("alice", "bob"),
   201  		as(alice,
   202  			mkfile("a/b", "hello"),
   203  			mkdir("b"),
   204  		),
   205  		as(bob,
   206  			disableUpdates(),
   207  		),
   208  		as(alice,
   209  			rename("a/b", "b/a"),
   210  		),
   211  		as(bob, noSync(),
   212  			setmtime("a/b", targetMtime),
   213  			reenableUpdates(),
   214  			lsdir("", m{"a": "DIR", "b": "DIR"}),
   215  			lsdir("a", m{}),
   216  			lsdir("b", m{"a": "FILE"}),
   217  			mtime("b/a", targetMtime),
   218  		),
   219  		as(alice,
   220  			lsdir("", m{"a": "DIR", "b": "DIR"}),
   221  			lsdir("a", m{}),
   222  			lsdir("b", m{"a": "FILE"}),
   223  			mtime("b/a", targetMtime),
   224  		),
   225  	)
   226  }
   227  
   228  // bob sets the mtime on an empty file while unstaged.  We want to
   229  // test this separately from a file with contents, to make sure we can
   230  // properly identify a file node that is empty (and hence can be
   231  // decoded as a DirBlock).
   232  func TestCrUnmergedSetMtimeEmptyFile(t *testing.T) {
   233  	targetMtime := time.Now().Add(1 * time.Minute)
   234  	test(t,
   235  		users("alice", "bob"),
   236  		as(alice,
   237  			mkfile("a/b", ""),
   238  		),
   239  		as(bob,
   240  			disableUpdates(),
   241  		),
   242  		as(alice,
   243  			write("a/c", "hello"),
   244  		),
   245  		as(bob, noSync(),
   246  			setmtime("a/b", targetMtime),
   247  			reenableUpdates(),
   248  			lsdir("a/", m{"b": "FILE", "c": "FILE"}),
   249  			read("a/c", "hello"),
   250  			mtime("a/b", targetMtime),
   251  		),
   252  		as(alice,
   253  			lsdir("a/", m{"b": "FILE", "c": "FILE"}),
   254  			read("a/c", "hello"),
   255  			mtime("a/b", targetMtime),
   256  		),
   257  	)
   258  }
   259  
   260  // bob sets the mtime on a dir while unstaged
   261  func TestCrUnmergedSetMtimeOnDir(t *testing.T) {
   262  	targetMtime := time.Now().Add(1 * time.Minute)
   263  	test(t,
   264  		users("alice", "bob"),
   265  		as(alice,
   266  			mkdir("a"),
   267  		),
   268  		as(bob,
   269  			disableUpdates(),
   270  		),
   271  		as(alice,
   272  			write("b", "hello"),
   273  		),
   274  		as(bob, noSync(),
   275  			setmtime("a", targetMtime),
   276  			reenableUpdates(),
   277  			lsdir("", m{"a": "DIR", "b": "FILE"}),
   278  			read("b", "hello"),
   279  			mtime("a", targetMtime),
   280  		),
   281  		as(alice,
   282  			lsdir("", m{"a": "DIR", "b": "FILE"}),
   283  			read("b", "hello"),
   284  			mtime("a", targetMtime),
   285  		),
   286  	)
   287  }
   288  
   289  // bob sets the mtime on a moved dir while unstaged
   290  func TestCrUnmergedSetMtimeOnMovedDir(t *testing.T) {
   291  	targetMtime := time.Now().Add(1 * time.Minute)
   292  	test(t,
   293  		users("alice", "bob"),
   294  		as(alice,
   295  			mkdir("a"),
   296  			mkdir("b"),
   297  		),
   298  		as(bob,
   299  			disableUpdates(),
   300  		),
   301  		as(alice,
   302  			rename("a", "b/a"),
   303  		),
   304  		as(bob, noSync(),
   305  			setmtime("a", targetMtime),
   306  			reenableUpdates(),
   307  			lsdir("", m{"b": "DIR"}),
   308  			lsdir("b", m{"a": "DIR"}),
   309  			mtime("b/a", targetMtime),
   310  		),
   311  		as(alice,
   312  			lsdir("", m{"b": "DIR"}),
   313  			lsdir("b", m{"a": "DIR"}),
   314  			mtime("b/a", targetMtime),
   315  		),
   316  	)
   317  }
   318  
   319  // bob sets the mtime of a dir modified by alice.
   320  func TestCrUnmergedSetMtimeOnModifiedDir(t *testing.T) {
   321  	targetMtime := time.Now().Add(1 * time.Minute)
   322  	test(t,
   323  		users("alice", "bob"),
   324  		as(alice,
   325  			mkdir("a"),
   326  		),
   327  		as(bob,
   328  			disableUpdates(),
   329  		),
   330  		as(alice,
   331  			mkfile("a/b", "hello"),
   332  		),
   333  		as(bob, noSync(),
   334  			setmtime("a", targetMtime),
   335  			reenableUpdates(),
   336  			lsdir("", m{"a$": "DIR"}),
   337  			lsdir("a", m{"b$": "FILE"}),
   338  			mtime("a", targetMtime),
   339  		),
   340  		as(alice,
   341  			lsdir("", m{"a$": "DIR"}),
   342  			lsdir("a", m{"b$": "FILE"}),
   343  			mtime("a", targetMtime),
   344  		),
   345  	)
   346  }
   347  
   348  // bob sets the mtime of a dir modified by both him and alice.
   349  func TestCrUnmergedSetMtimeOnDualModifiedDir(t *testing.T) {
   350  	targetMtime := time.Now().Add(1 * time.Minute)
   351  	test(t,
   352  		users("alice", "bob"),
   353  		as(alice,
   354  			mkdir("a"),
   355  		),
   356  		as(bob,
   357  			disableUpdates(),
   358  		),
   359  		as(alice,
   360  			mkfile("a/b", "hello"),
   361  		),
   362  		as(bob, noSync(),
   363  			mkfile("a/c", "hello"),
   364  			setmtime("a", targetMtime),
   365  			reenableUpdates(),
   366  			lsdir("", m{"a$": "DIR"}),
   367  			lsdir("a", m{"b$": "FILE", "c$": "FILE"}),
   368  			mtime("a", targetMtime),
   369  		),
   370  		as(alice,
   371  			lsdir("", m{"a$": "DIR"}),
   372  			lsdir("a", m{"b$": "FILE", "c$": "FILE"}),
   373  			mtime("a", targetMtime),
   374  		),
   375  	)
   376  }
   377  
   378  // bob deletes a non-conflicting file while unstaged
   379  func TestCrUnmergedRmfile(t *testing.T) {
   380  	test(t,
   381  		users("alice", "bob"),
   382  		as(alice,
   383  			mkfile("a/b", "hello"),
   384  		),
   385  		as(bob,
   386  			disableUpdates(),
   387  		),
   388  		as(alice,
   389  			write("a/c", "world"),
   390  		),
   391  		as(bob, noSync(),
   392  			rm("a/b"),
   393  			reenableUpdates(),
   394  			lsdir("a/", m{"c": "FILE"}),
   395  			read("a/c", "world"),
   396  		),
   397  		as(alice,
   398  			lsdir("a/", m{"c": "FILE"}),
   399  			read("a/c", "world"),
   400  		),
   401  	)
   402  }
   403  
   404  // bob deletes a non-conflicting dir while unstaged
   405  func TestCrUnmergedRmdir(t *testing.T) {
   406  	test(t,
   407  		users("alice", "bob"),
   408  		as(alice,
   409  			mkdir("a/b"),
   410  		),
   411  		as(bob,
   412  			disableUpdates(),
   413  		),
   414  		as(alice,
   415  			write("a/c", "world"),
   416  		),
   417  		as(bob, noSync(),
   418  			rmdir("a/b"),
   419  			reenableUpdates(),
   420  			lsdir("a/", m{"c": "FILE"}),
   421  			read("a/c", "world"),
   422  		),
   423  		as(alice,
   424  			lsdir("a/", m{"c": "FILE"}),
   425  			read("a/c", "world"),
   426  		),
   427  	)
   428  }
   429  
   430  // bob deletes a non-conflicting dir tree while unstaged.
   431  // Regression for KBFS-1202.
   432  func TestCrUnmergedRmdirTree(t *testing.T) {
   433  	test(t,
   434  		users("alice", "bob"),
   435  		as(alice,
   436  			mkdir("a/b"),
   437  			mkdir("a/b/c"),
   438  			mkdir("a/b/d"),
   439  			mkfile("a/b/c/e", "hello"),
   440  			mkfile("a/b/d/f", "world"),
   441  		),
   442  		as(bob,
   443  			disableUpdates(),
   444  		),
   445  		as(alice,
   446  			mkfile("a/g", "merged"),
   447  			disableUpdates(),
   448  		),
   449  		as(bob, noSync(),
   450  			rm("a/b/d/f"),
   451  			rm("a/b/c/e"),
   452  			rmdir("a/b/d"),
   453  			rmdir("a/b/c"),
   454  			rmdir("a/b"),
   455  			reenableUpdates(),
   456  			lsdir("a/", m{"g": "FILE"}),
   457  			read("a/g", "merged"),
   458  		),
   459  		as(alice, noSync(),
   460  			mkfile("a/h", "merged2"),
   461  			reenableUpdates(),
   462  			lsdir("a/", m{"g": "FILE", "h": "FILE"}),
   463  			read("a/g", "merged"),
   464  			read("a/h", "merged2"),
   465  		),
   466  		as(bob,
   467  			lsdir("a/", m{"g": "FILE", "h": "FILE"}),
   468  			read("a/g", "merged"),
   469  			read("a/h", "merged2"),
   470  		),
   471  	)
   472  }
   473  
   474  // bob renames a non-conflicting file while unstaged
   475  func TestCrUnmergedRenameInDir(t *testing.T) {
   476  	test(t,
   477  		users("alice", "bob"),
   478  		as(alice,
   479  			mkfile("a/b", "hello"),
   480  		),
   481  		as(bob,
   482  			disableUpdates(),
   483  		),
   484  		as(alice,
   485  			write("a/c", "world"),
   486  		),
   487  		as(bob, noSync(),
   488  			rename("a/b", "a/d"),
   489  			reenableUpdates(),
   490  			lsdir("a/", m{"c": "FILE", "d": "FILE"}),
   491  			read("a/c", "world"),
   492  			read("a/d", "hello"),
   493  		),
   494  		as(alice,
   495  			lsdir("a/", m{"c": "FILE", "d": "FILE"}),
   496  			read("a/c", "world"),
   497  			read("a/d", "hello"),
   498  		),
   499  	)
   500  }
   501  
   502  // bob creates and renames a non-conflicting file while unstaged
   503  func TestCrUnmergedCreateAndRenameInDir(t *testing.T) {
   504  	test(t,
   505  		users("alice", "bob"),
   506  		as(alice,
   507  			mkfile("a/b", "hello"),
   508  		),
   509  		as(bob,
   510  			disableUpdates(),
   511  		),
   512  		as(alice,
   513  			write("a/c", "world"),
   514  		),
   515  		as(bob, noSync(),
   516  			write("a/b2", "hellohello"),
   517  			rename("a/b2", "a/d"),
   518  			reenableUpdates(),
   519  			lsdir("a/", m{"b": "FILE", "c": "FILE", "d": "FILE"}),
   520  			read("a/b", "hello"),
   521  			read("a/c", "world"),
   522  			read("a/d", "hellohello"),
   523  		),
   524  		as(alice,
   525  			lsdir("a/", m{"b": "FILE", "c": "FILE", "d": "FILE"}),
   526  			read("a/b", "hello"),
   527  			read("a/c", "world"),
   528  			read("a/d", "hellohello"),
   529  		),
   530  	)
   531  }
   532  
   533  // bob renames a non-conflicting symlink(while unstaged),
   534  func TestCrUnmergedRenameSymlinkInDir(t *testing.T) {
   535  	test(t,
   536  		skip("dokan", "Does not work with Dokan."),
   537  		users("alice", "bob"),
   538  		as(alice,
   539  			mkfile("a/b", "hello"),
   540  			link("a/c", "b"),
   541  		),
   542  		as(bob,
   543  			disableUpdates(),
   544  		),
   545  		as(alice,
   546  			write("a/d", "world"),
   547  		),
   548  		as(bob, noSync(),
   549  			rename("a/c", "a/e"),
   550  			reenableUpdates(),
   551  			lsdir("a/", m{"b": "FILE", "d": "FILE", "e": "SYM"}),
   552  			read("a/d", "world"),
   553  			read("a/e", "hello"),
   554  		),
   555  		as(alice,
   556  			lsdir("a/", m{"b": "FILE", "d": "FILE", "e": "SYM"}),
   557  			read("a/d", "world"),
   558  			read("a/e", "hello"),
   559  		),
   560  	)
   561  }
   562  
   563  // bob renames a non-conflicting file in the root dir while unstaged
   564  func TestCrUnmergedRenameInRoot(t *testing.T) {
   565  	test(t,
   566  		users("alice", "bob"),
   567  		as(alice,
   568  			mkfile("b", "hello"),
   569  		),
   570  		as(bob,
   571  			disableUpdates(),
   572  		),
   573  		as(alice,
   574  			write("a/c", "world"),
   575  		),
   576  		as(bob, noSync(),
   577  			rename("b", "d"),
   578  			reenableUpdates(),
   579  			lsdir("", m{"d": "FILE", "a": "DIR"}),
   580  			lsdir("a/", m{"c": "FILE"}),
   581  			read("a/c", "world"),
   582  			read("d", "hello"),
   583  		),
   584  		as(alice,
   585  			lsdir("", m{"d": "FILE", "a": "DIR"}),
   586  			lsdir("a/", m{"c": "FILE"}),
   587  			read("a/c", "world"),
   588  			read("d", "hello"),
   589  		),
   590  	)
   591  }
   592  
   593  // bob renames a non-conflicting file across directories while unstaged
   594  func TestCrUnmergedRenameAcrossDirs(t *testing.T) {
   595  	test(t,
   596  		users("alice", "bob"),
   597  		as(alice,
   598  			mkfile("a/b", "hello"),
   599  			mkdir("d"),
   600  		),
   601  		as(bob,
   602  			disableUpdates(),
   603  		),
   604  		as(alice,
   605  			write("a/c", "world"),
   606  		),
   607  		as(bob, noSync(),
   608  			rename("a/b", "d/e"),
   609  			reenableUpdates(),
   610  			lsdir("a/", m{"c": "FILE"}),
   611  			lsdir("d/", m{"e": "FILE"}),
   612  			read("a/c", "world"),
   613  			read("d/e", "hello"),
   614  		),
   615  		as(alice,
   616  			lsdir("a/", m{"c": "FILE"}),
   617  			lsdir("d/", m{"e": "FILE"}),
   618  			read("a/c", "world"),
   619  			read("d/e", "hello"),
   620  		),
   621  	)
   622  }
   623  
   624  // bob renames a file over an existing file
   625  func TestCrUnmergedRenameFileOverFile(t *testing.T) {
   626  	test(t,
   627  		users("alice", "bob"),
   628  		as(alice,
   629  			mkfile("a/b", "hello"),
   630  			mkfile("a/c", "world"),
   631  		),
   632  		as(bob,
   633  			disableUpdates(),
   634  		),
   635  		as(alice,
   636  			write("a/d", "just another file"),
   637  		),
   638  		as(bob, noSync(),
   639  			rename("a/c", "a/b"),
   640  			reenableUpdates(),
   641  			lsdir("a/", m{"b": "FILE", "d": "FILE"}),
   642  			read("a/b", "world"),
   643  			read("a/d", "just another file"),
   644  		),
   645  		as(alice,
   646  			lsdir("a/", m{"b": "FILE", "d": "FILE"}),
   647  			read("a/b", "world"),
   648  			read("a/d", "just another file"),
   649  		),
   650  	)
   651  }
   652  
   653  // bob renames a dir over an existing empty dir
   654  func TestCrUnmergedRenameDirOverEmptyDir(t *testing.T) {
   655  	test(t,
   656  		users("alice", "bob"),
   657  		as(alice,
   658  			mkdir("a/b"),
   659  			mkfile("a/c/d", "hello"),
   660  		),
   661  		as(bob,
   662  			disableUpdates(),
   663  		),
   664  		as(alice,
   665  			write("a/e", "just another file"),
   666  		),
   667  		as(bob, noSync(),
   668  			rm("a/b"),
   669  			rename("a/c", "a/b"),
   670  			reenableUpdates(),
   671  			lsdir("a/", m{"b": "DIR", "e": "FILE"}),
   672  			lsdir("a/b", m{"d": "FILE"}),
   673  			read("a/b/d", "hello"),
   674  			read("a/e", "just another file"),
   675  		),
   676  		as(alice,
   677  			lsdir("a/", m{"b": "DIR", "e": "FILE"}),
   678  			lsdir("a/b", m{"d": "FILE"}),
   679  			read("a/b/d", "hello"),
   680  			read("a/e", "just another file"),
   681  		),
   682  	)
   683  }
   684  
   685  // alice makes a non-conflicting dir (containing a file) while bob is
   686  // unstaged
   687  func TestCrMergedDir(t *testing.T) {
   688  	test(t,
   689  		users("alice", "bob"),
   690  		as(alice,
   691  			mkfile("a/b", "hello"),
   692  		),
   693  		as(bob,
   694  			disableUpdates(),
   695  		),
   696  		as(alice,
   697  			write("a/d/e", "uh oh"),
   698  		),
   699  		as(bob, noSync(),
   700  			write("a/c", "world"),
   701  			reenableUpdates(),
   702  			lsdir("a/", m{"b": "FILE", "c": "FILE", "d": "DIR"}),
   703  			read("a/b", "hello"),
   704  			read("a/c", "world"),
   705  			lsdir("a/d", m{"e": "FILE"}),
   706  			read("a/d/e", "uh oh"),
   707  		),
   708  		as(alice,
   709  			lsdir("a/", m{"b": "FILE", "c": "FILE", "d": "DIR"}),
   710  			read("a/b", "hello"),
   711  			read("a/c", "world"),
   712  			lsdir("a/d", m{"e": "FILE"}),
   713  			read("a/d/e", "uh oh"),
   714  		),
   715  	)
   716  }
   717  
   718  // alice creates a non-conflicting symlink(while bob is unstaged),
   719  func TestCrMergedSymlink(t *testing.T) {
   720  	test(t,
   721  		skip("dokan", "Does not work with Dokan."),
   722  		users("alice", "bob"),
   723  		as(alice,
   724  			mkfile("a/b", "hello"),
   725  		),
   726  		as(bob,
   727  			disableUpdates(),
   728  		),
   729  		as(alice,
   730  			link("a/d", "b"),
   731  		),
   732  		as(bob, noSync(),
   733  			write("a/c", "world"),
   734  			reenableUpdates(),
   735  			lsdir("a/", m{"b": "FILE", "c": "FILE", "d": "SYM"}),
   736  			read("a/b", "hello"),
   737  			read("a/c", "world"),
   738  			read("a/d", "hello"),
   739  		),
   740  		as(alice,
   741  			lsdir("a/", m{"b": "FILE", "c": "FILE", "d": "SYM"}),
   742  			read("a/b", "hello"),
   743  			read("a/c", "world"),
   744  			read("a/d", "hello"),
   745  		),
   746  	)
   747  }
   748  
   749  // alice makes a non-conflicting file executable while bob is unstaged
   750  func TestCrMergedSetex(t *testing.T) {
   751  	test(t,
   752  		users("alice", "bob"),
   753  		as(alice,
   754  			mkfile("a/b", "hello"),
   755  		),
   756  		as(bob,
   757  			disableUpdates(),
   758  		),
   759  		as(alice,
   760  			setex("a/b", true),
   761  		),
   762  		as(bob, noSync(),
   763  			write("a/c", "world"),
   764  			reenableUpdates(),
   765  			lsdir("a/", m{"b": "EXEC", "c": "FILE"}),
   766  			read("a/c", "world"),
   767  		),
   768  		as(alice,
   769  			lsdir("a/", m{"b": "EXEC", "c": "FILE"}),
   770  			read("a/c", "world"),
   771  		),
   772  	)
   773  }
   774  
   775  // alice set the mtime of a non-conflicting file while bob is unstaged
   776  func TestCrMergedSetMtime(t *testing.T) {
   777  	targetMtime := time.Now().Add(1 * time.Minute)
   778  	test(t,
   779  		users("alice", "bob"),
   780  		as(alice,
   781  			mkfile("a/b", "hello"),
   782  		),
   783  		as(bob,
   784  			disableUpdates(),
   785  		),
   786  		as(alice,
   787  			setmtime("a/b", targetMtime),
   788  		),
   789  		as(bob, noSync(),
   790  			write("a/c", "world"),
   791  			reenableUpdates(),
   792  			lsdir("a/", m{"b": "FILE", "c": "FILE"}),
   793  			read("a/c", "world"),
   794  			mtime("a/b", targetMtime),
   795  		),
   796  		as(alice,
   797  			lsdir("a/", m{"b": "FILE", "c": "FILE"}),
   798  			read("a/c", "world"),
   799  			mtime("a/b", targetMtime),
   800  		),
   801  	)
   802  }
   803  
   804  // alice sets the mtime on a dir while unstaged
   805  func TestCrMergedSetMtimeOnDir(t *testing.T) {
   806  	targetMtime := time.Now().Add(1 * time.Minute)
   807  	test(t,
   808  		users("alice", "bob"),
   809  		as(alice,
   810  			mkdir("a"),
   811  		),
   812  		as(bob,
   813  			disableUpdates(),
   814  		),
   815  		as(alice,
   816  			setmtime("a", targetMtime),
   817  		),
   818  		as(bob, noSync(),
   819  			write("b", "hello"),
   820  			reenableUpdates(),
   821  			lsdir("", m{"a": "DIR", "b": "FILE"}),
   822  			read("b", "hello"),
   823  			mtime("a", targetMtime),
   824  		),
   825  		as(alice,
   826  			lsdir("", m{"a": "DIR", "b": "FILE"}),
   827  			read("b", "hello"),
   828  			mtime("a", targetMtime),
   829  		),
   830  	)
   831  }
   832  
   833  // alice sets the mtime on a moved dir while bob is unstaged
   834  func TestCrMergedSetMtimeOnMovedDir(t *testing.T) {
   835  	targetMtime := time.Now().Add(1 * time.Minute)
   836  	test(t,
   837  		users("alice", "bob"),
   838  		as(alice,
   839  			mkdir("a"),
   840  			mkdir("b"),
   841  		),
   842  		as(bob,
   843  			disableUpdates(),
   844  		),
   845  		as(alice,
   846  			setmtime("a", targetMtime),
   847  		),
   848  		as(bob, noSync(),
   849  			rename("a", "b/a"),
   850  			reenableUpdates(),
   851  			lsdir("", m{"b": "DIR"}),
   852  			lsdir("b", m{"a": "DIR"}),
   853  			mtime("b/a", targetMtime),
   854  		),
   855  		as(alice,
   856  			lsdir("", m{"b": "DIR"}),
   857  			lsdir("b", m{"a": "DIR"}),
   858  			mtime("b/a", targetMtime),
   859  		),
   860  	)
   861  }
   862  
   863  // alice sets the mtime of a dir modified by bob.
   864  func TestCrMergedSetMtimeOnModifiedDir(t *testing.T) {
   865  	targetMtime := time.Now().Add(1 * time.Minute)
   866  	test(t,
   867  		users("alice", "bob"),
   868  		as(alice,
   869  			mkdir("a"),
   870  		),
   871  		as(bob,
   872  			disableUpdates(),
   873  		),
   874  		as(alice,
   875  			setmtime("a", targetMtime),
   876  		),
   877  		as(bob, noSync(),
   878  			mkfile("a/b", "hello"),
   879  			reenableUpdates(),
   880  			lsdir("", m{"a$": "DIR"}),
   881  			lsdir("a", m{"b$": "FILE"}),
   882  			mtime("a", targetMtime),
   883  		),
   884  		as(alice,
   885  			lsdir("", m{"a$": "DIR"}),
   886  			lsdir("a", m{"b$": "FILE"}),
   887  			mtime("a", targetMtime),
   888  		),
   889  	)
   890  }
   891  
   892  // alice deletes a non-conflicting file while bob is unstaged
   893  func TestCrMergedRmfile(t *testing.T) {
   894  	test(t,
   895  		users("alice", "bob"),
   896  		as(alice,
   897  			mkfile("a/b", "hello"),
   898  		),
   899  		as(bob,
   900  			disableUpdates(),
   901  		),
   902  		as(alice,
   903  			rm("a/b"),
   904  		),
   905  		as(bob, noSync(),
   906  			write("a/c", "world"),
   907  			reenableUpdates(),
   908  			lsdir("a/", m{"c": "FILE"}),
   909  			read("a/c", "world"),
   910  		),
   911  		as(alice,
   912  			lsdir("a/", m{"c": "FILE"}),
   913  			read("a/c", "world"),
   914  		),
   915  	)
   916  }
   917  
   918  // alice deletes a non-conflicting dir while bob is unstaged
   919  func TestCrMergedRmdir(t *testing.T) {
   920  	test(t,
   921  		users("alice", "bob"),
   922  		as(alice,
   923  			mkdir("a/b"),
   924  		),
   925  		as(bob,
   926  			disableUpdates(),
   927  		),
   928  		as(alice,
   929  			rmdir("a/b"),
   930  		),
   931  		as(bob, noSync(),
   932  			write("a/c", "world"),
   933  			reenableUpdates(),
   934  			lsdir("a/", m{"c": "FILE"}),
   935  			read("a/c", "world"),
   936  		),
   937  		as(alice,
   938  			lsdir("a/", m{"c": "FILE"}),
   939  			read("a/c", "world"),
   940  		),
   941  	)
   942  }
   943  
   944  // alice deletes a non-conflicting dir tree while unstaged
   945  func TestCrMergedRmdirTree(t *testing.T) {
   946  	test(t,
   947  		users("alice", "bob"),
   948  		as(alice,
   949  			mkdir("a/b"),
   950  			mkdir("a/b/c"),
   951  			mkdir("a/b/d"),
   952  			mkfile("a/b/c/e", "hello"),
   953  			mkfile("a/b/d/f", "world"),
   954  		),
   955  		as(bob,
   956  			disableUpdates(),
   957  		),
   958  		as(alice,
   959  			rm("a/b/d/f"),
   960  			rm("a/b/c/e"),
   961  			rmdir("a/b/d"),
   962  			rmdir("a/b/c"),
   963  			rmdir("a/b"),
   964  		),
   965  		as(bob, noSync(),
   966  			mkfile("a/g", "unmerged"),
   967  			reenableUpdates(),
   968  			lsdir("a/", m{"g": "FILE"}),
   969  			read("a/g", "unmerged"),
   970  		),
   971  		as(alice,
   972  			lsdir("a/", m{"g": "FILE"}),
   973  			read("a/g", "unmerged"),
   974  		),
   975  	)
   976  }
   977  
   978  // alice renames a non-conflicting file while bob is unstaged
   979  func TestCrMergedRenameInDir(t *testing.T) {
   980  	test(t,
   981  		users("alice", "bob"),
   982  		as(alice,
   983  			mkfile("a/b", "hello"),
   984  		),
   985  		as(bob,
   986  			disableUpdates(),
   987  		),
   988  		as(alice,
   989  			rename("a/b", "a/d"),
   990  		),
   991  		as(bob, noSync(),
   992  			write("a/c", "world"),
   993  			reenableUpdates(),
   994  			lsdir("a/", m{"c": "FILE", "d": "FILE"}),
   995  			read("a/c", "world"),
   996  			read("a/d", "hello"),
   997  		),
   998  		as(alice,
   999  			lsdir("a/", m{"c": "FILE", "d": "FILE"}),
  1000  			read("a/c", "world"),
  1001  			read("a/d", "hello"),
  1002  		),
  1003  	)
  1004  }
  1005  
  1006  // alice renames a non-conflicting file in the root dir while bob is unstaged
  1007  func TestCrMergedRenameInRoot(t *testing.T) {
  1008  	test(t,
  1009  		users("alice", "bob"),
  1010  		as(alice,
  1011  			mkfile("b", "hello"),
  1012  		),
  1013  		as(bob,
  1014  			disableUpdates(),
  1015  		),
  1016  		as(alice,
  1017  			rename("b", "d"),
  1018  		),
  1019  		as(bob, noSync(),
  1020  			write("a/c", "world"),
  1021  			reenableUpdates(),
  1022  			lsdir("", m{"d": "FILE", "a": "DIR"}),
  1023  			lsdir("a/", m{"c": "FILE"}),
  1024  			read("a/c", "world"),
  1025  			read("d", "hello"),
  1026  		),
  1027  		as(alice,
  1028  			lsdir("", m{"d": "FILE", "a": "DIR"}),
  1029  			lsdir("a/", m{"c": "FILE"}),
  1030  			read("a/c", "world"),
  1031  			read("d", "hello"),
  1032  		),
  1033  	)
  1034  }
  1035  
  1036  // alice renames a non-conflicting file across directories while bob
  1037  // is unstaged
  1038  func TestCrMergedRenameAcrossDirs(t *testing.T) {
  1039  	test(t,
  1040  		users("alice", "bob"),
  1041  		as(alice,
  1042  			mkfile("a/b", "hello"),
  1043  			mkdir("d"),
  1044  		),
  1045  		as(bob,
  1046  			disableUpdates(),
  1047  		),
  1048  		as(alice,
  1049  			rename("a/b", "d/e"),
  1050  		),
  1051  		as(bob, noSync(),
  1052  			write("a/c", "world"),
  1053  			reenableUpdates(),
  1054  			lsdir("a/", m{"c": "FILE"}),
  1055  			lsdir("d/", m{"e": "FILE"}),
  1056  			read("a/c", "world"),
  1057  			read("d/e", "hello"),
  1058  		),
  1059  		as(alice,
  1060  			lsdir("a/", m{"c": "FILE"}),
  1061  			lsdir("d/", m{"e": "FILE"}),
  1062  			read("a/c", "world"),
  1063  			read("d/e", "hello"),
  1064  		),
  1065  	)
  1066  }
  1067  
  1068  // alice and bob write(the same dir (containing a file) while bob's unstaged),
  1069  func TestCrMergeDir(t *testing.T) {
  1070  	test(t,
  1071  		users("alice", "bob"),
  1072  		as(alice,
  1073  			mkdir("a"),
  1074  		),
  1075  		as(bob,
  1076  			disableUpdates(),
  1077  		),
  1078  		as(alice,
  1079  			write("a/b/c", "hello"),
  1080  		),
  1081  		as(bob, noSync(),
  1082  			write("a/b/d", "world"),
  1083  			reenableUpdates(),
  1084  			lsdir("a/", m{"b": "DIR"}),
  1085  			lsdir("a/b", m{"c": "FILE", "d": "FILE"}),
  1086  			read("a/b/c", "hello"),
  1087  			read("a/b/d", "world"),
  1088  		),
  1089  		as(alice,
  1090  			lsdir("a/", m{"b": "DIR"}),
  1091  			lsdir("a/b", m{"c": "FILE", "d": "FILE"}),
  1092  			read("a/b/c", "hello"),
  1093  			read("a/b/d", "world"),
  1094  		),
  1095  	)
  1096  }
  1097  
  1098  // alice and bob both delete the same file
  1099  func TestCrUnmergedBothRmfile(t *testing.T) {
  1100  	test(t,
  1101  		users("alice", "bob"),
  1102  		as(alice,
  1103  			mkfile("a/b", "hello"),
  1104  		),
  1105  		as(bob,
  1106  			disableUpdates(),
  1107  		),
  1108  		as(alice,
  1109  			write("a/c", "world"),
  1110  			rm("a/b"),
  1111  		),
  1112  		as(bob, noSync(),
  1113  			rm("a/b"),
  1114  			reenableUpdates(),
  1115  			lsdir("a/", m{"c": "FILE"}),
  1116  		),
  1117  		as(alice,
  1118  			lsdir("a/", m{"c": "FILE"}),
  1119  		),
  1120  	)
  1121  }
  1122  
  1123  // bob moves a file, and then deletes its parents.
  1124  func TestCrUnmergedMoveAndDelete(t *testing.T) {
  1125  	test(t,
  1126  		users("alice", "bob"),
  1127  		as(alice,
  1128  			write("a/b/c/d", "hello"),
  1129  		),
  1130  		as(bob,
  1131  			disableUpdates(),
  1132  		),
  1133  		as(alice,
  1134  			write("foo", "bar"),
  1135  		),
  1136  		as(bob, noSync(),
  1137  			rename("a/b/c/d", "a/b/c/e"),
  1138  			rm("a/b/c/e"),
  1139  			rmdir("a/b/c"),
  1140  			rmdir("a/b"),
  1141  			reenableUpdates(),
  1142  			lsdir("a/", m{}),
  1143  			read("foo", "bar"),
  1144  		),
  1145  		as(alice,
  1146  			lsdir("a/", m{}),
  1147  			read("foo", "bar"),
  1148  		),
  1149  	)
  1150  }
  1151  
  1152  // bob exclusively creates a file while on an unmerged branch.
  1153  func TestCrCreateFileExclOnStaged(t *testing.T) {
  1154  	test(t,
  1155  		users("alice", "bob"),
  1156  		as(alice,
  1157  			mkdir("a"),
  1158  		),
  1159  		as(bob,
  1160  			disableUpdates(),
  1161  		),
  1162  		as(alice,
  1163  			mkfile("a/b", "hello"),
  1164  		),
  1165  		as(bob, noSync(),
  1166  			mkfileexcl("a/c"),
  1167  			reenableUpdates(),
  1168  			lsdir("a/", m{"b$": "FILE", "c$": "FILE"}),
  1169  		),
  1170  		as(alice,
  1171  			lsdir("a/", m{"b$": "FILE", "c$": "FILE"}),
  1172  		),
  1173  	)
  1174  }
  1175  
  1176  // alice and bob both exclusively create the same file, but neither write to
  1177  // it. Since the creates are exclusive, only the winning one (alice) should
  1178  // succeed.
  1179  func TestCrBothCreateFileExcl(t *testing.T) {
  1180  	test(t,
  1181  		users("alice", "bob"),
  1182  		as(alice,
  1183  			mkdir("a"),
  1184  		),
  1185  		as(bob,
  1186  			disableUpdates(),
  1187  		),
  1188  		as(alice,
  1189  			mkfileexcl("a/b"),
  1190  		),
  1191  		as(bob, noSync(),
  1192  			expectError(mkfileexcl("a/b"), "b already exists"),
  1193  			reenableUpdates(),
  1194  			lsdir("a/", m{"b$": "FILE"}),
  1195  		),
  1196  		as(alice,
  1197  			lsdir("a/", m{"b$": "FILE"}),
  1198  		),
  1199  	)
  1200  }
  1201  
  1202  // alice and bob both exclusively create the same file, but neither write to
  1203  // it. This test is run in parallel. Bob's exclusive create is stalled on MD's
  1204  // Put. After stall happens, alice creates the file. This makes sure Alice's
  1205  // exclusive create happens precisely before Bob's MD Put.
  1206  func TestCrBothCreateFileExclParallel(t *testing.T) {
  1207  	test(t,
  1208  		users("alice", "bob"),
  1209  		as(alice,
  1210  			mkdir("a"),
  1211  		),
  1212  		as(bob,
  1213  			lsdir("a/", m{}),
  1214  		),
  1215  		as(bob, stallOnMDPut()),
  1216  		parallel(
  1217  			as(bob,
  1218  				expectError(mkfileexcl("a/b"), "b already exists"),
  1219  				lsdir("a/", m{"b$": "FILE"}),
  1220  			),
  1221  			sequential(
  1222  				as(bob, noSync(), waitForStalledMDPut()),
  1223  				as(alice,
  1224  					mkfileexcl("a/b"),
  1225  					lsdir("a/", m{"b$": "FILE"}),
  1226  				),
  1227  				as(bob, noSync(), undoStallOnMDPut()),
  1228  			),
  1229  		),
  1230  	)
  1231  }
  1232  
  1233  // alice and bob both create the same file, but neither write to it
  1234  func TestCrBothCreateFile(t *testing.T) {
  1235  	test(t,
  1236  		users("alice", "bob"),
  1237  		as(alice,
  1238  			mkdir("a"),
  1239  		),
  1240  		as(bob,
  1241  			disableUpdates(),
  1242  		),
  1243  		as(alice,
  1244  			mkfile("a/b", ""),
  1245  		),
  1246  		as(bob, noSync(),
  1247  			mkfile("a/b", ""),
  1248  			reenableUpdates(),
  1249  			lsdir("a/", m{"b$": "FILE"}),
  1250  			read("a/b", ""),
  1251  		),
  1252  		as(alice,
  1253  			lsdir("a/", m{"b$": "FILE"}),
  1254  			read("a/b", ""),
  1255  		),
  1256  	)
  1257  }
  1258  
  1259  // alice and bob both create the same file, and alice wrote to it
  1260  func TestCrBothCreateFileMergedWrite(t *testing.T) {
  1261  	test(t,
  1262  		users("alice", "bob"),
  1263  		as(alice,
  1264  			mkdir("a"),
  1265  		),
  1266  		as(bob,
  1267  			disableUpdates(),
  1268  		),
  1269  		as(alice,
  1270  			mkfile("a/b", "hello"),
  1271  		),
  1272  		as(bob, noSync(),
  1273  			mkfile("a/b", ""),
  1274  			reenableUpdates(),
  1275  			lsdir("a/", m{"b$": "FILE"}),
  1276  			read("a/b", "hello"),
  1277  		),
  1278  		as(alice,
  1279  			lsdir("a/", m{"b$": "FILE"}),
  1280  			read("a/b", "hello"),
  1281  		),
  1282  	)
  1283  }
  1284  
  1285  // alice and bob both create the same file, and alice truncated to it
  1286  func TestCrBothCreateFileMergedTruncate(t *testing.T) {
  1287  	const flen = 401001
  1288  	fdata := string(make([]byte, flen))
  1289  	test(t,
  1290  		users("alice", "bob"),
  1291  		as(alice,
  1292  			mkdir("a"),
  1293  		),
  1294  		as(bob,
  1295  			disableUpdates(),
  1296  		),
  1297  		as(alice,
  1298  			mkfile("a/b", ""),
  1299  			truncate("a/b", flen),
  1300  		),
  1301  		as(bob, noSync(),
  1302  			mkfile("a/b", ""),
  1303  			reenableUpdates(),
  1304  			lsdir("a/", m{"b$": "FILE"}),
  1305  			read("a/b", fdata),
  1306  		),
  1307  		as(alice,
  1308  			lsdir("a/", m{"b$": "FILE"}),
  1309  			read("a/b", fdata),
  1310  		),
  1311  	)
  1312  }
  1313  
  1314  // alice and bob both create the same file, and bob wrote to it
  1315  func TestCrBothCreateFileUnmergedWrite(t *testing.T) {
  1316  	test(t,
  1317  		users("alice", "bob"),
  1318  		as(alice,
  1319  			mkdir("a"),
  1320  		),
  1321  		as(bob,
  1322  			disableUpdates(),
  1323  		),
  1324  		as(alice,
  1325  			mkfile("a/b", ""),
  1326  		),
  1327  		as(bob, noSync(),
  1328  			mkfile("a/b", "hello"),
  1329  			reenableUpdates(),
  1330  			lsdir("a/", m{"b$": "FILE"}),
  1331  			read("a/b", "hello"),
  1332  		),
  1333  		as(alice,
  1334  			lsdir("a/", m{"b$": "FILE"}),
  1335  			read("a/b", "hello"),
  1336  		),
  1337  	)
  1338  }
  1339  
  1340  // alice and bob both create the same file, and bob truncated to it
  1341  func TestCrBothCreateFileUnmergedTruncate(t *testing.T) {
  1342  	const flen = 401001
  1343  	fdata := string(make([]byte, flen))
  1344  	test(t,
  1345  		users("alice", "bob"),
  1346  		as(alice,
  1347  			mkdir("a"),
  1348  		),
  1349  		as(bob,
  1350  			disableUpdates(),
  1351  		),
  1352  		as(alice,
  1353  			mkfile("a/b", ""),
  1354  		),
  1355  		as(bob, noSync(),
  1356  			mkfile("a/b", ""),
  1357  			truncate("a/b", flen),
  1358  			reenableUpdates(),
  1359  			lsdir("a/", m{"b$": "FILE"}),
  1360  			read("a/b", fdata),
  1361  		),
  1362  		as(alice,
  1363  			lsdir("a/", m{"b$": "FILE"}),
  1364  			read("a/b", fdata),
  1365  		),
  1366  	)
  1367  }
  1368  
  1369  // alice and bob both truncate the same file
  1370  func TestCrBothTruncateFile(t *testing.T) {
  1371  	test(t,
  1372  		users("alice", "bob"),
  1373  		as(alice,
  1374  			mkfile("a/b", "hello"),
  1375  		),
  1376  		as(bob,
  1377  			disableUpdates(),
  1378  		),
  1379  		as(alice,
  1380  			truncate("a/b", 0),
  1381  		),
  1382  		as(bob, noSync(),
  1383  			truncate("a/b", 0),
  1384  			reenableUpdates(),
  1385  			lsdir("a/", m{"b$": "FILE"}),
  1386  			read("a/b", ""),
  1387  		),
  1388  		as(alice,
  1389  			lsdir("a/", m{"b$": "FILE"}),
  1390  			read("a/b", ""),
  1391  		),
  1392  	)
  1393  }
  1394  
  1395  // alice and bob both truncate the same file to a non-zero size
  1396  func TestCrBothTruncateFileNonZero(t *testing.T) {
  1397  	test(t,
  1398  		users("alice", "bob"),
  1399  		as(alice,
  1400  			mkfile("a/b", "hello"),
  1401  		),
  1402  		as(bob,
  1403  			disableUpdates(),
  1404  		),
  1405  		as(alice,
  1406  			truncate("a/b", 4),
  1407  		),
  1408  		as(bob, noSync(),
  1409  			truncate("a/b", 4),
  1410  			reenableUpdates(),
  1411  			lsdir("a/", m{"b$": "FILE"}),
  1412  			read("a/b", "hell"),
  1413  		),
  1414  		as(alice,
  1415  			lsdir("a/", m{"b$": "FILE"}),
  1416  			read("a/b", "hell"),
  1417  		),
  1418  	)
  1419  }
  1420  
  1421  // alice and bob both truncate the same file, and alice wrote to it first
  1422  func TestCrBothTruncateFileMergedWrite(t *testing.T) {
  1423  	test(t,
  1424  		users("alice", "bob"),
  1425  		as(alice,
  1426  			mkfile("a/b", "hello"),
  1427  		),
  1428  		as(bob,
  1429  			disableUpdates(),
  1430  		),
  1431  		as(alice,
  1432  			write("a/b", "world"),
  1433  			truncate("a/b", 0),
  1434  		),
  1435  		as(bob, noSync(),
  1436  			truncate("a/b", 0),
  1437  			reenableUpdates(),
  1438  			lsdir("a/", m{"b$": "FILE"}),
  1439  			read("a/b", ""),
  1440  		),
  1441  		as(alice,
  1442  			lsdir("a/", m{"b$": "FILE"}),
  1443  			read("a/b", ""),
  1444  		),
  1445  	)
  1446  }
  1447  
  1448  // alice and bob both truncate the same file, and bob wrote to first
  1449  func TestCrBothTruncateFileUnmergedWrite(t *testing.T) {
  1450  	test(t,
  1451  		users("alice", "bob"),
  1452  		as(alice,
  1453  			mkfile("a/b", "hello"),
  1454  		),
  1455  		as(bob,
  1456  			disableUpdates(),
  1457  		),
  1458  		as(alice,
  1459  			truncate("a/b", 0),
  1460  		),
  1461  		as(bob, noSync(),
  1462  			write("a/b", "world"),
  1463  			truncate("a/b", 0),
  1464  			reenableUpdates(),
  1465  			lsdir("a/", m{"b$": "FILE"}),
  1466  			read("a/b", ""),
  1467  		),
  1468  		as(alice,
  1469  			lsdir("a/", m{"b$": "FILE"}),
  1470  			read("a/b", ""),
  1471  		),
  1472  	)
  1473  }
  1474  
  1475  // bob creates a dir, creates a file in dir, setattrs the file,
  1476  // removes the file, and removes the dir, all while unmerged.
  1477  // Regression test for KBFS-4114.
  1478  func TestCrSetattrRemovedFileInRemovedDir(t *testing.T) {
  1479  	targetMtime1 := time.Now().Add(1 * time.Minute)
  1480  	test(t,
  1481  		users("alice", "bob"),
  1482  		as(alice,
  1483  			mkdir("a"),
  1484  		),
  1485  		as(bob,
  1486  			disableUpdates(),
  1487  		),
  1488  		as(alice,
  1489  			mkdir("a/b"),
  1490  		),
  1491  		as(bob, noSync(),
  1492  			mkdir("a/c/d"),
  1493  			setmtime("a/c/d", targetMtime1),
  1494  			rm("a/c/d"),
  1495  			rmdir("a/c"),
  1496  			reenableUpdates(),
  1497  			lsdir("a/", m{"b$": "DIR"}),
  1498  		),
  1499  		as(alice,
  1500  			lsdir("a/", m{"b$": "DIR"}),
  1501  		),
  1502  	)
  1503  }