github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/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 }