github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/kbfs/test/dsl_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 package test 6 7 import ( 8 "bytes" 9 "fmt" 10 "path" 11 "reflect" 12 "regexp" 13 "sort" 14 "strings" 15 "sync" 16 "testing" 17 "time" 18 19 "github.com/keybase/client/go/kbfs/data" 20 "github.com/keybase/client/go/kbfs/kbfsmd" 21 "github.com/keybase/client/go/kbfs/libfs" 22 "github.com/keybase/client/go/kbfs/libkbfs" 23 "github.com/keybase/client/go/kbfs/test/clocktest" 24 "github.com/keybase/client/go/kbfs/tlf" 25 kbname "github.com/keybase/client/go/kbun" 26 "github.com/keybase/client/go/protocol/keybase1" 27 ) 28 29 type m map[string]string 30 31 const ( 32 alice = username("alice") 33 bob = username("bob") 34 charlie = username("charlie") 35 eve = username("eve") 36 ) 37 38 type opt struct { 39 ver kbfsmd.MetadataVer 40 usernames []kbname.NormalizedUsername 41 teams teamMap 42 implicitTeams teamMap 43 tlfName string 44 expectedCanonicalTlfName string 45 tlfType tlf.Type 46 tlfRevision kbfsmd.Revision 47 tlfTime string 48 tlfRelTime string 49 users map[kbname.NormalizedUsername]User 50 stallers map[kbname.NormalizedUsername]*libkbfs.NaïveStaller 51 tb testing.TB 52 initOnce sync.Once 53 engine Engine 54 blockSize int64 55 blockChangeSize int64 56 batchSize int 57 bwKBps int 58 timeout time.Duration 59 clock *clocktest.TestClock 60 isParallel bool 61 journal bool 62 } 63 64 // run{Test,Benchmark}OverMetadataVers are copied from 65 // libkbfs/bare_root_metadata_test.go, so as to avoid having libkbfs 66 // depend on testing. 67 68 // Also copy testMetadataVers, so that we can set it independently 69 // from libkbfs tests. 70 var testMetadataVers = []kbfsmd.MetadataVer{ 71 kbfsmd.InitialExtraMetadataVer, kbfsmd.ImplicitTeamsVer, 72 } 73 74 // runTestOverMetadataVers runs the given test function over all 75 // metadata versions to test. 76 func runTestOverMetadataVers( 77 t *testing.T, f func(t *testing.T, ver kbfsmd.MetadataVer)) { 78 for _, ver := range testMetadataVers { 79 ver := ver // capture range variable. 80 t.Run(ver.String(), func(t *testing.T) { 81 // Don't do t.Parallel() for now, as FUSE DSL 82 // tests might not like it. 83 f(t, ver) 84 }) 85 } 86 } 87 88 // runBenchmarkOverMetadataVers runs the given benchmark function over 89 // all metadata versions to test. 90 func runBenchmarkOverMetadataVers( 91 b *testing.B, f func(b *testing.B, ver kbfsmd.MetadataVer)) { 92 for _, ver := range testMetadataVers { 93 ver := ver // capture range variable. 94 b.Run(ver.String(), func(b *testing.B) { 95 f(b, ver) 96 }) 97 } 98 } 99 100 func runOneTestOrBenchmark( 101 tb testing.TB, ver kbfsmd.MetadataVer, actions ...optionOp) { 102 o := &opt{ 103 ver: ver, 104 tb: tb, 105 engine: createEngine(tb), 106 } 107 defer o.close() 108 for _, omod := range actions { 109 omod(o) 110 } 111 } 112 113 func test(t *testing.T, actions ...optionOp) { 114 runTestOverMetadataVers(t, func(t *testing.T, ver kbfsmd.MetadataVer) { 115 runOneTestOrBenchmark(t, ver, actions...) 116 }) 117 } 118 119 func benchmark(b *testing.B, actions ...optionOp) { 120 runBenchmarkOverMetadataVers( 121 b, func(b *testing.B, ver kbfsmd.MetadataVer) { 122 runOneTestOrBenchmark(silentBenchmark{b}, ver, actions...) 123 }) 124 } 125 126 func parallel(actions ...optionOp) optionOp { 127 return func(o *opt) { 128 o.isParallel = true 129 wg := &sync.WaitGroup{} 130 for _, omod := range actions { 131 wg.Add(1) 132 go func(omod optionOp) { 133 omod(o) 134 wg.Done() 135 }(omod) 136 } 137 wg.Wait() 138 } 139 } 140 141 func sequential(actions ...optionOp) optionOp { 142 return func(o *opt) { 143 for _, omod := range actions { 144 omod(o) 145 } 146 } 147 } 148 149 type errorList struct { 150 el []error 151 } 152 153 func (el errorList) Error() string { 154 return fmt.Sprintf("%v", el.el) 155 } 156 157 func (o *opt) close() { 158 var el []error 159 // Make sure Shutdown is called properly for every user, even 160 // if any of the calls fail. 161 for _, user := range o.users { 162 err := o.engine.Shutdown(user) 163 if err != nil { 164 el = append(el, err) 165 } 166 } 167 168 var err error 169 if len(el) > 0 { 170 err = errorList{el} 171 } 172 173 o.expectSuccess("Shutdown", err) 174 } 175 176 func (o *opt) runInitOnce() { 177 o.initOnce.Do(func() { 178 o.clock = &clocktest.TestClock{} 179 o.clock.Set(time.Unix(1, 0)) 180 o.users = o.engine.InitTest(o.ver, o.blockSize, 181 o.blockChangeSize, o.batchSize, o.bwKBps, o.timeout, o.usernames, 182 o.teams, o.implicitTeams, o.clock, o.journal) 183 o.stallers = o.makeStallers() 184 }) 185 } 186 187 func (o *opt) makeStallers() ( 188 stallers map[kbname.NormalizedUsername]*libkbfs.NaïveStaller) { 189 stallers = make(map[kbname.NormalizedUsername]*libkbfs.NaïveStaller) 190 for username, user := range o.users { 191 stallers[username] = o.engine.MakeNaïveStaller(user) 192 } 193 return stallers 194 } 195 196 func ntimesString(n int, s string) string { 197 var bs bytes.Buffer 198 for i := 0; i < n; i++ { 199 bs.WriteString(s) 200 } 201 return bs.String() 202 } 203 204 type optionOp func(*opt) 205 206 func blockSize(n int64) optionOp { 207 return func(o *opt) { 208 o.blockSize = n 209 } 210 } 211 212 func blockChangeSize(n int64) optionOp { 213 return func(o *opt) { 214 o.blockChangeSize = n 215 } 216 } 217 218 func batchSize(n int) optionOp { 219 return func(o *opt) { 220 o.batchSize = n 221 } 222 } 223 224 func bandwidth(n int) optionOp { 225 return func(o *opt) { 226 o.bwKBps = n 227 } 228 } 229 230 func opTimeout(n time.Duration) optionOp { 231 return func(o *opt) { 232 o.timeout = n 233 } 234 } 235 236 func journal() optionOp { 237 return func(o *opt) { 238 o.journal = true 239 } 240 } 241 242 func skip(implementation, reason string) optionOp { 243 return func(o *opt) { 244 if o.engine.Name() == implementation { 245 o.tb.Skip(reason) 246 } 247 } 248 } 249 250 func users(ns ...username) optionOp { 251 return func(o *opt) { 252 var a []string 253 for _, u := range ns { 254 username := kbname.NewNormalizedUsername(string(u)) 255 o.usernames = append(o.usernames, username) 256 a = append(a, string(username)) 257 } 258 // Default to the private TLF shared by all the users. 259 sort.Strings(a) 260 tlfName := strings.Join(a, ",") 261 inPrivateTlf(tlfName)(o) 262 } 263 } 264 265 func team(teamName kbname.NormalizedUsername, writers string, 266 readers string) optionOp { 267 return func(o *opt) { 268 if o.ver < kbfsmd.SegregatedKeyBundlesVer { 269 o.tb.Skip("mdv2 doesn't support teams") 270 } 271 if o.teams == nil { 272 o.teams = make(teamMap) 273 } 274 var writerNames, readerNames []kbname.NormalizedUsername 275 for _, w := range strings.Split(writers, ",") { 276 writerNames = append(writerNames, kbname.NormalizedUsername(w)) 277 } 278 if readers != "" { 279 for _, r := range strings.Split(readers, ",") { 280 readerNames = append(readerNames, kbname.NormalizedUsername(r)) 281 } 282 } 283 o.teams[teamName] = teamMembers{writerNames, readerNames} 284 } 285 } 286 287 func implicitTeam(writers string, readers string) optionOp { 288 return func(o *opt) { 289 if o.ver < kbfsmd.ImplicitTeamsVer { 290 o.tb.Skip("mdv2 doesn't support teams") 291 } 292 if o.implicitTeams == nil { 293 o.implicitTeams = make(teamMap) 294 } 295 296 var writerNames, readerNames []kbname.NormalizedUsername 297 for _, w := range strings.Split(writers, ",") { 298 writerNames = append(writerNames, kbname.NormalizedUsername(w)) 299 } 300 isPublic := false 301 if readers != "" { 302 for _, r := range strings.Split(readers, ",") { 303 readerNames = append(readerNames, kbname.NormalizedUsername(r)) 304 } 305 isPublic = len(readerNames) == 1 && readers == "public" 306 } 307 var teamName tlf.CanonicalName 308 if isPublic { 309 teamName = tlf.MakeCanonicalName( 310 writerNames, nil, nil, nil, nil) 311 } else { 312 teamName = tlf.MakeCanonicalName( 313 writerNames, nil, readerNames, nil, nil) 314 } 315 o.implicitTeams[kbname.NormalizedUsername(teamName)] = 316 teamMembers{writerNames, readerNames} 317 } 318 } 319 320 func inPrivateTlf(name string) optionOp { 321 return func(o *opt) { 322 o.tlfName = name 323 o.expectedCanonicalTlfName = name 324 o.tlfType = tlf.Private 325 } 326 } 327 328 func inPrivateTlfAtRevision(name string, rev kbfsmd.Revision) optionOp { 329 return func(o *opt) { 330 o.tlfName = name 331 o.expectedCanonicalTlfName = name 332 o.tlfType = tlf.Private 333 o.tlfRevision = rev 334 } 335 } 336 337 func inPrivateTlfAtTime(name string, timeString string) optionOp { 338 return func(o *opt) { 339 o.tlfName = name 340 o.expectedCanonicalTlfName = name 341 o.tlfType = tlf.Private 342 o.tlfTime = timeString 343 } 344 } 345 346 func inPrivateTlfAtRelativeTime(name string, relTimeString string) optionOp { 347 return func(o *opt) { 348 o.tlfName = name 349 o.expectedCanonicalTlfName = name 350 o.tlfType = tlf.Private 351 o.tlfRelTime = relTimeString 352 } 353 } 354 355 func inPrivateTlfNonCanonical(name, expectedCanonicalName string) optionOp { 356 return func(o *opt) { 357 o.tlfName = name 358 o.expectedCanonicalTlfName = expectedCanonicalName 359 o.tlfType = tlf.Private 360 } 361 } 362 363 func inPublicTlf(name string) optionOp { 364 return func(o *opt) { 365 o.tlfName = name 366 o.expectedCanonicalTlfName = name 367 o.tlfType = tlf.Public 368 } 369 } 370 371 func inPublicTlfNonCanonical(name, expectedCanonicalName string) optionOp { 372 return func(o *opt) { 373 o.tlfName = name 374 o.expectedCanonicalTlfName = expectedCanonicalName 375 o.tlfType = tlf.Public 376 } 377 } 378 379 func inSingleTeamTlf(name string) optionOp { 380 return func(o *opt) { 381 o.tlfName = name 382 o.expectedCanonicalTlfName = name 383 o.tlfType = tlf.SingleTeam 384 } 385 } 386 387 func inSingleTeamNonCanonical(name, expectedCanonicalName string) optionOp { 388 return func(o *opt) { 389 o.tlfName = name 390 o.expectedCanonicalTlfName = expectedCanonicalName 391 o.tlfType = tlf.SingleTeam 392 } 393 } 394 395 func addNewAssertion(oldAssertion, newAssertion string) optionOp { 396 return func(o *opt) { 397 o.tb.Logf("addNewAssertion: %q -> %q", oldAssertion, newAssertion) 398 for _, u := range o.users { 399 err := o.engine.AddNewAssertion(u, oldAssertion, newAssertion) 400 o.expectSuccess("addNewAssertion", err) 401 // Sync the TLF to wait for the TLF handle change 402 // notifications to be processed. Ignore the error since 403 // the user might not even have access to that TLF. 404 _ = o.engine.SyncFromServer(u, o.tlfName, o.tlfType) 405 } 406 } 407 } 408 409 func changeTeamName(oldName, newName string) optionOp { 410 return func(o *opt) { 411 o.teams[kbname.NormalizedUsername(newName)] = 412 o.teams[kbname.NormalizedUsername(oldName)] 413 delete(o.teams, kbname.NormalizedUsername(oldName)) 414 o.tb.Logf("changeTeamName: %q -> %q", oldName, newName) 415 for _, u := range o.users { 416 err := o.engine.ChangeTeamName(u, oldName, newName) 417 o.expectSuccess("changeTeamName", err) 418 } 419 } 420 } 421 422 type fileOp struct { 423 operation func(*ctx) error 424 flags fileOpFlags 425 description string 426 } 427 type fileOpFlags uint32 428 429 const ( 430 Defaults = fileOpFlags(0) 431 IsInit = fileOpFlags(1) 432 ) 433 434 type ctx struct { 435 *opt 436 user User 437 username kbname.NormalizedUsername 438 rootNode Node 439 noSyncInit bool 440 staller *libkbfs.NaïveStaller 441 noSyncEnd bool 442 } 443 444 func runFileOpHelper(c *ctx, fop fileOp) (string, error) { 445 desc := fmt.Sprintf("(%s) %s", c.username, fop.description) 446 c.tb.Log(desc) 447 err := fop.operation(c) 448 if err != nil { 449 c.tb.Logf("%s failed with %s", desc, err) 450 } 451 return desc, err 452 } 453 454 func runFileOp(c *ctx, fop fileOp) (string, error) { 455 if c.rootNode == nil && fop.flags&IsInit == 0 { 456 initOp := initRoot() 457 desc, err := runFileOpHelper(c, initOp) 458 if err != nil { 459 desc = fmt.Sprintf("%s for %s", desc, fop.description) 460 return desc, err 461 } 462 } 463 return runFileOpHelper(c, fop) 464 } 465 466 func expectError(op fileOp, reasonPrefix string) fileOp { 467 return fileOp{func(c *ctx) error { 468 _, err := runFileOp(c, op) 469 if err == nil { 470 return fmt.Errorf("Didn't get expected error (success while expecting failure): %q", reasonPrefix) 471 } 472 // Real filesystems don't give us the exact errors we wish for. 473 if c.engine.Name() == "libkbfs" && 474 !strings.HasPrefix(err.Error(), reasonPrefix) { 475 return fmt.Errorf("Got the wrong error: expected prefix %q, got %q", reasonPrefix, err.Error()) 476 } 477 return nil 478 }, IsInit, /* So that we can use expectError with e.g. initRoot(). */ 479 fmt.Sprintf("expectError(%s, %s)", 480 op.description, reasonPrefix)} 481 } 482 483 func noSync() fileOp { 484 return fileOp{func(c *ctx) error { 485 c.noSyncInit = true 486 return nil 487 }, IsInit, "noSync()"} 488 } 489 490 func resetTimer() fileOp { 491 return fileOp{func(c *ctx) error { 492 switch b := c.tb.(type) { 493 case *testing.B: 494 b.ResetTimer() 495 case silentBenchmark: 496 b.ResetTimer() 497 } 498 return nil 499 }, Defaults, "resetTimer()"} 500 } 501 502 func startTimer() fileOp { 503 return fileOp{func(c *ctx) error { 504 switch b := c.tb.(type) { 505 case *testing.B: 506 b.StartTimer() 507 case silentBenchmark: 508 b.StartTimer() 509 } 510 return nil 511 }, Defaults, "startTimer()"} 512 } 513 514 func stopTimer() fileOp { 515 return fileOp{func(c *ctx) error { 516 switch b := c.tb.(type) { 517 case *testing.B: 518 b.StopTimer() 519 case silentBenchmark: 520 b.StopTimer() 521 } 522 return nil 523 }, Defaults, "stopTimer()"} 524 } 525 526 func getBenchN(n *int) fileOp { 527 return fileOp{func(c *ctx) error { 528 switch b := c.tb.(type) { 529 case *testing.B: 530 *n = b.N 531 case silentBenchmark: 532 *n = b.N 533 } 534 return nil 535 }, Defaults, "getBenchN()"} 536 } 537 538 // noSyncEnd turns off the SyncFromServer call at the end of each `as` 539 // block. It's also turned off by a `disableUpdates()` call or a 540 // `noSync()` call. 541 func noSyncEnd() fileOp { 542 return fileOp{func(c *ctx) error { 543 c.noSyncEnd = true 544 return nil 545 }, IsInit, "noSyncEnd()"} 546 } 547 548 func (o *opt) expectSuccess(reason string, err error) { 549 if err != nil { 550 if o.isParallel { 551 // FailNow/Fatalf can only be called from the goroutine running the Test 552 // function. In parallel tests, this is not always true. So we use Errorf 553 // to mark the test as failed without an implicit FailNow. 554 o.tb.Errorf("Error %s: %v", reason, err) 555 } else { 556 o.tb.Fatalf("Error %s: %v", reason, err) 557 } 558 } 559 } 560 561 func addTime(d time.Duration) fileOp { 562 return fileOp{func(c *ctx) error { 563 c.clock.Add(d) 564 return nil 565 }, Defaults, fmt.Sprintf("addTime(%s)", d)} 566 } 567 568 func as(user username, fops ...fileOp) optionOp { 569 return func(o *opt) { 570 o.tb.Log("as:", user) 571 o.runInitOnce() 572 u := kbname.NewNormalizedUsername(string(user)) 573 ctx := &ctx{ 574 opt: o, 575 user: o.users[u], 576 username: u, 577 staller: o.stallers[u], 578 } 579 580 for _, fop := range fops { 581 desc, err := runFileOp(ctx, fop) 582 ctx.expectSuccess(desc, err) 583 } 584 585 // Sync everything to disk after this round of operations. 586 if !ctx.noSyncInit { 587 err := ctx.engine.SyncAll(ctx.user, ctx.tlfName, ctx.tlfType) 588 ctx.expectSuccess("SyncAll", err) 589 if !ctx.noSyncEnd { 590 err := ctx.engine.SyncFromServer( 591 ctx.user, ctx.tlfName, ctx.tlfType) 592 ctx.expectSuccess("SyncFromServer", err) 593 } 594 } 595 } 596 } 597 598 // initRoot initializes the root for an invocation of as(). Usually 599 // not called directly. 600 func initRoot() fileOp { 601 return fileOp{func(c *ctx) error { 602 if !c.noSyncInit { 603 // Do this before GetRootDir so that we pick 604 // up any TLF name changes. 605 err := c.engine.SyncFromServer(c.user, c.tlfName, c.tlfType) 606 if err != nil { 607 return err 608 } 609 } 610 var root Node 611 var err error 612 switch { 613 case c.tlfRevision != kbfsmd.RevisionUninitialized: 614 root, err = c.engine.GetRootDirAtRevision( 615 c.user, c.tlfName, c.tlfType, c.tlfRevision, 616 c.expectedCanonicalTlfName) 617 case c.tlfTime != "": 618 root, err = c.engine.GetRootDirAtTimeString( 619 c.user, c.tlfName, c.tlfType, c.tlfTime, 620 c.expectedCanonicalTlfName) 621 case c.tlfRelTime != "": 622 root, err = c.engine.GetRootDirAtRelTimeString( 623 c.user, c.tlfName, c.tlfType, c.tlfRelTime, 624 c.expectedCanonicalTlfName) 625 default: 626 root, err = c.engine.GetRootDir( 627 c.user, c.tlfName, c.tlfType, c.expectedCanonicalTlfName) 628 } 629 if err != nil { 630 return err 631 } 632 c.rootNode = root 633 return nil 634 }, IsInit, "initRoot()"} 635 } 636 637 func custom(f func(func(fileOp) error) error) fileOp { 638 return fileOp{func(c *ctx) error { 639 return f(func(fop fileOp) error { return fop.operation(c) }) 640 }, Defaults, "custom()"} 641 } 642 643 func mkdir(name string) fileOp { 644 return fileOp{func(c *ctx) error { 645 _, _, err := c.getNode(name, createDir, resolveAllSyms) 646 return err 647 }, Defaults, fmt.Sprintf("mkdir(%s)", name)} 648 } 649 650 func write(name string, contents string) fileOp { 651 return writeBS(name, []byte(contents)) 652 } 653 654 func writeBS(name string, contents []byte) fileOp { 655 return pwriteBS(name, contents, 0) 656 } 657 658 func pwriteBS(name string, contents []byte, off int64) fileOp { 659 return pwriteBSSync(name, contents, off, true) 660 } 661 662 func pwriteBSSync(name string, contents []byte, off int64, sync bool) fileOp { 663 return fileOp{func(c *ctx) error { 664 f, _, err := c.getNode(name, createFile, resolveAllSyms) 665 if err != nil { 666 return err 667 } 668 return c.engine.WriteFile(c.user, f, contents, off, sync) 669 }, Defaults, fmt.Sprintf("pwriteBSSync(%s, %d bytes, off=%d, sync=%t)", 670 name, len(contents), off, sync)} 671 } 672 673 func truncate(name string, size uint64) fileOp { 674 return fileOp{func(c *ctx) error { 675 f, _, err := c.getNode(name, createFile, resolveAllSyms) 676 if err != nil { 677 return err 678 } 679 return c.engine.TruncateFile(c.user, f, size, true) 680 }, Defaults, fmt.Sprintf("truncate(%s, %d)", name, size)} 681 } 682 683 func read(name string, contents string) fileOp { 684 return preadBS(name, []byte(contents), 0) 685 } 686 func preadBS(name string, contents []byte, at int64) fileOp { 687 return fileOp{func(c *ctx) error { 688 file, _, err := c.getNode(name, noCreate, resolveAllSyms) 689 if err != nil { 690 return err 691 } 692 bs := make([]byte, len(contents)) 693 l, err := c.engine.ReadFile(c.user, file, at, bs) 694 if err != nil { 695 return err 696 } 697 bs = bs[:l] 698 if !bytes.Equal(bs, contents) { 699 return fmt.Errorf("Read (name=%s) got=%d, expected=%d bytes: contents=%s differ from expected=%s", name, len(bs), len(contents), bs, contents) 700 } 701 return nil 702 }, Defaults, fmt.Sprintf("preadBS(%s, %d bytes, at=%d)", 703 name, len(contents), at)} 704 } 705 706 func exists(filename string) fileOp { 707 return fileOp{func(c *ctx) error { 708 _, _, err := c.getNode(filename, noCreate, resolveAllSyms) 709 return err 710 }, Defaults, fmt.Sprintf("exists(%s)", filename)} 711 } 712 func notExists(filename string) fileOp { 713 return fileOp{func(c *ctx) error { 714 _, _, err := c.getNode(filename, noCreate, resolveAllSyms) 715 if err == nil { 716 return fmt.Errorf("File that should not exist exists: %q", filename) 717 } 718 return nil 719 }, Defaults, fmt.Sprintf("notExists(%s)", filename)} 720 } 721 722 func mkfileexcl(name string) fileOp { 723 return fileOp{func(c *ctx) error { 724 _, _, err := c.getNode(name, createFileExcl, resolveAllSyms) 725 return err 726 }, Defaults, fmt.Sprintf("mkfileexcl(%s)", name)} 727 } 728 729 func mkfile(name string, contents string) fileOp { 730 return fileOp{func(c *ctx) error { 731 f, wasCreated, err := c.getNode(name, createFile, resolveAllSyms) 732 if err != nil { 733 return err 734 } 735 if !wasCreated { 736 return fmt.Errorf("File %s already existed when mkfile was called", 737 name) 738 } 739 // Skip the write if the requested contents is empty. 740 if len(contents) == 0 { 741 return nil 742 } 743 return c.engine.WriteFile(c.user, f, []byte(contents), 0, true) 744 }, Defaults, fmt.Sprintf("mkfile(%s, %d bytes)", name, len(contents))} 745 } 746 747 func link(fromName, toPath string) fileOp { 748 return fileOp{func(c *ctx) error { 749 dir, name := path.Split(fromName) 750 parent, _, err := c.getNode(dir, noCreate, resolveAllSyms) 751 if err != nil { 752 return err 753 } 754 return c.engine.CreateLink(c.user, parent, name, toPath) 755 }, Defaults, fmt.Sprintf("link(%s => %s)", fromName, toPath)} 756 } 757 758 func setex(filepath string, ex bool) fileOp { 759 return fileOp{func(c *ctx) error { 760 file, _, err := c.getNode(filepath, noCreate, resolveAllSyms) 761 if err != nil { 762 return err 763 } 764 return c.engine.SetEx(c.user, file, ex) 765 }, Defaults, fmt.Sprintf("setex(%s, %t)", filepath, ex)} 766 } 767 768 func setmtime(filepath string, mtime time.Time) fileOp { 769 return fileOp{func(c *ctx) error { 770 file, _, err := c.getNode(filepath, noCreate, dontResolveFinalSym) 771 if err != nil { 772 return err 773 } 774 return c.engine.SetMtime(c.user, file, mtime) 775 }, Defaults, fmt.Sprintf("setmtime(%s, %s)", filepath, mtime)} 776 } 777 778 func mtime(filepath string, expectedMtime time.Time) fileOp { 779 return fileOp{func(c *ctx) error { 780 // If the expected time is zero, use the clock's current time. 781 if expectedMtime.IsZero() { 782 expectedMtime = c.clock.Now() 783 } 784 785 file, _, err := c.getNode(filepath, noCreate, dontResolveFinalSym) 786 if err != nil { 787 return err 788 } 789 mtime, err := c.engine.GetMtime(c.user, file) 790 if err != nil { 791 return err 792 } 793 if !libfs.TimeEqual(mtime, expectedMtime) { 794 return fmt.Errorf("Mtime (name=%s) got=%s, expected=%s", filepath, 795 mtime, expectedMtime) 796 } 797 return nil 798 }, Defaults, fmt.Sprintf("mtime(%s, %s)", filepath, expectedMtime)} 799 } 800 801 func rm(filepath string) fileOp { 802 return fileOp{func(c *ctx) error { 803 dir, name := path.Split(filepath) 804 parent, _, err := c.getNode(dir, noCreate, resolveAllSyms) 805 if err != nil { 806 return err 807 } 808 return c.engine.RemoveEntry(c.user, parent, name) 809 }, Defaults, fmt.Sprintf("rm(%s)", filepath)} 810 } 811 812 func rmdir(filepath string) fileOp { 813 return fileOp{func(c *ctx) error { 814 dir, name := path.Split(filepath) 815 parent, _, err := c.getNode(dir, noCreate, resolveAllSyms) 816 if err != nil { 817 return err 818 } 819 return c.engine.RemoveDir(c.user, parent, name) 820 }, Defaults, fmt.Sprintf("rmdir(%s)", filepath)} 821 } 822 823 func rename(src, dst string) fileOp { 824 return fileOp{func(c *ctx) error { 825 sdir, sname := path.Split(src) 826 sparent, _, err := c.getNode(sdir, noCreate, resolveAllSyms) 827 if err != nil { 828 return err 829 } 830 ddir, dname := path.Split(dst) 831 dparent, _, err := c.getNode(ddir, createDir, resolveAllSyms) 832 if err != nil { 833 return err 834 } 835 return c.engine.Rename(c.user, sparent, sname, dparent, dname) 836 }, Defaults, fmt.Sprintf("rename(%s => %s)", src, dst)} 837 } 838 839 func disableUpdates() fileOp { 840 return fileOp{func(c *ctx) error { 841 c.noSyncEnd = true 842 err := c.engine.SyncFromServer(c.user, c.tlfName, c.tlfType) 843 if err != nil { 844 return err 845 } 846 return c.engine.DisableUpdatesForTesting(c.user, c.tlfName, c.tlfType) 847 }, IsInit, "disableUpdates()"} 848 } 849 850 func stallOnMDPut() fileOp { 851 return fileOp{func(c *ctx) error { 852 // TODO: Allow test to pass in a more precise maxStalls limit. 853 c.staller.StallMDOp(libkbfs.StallableMDPut, 100, false) 854 return nil 855 }, Defaults, "stallOnMDPut()"} 856 } 857 858 func waitForStalledMDPut() fileOp { 859 return fileOp{func(c *ctx) error { 860 c.staller.WaitForStallMDOp(libkbfs.StallableMDPut) 861 return nil 862 }, IsInit, "waitForStalledMDPut()"} 863 } 864 865 func undoStallOnMDPut() fileOp { 866 return fileOp{func(c *ctx) error { 867 c.staller.UndoStallMDOp(libkbfs.StallableMDPut) 868 return nil 869 }, IsInit, "undoStallOnMDPut()"} 870 } 871 872 func stallOnMDGetForTLF() fileOp { 873 return fileOp{func(c *ctx) error { 874 // TODO: Allow test to pass in a more precise maxStalls limit. 875 c.staller.StallMDOp(libkbfs.StallableMDGetForTLF, 100, false) 876 return nil 877 }, Defaults, "stallOnMDGetForTLF()"} 878 } 879 880 func waitForStalledMDGetForTLF() fileOp { 881 return fileOp{func(c *ctx) error { 882 c.staller.WaitForStallMDOp(libkbfs.StallableMDGetForTLF) 883 return nil 884 }, IsInit, "waitForStalledMDGetForTLF()"} 885 } 886 887 func unstallOneMDGetForTLF() fileOp { 888 return fileOp{func(c *ctx) error { 889 c.staller.UnstallOneMDOp(libkbfs.StallableMDGetForTLF) 890 return nil 891 }, IsInit, "unstallOneMDGetForTLF()"} 892 } 893 894 func undoStallOnMDGetForTLF() fileOp { 895 return fileOp{func(c *ctx) error { 896 c.staller.UndoStallMDOp(libkbfs.StallableMDGetForTLF) 897 return nil 898 }, IsInit, "undoStallOnMDGetForTLF()"} 899 } 900 901 func stallOnMDGetRange() fileOp { 902 return fileOp{func(c *ctx) error { 903 // TODO: Allow test to pass in a more precise maxStalls limit. 904 c.staller.StallMDOp(libkbfs.StallableMDGetRange, 100, false) 905 return nil 906 }, Defaults, "stallOnMDGetRange()"} 907 } 908 909 func waitForStalledMDGetRange() fileOp { 910 return fileOp{func(c *ctx) error { 911 c.staller.WaitForStallMDOp(libkbfs.StallableMDGetRange) 912 return nil 913 }, IsInit, "waitForStalledMDGetRange()"} 914 } 915 916 func unstallOneMDGetRange() fileOp { 917 return fileOp{func(c *ctx) error { 918 c.staller.UnstallOneMDOp(libkbfs.StallableMDGetRange) 919 return nil 920 }, IsInit, "unstallOneMDGetRange()"} 921 } 922 923 func undoStallOnMDGetRange() fileOp { 924 return fileOp{func(c *ctx) error { 925 c.staller.UndoStallMDOp(libkbfs.StallableMDGetRange) 926 return nil 927 }, IsInit, "undoStallOnMDGetRange()"} 928 } 929 930 func stallOnMDResolveBranch() fileOp { 931 return fileOp{func(c *ctx) error { 932 // TODO: Allow test to pass in a more precise maxStalls limit. 933 c.staller.StallMDOp(libkbfs.StallableMDResolveBranch, 100, false) 934 return nil 935 }, Defaults, "stallOnMDResolveBranch()"} 936 } 937 938 func waitForStalledMDResolveBranch() fileOp { 939 return fileOp{func(c *ctx) error { 940 c.staller.WaitForStallMDOp(libkbfs.StallableMDResolveBranch) 941 return nil 942 }, IsInit, "waitForStalledMDResolveBranch()"} 943 } 944 945 func unstallOneMDResolveBranch() fileOp { 946 return fileOp{func(c *ctx) error { 947 c.staller.UnstallOneMDOp(libkbfs.StallableMDResolveBranch) 948 return nil 949 }, IsInit, "unstallOneMDResolveBranch()"} 950 } 951 952 func undoStallOnMDResolveBranch() fileOp { 953 return fileOp{func(c *ctx) error { 954 c.staller.UndoStallMDOp(libkbfs.StallableMDResolveBranch) 955 return nil 956 }, IsInit, "undoStallOnMDResolveBranch()"} 957 } 958 959 func reenableUpdates() fileOp { 960 return fileOp{func(c *ctx) error { 961 c.noSyncEnd = false 962 err := c.engine.ReenableUpdates(c.user, c.tlfName, c.tlfType) 963 if err != nil { 964 return err 965 } 966 return c.engine.SyncFromServer(c.user, c.tlfName, c.tlfType) 967 }, IsInit, "reenableUpdates()"} 968 } 969 970 func reenableUpdatesNoSync() fileOp { 971 return fileOp{func(c *ctx) error { 972 return c.engine.ReenableUpdates(c.user, c.tlfName, c.tlfType) 973 }, IsInit, "reenableUpdatesNoSync()"} 974 } 975 976 func forceQuotaReclamation() fileOp { 977 return fileOp{func(c *ctx) error { 978 err := c.engine.ForceQuotaReclamation(c.user, c.tlfName, c.tlfType) 979 if err != nil { 980 return err 981 } 982 // Wait for QR to finish. 983 return c.engine.SyncFromServer(c.user, c.tlfName, c.tlfType) 984 }, IsInit, "forceQuotaReclamation()"} 985 } 986 987 func rekey() fileOp { 988 return fileOp{func(c *ctx) error { 989 return c.engine.Rekey(c.user, c.tlfName, c.tlfType) 990 }, IsInit, "rekey()"} 991 } 992 993 func enableJournal() fileOp { 994 return fileOp{func(c *ctx) error { 995 return c.engine.EnableJournal(c.user, c.tlfName, c.tlfType) 996 }, IsInit, "enableJournal()"} 997 } 998 999 func pauseJournal() fileOp { 1000 return fileOp{func(c *ctx) error { 1001 return c.engine.PauseJournal(c.user, c.tlfName, c.tlfType) 1002 }, IsInit, "pauseJournal()"} 1003 } 1004 1005 func resumeJournal() fileOp { 1006 return fileOp{func(c *ctx) error { 1007 return c.engine.ResumeJournal(c.user, c.tlfName, c.tlfType) 1008 }, IsInit, "resumeJournal()"} 1009 } 1010 1011 func flushJournal() fileOp { 1012 return fileOp{func(c *ctx) error { 1013 return c.engine.FlushJournal(c.user, c.tlfName, c.tlfType) 1014 }, IsInit, "flushJournal()"} 1015 } 1016 1017 func checkUnflushedPaths(expectedPaths []string) fileOp { 1018 return fileOp{func(c *ctx) error { 1019 paths, err := c.engine.UnflushedPaths(c.user, c.tlfName, c.tlfType) 1020 if err != nil { 1021 return err 1022 } 1023 1024 sort.Strings(expectedPaths) 1025 sort.Strings(paths) 1026 if !reflect.DeepEqual(expectedPaths, paths) { 1027 return fmt.Errorf("Expected unflushed paths %v, got %v", 1028 expectedPaths, paths) 1029 } 1030 return nil 1031 }, IsInit, fmt.Sprintf("checkUnflushedPaths(%s)", expectedPaths)} 1032 } 1033 1034 func checkPrevRevisions(filepath string, counts []uint8) fileOp { 1035 return fileOp{func(c *ctx) error { 1036 file, _, err := c.getNode(filepath, noCreate, resolveAllSyms) 1037 if err != nil { 1038 return err 1039 } 1040 pr, err := c.engine.GetPrevRevisions(c.user, file) 1041 if err != nil { 1042 return err 1043 } 1044 if len(pr) != len(counts) { 1045 return fmt.Errorf("Wrong number of prev revisions: %v", pr) 1046 } 1047 for i, c := range counts { 1048 if c != pr[i].Count { 1049 return fmt.Errorf("Unexpected prev revision counts: %v", pr) 1050 } 1051 } 1052 return nil 1053 }, Defaults, fmt.Sprintf("prevRevisions(%s, %v)", filepath, counts)} 1054 } 1055 1056 type expectedEdit struct { 1057 tlfName string 1058 tlfType keybase1.FolderType 1059 writer string 1060 files []string 1061 deletedFiles []string 1062 } 1063 1064 func checkUserEditHistoryWithSort( 1065 expectedEdits []expectedEdit, doSort bool) fileOp { 1066 return fileOp{func(c *ctx) error { 1067 history, err := c.engine.UserEditHistory(c.user) 1068 if err != nil { 1069 return err 1070 } 1071 1072 // Convert the history to expected edits. 1073 hEdits := make([]expectedEdit, len(history)) 1074 for i, h := range history { 1075 if len(h.History) != 1 { 1076 return fmt.Errorf( 1077 "Unexpected history of size %d: %#v", len(h.History), h) 1078 } 1079 hEdits[i].tlfName = h.Folder.Name 1080 hEdits[i].tlfType = h.Folder.FolderType 1081 hEdits[i].writer = h.History[0].WriterName 1082 for _, we := range h.History[0].Edits { 1083 hEdits[i].files = append(hEdits[i].files, we.Filename) 1084 } 1085 if doSort { 1086 sort.Strings(hEdits[i].files) 1087 sort.Strings(expectedEdits[i].files) 1088 } 1089 for _, we := range h.History[0].Deletes { 1090 hEdits[i].deletedFiles = append( 1091 hEdits[i].deletedFiles, we.Filename) 1092 } 1093 if doSort { 1094 sort.Strings(hEdits[i].deletedFiles) 1095 sort.Strings(expectedEdits[i].deletedFiles) 1096 } 1097 } 1098 1099 if !reflect.DeepEqual(expectedEdits, hEdits) { 1100 return fmt.Errorf("Expected edit history %v, got %v", 1101 expectedEdits, hEdits) 1102 } 1103 return nil 1104 }, Defaults, "checkUserEditHistory()"} 1105 } 1106 1107 func checkUserEditHistory(expectedEdits []expectedEdit) fileOp { 1108 return checkUserEditHistoryWithSort(expectedEdits, false) 1109 } 1110 1111 func checkDirtyPaths(expectedPaths []string) fileOp { 1112 return fileOp{func(c *ctx) error { 1113 paths, err := c.engine.DirtyPaths(c.user, c.tlfName, c.tlfType) 1114 if err != nil { 1115 return err 1116 } 1117 1118 sort.Strings(expectedPaths) 1119 sort.Strings(paths) 1120 if !reflect.DeepEqual(expectedPaths, paths) { 1121 return fmt.Errorf("Expected dirty paths %v, got %v", 1122 expectedPaths, paths) 1123 } 1124 return nil 1125 }, IsInit, fmt.Sprintf("checkDirtyPaths(%s)", expectedPaths)} 1126 } 1127 1128 func forceConflict() fileOp { 1129 return fileOp{func(c *ctx) error { 1130 return c.engine.ForceConflict(c.user, c.tlfName, c.tlfType) 1131 }, IsInit, "forceConflict()"} 1132 } 1133 1134 func clearConflicts() fileOp { 1135 return fileOp{func(c *ctx) error { 1136 return c.engine.ClearConflicts(c.user, c.tlfName, c.tlfType) 1137 }, IsInit, "clearConflicts()"} 1138 } 1139 1140 func lsfavoritesOp(c *ctx, expected []string, t tlf.Type) error { 1141 favorites, err := c.engine.GetFavorites(c.user, t) 1142 if err != nil { 1143 return err 1144 } 1145 c.tb.Log("lsfavorites", t, "=>", favorites) 1146 for _, f := range expected { 1147 if favorites[f] { 1148 delete(favorites, f) 1149 continue 1150 } 1151 1152 p, err := tlf.CanonicalToPreferredName(c.username, tlf.CanonicalName(f)) 1153 if err != nil { 1154 return err 1155 } 1156 if favorites[string(p)] { 1157 delete(favorites, string(p)) 1158 } else { 1159 return fmt.Errorf("Missing favorite %s", f) 1160 } 1161 } 1162 1163 for f := range favorites { 1164 return fmt.Errorf("Unexpected favorite %s", f) 1165 } 1166 return nil 1167 } 1168 1169 func lspublicfavorites(contents []string) fileOp { 1170 return fileOp{func(c *ctx) error { 1171 return lsfavoritesOp(c, contents, tlf.Public) 1172 }, Defaults, fmt.Sprintf("lspublicfavorites(%s)", contents)} 1173 } 1174 1175 func lsprivatefavorites(contents []string) fileOp { 1176 return fileOp{func(c *ctx) error { 1177 return lsfavoritesOp(c, contents, tlf.Private) 1178 }, Defaults, fmt.Sprintf("lsprivatefavorites(%s)", contents)} 1179 } 1180 1181 func lsteamfavorites(contents []string) fileOp { 1182 return fileOp{func(c *ctx) error { 1183 return lsfavoritesOp(c, contents, tlf.SingleTeam) 1184 }, Defaults, fmt.Sprintf("lsteamfavorites(%s)", contents)} 1185 } 1186 1187 func lsdir(name string, contents m) fileOp { 1188 return fileOp{func(c *ctx) error { 1189 folder, _, err := c.getNode(name, noCreate, resolveAllSyms) 1190 if err != nil { 1191 return err 1192 } 1193 entries, err := c.engine.GetDirChildrenTypes(c.user, folder) 1194 if err != nil { 1195 return err 1196 } 1197 c.tb.Log("lsdir =>", entries) 1198 outer: 1199 for restr, ty := range contents { 1200 re := regexp.MustCompile(restr) 1201 for node, ty2 := range entries { 1202 // Windows does not mark "executable" bits in any way. 1203 if re.MatchString(node) && (ty == ty2 || 1204 (c.engine.Name() == "dokan" && ty == "EXEC" && ty2 == "FILE")) { 1205 delete(entries, node) 1206 continue outer 1207 } 1208 } 1209 return fmt.Errorf("%s of type %s not found", restr, ty) 1210 } 1211 // and make sure everything is matched 1212 for node, ty := range entries { 1213 return fmt.Errorf("unexpected %s of type %s found in %s", node, ty, name) 1214 } 1215 return nil 1216 }, Defaults, fmt.Sprintf("lsdir(%s, %d bytes)", name, len(contents))} 1217 } 1218 1219 // createType specifies whether getNode should create any nodes that 1220 // don't exist. 1221 type createType int 1222 1223 const ( 1224 noCreate createType = iota 1225 createDir 1226 createFile 1227 createFileExcl 1228 ) 1229 1230 func (c createType) String() string { 1231 switch c { 1232 case noCreate: 1233 return "noCreate" 1234 case createDir: 1235 return "createDir" 1236 case createFile: 1237 return "createFile" 1238 case createFileExcl: 1239 return "createFileExcl" 1240 default: 1241 return fmt.Sprintf("unknownCreateType:%d", c) 1242 } 1243 } 1244 1245 // symBehavior specifies what getNode should do with symlinks. 1246 type symBehavior int 1247 1248 const ( 1249 resolveAllSyms symBehavior = iota 1250 dontResolveFinalSym 1251 ) 1252 1253 func (c *ctx) getNode(filepath string, create createType, sym symBehavior) ( 1254 Node, bool, error) { 1255 if filepath == "" || filepath == "/" { 1256 return c.rootNode, false, nil 1257 } 1258 if filepath[len(filepath)-1] == '/' { 1259 filepath = filepath[:len(filepath)-1] 1260 } 1261 components := strings.Split(filepath, "/") 1262 c.tb.Log("getNode:", filepath, create, components, len(components)) 1263 var symPath string 1264 var err error 1265 var node, parent Node 1266 parent = c.rootNode 1267 wasCreated := false 1268 for i, name := range components { 1269 node, symPath, err = c.engine.Lookup(c.user, parent, name) 1270 c.tb.Log("getNode:", i, name, node, symPath, err) 1271 1272 if i+1 == len(components) { // last element in path 1273 switch { 1274 case err == nil: 1275 if create == createFileExcl { 1276 return nil, false, data.NameExistsError{} 1277 } 1278 case create == createFileExcl: 1279 c.tb.Log("getNode: CreateFileExcl") 1280 node, err = c.engine.CreateFileExcl(c.user, parent, name) 1281 wasCreated = true 1282 case create == createFile: 1283 c.tb.Log("getNode: CreateFile") 1284 node, err = c.engine.CreateFile(c.user, parent, name) 1285 wasCreated = true 1286 case create == createDir: 1287 c.tb.Log("getNode: CreateDir") 1288 node, err = c.engine.CreateDir(c.user, parent, name) 1289 wasCreated = true 1290 case create == noCreate: 1291 // let it error! 1292 default: 1293 panic("unreachable") 1294 } 1295 } else if err != nil && create != noCreate { 1296 // intermediate element in path 1297 c.tb.Log("getNode: CreateDir") 1298 node, err = c.engine.CreateDir(c.user, parent, name) 1299 wasCreated = true 1300 } // otherwise let it error! 1301 1302 if err != nil { 1303 return nil, false, err 1304 } 1305 1306 parent = node 1307 // If this is a symlink, and either we're supposed to resolve 1308 // all symlinks or this isn't the final one in the path, then 1309 // go ahead and recurse on the resolved path. 1310 if len(symPath) > 0 && 1311 (sym == resolveAllSyms || i != len(components)-1) { 1312 var tmp []string 1313 if symPath[0] == '/' { 1314 tmp = []string{symPath} 1315 } else { 1316 tmp = components[:i] 1317 tmp = append(tmp, symPath) 1318 } 1319 tmp = append(tmp, components[i+1:]...) 1320 newpath := path.Clean(path.Join(tmp...)) 1321 c.tb.Log("getNode: symlink ", symPath, " redirecting to ", newpath) 1322 return c.getNode(newpath, create, sym) 1323 } 1324 } 1325 return node, wasCreated, nil 1326 } 1327 1328 // crnameAtTime returns the name of a conflict file, at a given 1329 // duration past the default time. 1330 func crnameAtTime(path string, user username, d time.Duration) string { 1331 cre := libkbfs.WriterDeviceDateConflictRenamer{} 1332 return cre.ConflictRenameHelper(time.Unix(1, 0).Add(d), string(user), 1333 "dev1", path) 1334 } 1335 1336 // crnameAtTimeEsc returns the name of a conflict file with regular 1337 // expression escapes, at a given duration past the default time. 1338 func crnameAtTimeEsc(path string, user username, d time.Duration) string { 1339 return regexp.QuoteMeta(crnameAtTime(path, user, d)) 1340 } 1341 1342 // crname returns the name of a conflict file. 1343 func crname(path string, user username) string { 1344 return crnameAtTime(path, user, 0) 1345 } 1346 1347 // crnameEsc returns the name of a conflict file with regular expression escapes. 1348 func crnameEsc(path string, user username) string { 1349 return crnameAtTimeEsc(path, user, 0) 1350 } 1351 1352 type silentBenchmark struct{ *testing.B } 1353 1354 func (silentBenchmark) Log(args ...interface{}) {} 1355 func (silentBenchmark) Logf(format string, args ...interface{}) {}