github.com/hxx258456/ccgo@v0.0.5-0.20230213014102-48b35f46f66f/net/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 if err != nil { 216 t.Fatalf("Create: %v", err) 217 } 218 219 tweedle, err := m.Create(now, LockDetails{ 220 Root: "/tweedle", 221 Duration: infiniteTimeout, 222 ZeroDepth: false, 223 }) 224 if err != nil { 225 t.Fatalf("Create: %v", err) 226 } 227 if err := m.consistent(); err != nil { 228 t.Fatalf("Create: inconsistent state: %v", err) 229 } 230 231 // Test a mismatch between name and condition. 232 _, err = m.Confirm(now, "/tweedle/dee", "", Condition{Token: alice}) 233 if err != ErrConfirmationFailed { 234 t.Fatalf("Confirm (mismatch): got %v, want ErrConfirmationFailed", err) 235 } 236 if err := m.consistent(); err != nil { 237 t.Fatalf("Confirm (mismatch): inconsistent state: %v", err) 238 } 239 240 // Test two names (that fall under the same lock) in the one Confirm call. 241 release, err := m.Confirm(now, "/tweedle/dee", "/tweedle/dum", Condition{Token: tweedle}) 242 if err != nil { 243 t.Fatalf("Confirm (twins): %v", err) 244 } 245 if err := m.consistent(); err != nil { 246 t.Fatalf("Confirm (twins): inconsistent state: %v", err) 247 } 248 release() 249 if err := m.consistent(); err != nil { 250 t.Fatalf("release (twins): inconsistent state: %v", err) 251 } 252 253 // Test the same two names in overlapping Confirm / release calls. 254 releaseDee, err := m.Confirm(now, "/tweedle/dee", "", Condition{Token: tweedle}) 255 if err != nil { 256 t.Fatalf("Confirm (sequence #0): %v", err) 257 } 258 if err := m.consistent(); err != nil { 259 t.Fatalf("Confirm (sequence #0): inconsistent state: %v", err) 260 } 261 262 _, err = m.Confirm(now, "/tweedle/dum", "", Condition{Token: tweedle}) 263 if err != ErrConfirmationFailed { 264 t.Fatalf("Confirm (sequence #1): got %v, want ErrConfirmationFailed", err) 265 } 266 if err := m.consistent(); err != nil { 267 t.Fatalf("Confirm (sequence #1): inconsistent state: %v", err) 268 } 269 270 releaseDee() 271 if err := m.consistent(); err != nil { 272 t.Fatalf("release (sequence #2): inconsistent state: %v", err) 273 } 274 275 releaseDum, err := m.Confirm(now, "/tweedle/dum", "", Condition{Token: tweedle}) 276 if err != nil { 277 t.Fatalf("Confirm (sequence #3): %v", err) 278 } 279 if err := m.consistent(); err != nil { 280 t.Fatalf("Confirm (sequence #3): inconsistent state: %v", err) 281 } 282 283 // Test that you can't unlock a held lock. 284 err = m.Unlock(now, tweedle) 285 if err != ErrLocked { 286 t.Fatalf("Unlock (sequence #4): got %v, want ErrLocked", err) 287 } 288 289 releaseDum() 290 if err := m.consistent(); err != nil { 291 t.Fatalf("release (sequence #5): inconsistent state: %v", err) 292 } 293 294 err = m.Unlock(now, tweedle) 295 if err != nil { 296 t.Fatalf("Unlock (sequence #6): %v", err) 297 } 298 if err := m.consistent(); err != nil { 299 t.Fatalf("Unlock (sequence #6): inconsistent state: %v", err) 300 } 301 } 302 303 func TestMemLSNonCanonicalRoot(t *testing.T) { 304 now := time.Unix(0, 0) 305 m := NewMemLS().(*memLS) 306 token, err := m.Create(now, LockDetails{ 307 Root: "/foo/./bar//", 308 Duration: 1 * time.Second, 309 }) 310 if err != nil { 311 t.Fatalf("Create: %v", err) 312 } 313 if err := m.consistent(); err != nil { 314 t.Fatalf("Create: inconsistent state: %v", err) 315 } 316 if err := m.Unlock(now, token); err != nil { 317 t.Fatalf("Unlock: %v", err) 318 } 319 if err := m.consistent(); err != nil { 320 t.Fatalf("Unlock: inconsistent state: %v", err) 321 } 322 } 323 324 func TestMemLSExpiry(t *testing.T) { 325 m := NewMemLS().(*memLS) 326 testCases := []string{ 327 "setNow 0", 328 "create /a.5", 329 "want /a.5", 330 "create /c.6", 331 "want /a.5 /c.6", 332 "create /a/b.7", 333 "want /a.5 /a/b.7 /c.6", 334 "setNow 4", 335 "want /a.5 /a/b.7 /c.6", 336 "setNow 5", 337 "want /a/b.7 /c.6", 338 "setNow 6", 339 "want /a/b.7", 340 "setNow 7", 341 "want ", 342 "setNow 8", 343 "want ", 344 "create /a.12", 345 "create /b.13", 346 "create /c.15", 347 "create /a/d.16", 348 "want /a.12 /a/d.16 /b.13 /c.15", 349 "refresh /a.14", 350 "want /a.14 /a/d.16 /b.13 /c.15", 351 "setNow 12", 352 "want /a.14 /a/d.16 /b.13 /c.15", 353 "setNow 13", 354 "want /a.14 /a/d.16 /c.15", 355 "setNow 14", 356 "want /a/d.16 /c.15", 357 "refresh /a/d.20", 358 "refresh /c.20", 359 "want /a/d.20 /c.20", 360 "setNow 20", 361 "want ", 362 } 363 364 tokens := map[string]string{} 365 zTime := time.Unix(0, 0) 366 now := zTime 367 for i, tc := range testCases { 368 j := strings.IndexByte(tc, ' ') 369 if j < 0 { 370 t.Fatalf("test case #%d %q: invalid command", i, tc) 371 } 372 op, arg := tc[:j], tc[j+1:] 373 switch op { 374 default: 375 t.Fatalf("test case #%d %q: invalid operation %q", i, tc, op) 376 377 case "create", "refresh": 378 parts := strings.Split(arg, ".") 379 if len(parts) != 2 { 380 t.Fatalf("test case #%d %q: invalid create", i, tc) 381 } 382 root := parts[0] 383 d, err := strconv.Atoi(parts[1]) 384 if err != nil { 385 t.Fatalf("test case #%d %q: invalid duration", i, tc) 386 } 387 dur := time.Unix(0, 0).Add(time.Duration(d) * time.Second).Sub(now) 388 389 switch op { 390 case "create": 391 token, err := m.Create(now, LockDetails{ 392 Root: root, 393 Duration: dur, 394 ZeroDepth: true, 395 }) 396 if err != nil { 397 t.Fatalf("test case #%d %q: Create: %v", i, tc, err) 398 } 399 tokens[root] = token 400 401 case "refresh": 402 token := tokens[root] 403 if token == "" { 404 t.Fatalf("test case #%d %q: no token for %q", i, tc, root) 405 } 406 got, err := m.Refresh(now, token, dur) 407 if err != nil { 408 t.Fatalf("test case #%d %q: Refresh: %v", i, tc, err) 409 } 410 want := LockDetails{ 411 Root: root, 412 Duration: dur, 413 ZeroDepth: true, 414 } 415 if got != want { 416 t.Fatalf("test case #%d %q:\ngot %v\nwant %v", i, tc, got, want) 417 } 418 } 419 420 case "setNow": 421 d, err := strconv.Atoi(arg) 422 if err != nil { 423 t.Fatalf("test case #%d %q: invalid duration", i, tc) 424 } 425 now = time.Unix(0, 0).Add(time.Duration(d) * time.Second) 426 427 case "want": 428 m.mu.Lock() 429 m.collectExpiredNodes(now) 430 got := make([]string, 0, len(m.byToken)) 431 for _, n := range m.byToken { 432 got = append(got, fmt.Sprintf("%s.%d", 433 n.details.Root, n.expiry.Sub(zTime)/time.Second)) 434 } 435 m.mu.Unlock() 436 sort.Strings(got) 437 want := []string{} 438 if arg != "" { 439 want = strings.Split(arg, " ") 440 } 441 if !reflect.DeepEqual(got, want) { 442 t.Fatalf("test case #%d %q:\ngot %q\nwant %q", i, tc, got, want) 443 } 444 } 445 446 if err := m.consistent(); err != nil { 447 t.Fatalf("test case #%d %q: inconsistent state: %v", i, tc, err) 448 } 449 } 450 } 451 452 func TestMemLS(t *testing.T) { 453 now := time.Unix(0, 0) 454 m := NewMemLS().(*memLS) 455 rng := rand.New(rand.NewSource(0)) 456 tokens := map[string]string{} 457 nConfirm, nCreate, nRefresh, nUnlock := 0, 0, 0, 0 458 const N = 2000 459 460 for i := 0; i < N; i++ { 461 name := lockTestNames[rng.Intn(len(lockTestNames))] 462 duration := lockTestDurations[rng.Intn(len(lockTestDurations))] 463 confirmed, unlocked := false, false 464 465 // If the name was already locked, we randomly confirm/release, refresh 466 // or unlock it. Otherwise, we create a lock. 467 token := tokens[name] 468 if token != "" { 469 switch rng.Intn(3) { 470 case 0: 471 confirmed = true 472 nConfirm++ 473 release, err := m.Confirm(now, name, "", Condition{Token: token}) 474 if err != nil { 475 t.Fatalf("iteration #%d: Confirm %q: %v", i, name, err) 476 } 477 if err := m.consistent(); err != nil { 478 t.Fatalf("iteration #%d: inconsistent state: %v", i, err) 479 } 480 release() 481 482 case 1: 483 nRefresh++ 484 if _, err := m.Refresh(now, token, duration); err != nil { 485 t.Fatalf("iteration #%d: Refresh %q: %v", i, name, err) 486 } 487 488 case 2: 489 unlocked = true 490 nUnlock++ 491 if err := m.Unlock(now, token); err != nil { 492 t.Fatalf("iteration #%d: Unlock %q: %v", i, name, err) 493 } 494 } 495 496 } else { 497 nCreate++ 498 var err error 499 token, err = m.Create(now, LockDetails{ 500 Root: name, 501 Duration: duration, 502 ZeroDepth: lockTestZeroDepth(name), 503 }) 504 if err != nil { 505 t.Fatalf("iteration #%d: Create %q: %v", i, name, err) 506 } 507 } 508 509 if !confirmed { 510 if duration == 0 || unlocked { 511 // A zero-duration lock should expire immediately and is 512 // effectively equivalent to being unlocked. 513 tokens[name] = "" 514 } else { 515 tokens[name] = token 516 } 517 } 518 519 if err := m.consistent(); err != nil { 520 t.Fatalf("iteration #%d: inconsistent state: %v", i, err) 521 } 522 } 523 524 if nConfirm < N/10 { 525 t.Fatalf("too few Confirm calls: got %d, want >= %d", nConfirm, N/10) 526 } 527 if nCreate < N/10 { 528 t.Fatalf("too few Create calls: got %d, want >= %d", nCreate, N/10) 529 } 530 if nRefresh < N/10 { 531 t.Fatalf("too few Refresh calls: got %d, want >= %d", nRefresh, N/10) 532 } 533 if nUnlock < N/10 { 534 t.Fatalf("too few Unlock calls: got %d, want >= %d", nUnlock, N/10) 535 } 536 } 537 538 func (m *memLS) consistent() error { 539 m.mu.Lock() 540 defer m.mu.Unlock() 541 542 // If m.byName is non-empty, then it must contain an entry for the root "/", 543 // and its refCount should equal the number of locked nodes. 544 if len(m.byName) > 0 { 545 n := m.byName["/"] 546 if n == nil { 547 return fmt.Errorf(`non-empty m.byName does not contain the root "/"`) 548 } 549 if n.refCount != len(m.byToken) { 550 return fmt.Errorf("root node refCount=%d, differs from len(m.byToken)=%d", n.refCount, len(m.byToken)) 551 } 552 } 553 554 for name, n := range m.byName { 555 // The map keys should be consistent with the node's copy of the key. 556 if n.details.Root != name { 557 return fmt.Errorf("node name %q != byName map key %q", n.details.Root, name) 558 } 559 560 // A name must be clean, and start with a "/". 561 if len(name) == 0 || name[0] != '/' { 562 return fmt.Errorf(`node name %q does not start with "/"`, name) 563 } 564 if name != path.Clean(name) { 565 return fmt.Errorf(`node name %q is not clean`, name) 566 } 567 568 // A node's refCount should be positive. 569 if n.refCount <= 0 { 570 return fmt.Errorf("non-positive refCount for node at name %q", name) 571 } 572 573 // A node's refCount should be the number of self-or-descendents that 574 // are locked (i.e. have a non-empty token). 575 var list []string 576 for name0, n0 := range m.byName { 577 // All of lockTestNames' name fragments are one byte long: '_', 'i' or 'z', 578 // so strings.HasPrefix is equivalent to self-or-descendent name match. 579 // We don't have to worry about "/foo/bar" being a false positive match 580 // for "/foo/b". 581 if strings.HasPrefix(name0, name) && n0.token != "" { 582 list = append(list, name0) 583 } 584 } 585 if n.refCount != len(list) { 586 sort.Strings(list) 587 return fmt.Errorf("node at name %q has refCount %d but locked self-or-descendents are %q (len=%d)", 588 name, n.refCount, list, len(list)) 589 } 590 591 // A node n is in m.byToken if it has a non-empty token. 592 if n.token != "" { 593 if _, ok := m.byToken[n.token]; !ok { 594 return fmt.Errorf("node at name %q has token %q but not in m.byToken", name, n.token) 595 } 596 } 597 598 // A node n is in m.byExpiry if it has a non-negative byExpiryIndex. 599 if n.byExpiryIndex >= 0 { 600 if n.byExpiryIndex >= len(m.byExpiry) { 601 return fmt.Errorf("node at name %q has byExpiryIndex %d but m.byExpiry has length %d", name, n.byExpiryIndex, len(m.byExpiry)) 602 } 603 if n != m.byExpiry[n.byExpiryIndex] { 604 return fmt.Errorf("node at name %q has byExpiryIndex %d but that indexes a different node", name, n.byExpiryIndex) 605 } 606 } 607 } 608 609 for token, n := range m.byToken { 610 // The map keys should be consistent with the node's copy of the key. 611 if n.token != token { 612 return fmt.Errorf("node token %q != byToken map key %q", n.token, token) 613 } 614 615 // Every node in m.byToken is in m.byName. 616 if _, ok := m.byName[n.details.Root]; !ok { 617 return fmt.Errorf("node at name %q in m.byToken but not in m.byName", n.details.Root) 618 } 619 } 620 621 for i, n := range m.byExpiry { 622 // The slice indices should be consistent with the node's copy of the index. 623 if n.byExpiryIndex != i { 624 return fmt.Errorf("node byExpiryIndex %d != byExpiry slice index %d", n.byExpiryIndex, i) 625 } 626 627 // Every node in m.byExpiry is in m.byName. 628 if _, ok := m.byName[n.details.Root]; !ok { 629 return fmt.Errorf("node at name %q in m.byExpiry but not in m.byName", n.details.Root) 630 } 631 632 // No node in m.byExpiry should be held. 633 if n.held { 634 return fmt.Errorf("node at name %q in m.byExpiry is held", n.details.Root) 635 } 636 } 637 return nil 638 } 639 640 func TestParseTimeout(t *testing.T) { 641 testCases := []struct { 642 s string 643 want time.Duration 644 wantErr error 645 }{{ 646 "", 647 infiniteTimeout, 648 nil, 649 }, { 650 "Infinite", 651 infiniteTimeout, 652 nil, 653 }, { 654 "Infinitesimal", 655 0, 656 errInvalidTimeout, 657 }, { 658 "infinite", 659 0, 660 errInvalidTimeout, 661 }, { 662 "Second-0", 663 0 * time.Second, 664 nil, 665 }, { 666 "Second-123", 667 123 * time.Second, 668 nil, 669 }, { 670 " Second-456 ", 671 456 * time.Second, 672 nil, 673 }, { 674 "Second-4100000000", 675 4100000000 * time.Second, 676 nil, 677 }, { 678 "junk", 679 0, 680 errInvalidTimeout, 681 }, { 682 "Second-", 683 0, 684 errInvalidTimeout, 685 }, { 686 "Second--1", 687 0, 688 errInvalidTimeout, 689 }, { 690 "Second--123", 691 0, 692 errInvalidTimeout, 693 }, { 694 "Second-+123", 695 0, 696 errInvalidTimeout, 697 }, { 698 "Second-0x123", 699 0, 700 errInvalidTimeout, 701 }, { 702 "second-123", 703 0, 704 errInvalidTimeout, 705 }, { 706 "Second-4294967295", 707 4294967295 * time.Second, 708 nil, 709 }, { 710 // Section 10.7 says that "The timeout value for TimeType "Second" 711 // must not be greater than 2^32-1." 712 "Second-4294967296", 713 0, 714 errInvalidTimeout, 715 }, { 716 // This test case comes from section 9.10.9 of the spec. It says, 717 // 718 // "In this request, the client has specified that it desires an 719 // infinite-length lock, if available, otherwise a timeout of 4.1 720 // billion seconds, if available." 721 // 722 // The Go WebDAV package always supports infinite length locks, 723 // and ignores the fallback after the comma. 724 "Infinite, Second-4100000000", 725 infiniteTimeout, 726 nil, 727 }} 728 729 for _, tc := range testCases { 730 got, gotErr := parseTimeout(tc.s) 731 if got != tc.want || gotErr != tc.wantErr { 732 t.Errorf("parsing %q:\ngot %v, %v\nwant %v, %v", tc.s, got, gotErr, tc.want, tc.wantErr) 733 } 734 } 735 }