github.com/keybase/client/go@v0.0.0-20241007131713-f10651d043c8/kbfs/test/journal_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  	"fmt"
    11  	"testing"
    12  	"time"
    13  
    14  	"github.com/keybase/client/go/kbfs/libkbfs"
    15  )
    16  
    17  // bob creates a file while running the journal.
    18  func TestJournalSimple(t *testing.T) {
    19  	test(t, journal(),
    20  		users("alice", "bob"),
    21  		as(alice,
    22  			mkdir("a"),
    23  		),
    24  		as(bob,
    25  			enableJournal(),
    26  			pauseJournal(),
    27  			mkfile("a/b", "hello"),
    28  			checkUnflushedPaths([]string{
    29  				"/keybase/private/alice,bob/a",
    30  				"/keybase/private/alice,bob/a/b",
    31  			}),
    32  			// Check the data -- this should read from the journal if
    33  			// it hasn't flushed yet.
    34  			lsdir("a/", m{"b$": "FILE"}),
    35  			read("a/b", "hello"),
    36  		),
    37  		// Force a SyncAll to the journal.
    38  		as(bob,
    39  			resumeJournal(),
    40  			flushJournal(),
    41  			checkUnflushedPaths(nil),
    42  		),
    43  		as(alice,
    44  			lsdir("a/", m{"b$": "FILE"}),
    45  			read("a/b", "hello"),
    46  		),
    47  	)
    48  }
    49  
    50  // bob exclusively creates a file while running the journal.  For now
    51  // this is treated like a normal file create.
    52  func TestJournalExclWrite(t *testing.T) {
    53  	test(t, journal(),
    54  		users("alice", "bob"),
    55  		as(alice,
    56  			mkdir("a"),
    57  		),
    58  		as(bob,
    59  			enableJournal(),
    60  			pauseJournal(),
    61  			mkfile("a/c", "hello"),
    62  			mkfileexcl("a/b"),
    63  			checkUnflushedPaths([]string{
    64  				"/keybase/private/alice,bob/a",
    65  				"/keybase/private/alice,bob/a/c",
    66  			}),
    67  			lsdir("a/", m{"b$": "FILE", "c$": "FILE"}),
    68  		),
    69  		// Force a SyncAll to the journal.
    70  		as(bob,
    71  			resumeJournal(),
    72  			flushJournal(),
    73  			checkUnflushedPaths(nil),
    74  		),
    75  		as(alice,
    76  			lsdir("a/", m{"b$": "FILE", "c$": "FILE"}),
    77  		),
    78  	)
    79  }
    80  
    81  // bob creates a conflicting file while running the journal.
    82  func TestJournalCrSimple(t *testing.T) {
    83  	test(t, journal(),
    84  		users("alice", "bob"),
    85  		as(alice,
    86  			mkdir("a"),
    87  		),
    88  		as(bob,
    89  			enableJournal(),
    90  			pauseJournal(),
    91  			mkfile("a/b", "uh oh"),
    92  			checkUnflushedPaths([]string{
    93  				"/keybase/private/alice,bob/a",
    94  				"/keybase/private/alice,bob/a/b",
    95  			}),
    96  			// Don't flush yet.
    97  		),
    98  		as(alice,
    99  			mkfile("a/b", "hello"),
   100  		),
   101  		as(bob, noSync(),
   102  			resumeJournal(),
   103  			// This should kick off conflict resolution.
   104  			flushJournal(),
   105  		),
   106  		as(bob,
   107  			lsdir("a/", m{"b$": "FILE", crnameEsc("b", bob): "FILE"}),
   108  			read("a/b", "hello"),
   109  			read(crname("a/b", bob), "uh oh"),
   110  			checkUnflushedPaths(nil),
   111  		),
   112  		as(alice,
   113  			lsdir("a/", m{"b$": "FILE", crnameEsc("b", bob): "FILE"}),
   114  			read("a/b", "hello"),
   115  			read(crname("a/b", bob), "uh oh"),
   116  		),
   117  	)
   118  }
   119  
   120  func makeBusyWork(filename string, iters int) (busyWork []fileOp) {
   121  	busyWork = append(busyWork, mkfile(filename, "hello"))
   122  	for i := 0; i < iters; i++ {
   123  		content := fmt.Sprintf("a%d", i)
   124  		busyWork = append(busyWork, write(filename, content))
   125  	}
   126  	busyWork = append(busyWork, rm(filename))
   127  	return busyWork
   128  }
   129  
   130  // bob creates many conflicting files while running the journal.
   131  func TestJournalCrManyFiles(t *testing.T) {
   132  	busyWork := makeBusyWork("hi", 20)
   133  
   134  	test(t, journal(),
   135  		users("alice", "bob"),
   136  		as(alice,
   137  			mkdir("a"),
   138  		),
   139  		as(bob,
   140  			enableJournal(),
   141  			checkUnflushedPaths(nil),
   142  			pauseJournal(),
   143  		),
   144  		as(bob, busyWork...),
   145  		as(bob,
   146  			checkUnflushedPaths([]string{
   147  				"/keybase/private/alice,bob",
   148  				"/keybase/private/alice,bob/hi",
   149  			}),
   150  			// Don't flush yet.
   151  		),
   152  		as(alice,
   153  			mkfile("a/b", "hello"),
   154  		),
   155  		as(bob, noSync(),
   156  			resumeJournal(),
   157  			// This should kick off conflict resolution.
   158  			flushJournal(),
   159  		),
   160  		as(bob,
   161  			lsdir("a/", m{"b$": "FILE"}),
   162  			read("a/b", "hello"),
   163  			checkUnflushedPaths(nil),
   164  		),
   165  		as(alice,
   166  			lsdir("a/", m{"b$": "FILE"}),
   167  			read("a/b", "hello"),
   168  		),
   169  	)
   170  }
   171  
   172  // bob creates a conflicting file while running the journal.
   173  func TestJournalDoubleCrSimple(t *testing.T) {
   174  	test(t, journal(),
   175  		users("alice", "bob"),
   176  		as(alice,
   177  			mkdir("a"),
   178  		),
   179  		as(bob,
   180  			enableJournal(),
   181  			pauseJournal(),
   182  			mkfile("a/b", "uh oh"),
   183  			checkUnflushedPaths([]string{
   184  				"/keybase/private/alice,bob/a",
   185  				"/keybase/private/alice,bob/a/b",
   186  			}),
   187  			// Don't flush yet.
   188  		),
   189  		as(alice,
   190  			mkfile("a/b", "hello"),
   191  		),
   192  		as(bob, noSync(),
   193  			resumeJournal(),
   194  			// This should kick off conflict resolution.
   195  			flushJournal(),
   196  		),
   197  		as(bob,
   198  			lsdir("a/", m{"b$": "FILE", crnameEsc("b", bob): "FILE"}),
   199  			read("a/b", "hello"),
   200  			read(crname("a/b", bob), "uh oh"),
   201  			checkUnflushedPaths(nil),
   202  		),
   203  		as(alice,
   204  			lsdir("a/", m{"b$": "FILE", crnameEsc("b", bob): "FILE"}),
   205  			read("a/b", "hello"),
   206  			read(crname("a/b", bob), "uh oh"),
   207  		),
   208  		as(bob,
   209  			pauseJournal(),
   210  			mkfile("a/c", "uh oh"),
   211  			checkUnflushedPaths([]string{
   212  				"/keybase/private/alice,bob/a",
   213  				"/keybase/private/alice,bob/a/c",
   214  			}),
   215  			// Don't flush yet.
   216  		),
   217  		as(alice,
   218  			mkfile("a/c", "hello"),
   219  		),
   220  		as(bob, noSync(),
   221  			resumeJournal(),
   222  			// This should kick off conflict resolution.
   223  			flushJournal(),
   224  		),
   225  		as(bob,
   226  			lsdir("a/", m{"b$": "FILE", crnameEsc("b", bob): "FILE", "c$": "FILE", crnameEsc("c", bob): "FILE"}),
   227  			read("a/b", "hello"),
   228  			read(crname("a/b", bob), "uh oh"),
   229  			read("a/c", "hello"),
   230  			read(crname("a/c", bob), "uh oh"),
   231  			checkUnflushedPaths(nil),
   232  		),
   233  		as(alice,
   234  			lsdir("a/", m{"b$": "FILE", crnameEsc("b", bob): "FILE", "c$": "FILE", crnameEsc("c", bob): "FILE"}),
   235  			read("a/b", "hello"),
   236  			read(crname("a/b", bob), "uh oh"),
   237  			read("a/c", "hello"),
   238  			read(crname("a/c", bob), "uh oh"),
   239  		),
   240  	)
   241  }
   242  
   243  // bob writes a multi-block file that conflicts with a file created by
   244  // alice when journaling is on.
   245  func TestJournalCrConflictUnmergedWriteMultiblockFile(t *testing.T) {
   246  	test(t, journal(), blockSize(100), blockChangeSize(5),
   247  		users("alice", "bob"),
   248  		as(alice,
   249  			mkdir("a"),
   250  		),
   251  		as(bob,
   252  			enableJournal(),
   253  			disableUpdates(),
   254  			checkUnflushedPaths(nil),
   255  		),
   256  		as(alice,
   257  			write("a/b", "hello"),
   258  		),
   259  		as(bob, noSync(),
   260  			pauseJournal(),
   261  			write("a/b", ntimesString(15, "0123456789")),
   262  			checkUnflushedPaths([]string{
   263  				"/keybase/private/alice,bob/a",
   264  				"/keybase/private/alice,bob/a/b",
   265  			}),
   266  			resumeJournal(),
   267  			flushJournal(),
   268  			reenableUpdates(),
   269  		),
   270  		as(bob,
   271  			lsdir("a/", m{"b$": "FILE", crnameEsc("b", bob): "FILE"}),
   272  			read("a/b", "hello"),
   273  			read(crname("a/b", bob), ntimesString(15, "0123456789")),
   274  			checkUnflushedPaths(nil),
   275  		),
   276  		as(alice,
   277  			lsdir("a/", m{"b$": "FILE", crnameEsc("b", bob): "FILE"}),
   278  			read("a/b", "hello"),
   279  			read(crname("a/b", bob), ntimesString(15, "0123456789")),
   280  		),
   281  	)
   282  }
   283  
   284  // bob creates a conflicting file while running the journal, but then
   285  // its resolution also conflicts.
   286  func testJournalCrResolutionHitsConflict(t *testing.T, options []optionOp) {
   287  	test(t, append(options,
   288  		journal(),
   289  		users("alice", "bob"),
   290  		as(alice,
   291  			mkdir("a"),
   292  		),
   293  		as(bob,
   294  			enableJournal(),
   295  			pauseJournal(),
   296  			mkfile("a/b", "uh oh"),
   297  			checkUnflushedPaths([]string{
   298  				"/keybase/private/alice,bob/a",
   299  				"/keybase/private/alice,bob/a/b",
   300  			}),
   301  			// Don't flush yet.
   302  		),
   303  		as(alice,
   304  			mkfile("a/b", "hello"),
   305  		),
   306  		as(bob, noSync(),
   307  			stallOnMDResolveBranch(),
   308  			resumeJournal(),
   309  			// Wait for CR to finish before introducing new commit
   310  			// from alice.
   311  			waitForStalledMDResolveBranch(),
   312  		),
   313  		as(alice,
   314  			mkfile("a/c", "new file"),
   315  		),
   316  		as(bob, noSync(),
   317  			// Wait for one more, and cause another conflict, just to
   318  			// be sadistic.
   319  			unstallOneMDResolveBranch(),
   320  			waitForStalledMDResolveBranch(),
   321  		),
   322  		as(alice,
   323  			mkfile("a/d", "new file2"),
   324  		),
   325  		as(bob, noSync(),
   326  			// Let bob's CR proceed, which should trigger CR again on
   327  			// top of the resolution.
   328  			unstallOneMDResolveBranch(),
   329  			waitForStalledMDResolveBranch(),
   330  			undoStallOnMDResolveBranch(),
   331  			flushJournal(),
   332  		),
   333  		as(bob,
   334  			lsdir("a/", m{"b$": "FILE", crnameEsc("b", bob): "FILE", "c$": "FILE", "d$": "FILE"}),
   335  			read("a/b", "hello"),
   336  			read(crname("a/b", bob), "uh oh"),
   337  			read("a/c", "new file"),
   338  			read("a/d", "new file2"),
   339  			checkUnflushedPaths(nil),
   340  		),
   341  		as(alice,
   342  			lsdir("a/", m{"b$": "FILE", crnameEsc("b", bob): "FILE", "c$": "FILE", "d$": "FILE"}),
   343  			read("a/b", "hello"),
   344  			read(crname("a/b", bob), "uh oh"),
   345  			read("a/c", "new file"),
   346  			read("a/d", "new file2"),
   347  		),
   348  	)...)
   349  }
   350  
   351  func TestJournalCrResolutionHitsConflict(t *testing.T) {
   352  	testJournalCrResolutionHitsConflict(t, nil)
   353  }
   354  
   355  func TestJournalCrResolutionHitsConflictWithIndirectBlocks(t *testing.T) {
   356  	testJournalCrResolutionHitsConflict(t,
   357  		[]optionOp{blockChangeSize(100), blockChangeSize(5)})
   358  }
   359  
   360  // Check that simple quota reclamation works when journaling is enabled.
   361  func TestJournalQRSimple(t *testing.T) {
   362  	test(t, journal(),
   363  		users("alice"),
   364  		as(alice,
   365  			mkfile("a", "hello"),
   366  			addTime(1*time.Minute),
   367  			enableJournal(),
   368  			mkfile("b", "hello2"),
   369  			rm("b"),
   370  			addTime(2*time.Minute),
   371  			flushJournal(),
   372  			pauseJournal(),
   373  			addTime(2*time.Minute),
   374  			mkfile("c", "hello3"),
   375  			mkfile("d", "hello4"),
   376  			addTime(2*time.Minute),
   377  			forceQuotaReclamation(),
   378  			checkUnflushedPaths([]string{
   379  				"/keybase/private/alice",
   380  				"/keybase/private/alice/c",
   381  				"/keybase/private/alice/d",
   382  			}),
   383  			resumeJournal(),
   384  			flushJournal(),
   385  			checkUnflushedPaths(nil),
   386  		),
   387  	)
   388  }
   389  
   390  // bob creates a bunch of files in a journal and the operations get
   391  // coalesced together.
   392  func TestJournalCoalescingBasicCreates(t *testing.T) {
   393  	var busyWork []fileOp
   394  	var reads []fileOp
   395  	listing := m{"^a$": "DIR"}
   396  	iters := libkbfs.ForcedBranchSquashRevThreshold + 1
   397  	unflushedPaths := []string{"/keybase/private/alice,bob"}
   398  	for i := 0; i < iters; i++ {
   399  		name := fmt.Sprintf("a%d", i)
   400  		contents := fmt.Sprintf("hello%d", i)
   401  		busyWork = append(busyWork, mkfile(name, contents))
   402  		reads = append(reads, read(name, contents))
   403  		listing["^"+name+"$"] = "FILE"
   404  		unflushedPaths = append(
   405  			unflushedPaths, "/keybase/private/alice,bob/"+name)
   406  	}
   407  
   408  	test(t, journal(), batchSize(1),
   409  		users("alice", "bob"),
   410  		as(alice,
   411  			mkdir("a"),
   412  		),
   413  		as(bob,
   414  			enableJournal(),
   415  			checkUnflushedPaths(nil),
   416  			pauseJournal(),
   417  		),
   418  		as(bob, busyWork...),
   419  		as(bob,
   420  			checkUnflushedPaths(unflushedPaths),
   421  			resumeJournal(),
   422  			// This should kick off conflict resolution.
   423  			flushJournal(),
   424  		),
   425  		as(bob,
   426  			lsdir("", listing),
   427  			checkUnflushedPaths(nil),
   428  		),
   429  		as(bob, reads...),
   430  		as(alice,
   431  			lsdir("", listing),
   432  		),
   433  		as(alice, reads...),
   434  	)
   435  }
   436  
   437  // bob creates a bunch of files in a journal and the operations get
   438  // coalesced together, multiple times.  Then alice writes something
   439  // non-conflicting, forcing CR to happen on top of the unmerged local
   440  // squashes.  This is a regression for KBFS-1838.
   441  func TestJournalCoalescingCreatesPlusCR(t *testing.T) {
   442  	var busyWork []fileOp
   443  	var reads []fileOp
   444  	listing := m{"^a$": "DIR", "^b$": "DIR"}
   445  	iters := libkbfs.ForcedBranchSquashRevThreshold + 1
   446  	unflushedPaths := []string{"/keybase/private/alice,bob"}
   447  	for i := 0; i < iters; i++ {
   448  		name := fmt.Sprintf("a%d", i)
   449  		contents := fmt.Sprintf("hello%d", i)
   450  		busyWork = append(busyWork, mkfile(name, contents))
   451  		listing["^"+name+"$"] = "FILE"
   452  		unflushedPaths = append(
   453  			unflushedPaths, "/keybase/private/alice,bob/"+name)
   454  	}
   455  
   456  	busyWork2 := []fileOp{}
   457  	for i := 0; i < iters; i++ {
   458  		name := fmt.Sprintf("a%d", i)
   459  		contents := fmt.Sprintf("hello%d", i+iters)
   460  		busyWork2 = append(busyWork2, write(name, contents))
   461  		reads = append(reads, read(name, contents))
   462  	}
   463  
   464  	test(t, journal(), batchSize(1),
   465  		users("alice", "bob"),
   466  		as(alice,
   467  			mkdir("a"),
   468  		),
   469  		as(bob,
   470  			enableJournal(),
   471  			checkUnflushedPaths(nil),
   472  			pauseJournal(),
   473  		),
   474  		as(bob, busyWork...),
   475  		as(bob,
   476  			checkUnflushedPaths(unflushedPaths),
   477  			// Coalescing, round 1.
   478  			flushJournal(),
   479  		),
   480  		as(bob, busyWork2...),
   481  		as(bob,
   482  			checkUnflushedPaths(unflushedPaths),
   483  			// Coalescing, round 2.
   484  			flushJournal(),
   485  		),
   486  		as(alice,
   487  			// Non-conflict write to force CR on top of the local
   488  			// squashes.
   489  			mkdir("b"),
   490  		),
   491  		as(bob,
   492  			// This should try to flush the coalescing, but will hit a
   493  			// conflict.  It will get resolved at the next sync.
   494  			resumeJournal(),
   495  		),
   496  		as(bob,
   497  			lsdir("", listing),
   498  			checkUnflushedPaths(nil),
   499  		),
   500  		as(bob, reads...),
   501  		as(alice,
   502  			lsdir("", listing),
   503  		),
   504  		as(alice, reads...),
   505  	)
   506  }
   507  
   508  // bob creates a bunch of files in a subdirectory and the operations
   509  // get coalesced together.  Then alice writes something
   510  // non-conflicting, forcing CR to happen on top of the unmerged local
   511  // squashes -- this happens multiple times before bob's flush is able
   512  // to succeed.  This is a regression for KBFS-1979.
   513  func TestJournalCoalescingCreatesPlusMultiCR(t *testing.T) {
   514  	busyWork := []fileOp{noSyncEnd()}
   515  	busyWork2 := []fileOp{noSyncEnd()}
   516  	listing := m{}
   517  	iters := libkbfs.ForcedBranchSquashRevThreshold + 1
   518  	targetMtime := time.Now().Add(1 * time.Minute)
   519  	for i := 0; i < iters; i++ {
   520  		name := fmt.Sprintf("%d", i)
   521  		contents := fmt.Sprintf("hello%d", i)
   522  		busyWork = append(busyWork, mkfile("a/"+name+".tmp", contents))
   523  		busyWork = append(busyWork, setmtime("a/"+name+".tmp", targetMtime))
   524  		busyWork2 = append(busyWork2, rename("a/"+name+".tmp", "a/"+name))
   525  
   526  		listing["^"+name+"$"] = "FILE"
   527  	}
   528  	busyWork = append(busyWork, setmtime("a", targetMtime))
   529  
   530  	test(t, journal(), batchSize(1),
   531  		users("alice", "bob"),
   532  		as(alice,
   533  			mkdir("a"),
   534  		),
   535  		as(bob,
   536  			enableJournal(),
   537  			pauseJournal(),
   538  		),
   539  		as(bob, busyWork...),
   540  		as(bob, noSyncEnd(),
   541  			// Coalescing, round 1.
   542  			flushJournal(),
   543  		),
   544  		// Second sync to wait for the CR caused by `flushJournal`.
   545  		as(bob, noSyncEnd(),
   546  			flushJournal(),
   547  		),
   548  		as(bob, busyWork2...),
   549  		as(bob, noSyncEnd(),
   550  			// Coalescing, round 2.
   551  			flushJournal(),
   552  		),
   553  		// Second sync to wait for the CR caused by `flushJournal`.
   554  		as(bob, noSyncEnd(),
   555  			flushJournal(),
   556  		),
   557  		as(alice,
   558  			// Non-conflict write to force CR on top of the local
   559  			// squashes.
   560  			mkdir("b"),
   561  		),
   562  		as(bob, noSyncEnd(),
   563  			flushJournal(),
   564  		),
   565  		// Second sync to wait for the CR caused by `flushJournal`.
   566  		as(bob, noSyncEnd(),
   567  			flushJournal(),
   568  			// Disable updates to make sure we don't get notified of
   569  			// alice's next write until we attempt a journal flush, so
   570  			// that we have two subsequent CRs that run to completion.
   571  			disableUpdates(),
   572  		),
   573  		as(alice,
   574  			// Non-conflict write to force CR on top of the local
   575  			// squashes.
   576  			mkdir("c"),
   577  		),
   578  		as(bob,
   579  			reenableUpdates(),
   580  			resumeJournal(),
   581  			flushJournal(),
   582  		),
   583  		as(bob,
   584  			// Force CR to finish before the flush call with another
   585  			// disable/reenable.
   586  			disableUpdates(),
   587  			reenableUpdates(),
   588  			flushJournal(),
   589  		),
   590  		as(bob,
   591  			checkUnflushedPaths(nil),
   592  			lsdir("", m{"^a$": "DIR", "^b$": "DIR", "^c$": "DIR"}),
   593  			lsdir("a", listing),
   594  		),
   595  		as(alice,
   596  			lsdir("", m{"^a$": "DIR", "^b$": "DIR", "^c$": "DIR"}),
   597  			lsdir("a", listing),
   598  		),
   599  	)
   600  }
   601  
   602  // bob creates and appends to a file in a journal and the operations
   603  // get coalesced together.
   604  func TestJournalCoalescingWrites(t *testing.T) {
   605  	var busyWork []fileOp
   606  	iters := libkbfs.ForcedBranchSquashRevThreshold + 1
   607  	var contents string
   608  	for i := 0; i < iters; i++ {
   609  		contents += fmt.Sprintf("hello%d", i)
   610  		busyWork = append(busyWork, write("a/b", contents))
   611  	}
   612  
   613  	test(t, journal(), blockSize(100), blockChangeSize(5), batchSize(1),
   614  		users("alice", "bob"),
   615  		as(alice,
   616  			mkdir("a"),
   617  		),
   618  		as(bob,
   619  			enableJournal(),
   620  			checkUnflushedPaths(nil),
   621  			pauseJournal(),
   622  		),
   623  		as(bob, busyWork...),
   624  		as(bob,
   625  			checkUnflushedPaths([]string{
   626  				"/keybase/private/alice,bob/a",
   627  				"/keybase/private/alice,bob/a/b",
   628  			}),
   629  			resumeJournal(),
   630  			// This should kick off conflict resolution.
   631  			flushJournal(),
   632  		),
   633  		as(bob,
   634  			lsdir("", m{"a": "DIR"}),
   635  			lsdir("a", m{"b": "FILE"}),
   636  			read("a/b", contents),
   637  			checkUnflushedPaths(nil),
   638  		),
   639  		as(alice,
   640  			lsdir("", m{"a": "DIR"}),
   641  			lsdir("a", m{"b": "FILE"}),
   642  			read("a/b", contents),
   643  		),
   644  	)
   645  }
   646  
   647  // bob does a bunch of operations in a journal and the operations get
   648  // coalesced together.
   649  func TestJournalCoalescingMixedOperations(t *testing.T) {
   650  	busyWork := makeBusyWork("hi", libkbfs.ForcedBranchSquashRevThreshold+1)
   651  
   652  	targetMtime := time.Now().Add(1 * time.Minute)
   653  	test(t, journal(), blockSize(100), blockChangeSize(5), batchSize(1),
   654  		users("alice", "bob"),
   655  		as(alice,
   656  			mkdir("a"),
   657  			mkfile("a/b", "hello"),
   658  			mkfile("a/c", "hello2"),
   659  			mkfile("a/d", "hello3"),
   660  			mkfile("a/e", "hello4"),
   661  		),
   662  		as(bob,
   663  			enableJournal(),
   664  			checkUnflushedPaths(nil),
   665  			pauseJournal(),
   666  			// bob does a bunch of stuff:
   667  			//  * writes to an existing file a/b
   668  			//  * creates a new directory f
   669  			//  * creates and writes to a new file f/g
   670  			//  * creates and writes to a new file h
   671  			//  * removes an existing file a/c
   672  			//  * renames an existing file a/d -> a/i
   673  			//  * sets the mtime on a/e
   674  			//  * does a bunch of busy work to ensure we hit the squash limit
   675  			write("a/b", "world"),
   676  			mkdir("f"),
   677  			mkfile("f/g", "hello5"),
   678  			mkfile("h", "hello6"),
   679  			rm("a/c"),
   680  			rename("a/d", "a/i"),
   681  			setmtime("a/e", targetMtime),
   682  		),
   683  		as(bob, busyWork...),
   684  		as(bob,
   685  			checkUnflushedPaths([]string{
   686  				"/keybase/private/alice,bob",
   687  				"/keybase/private/alice,bob/a",
   688  				"/keybase/private/alice,bob/a/b",
   689  				"/keybase/private/alice,bob/a/e",
   690  				"/keybase/private/alice,bob/f",
   691  				"/keybase/private/alice,bob/f/g",
   692  				"/keybase/private/alice,bob/h",
   693  				"/keybase/private/alice,bob/hi",
   694  			}),
   695  			resumeJournal(),
   696  			// This should kick off conflict resolution.
   697  			flushJournal(),
   698  		),
   699  		as(bob,
   700  			lsdir("", m{"a": "DIR", "f": "DIR", "h": "FILE"}),
   701  			lsdir("a", m{"b": "FILE", "e": "FILE", "i": "FILE"}),
   702  			read("a/b", "world"),
   703  			read("a/e", "hello4"),
   704  			mtime("a/e", targetMtime),
   705  			read("a/i", "hello3"),
   706  			lsdir("f", m{"g": "FILE"}),
   707  			read("f/g", "hello5"),
   708  			read("h", "hello6"),
   709  			checkUnflushedPaths(nil),
   710  		),
   711  		as(alice,
   712  			lsdir("", m{"a": "DIR", "f": "DIR", "h": "FILE"}),
   713  			lsdir("a", m{"b": "FILE", "e": "FILE", "i": "FILE"}),
   714  			read("a/b", "world"),
   715  			read("a/e", "hello4"),
   716  			mtime("a/e", targetMtime),
   717  			read("a/i", "hello3"),
   718  			lsdir("f", m{"g": "FILE"}),
   719  			read("f/g", "hello5"),
   720  			read("h", "hello6"),
   721  		),
   722  	)
   723  }
   724  
   725  // bob makes a bunch of changes that cancel each other out, and get
   726  // coalesced together.
   727  func TestJournalCoalescingNoChanges(t *testing.T) {
   728  	busyWork := makeBusyWork("hi", libkbfs.ForcedBranchSquashRevThreshold+1)
   729  
   730  	test(t, journal(), batchSize(1),
   731  		users("alice", "bob"),
   732  		as(alice,
   733  			mkdir("a"),
   734  		),
   735  		as(bob,
   736  			enableJournal(),
   737  			checkUnflushedPaths(nil),
   738  			pauseJournal(),
   739  		),
   740  		as(bob, busyWork...),
   741  		as(bob,
   742  			checkUnflushedPaths([]string{
   743  				"/keybase/private/alice,bob",
   744  				"/keybase/private/alice,bob/hi",
   745  			}),
   746  			resumeJournal(),
   747  			// This should kick off conflict resolution.
   748  			flushJournal(),
   749  		),
   750  		as(bob,
   751  			lsdir("", m{"a$": "DIR"}),
   752  			lsdir("a", m{}),
   753  			checkUnflushedPaths(nil),
   754  		),
   755  		as(alice,
   756  			lsdir("", m{"a$": "DIR"}),
   757  			lsdir("a", m{}),
   758  		),
   759  	)
   760  }
   761  
   762  // bob creates a conflicting file while running the journal.
   763  func TestJournalDoubleCrRemovalAfterQR(t *testing.T) {
   764  	test(t, journal(),
   765  		users("alice", "bob"),
   766  		as(alice,
   767  			mkdir("a"),
   768  			mkdir("b"),
   769  		),
   770  		as(bob,
   771  			enableJournal(),
   772  			pauseJournal(),
   773  			mkfile("a/c", "uh oh"),
   774  			// Don't flush yet.
   775  		),
   776  		as(bob,
   777  			rm("a/c"),
   778  			rmdir("a"),
   779  		),
   780  		as(alice,
   781  			mkfile("a/c", "hello"),
   782  		),
   783  		as(alice,
   784  			rm("a/c"),
   785  			rmdir("a"),
   786  			rmdir("b"),
   787  		),
   788  		as(bob, noSync(),
   789  			stallOnMDResolveBranch(),
   790  			resumeJournal(),
   791  			// Wait for CR to finish before alice does QR.
   792  			waitForStalledMDResolveBranch(),
   793  		),
   794  		as(alice,
   795  			// Quota reclamation.
   796  			addTime(2*time.Minute),
   797  			forceQuotaReclamation(),
   798  		),
   799  		as(bob, noSync(),
   800  			// Now resolve that conflict over the QR.
   801  			undoStallOnMDResolveBranch(),
   802  			flushJournal(),
   803  		),
   804  		as(bob,
   805  			lsdir("", m{}),
   806  		),
   807  		as(alice,
   808  			lsdir("", m{}),
   809  		),
   810  	)
   811  }
   812  
   813  // Regression test for KBFS-2825.  alice and bob both make a bunch of
   814  // identical creates, within an identical, deep directory structure.
   815  // There's lots of squashing, CR, and journaling going on.  In the
   816  // end, all of bob's files should be conflicted.
   817  func testJournalCoalescingConflictingCreates(t *testing.T, bSize int64) {
   818  	var busyWork []fileOp
   819  	iters := libkbfs.ForcedBranchSquashRevThreshold + 1
   820  	listing := m{}
   821  	for i := 0; i < iters; i++ {
   822  		filename := fmt.Sprintf("%d", i)
   823  		fullname := fmt.Sprintf("a/b/c/d/%s", filename)
   824  		contents := fmt.Sprintf("hello%d", i)
   825  		busyWork = append(busyWork, mkfile(fullname, contents))
   826  		listing["^"+filename+"$"] = "FILE"
   827  		listing["^"+crnameEsc(filename, bob)+"$"] = "FILE"
   828  	}
   829  
   830  	test(t, journal(), batchSize(1), blockSize(bSize),
   831  		users("alice", "bob"),
   832  		as(alice,
   833  			mkdir("a/b/c/d"),
   834  			enableJournal(),
   835  			flushJournal(),
   836  		),
   837  		as(bob,
   838  			lsdir("a/b/c/d", m{}),
   839  		),
   840  		as(bob,
   841  			enableJournal(),
   842  			pauseJournal(),
   843  		),
   844  		as(bob, busyWork...),
   845  		as(bob,
   846  			flushJournal(),
   847  		),
   848  		as(alice, busyWork...),
   849  		as(alice,
   850  			flushJournal(),
   851  		),
   852  		as(bob,
   853  			flushJournal(),
   854  		),
   855  		as(bob,
   856  			flushJournal(),
   857  			disableUpdates(),
   858  		),
   859  		as(alice,
   860  			mkdir("g"),
   861  			flushJournal(),
   862  		),
   863  		as(bob,
   864  			reenableUpdates(),
   865  			resumeJournal(),
   866  			flushJournal(),
   867  		),
   868  		as(bob,
   869  			disableUpdates(),
   870  			reenableUpdates(),
   871  			flushJournal(),
   872  		),
   873  		as(alice,
   874  			lsdir("a/b/c/d", listing),
   875  		),
   876  		as(bob,
   877  			lsdir("a/b/c/d", listing),
   878  		),
   879  	)
   880  }
   881  
   882  func TestJournalCoalescingConflictingCreates(t *testing.T) {
   883  	testJournalCoalescingConflictingCreates(t, 0)
   884  }
   885  
   886  func TestJournalCoalescingConflictingCreatesMultiblock(t *testing.T) {
   887  	testJournalCoalescingConflictingCreates(t, 1024)
   888  }
   889  
   890  func testJournalConflictClearing(
   891  	t *testing.T, tlfBaseName string, switchTlf func(string) optionOp,
   892  	lsfavs func([]string) fileOp, isBackedByTeam, expectSelfFav bool) {
   893  	iteamSuffix := ""
   894  	if isBackedByTeam {
   895  		iteamSuffix = " #1"
   896  	}
   897  	conflict1 := fmt.Sprintf(
   898  		"%s (local conflicted copy 2004-12-23%s)", tlfBaseName, iteamSuffix)
   899  	conflict2 := fmt.Sprintf(
   900  		"%s (local conflicted copy 2004-12-23 #2)", tlfBaseName)
   901  	var expectedFavs []string
   902  	if expectSelfFav {
   903  		expectedFavs = []string{"bob"}
   904  	}
   905  	expectedFavs = append(expectedFavs, tlfBaseName, conflict1, conflict2)
   906  	iteamOp := func(*opt) {}
   907  	if isBackedByTeam {
   908  		iteamOp = implicitTeam("alice,bob", "")
   909  	}
   910  	test(t, journal(),
   911  		users("alice", "bob"),
   912  		iteamOp,
   913  		team("ab", "alice,bob", ""),
   914  		switchTlf(tlfBaseName),
   915  		as(alice,
   916  			mkfile("a/b", "hello"),
   917  		),
   918  		as(bob,
   919  			enableJournal(),
   920  			addTime(35*365*24*time.Hour),
   921  			lsdir("", m{"a$": "DIR"}),
   922  			lsdir("a/", m{"b$": "FILE"}),
   923  			forceConflict(),
   924  			mkfile("a/c", "foo"),
   925  			clearConflicts(),
   926  			lsdir("", m{"a$": "DIR"}),
   927  			lsdir("a/", m{"b$": "FILE"}),
   928  		),
   929  		as(alice,
   930  			lsdir("", m{"a$": "DIR"}),
   931  			lsdir("a/", m{"b$": "FILE"}),
   932  		),
   933  		as(bob,
   934  			lsdir("", m{"a$": "DIR"}),
   935  			lsdir("a/", m{"b$": "FILE"}),
   936  		),
   937  		switchTlf(conflict1),
   938  		as(bob, noSync(),
   939  			lsdir("a/", m{"b$": "FILE", "c$": "FILE"}),
   940  			read("a/c", "foo"),
   941  		),
   942  		// Add a second conflict for the same date.
   943  		switchTlf(tlfBaseName),
   944  		as(bob,
   945  			addTime(1*time.Minute),
   946  			lsdir("", m{"a$": "DIR"}),
   947  			lsdir("a/", m{"b$": "FILE"}),
   948  			forceConflict(),
   949  			mkfile("a/d", "foo"),
   950  			clearConflicts(),
   951  		),
   952  		switchTlf(conflict2),
   953  		as(bob, noSync(),
   954  			lsdir("a/", m{"b$": "FILE", "d$": "FILE"}),
   955  			read("a/d", "foo"),
   956  			lsfavs(expectedFavs),
   957  		),
   958  	)
   959  }
   960  
   961  func TestJournalConflictClearingPrivate(t *testing.T) {
   962  	testJournalConflictClearing(
   963  		t, "alice,bob", inPrivateTlf, lsprivatefavorites, false, true)
   964  }
   965  
   966  func TestJournalConflictClearingPrivateImplicit(t *testing.T) {
   967  	testJournalConflictClearing(
   968  		t, "alice,bob", inPrivateTlf, lsprivatefavorites, true, true)
   969  }
   970  
   971  func TestJournalConflictClearingPublic(t *testing.T) {
   972  	testJournalConflictClearing(
   973  		t, "alice,bob", inPublicTlf, lspublicfavorites, false, true)
   974  }
   975  
   976  func TestJournalConflictClearingPublicImplicit(t *testing.T) {
   977  	testJournalConflictClearing(
   978  		t, "alice,bob", inPublicTlf, lspublicfavorites, true, true)
   979  }
   980  
   981  func TestJournalConflictClearingTeam(t *testing.T) {
   982  	testJournalConflictClearing(
   983  		t, "ab", inSingleTeamTlf, lsteamfavorites, true, false)
   984  }