github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/kbfs/test/edit_history_test.go (about) 1 // Copyright 2018 Keybase Inc. All rights reserved. 2 // Use of this source code is governed by a BSD 3 // license that can be found in the LICENSE file. 4 5 package test 6 7 import ( 8 "fmt" 9 "testing" 10 "time" 11 12 "github.com/keybase/client/go/protocol/keybase1" 13 ) 14 15 func TestEditHistorySimple(t *testing.T) { 16 // Bob writes one file. 17 expectedEdits1 := []expectedEdit{ 18 { 19 "alice,bob", 20 keybase1.FolderType_PRIVATE, 21 "bob", 22 []string{"/keybase/private/alice,bob/a/b"}, 23 nil, 24 }, 25 } 26 // Alice writes one file. 27 expectedEdits2 := []expectedEdit{ 28 { 29 "alice,bob", 30 keybase1.FolderType_PRIVATE, 31 "alice", 32 []string{"/keybase/private/alice,bob/a/c"}, 33 nil, 34 }, 35 expectedEdits1[0], 36 } 37 // Bob overwrites his first file. 38 expectedEdits3 := []expectedEdit{ 39 expectedEdits1[0], 40 expectedEdits2[0], 41 } 42 // Alice deletes the file she wrote. 43 expectedEdits4 := []expectedEdit{ 44 expectedEdits3[0], 45 { 46 "alice,bob", 47 keybase1.FolderType_PRIVATE, 48 "alice", 49 nil, 50 []string{"/keybase/private/alice,bob/a/c"}, 51 }, 52 } 53 54 test(t, 55 users("alice", "bob"), 56 as(alice, 57 mkdir("a"), 58 ), 59 as(bob, 60 mkfile("a/b", "hello"), 61 ), 62 as(bob, 63 checkUserEditHistory(expectedEdits1), 64 ), 65 as(alice, 66 checkUserEditHistory(expectedEdits1), 67 ), 68 as(alice, 69 addTime(1*time.Minute), 70 mkfile("a/c", "hello2"), 71 ), 72 as(alice, 73 checkUserEditHistory(expectedEdits2), 74 ), 75 as(bob, 76 checkUserEditHistory(expectedEdits2), 77 ), 78 as(bob, 79 addTime(1*time.Minute), 80 write("a/b", "hello again"), 81 ), 82 as(bob, 83 checkUserEditHistory(expectedEdits3), 84 ), 85 as(alice, 86 checkUserEditHistory(expectedEdits3), 87 ), 88 as(alice, 89 addTime(1*time.Minute), 90 rm("a/c"), 91 ), 92 as(alice, 93 checkUserEditHistory(expectedEdits4), 94 ), 95 as(bob, 96 checkUserEditHistory(expectedEdits4), 97 ), 98 ) 99 } 100 101 func TestEditHistoryMultiTlf(t *testing.T) { 102 // Bob writes one file to private. 103 expectedEdits1 := []expectedEdit{ 104 { 105 "alice,bob", 106 keybase1.FolderType_PRIVATE, 107 "bob", 108 []string{"/keybase/private/alice,bob/a"}, 109 nil, 110 }, 111 } 112 // Alice writes one file to public. 113 expectedEdits2 := []expectedEdit{ 114 { 115 "alice,bob", 116 keybase1.FolderType_PUBLIC, 117 "alice", 118 []string{"/keybase/public/alice,bob/b"}, 119 nil, 120 }, 121 expectedEdits1[0], 122 } 123 // Bob writes one file to team TLF. 124 expectedEdits3 := []expectedEdit{ 125 { 126 "ab", 127 keybase1.FolderType_TEAM, 128 "bob", 129 []string{"/keybase/team/ab/c"}, 130 nil, 131 }, 132 expectedEdits2[0], 133 expectedEdits1[0], 134 } 135 136 test(t, 137 users("alice", "bob"), 138 team("ab", "alice,bob", ""), 139 as(bob, 140 mkfile("a", "hello"), 141 ), 142 as(bob, 143 checkUserEditHistory(expectedEdits1), 144 ), 145 as(alice, 146 checkUserEditHistory(expectedEdits1), 147 ), 148 inPublicTlf("alice,bob"), 149 as(alice, 150 addTime(1*time.Minute), 151 mkfile("b", "hello"), 152 ), 153 as(alice, 154 checkUserEditHistory(expectedEdits2), 155 ), 156 as(bob, 157 checkUserEditHistory(expectedEdits2), 158 ), 159 inSingleTeamTlf("ab"), 160 as(bob, 161 addTime(1*time.Minute), 162 write("c", "hello again"), 163 ), 164 as(bob, 165 checkUserEditHistory(expectedEdits3), 166 ), 167 as(alice, 168 checkUserEditHistory(expectedEdits3), 169 ), 170 ) 171 } 172 173 func TestEditHistorySelfClusters(t *testing.T) { 174 // Bob writes one file to private. 175 expectedEdits1 := []expectedEdit{ 176 { 177 "alice,bob", 178 keybase1.FolderType_PRIVATE, 179 "bob", 180 []string{"/keybase/private/alice,bob/a"}, 181 nil, 182 }, 183 } 184 // Alice writes to ten team TLFs, but bob should still see his own 185 // write from above. 186 expectedEdits2Alice := make([]expectedEdit, 0, 10) 187 expectedEdits2Bob := make([]expectedEdit, 0, 10) 188 for i := 9; i >= 0; i-- { 189 team := fmt.Sprintf("ab%d", i) 190 e := expectedEdit{ 191 team, 192 keybase1.FolderType_TEAM, 193 "alice", 194 []string{fmt.Sprintf("/keybase/team/%s/a", team)}, 195 nil, 196 } 197 expectedEdits2Alice = append(expectedEdits2Alice, e) 198 expectedEdits2Bob = append(expectedEdits2Bob, e) 199 } 200 expectedEdits2Bob[9] = expectedEdits1[0] 201 202 test(t, 203 users("alice", "bob"), 204 team("ab0", "alice,bob", ""), 205 team("ab1", "alice,bob", ""), 206 team("ab2", "alice,bob", ""), 207 team("ab3", "alice,bob", ""), 208 team("ab4", "alice,bob", ""), 209 team("ab5", "alice,bob", ""), 210 team("ab6", "alice,bob", ""), 211 team("ab7", "alice,bob", ""), 212 team("ab8", "alice,bob", ""), 213 team("ab9", "alice,bob", ""), 214 as(bob, 215 mkfile("a", "hello"), 216 ), 217 as(bob, 218 checkUserEditHistory(expectedEdits1), 219 ), 220 as(alice, 221 checkUserEditHistory(expectedEdits1), 222 ), 223 inSingleTeamTlf("ab0"), 224 as(alice, 225 addTime(1*time.Minute), 226 mkfile("a", "hello"), 227 ), 228 inSingleTeamTlf("ab1"), 229 as(alice, 230 addTime(1*time.Minute), 231 mkfile("a", "hello"), 232 ), 233 inSingleTeamTlf("ab2"), 234 as(alice, 235 addTime(1*time.Minute), 236 mkfile("a", "hello"), 237 ), 238 inSingleTeamTlf("ab3"), 239 as(alice, 240 addTime(1*time.Minute), 241 mkfile("a", "hello"), 242 ), 243 inSingleTeamTlf("ab4"), 244 as(alice, 245 addTime(1*time.Minute), 246 mkfile("a", "hello"), 247 ), 248 inSingleTeamTlf("ab5"), 249 as(alice, 250 addTime(1*time.Minute), 251 mkfile("a", "hello"), 252 ), 253 inSingleTeamTlf("ab6"), 254 as(alice, 255 addTime(1*time.Minute), 256 mkfile("a", "hello"), 257 ), 258 inSingleTeamTlf("ab7"), 259 as(alice, 260 addTime(1*time.Minute), 261 mkfile("a", "hello"), 262 ), 263 inSingleTeamTlf("ab8"), 264 as(alice, 265 addTime(1*time.Minute), 266 mkfile("a", "hello"), 267 ), 268 inSingleTeamTlf("ab9"), 269 as(alice, 270 addTime(1*time.Minute), 271 mkfile("a", "hello"), 272 ), 273 as(alice, 274 checkUserEditHistory(expectedEdits2Alice), 275 ), 276 as(bob, 277 checkUserEditHistory(expectedEdits2Bob), 278 ), 279 ) 280 } 281 282 func TestEditHistoryUnflushed(t *testing.T) { 283 // Bob writes one file. 284 expectedEdits1 := []expectedEdit{ 285 { 286 "alice,bob", 287 keybase1.FolderType_PRIVATE, 288 "bob", 289 []string{"/keybase/private/alice,bob/a/b"}, 290 nil, 291 }, 292 } 293 // Alice and Bob both write a second file, but alice's is unflushed. 294 expectedEdits2Alice := []expectedEdit{ 295 { 296 "alice,bob", 297 keybase1.FolderType_PRIVATE, 298 "alice", 299 []string{"/keybase/private/alice,bob/a/c"}, 300 nil, 301 }, 302 expectedEdits1[0], 303 } 304 expectedEdits2Bob := []expectedEdit{ 305 { 306 "alice,bob", 307 keybase1.FolderType_PRIVATE, 308 "bob", 309 []string{ 310 "/keybase/private/alice,bob/a/d", 311 "/keybase/private/alice,bob/a/b", 312 }, 313 nil, 314 }, 315 } 316 // Alice runs CR and flushes her journal. 317 expectedEdits3 := []expectedEdit{ 318 expectedEdits2Alice[0], 319 expectedEdits2Bob[0], 320 } 321 322 expectedEdits4 := []expectedEdit{ 323 { 324 "alice,bob", 325 keybase1.FolderType_PRIVATE, 326 "alice", 327 nil, 328 []string{ 329 "/keybase/private/alice,bob/a/d", 330 "/keybase/private/alice,bob/a/c", 331 "/keybase/private/alice,bob/a/b", 332 }, 333 }, 334 } 335 336 test(t, journal(), 337 users("alice", "bob"), 338 as(alice, 339 mkdir("a"), 340 ), 341 as(alice, 342 enableJournal(), 343 ), 344 as(bob, 345 mkfile("a/b", "hello"), 346 ), 347 as(bob, 348 checkUserEditHistory(expectedEdits1), 349 ), 350 as(alice, 351 checkUserEditHistory(expectedEdits1), 352 ), 353 as(alice, 354 pauseJournal(), 355 addTime(1*time.Minute), 356 mkfile("a/c", "hello2"), 357 ), 358 as(bob, 359 addTime(1*time.Minute), 360 mkfile("a/d", "hello"), 361 ), 362 as(bob, 363 checkUserEditHistory(expectedEdits2Bob), 364 ), 365 as(alice, noSync(), 366 checkUserEditHistory(expectedEdits2Alice), 367 ), 368 as(alice, noSync(), 369 resumeJournal(), 370 // This should kick off conflict resolution. 371 flushJournal(), 372 ), 373 as(alice, 374 // Extra flush to make sure the edit history messages have 375 // been received by all users. 376 flushJournal(), 377 ), 378 as(alice, 379 checkUserEditHistory(expectedEdits3), 380 ), 381 as(bob, 382 checkUserEditHistory(expectedEdits3), 383 ), 384 as(alice, 385 pauseJournal(), 386 addTime(1*time.Minute), 387 rm("a/b"), 388 rm("a/c"), 389 rm("a/d"), 390 rmdir("a"), 391 ), 392 as(alice, 393 checkUnflushedPaths([]string{ 394 "/keybase/private/alice,bob", 395 "/keybase/private/alice,bob/a", 396 }), 397 ), 398 as(alice, noSync(), 399 resumeJournal(), 400 flushJournal(), 401 ), 402 as(alice, 403 checkUserEditHistory(expectedEdits4), 404 ), 405 as(bob, 406 checkUserEditHistory(expectedEdits4), 407 ), 408 ) 409 } 410 411 func TestEditHistoryRenameParent(t *testing.T) { 412 // Bob writes one file, and alice renames the parent dir. 413 expectedEdits := []expectedEdit{ 414 { 415 "alice,bob", 416 keybase1.FolderType_PRIVATE, 417 "bob", 418 []string{"/keybase/private/alice,bob/c/b"}, 419 nil, 420 }, 421 } 422 423 expectedEdits2 := []expectedEdit{ 424 { 425 "alice,bob", 426 keybase1.FolderType_PRIVATE, 427 "alice", 428 nil, 429 []string{"/keybase/private/alice,bob/c/b"}, 430 }, 431 } 432 433 test(t, 434 users("alice", "bob"), 435 as(alice, 436 mkdir("a"), 437 ), 438 as(bob, 439 mkfile("a/b", "hello"), 440 ), 441 as(alice, 442 addTime(1*time.Minute), 443 rename("a", "c"), 444 ), 445 as(alice, 446 checkUserEditHistory(expectedEdits), 447 ), 448 as(bob, 449 checkUserEditHistory(expectedEdits), 450 ), 451 as(alice, 452 addTime(1*time.Minute), 453 rm("c/b"), 454 ), 455 as(alice, 456 checkUserEditHistory(expectedEdits2), 457 ), 458 as(bob, 459 checkUserEditHistory(expectedEdits2), 460 ), 461 ) 462 } 463 464 func TestEditHistoryRenameParentAcrossDirs(t *testing.T) { 465 // Bob writes one file, and alice renames the parent dir into a 466 // different subdirectory. 467 expectedEdits := []expectedEdit{ 468 { 469 "alice,bob", 470 keybase1.FolderType_PRIVATE, 471 "bob", 472 []string{"/keybase/private/alice,bob/d/c/b"}, 473 nil, 474 }, 475 } 476 477 expectedEdits2 := []expectedEdit{ 478 { 479 "alice,bob", 480 keybase1.FolderType_PRIVATE, 481 "alice", 482 nil, 483 []string{"/keybase/private/alice,bob/d/c/b"}, 484 }, 485 } 486 487 test(t, 488 users("alice", "bob"), 489 as(alice, 490 mkdir("a"), 491 mkdir("d"), 492 ), 493 as(bob, 494 mkfile("a/b", "hello"), 495 ), 496 as(alice, 497 addTime(1*time.Minute), 498 rename("a", "d/c"), 499 ), 500 as(alice, 501 checkUserEditHistory(expectedEdits), 502 ), 503 as(bob, 504 checkUserEditHistory(expectedEdits), 505 ), 506 as(alice, 507 addTime(1*time.Minute), 508 rm("d/c/b"), 509 ), 510 as(alice, 511 checkUserEditHistory(expectedEdits2), 512 ), 513 as(bob, 514 checkUserEditHistory(expectedEdits2), 515 ), 516 ) 517 } 518 519 // Regression test for HOTPOT-616. 520 func TestEditHistoryRenameDirAndReuseNameForFile(t *testing.T) { 521 // Alice creates a dir and puts files in it, creates a file, 522 // renames the dir to something new, renames the file to 523 // the old dir name, and then nukes the old directory. 524 expectedEdits := []expectedEdit{ 525 { 526 "alice", 527 keybase1.FolderType_PRIVATE, 528 "alice", 529 []string{"/keybase/private/alice/a"}, 530 []string{ 531 "/keybase/private/alice/b/c/d", 532 "/keybase/private/alice/b/b", 533 }, 534 }, 535 } 536 537 test(t, batchSize(20), 538 users("alice"), 539 as(alice, 540 mkdir("a"), 541 mkfile("a/b", ""), 542 pwriteBSSync("a/b", []byte("hello"), 0, false), 543 mkdir("a/c"), 544 mkfile("a/c/d", ""), 545 pwriteBSSync("a/c/d", []byte("hello"), 0, false), 546 ), 547 as(alice, 548 mkfile("e", ""), 549 pwriteBSSync("e", []byte("world"), 0, false), 550 rename("a", "b"), 551 rename("e", "a"), 552 rm("b/c/d"), 553 rmdir("b/c"), 554 rm("b/b"), 555 rmdir("b"), 556 ), 557 as(alice, 558 checkUserEditHistory(expectedEdits), 559 ), 560 ) 561 } 562 563 // Regression test for https://github.com/keybase/client/issues/19151. 564 func TestEditHistoryRenameDirAndReuseNameForLink(t *testing.T) { 565 // Alice creates a dir and puts files in it, renames the dir to 566 // something new and then removes it, and makes a symlink using 567 // the old name to the new name. 568 expectedEdits := []expectedEdit{ 569 { 570 "alice", 571 keybase1.FolderType_PRIVATE, 572 "alice", 573 nil, 574 []string{ 575 "/keybase/private/alice/b/c/d", 576 "/keybase/private/alice/b/b", 577 }, 578 }, 579 } 580 581 test(t, batchSize(20), 582 users("alice"), 583 as(alice, 584 mkdir("a"), 585 mkfile("a/b", ""), 586 pwriteBSSync("a/b", []byte("hello"), 0, false), 587 mkdir("a/c"), 588 mkfile("a/c/d", ""), 589 pwriteBSSync("a/c/d", []byte("hello"), 0, false), 590 ), 591 as(alice, 592 rename("a", "b"), 593 rm("b/c/d"), 594 rmdir("b/c"), 595 rm("b/b"), 596 rmdir("b"), 597 mkdir("e"), 598 link("a", "e"), 599 ), 600 as(alice, 601 checkUserEditHistory(expectedEdits), 602 ), 603 ) 604 } 605 606 // Regression test for HOTPOT-803. 607 func TestEditHistoryUnflushedRenameOverNewFile(t *testing.T) { 608 // Alice creates a file in the first revision, but then creates 609 // a file in the second revision that is renamed over the 610 // original file. She also creates a file that is removed. 611 expectedEdits := []expectedEdit{ 612 { 613 "alice", 614 keybase1.FolderType_PRIVATE, 615 "alice", 616 []string{ 617 "/keybase/private/alice/a", 618 }, 619 []string{ 620 "/keybase/private/alice/c", 621 }, 622 }, 623 } 624 625 test(t, journal(), 626 users("alice"), 627 as(alice, 628 mkfile("a", "a foo"), 629 ), 630 as(alice, 631 enableJournal(), 632 ), 633 as(alice, 634 pwriteBSSync("b", []byte("b foo"), 0, false), 635 rename("a", "c"), 636 pwriteBSSync("a", []byte("a2 foo"), 0, false), 637 rename("b", "a"), 638 rm("c"), 639 ), 640 as(alice, 641 lsdir("", m{"a$": "FILE"}), 642 read("a", "b foo"), 643 checkUserEditHistory(expectedEdits), 644 ), 645 ) 646 } 647 648 // A more complex regression test for HOTPOT-803 than the above test, 649 // but it is more faithful to the actual user log. 650 func TestEditHistoryUnflushedRenameOverTwoNewFiles(t *testing.T) { 651 // Alice creates two files in the first revision, but then creates 652 // 2 files in the second revision that are renamed over the 653 // original two files. She also creates two files that are removed. 654 expectedEdits := []expectedEdit{ 655 { 656 "alice", 657 keybase1.FolderType_PRIVATE, 658 "alice", 659 []string{ 660 "/keybase/private/alice/a", 661 "/keybase/private/alice/b", 662 }, 663 []string{ 664 "/keybase/private/alice/f", 665 "/keybase/private/alice/e", 666 }, 667 }, 668 } 669 670 test(t, journal(), 671 users("alice"), 672 as(alice, 673 mkfile("a", "a foo"), 674 mkfile("b", "b foo"), 675 ), 676 as(alice, 677 enableJournal(), 678 ), 679 as(alice, 680 pwriteBSSync("c", []byte("c foo"), 0, false), 681 pwriteBSSync("d", []byte("d foo"), 0, false), 682 rename("b", "e"), 683 pwriteBSSync("f", []byte("f foo"), 0, false), 684 rename("a", "f"), 685 rename("c", "b"), 686 pwriteBSSync("a", []byte("a2 foo"), 0, false), 687 rename("d", "a"), 688 rm("e"), 689 rm("f"), 690 ), 691 as(alice, 692 lsdir("", m{"a$": "FILE", "b$": "FILE"}), 693 read("a", "d foo"), 694 read("b", "c foo"), 695 // Sort the entries because when we add in the rename 696 // operations, the order is undefined and 'a' and 'b' 697 // could be swapped since they happen in the same revision 698 // and are independent. 699 checkUserEditHistoryWithSort(expectedEdits, true), 700 ), 701 ) 702 }