github.com/Andyfoo/golang/x/net@v0.0.0-20190901054642-57c1bf301704/webdav/lock_test.go (about) 1 // Copyright 2014 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package webdav 6 7 import ( 8 "fmt" 9 "math/rand" 10 "path" 11 "reflect" 12 "sort" 13 "strconv" 14 "strings" 15 "testing" 16 "time" 17 ) 18 19 func TestWalkToRoot(t *testing.T) { 20 testCases := []struct { 21 name string 22 want []string 23 }{{ 24 "/a/b/c/d", 25 []string{ 26 "/a/b/c/d", 27 "/a/b/c", 28 "/a/b", 29 "/a", 30 "/", 31 }, 32 }, { 33 "/a", 34 []string{ 35 "/a", 36 "/", 37 }, 38 }, { 39 "/", 40 []string{ 41 "/", 42 }, 43 }} 44 45 for _, tc := range testCases { 46 var got []string 47 if !walkToRoot(tc.name, func(name0 string, first bool) bool { 48 if first != (len(got) == 0) { 49 t.Errorf("name=%q: first=%t but len(got)==%d", tc.name, first, len(got)) 50 return false 51 } 52 got = append(got, name0) 53 return true 54 }) { 55 continue 56 } 57 if !reflect.DeepEqual(got, tc.want) { 58 t.Errorf("name=%q:\ngot %q\nwant %q", tc.name, got, tc.want) 59 } 60 } 61 } 62 63 var lockTestDurations = []time.Duration{ 64 infiniteTimeout, // infiniteTimeout means to never expire. 65 0, // A zero duration means to expire immediately. 66 100 * time.Hour, // A very large duration will not expire in these tests. 67 } 68 69 // lockTestNames are the names of a set of mutually compatible locks. For each 70 // name fragment: 71 // - _ means no explicit lock. 72 // - i means an infinite-depth lock, 73 // - z means a zero-depth lock, 74 var lockTestNames = []string{ 75 "/_/_/_/_/z", 76 "/_/_/i", 77 "/_/z", 78 "/_/z/i", 79 "/_/z/z", 80 "/_/z/_/i", 81 "/_/z/_/z", 82 "/i", 83 "/z", 84 "/z/_/i", 85 "/z/_/z", 86 } 87 88 func lockTestZeroDepth(name string) bool { 89 switch name[len(name)-1] { 90 case 'i': 91 return false 92 case 'z': 93 return true 94 } 95 panic(fmt.Sprintf("lock name %q did not end with 'i' or 'z'", name)) 96 } 97 98 func TestMemLSCanCreate(t *testing.T) { 99 now := time.Unix(0, 0) 100 m := NewMemLS().(*memLS) 101 102 for _, name := range lockTestNames { 103 _, err := m.Create(now, LockDetails{ 104 Root: name, 105 Duration: infiniteTimeout, 106 ZeroDepth: lockTestZeroDepth(name), 107 }) 108 if err != nil { 109 t.Fatalf("creating lock for %q: %v", name, err) 110 } 111 } 112 113 wantCanCreate := func(name string, zeroDepth bool) bool { 114 for _, n := range lockTestNames { 115 switch { 116 case n == name: 117 // An existing lock has the same name as the proposed lock. 118 return false 119 case strings.HasPrefix(n, name): 120 // An existing lock would be a child of the proposed lock, 121 // which conflicts if the proposed lock has infinite depth. 122 if !zeroDepth { 123 return false 124 } 125 case strings.HasPrefix(name, n): 126 // An existing lock would be an ancestor of the proposed lock, 127 // which conflicts if the ancestor has infinite depth. 128 if n[len(n)-1] == 'i' { 129 return false 130 } 131 } 132 } 133 return true 134 } 135 136 var check func(int, string) 137 check = func(recursion int, name string) { 138 for _, zeroDepth := range []bool{false, true} { 139 got := m.canCreate(name, zeroDepth) 140 want := wantCanCreate(name, zeroDepth) 141 if got != want { 142 t.Errorf("canCreate name=%q zeroDepth=%t: got %t, want %t", name, zeroDepth, got, want) 143 } 144 } 145 if recursion == 6 { 146 return 147 } 148 if name != "/" { 149 name += "/" 150 } 151 for _, c := range "_iz" { 152 check(recursion+1, name+string(c)) 153 } 154 } 155 check(0, "/") 156 } 157 158 func TestMemLSLookup(t *testing.T) { 159 now := time.Unix(0, 0) 160 m := NewMemLS().(*memLS) 161 162 badToken := m.nextToken() 163 t.Logf("badToken=%q", badToken) 164 165 for _, name := range lockTestNames { 166 token, err := m.Create(now, LockDetails{ 167 Root: name, 168 Duration: infiniteTimeout, 169 ZeroDepth: lockTestZeroDepth(name), 170 }) 171 if err != nil { 172 t.Fatalf("creating lock for %q: %v", name, err) 173 } 174 t.Logf("%-15q -> node=%p token=%q", name, m.byName[name], token) 175 } 176 177 baseNames := append([]string{"/a", "/b/c"}, lockTestNames...) 178 for _, baseName := range baseNames { 179 for _, suffix := range []string{"", "/0", "/1/2/3"} { 180 name := baseName + suffix 181 182 goodToken := "" 183 base := m.byName[baseName] 184 if base != nil && (suffix == "" || !lockTestZeroDepth(baseName)) { 185 goodToken = base.token 186 } 187 188 for _, token := range []string{badToken, goodToken} { 189 if token == "" { 190 continue 191 } 192 193 got := m.lookup(name, Condition{Token: token}) 194 want := base 195 if token == badToken { 196 want = nil 197 } 198 if got != want { 199 t.Errorf("name=%-20qtoken=%q (bad=%t): got %p, want %p", 200 name, token, token == badToken, got, want) 201 } 202 } 203 } 204 } 205 } 206 207 func TestMemLSConfirm(t *testing.T) { 208 now := time.Unix(0, 0) 209 m := NewMemLS().(*memLS) 210 alice, err := m.Create(now, LockDetails{ 211 Root: "/alice", 212 Duration: infiniteTimeout, 213 ZeroDepth: false, 214 }) 215 tweedle, err := m.Create(now, LockDetails{ 216 Root: "/tweedle", 217 Duration: infiniteTimeout, 218 ZeroDepth: false, 219 }) 220 if err != nil { 221 t.Fatalf("Create: %v", err) 222 } 223 if err := m.consistent(); err != nil { 224 t.Fatalf("Create: inconsistent state: %v", err) 225 } 226 227 // Test a mismatch between name and condition. 228 _, err = m.Confirm(now, "/tweedle/dee", "", Condition{Token: alice}) 229 if err != ErrConfirmationFailed { 230 t.Fatalf("Confirm (mismatch): got %v, want ErrConfirmationFailed", err) 231 } 232 if err := m.consistent(); err != nil { 233 t.Fatalf("Confirm (mismatch): inconsistent state: %v", err) 234 } 235 236 // Test two names (that fall under the same lock) in the one Confirm call. 237 release, err := m.Confirm(now, "/tweedle/dee", "/tweedle/dum", Condition{Token: tweedle}) 238 if err != nil { 239 t.Fatalf("Confirm (twins): %v", err) 240 } 241 if err := m.consistent(); err != nil { 242 t.Fatalf("Confirm (twins): inconsistent state: %v", err) 243 } 244 release() 245 if err := m.consistent(); err != nil { 246 t.Fatalf("release (twins): inconsistent state: %v", err) 247 } 248 249 // Test the same two names in overlapping Confirm / release calls. 250 releaseDee, err := m.Confirm(now, "/tweedle/dee", "", Condition{Token: tweedle}) 251 if err != nil { 252 t.Fatalf("Confirm (sequence #0): %v", err) 253 } 254 if err := m.consistent(); err != nil { 255 t.Fatalf("Confirm (sequence #0): inconsistent state: %v", err) 256 } 257 258 _, err = m.Confirm(now, "/tweedle/dum", "", Condition{Token: tweedle}) 259 if err != ErrConfirmationFailed { 260 t.Fatalf("Confirm (sequence #1): got %v, want ErrConfirmationFailed", err) 261 } 262 if err := m.consistent(); err != nil { 263 t.Fatalf("Confirm (sequence #1): inconsistent state: %v", err) 264 } 265 266 releaseDee() 267 if err := m.consistent(); err != nil { 268 t.Fatalf("release (sequence #2): inconsistent state: %v", err) 269 } 270 271 releaseDum, err := m.Confirm(now, "/tweedle/dum", "", Condition{Token: tweedle}) 272 if err != nil { 273 t.Fatalf("Confirm (sequence #3): %v", err) 274 } 275 if err := m.consistent(); err != nil { 276 t.Fatalf("Confirm (sequence #3): inconsistent state: %v", err) 277 } 278 279 // Test that you can't unlock a held lock. 280 err = m.Unlock(now, tweedle) 281 if err != ErrLocked { 282 t.Fatalf("Unlock (sequence #4): got %v, want ErrLocked", err) 283 } 284 285 releaseDum() 286 if err := m.consistent(); err != nil { 287 t.Fatalf("release (sequence #5): inconsistent state: %v", err) 288 } 289 290 err = m.Unlock(now, tweedle) 291 if err != nil { 292 t.Fatalf("Unlock (sequence #6): %v", err) 293 } 294 if err := m.consistent(); err != nil { 295 t.Fatalf("Unlock (sequence #6): inconsistent state: %v", err) 296 } 297 } 298 299 func TestMemLSNonCanonicalRoot(t *testing.T) { 300 now := time.Unix(0, 0) 301 m := NewMemLS().(*memLS) 302 token, err := m.Create(now, LockDetails{ 303 Root: "/foo/./bar//", 304 Duration: 1 * time.Second, 305 }) 306 if err != nil { 307 t.Fatalf("Create: %v", err) 308 } 309 if err := m.consistent(); err != nil { 310 t.Fatalf("Create: inconsistent state: %v", err) 311 } 312 if err := m.Unlock(now, token); err != nil { 313 t.Fatalf("Unlock: %v", err) 314 } 315 if err := m.consistent(); err != nil { 316 t.Fatalf("Unlock: inconsistent state: %v", err) 317 } 318 } 319 320 func TestMemLSExpiry(t *testing.T) { 321 m := NewMemLS().(*memLS) 322 testCases := []string{ 323 "setNow 0", 324 "create /a.5", 325 "want /a.5", 326 "create /c.6", 327 "want /a.5 /c.6", 328 "create /a/b.7", 329 "want /a.5 /a/b.7 /c.6", 330 "setNow 4", 331 "want /a.5 /a/b.7 /c.6", 332 "setNow 5", 333 "want /a/b.7 /c.6", 334 "setNow 6", 335 "want /a/b.7", 336 "setNow 7", 337 "want ", 338 "setNow 8", 339 "want ", 340 "create /a.12", 341 "create /b.13", 342 "create /c.15", 343 "create /a/d.16", 344 "want /a.12 /a/d.16 /b.13 /c.15", 345 "refresh /a.14", 346 "want /a.14 /a/d.16 /b.13 /c.15", 347 "setNow 12", 348 "want /a.14 /a/d.16 /b.13 /c.15", 349 "setNow 13", 350 "want /a.14 /a/d.16 /c.15", 351 "setNow 14", 352 "want /a/d.16 /c.15", 353 "refresh /a/d.20", 354 "refresh /c.20", 355 "want /a/d.20 /c.20", 356 "setNow 20", 357 "want ", 358 } 359 360 tokens := map[string]string{} 361 zTime := time.Unix(0, 0) 362 now := zTime 363 for i, tc := range testCases { 364 j := strings.IndexByte(tc, ' ') 365 if j < 0 { 366 t.Fatalf("test case #%d %q: invalid command", i, tc) 367 } 368 op, arg := tc[:j], tc[j+1:] 369 switch op { 370 default: 371 t.Fatalf("test case #%d %q: invalid operation %q", i, tc, op) 372 373 case "create", "refresh": 374 parts := strings.Split(arg, ".") 375 if len(parts) != 2 { 376 t.Fatalf("test case #%d %q: invalid create", i, tc) 377 } 378 root := parts[0] 379 d, err := strconv.Atoi(parts[1]) 380 if err != nil { 381 t.Fatalf("test case #%d %q: invalid duration", i, tc) 382 } 383 dur := time.Unix(0, 0).Add(time.Duration(d) * time.Second).Sub(now) 384 385 switch op { 386 case "create": 387 token, err := m.Create(now, LockDetails{ 388 Root: root, 389 Duration: dur, 390 ZeroDepth: true, 391 }) 392 if err != nil { 393 t.Fatalf("test case #%d %q: Create: %v", i, tc, err) 394 } 395 tokens[root] = token 396 397 case "refresh": 398 token := tokens[root] 399 if token == "" { 400 t.Fatalf("test case #%d %q: no token for %q", i, tc, root) 401 } 402 got, err := m.Refresh(now, token, dur) 403 if err != nil { 404 t.Fatalf("test case #%d %q: Refresh: %v", i, tc, err) 405 } 406 want := LockDetails{ 407 Root: root, 408 Duration: dur, 409 ZeroDepth: true, 410 } 411 if got != want { 412 t.Fatalf("test case #%d %q:\ngot %v\nwant %v", i, tc, got, want) 413 } 414 } 415 416 case "setNow": 417 d, err := strconv.Atoi(arg) 418 if err != nil { 419 t.Fatalf("test case #%d %q: invalid duration", i, tc) 420 } 421 now = time.Unix(0, 0).Add(time.Duration(d) * time.Second) 422 423 case "want": 424 m.mu.Lock() 425 m.collectExpiredNodes(now) 426 got := make([]string, 0, len(m.byToken)) 427 for _, n := range m.byToken { 428 got = append(got, fmt.Sprintf("%s.%d", 429 n.details.Root, n.expiry.Sub(zTime)/time.Second)) 430 } 431 m.mu.Unlock() 432 sort.Strings(got) 433 want := []string{} 434 if arg != "" { 435 want = strings.Split(arg, " ") 436 } 437 if !reflect.DeepEqual(got, want) { 438 t.Fatalf("test case #%d %q:\ngot %q\nwant %q", i, tc, got, want) 439 } 440 } 441 442 if err := m.consistent(); err != nil { 443 t.Fatalf("test case #%d %q: inconsistent state: %v", i, tc, err) 444 } 445 } 446 } 447 448 func TestMemLS(t *testing.T) { 449 now := time.Unix(0, 0) 450 m := NewMemLS().(*memLS) 451 rng := rand.New(rand.NewSource(0)) 452 tokens := map[string]string{} 453 nConfirm, nCreate, nRefresh, nUnlock := 0, 0, 0, 0 454 const N = 2000 455 456 for i := 0; i < N; i++ { 457 name := lockTestNames[rng.Intn(len(lockTestNames))] 458 duration := lockTestDurations[rng.Intn(len(lockTestDurations))] 459 confirmed, unlocked := false, false 460 461 // If the name was already locked, we randomly confirm/release, refresh 462 // or unlock it. Otherwise, we create a lock. 463 token := tokens[name] 464 if token != "" { 465 switch rng.Intn(3) { 466 case 0: 467 confirmed = true 468 nConfirm++ 469 release, err := m.Confirm(now, name, "", Condition{Token: token}) 470 if err != nil { 471 t.Fatalf("iteration #%d: Confirm %q: %v", i, name, err) 472 } 473 if err := m.consistent(); err != nil { 474 t.Fatalf("iteration #%d: inconsistent state: %v", i, err) 475 } 476 release() 477 478 case 1: 479 nRefresh++ 480 if _, err := m.Refresh(now, token, duration); err != nil { 481 t.Fatalf("iteration #%d: Refresh %q: %v", i, name, err) 482 } 483 484 case 2: 485 unlocked = true 486 nUnlock++ 487 if err := m.Unlock(now, token); err != nil { 488 t.Fatalf("iteration #%d: Unlock %q: %v", i, name, err) 489 } 490 } 491 492 } else { 493 nCreate++ 494 var err error 495 token, err = m.Create(now, LockDetails{ 496 Root: name, 497 Duration: duration, 498 ZeroDepth: lockTestZeroDepth(name), 499 }) 500 if err != nil { 501 t.Fatalf("iteration #%d: Create %q: %v", i, name, err) 502 } 503 } 504 505 if !confirmed { 506 if duration == 0 || unlocked { 507 // A zero-duration lock should expire immediately and is 508 // effectively equivalent to being unlocked. 509 tokens[name] = "" 510 } else { 511 tokens[name] = token 512 } 513 } 514 515 if err := m.consistent(); err != nil { 516 t.Fatalf("iteration #%d: inconsistent state: %v", i, err) 517 } 518 } 519 520 if nConfirm < N/10 { 521 t.Fatalf("too few Confirm calls: got %d, want >= %d", nConfirm, N/10) 522 } 523 if nCreate < N/10 { 524 t.Fatalf("too few Create calls: got %d, want >= %d", nCreate, N/10) 525 } 526 if nRefresh < N/10 { 527 t.Fatalf("too few Refresh calls: got %d, want >= %d", nRefresh, N/10) 528 } 529 if nUnlock < N/10 { 530 t.Fatalf("too few Unlock calls: got %d, want >= %d", nUnlock, N/10) 531 } 532 } 533 534 func (m *memLS) consistent() error { 535 m.mu.Lock() 536 defer m.mu.Unlock() 537 538 // If m.byName is non-empty, then it must contain an entry for the root "/", 539 // and its refCount should equal the number of locked nodes. 540 if len(m.byName) > 0 { 541 n := m.byName["/"] 542 if n == nil { 543 return fmt.Errorf(`non-empty m.byName does not contain the root "/"`) 544 } 545 if n.refCount != len(m.byToken) { 546 return fmt.Errorf("root node refCount=%d, differs from len(m.byToken)=%d", n.refCount, len(m.byToken)) 547 } 548 } 549 550 for name, n := range m.byName { 551 // The map keys should be consistent with the node's copy of the key. 552 if n.details.Root != name { 553 return fmt.Errorf("node name %q != byName map key %q", n.details.Root, name) 554 } 555 556 // A name must be clean, and start with a "/". 557 if len(name) == 0 || name[0] != '/' { 558 return fmt.Errorf(`node name %q does not start with "/"`, name) 559 } 560 if name != path.Clean(name) { 561 return fmt.Errorf(`node name %q is not clean`, name) 562 } 563 564 // A node's refCount should be positive. 565 if n.refCount <= 0 { 566 return fmt.Errorf("non-positive refCount for node at name %q", name) 567 } 568 569 // A node's refCount should be the number of self-or-descendents that 570 // are locked (i.e. have a non-empty token). 571 var list []string 572 for name0, n0 := range m.byName { 573 // All of lockTestNames' name fragments are one byte long: '_', 'i' or 'z', 574 // so strings.HasPrefix is equivalent to self-or-descendent name match. 575 // We don't have to worry about "/foo/bar" being a false positive match 576 // for "/foo/b". 577 if strings.HasPrefix(name0, name) && n0.token != "" { 578 list = append(list, name0) 579 } 580 } 581 if n.refCount != len(list) { 582 sort.Strings(list) 583 return fmt.Errorf("node at name %q has refCount %d but locked self-or-descendents are %q (len=%d)", 584 name, n.refCount, list, len(list)) 585 } 586 587 // A node n is in m.byToken if it has a non-empty token. 588 if n.token != "" { 589 if _, ok := m.byToken[n.token]; !ok { 590 return fmt.Errorf("node at name %q has token %q but not in m.byToken", name, n.token) 591 } 592 } 593 594 // A node n is in m.byExpiry if it has a non-negative byExpiryIndex. 595 if n.byExpiryIndex >= 0 { 596 if n.byExpiryIndex >= len(m.byExpiry) { 597 return fmt.Errorf("node at name %q has byExpiryIndex %d but m.byExpiry has length %d", name, n.byExpiryIndex, len(m.byExpiry)) 598 } 599 if n != m.byExpiry[n.byExpiryIndex] { 600 return fmt.Errorf("node at name %q has byExpiryIndex %d but that indexes a different node", name, n.byExpiryIndex) 601 } 602 } 603 } 604 605 for token, n := range m.byToken { 606 // The map keys should be consistent with the node's copy of the key. 607 if n.token != token { 608 return fmt.Errorf("node token %q != byToken map key %q", n.token, token) 609 } 610 611 // Every node in m.byToken is in m.byName. 612 if _, ok := m.byName[n.details.Root]; !ok { 613 return fmt.Errorf("node at name %q in m.byToken but not in m.byName", n.details.Root) 614 } 615 } 616 617 for i, n := range m.byExpiry { 618 // The slice indices should be consistent with the node's copy of the index. 619 if n.byExpiryIndex != i { 620 return fmt.Errorf("node byExpiryIndex %d != byExpiry slice index %d", n.byExpiryIndex, i) 621 } 622 623 // Every node in m.byExpiry is in m.byName. 624 if _, ok := m.byName[n.details.Root]; !ok { 625 return fmt.Errorf("node at name %q in m.byExpiry but not in m.byName", n.details.Root) 626 } 627 628 // No node in m.byExpiry should be held. 629 if n.held { 630 return fmt.Errorf("node at name %q in m.byExpiry is held", n.details.Root) 631 } 632 } 633 return nil 634 } 635 636 func TestParseTimeout(t *testing.T) { 637 testCases := []struct { 638 s string 639 want time.Duration 640 wantErr error 641 }{{ 642 "", 643 infiniteTimeout, 644 nil, 645 }, { 646 "Infinite", 647 infiniteTimeout, 648 nil, 649 }, { 650 "Infinitesimal", 651 0, 652 errInvalidTimeout, 653 }, { 654 "infinite", 655 0, 656 errInvalidTimeout, 657 }, { 658 "Second-0", 659 0 * time.Second, 660 nil, 661 }, { 662 "Second-123", 663 123 * time.Second, 664 nil, 665 }, { 666 " Second-456 ", 667 456 * time.Second, 668 nil, 669 }, { 670 "Second-4100000000", 671 4100000000 * time.Second, 672 nil, 673 }, { 674 "junk", 675 0, 676 errInvalidTimeout, 677 }, { 678 "Second-", 679 0, 680 errInvalidTimeout, 681 }, { 682 "Second--1", 683 0, 684 errInvalidTimeout, 685 }, { 686 "Second--123", 687 0, 688 errInvalidTimeout, 689 }, { 690 "Second-+123", 691 0, 692 errInvalidTimeout, 693 }, { 694 "Second-0x123", 695 0, 696 errInvalidTimeout, 697 }, { 698 "second-123", 699 0, 700 errInvalidTimeout, 701 }, { 702 "Second-4294967295", 703 4294967295 * time.Second, 704 nil, 705 }, { 706 // Section 10.7 says that "The timeout value for TimeType "Second" 707 // must not be greater than 2^32-1." 708 "Second-4294967296", 709 0, 710 errInvalidTimeout, 711 }, { 712 // This test case comes from section 9.10.9 of the spec. It says, 713 // 714 // "In this request, the client has specified that it desires an 715 // infinite-length lock, if available, otherwise a timeout of 4.1 716 // billion seconds, if available." 717 // 718 // The Go WebDAV package always supports infinite length locks, 719 // and ignores the fallback after the comma. 720 "Infinite, Second-4100000000", 721 infiniteTimeout, 722 nil, 723 }} 724 725 for _, tc := range testCases { 726 got, gotErr := parseTimeout(tc.s) 727 if got != tc.want || gotErr != tc.wantErr { 728 t.Errorf("parsing %q:\ngot %v, %v\nwant %v, %v", tc.s, got, gotErr, tc.want, tc.wantErr) 729 } 730 } 731 }