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
7package test
8
9import (
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.
18func 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.
52func 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.
82func 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
120func 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.
131func 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.
173func 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.
245func 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.
286func 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
351func TestJournalCrResolutionHitsConflict(t *testing.T) {
352	testJournalCrResolutionHitsConflict(t, nil)
353}
354
355func 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.
361func 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.
392func 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.
441func 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.
513func 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.
604func 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.
649func 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.
727func 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.
763func 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.
817func 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
882func TestJournalCoalescingConflictingCreates(t *testing.T) {
883	testJournalCoalescingConflictingCreates(t, 0)
884}
885
886func TestJournalCoalescingConflictingCreatesMultiblock(t *testing.T) {
887	testJournalCoalescingConflictingCreates(t, 1024)
888}
889
890func 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
961func TestJournalConflictClearingPrivate(t *testing.T) {
962	testJournalConflictClearing(
963		t, "alice,bob", inPrivateTlf, lsprivatefavorites, false, true)
964}
965
966func TestJournalConflictClearingPrivateImplicit(t *testing.T) {
967	testJournalConflictClearing(
968		t, "alice,bob", inPrivateTlf, lsprivatefavorites, true, true)
969}
970
971func TestJournalConflictClearingPublic(t *testing.T) {
972	testJournalConflictClearing(
973		t, "alice,bob", inPublicTlf, lspublicfavorites, false, true)
974}
975
976func TestJournalConflictClearingPublicImplicit(t *testing.T) {
977	testJournalConflictClearing(
978		t, "alice,bob", inPublicTlf, lspublicfavorites, true, true)
979}
980
981func TestJournalConflictClearingTeam(t *testing.T) {
982	testJournalConflictClearing(
983		t, "ab", inSingleTeamTlf, lsteamfavorites, true, false)
984}
985