123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735 |
- // Copyright 2014 The Go Authors. All rights reserved.
- // Use of this source code is governed by a BSD-style
- // license that can be found in the LICENSE file.
- package webdav
- import (
- "fmt"
- "math/rand"
- "path"
- "reflect"
- "sort"
- "strconv"
- "strings"
- "testing"
- "time"
- )
- func TestWalkToRoot(t *testing.T) {
- testCases := []struct {
- name string
- want []string
- }{{
- "/a/b/c/d",
- []string{
- "/a/b/c/d",
- "/a/b/c",
- "/a/b",
- "/a",
- "/",
- },
- }, {
- "/a",
- []string{
- "/a",
- "/",
- },
- }, {
- "/",
- []string{
- "/",
- },
- }}
- for _, tc := range testCases {
- var got []string
- if !walkToRoot(tc.name, func(name0 string, first bool) bool {
- if first != (len(got) == 0) {
- t.Errorf("name=%q: first=%t but len(got)==%d", tc.name, first, len(got))
- return false
- }
- got = append(got, name0)
- return true
- }) {
- continue
- }
- if !reflect.DeepEqual(got, tc.want) {
- t.Errorf("name=%q:\ngot %q\nwant %q", tc.name, got, tc.want)
- }
- }
- }
- var lockTestDurations = []time.Duration{
- infiniteTimeout, // infiniteTimeout means to never expire.
- 0, // A zero duration means to expire immediately.
- 100 * time.Hour, // A very large duration will not expire in these tests.
- }
- // lockTestNames are the names of a set of mutually compatible locks. For each
- // name fragment:
- // - _ means no explicit lock.
- // - i means an infinite-depth lock,
- // - z means a zero-depth lock,
- var lockTestNames = []string{
- "/_/_/_/_/z",
- "/_/_/i",
- "/_/z",
- "/_/z/i",
- "/_/z/z",
- "/_/z/_/i",
- "/_/z/_/z",
- "/i",
- "/z",
- "/z/_/i",
- "/z/_/z",
- }
- func lockTestZeroDepth(name string) bool {
- switch name[len(name)-1] {
- case 'i':
- return false
- case 'z':
- return true
- }
- panic(fmt.Sprintf("lock name %q did not end with 'i' or 'z'", name))
- }
- func TestMemLSCanCreate(t *testing.T) {
- now := time.Unix(0, 0)
- m := NewMemLS().(*memLS)
- for _, name := range lockTestNames {
- _, err := m.Create(now, LockDetails{
- Root: name,
- Duration: infiniteTimeout,
- ZeroDepth: lockTestZeroDepth(name),
- })
- if err != nil {
- t.Fatalf("creating lock for %q: %v", name, err)
- }
- }
- wantCanCreate := func(name string, zeroDepth bool) bool {
- for _, n := range lockTestNames {
- switch {
- case n == name:
- // An existing lock has the same name as the proposed lock.
- return false
- case strings.HasPrefix(n, name):
- // An existing lock would be a child of the proposed lock,
- // which conflicts if the proposed lock has infinite depth.
- if !zeroDepth {
- return false
- }
- case strings.HasPrefix(name, n):
- // An existing lock would be an ancestor of the proposed lock,
- // which conflicts if the ancestor has infinite depth.
- if n[len(n)-1] == 'i' {
- return false
- }
- }
- }
- return true
- }
- var check func(int, string)
- check = func(recursion int, name string) {
- for _, zeroDepth := range []bool{false, true} {
- got := m.canCreate(name, zeroDepth)
- want := wantCanCreate(name, zeroDepth)
- if got != want {
- t.Errorf("canCreate name=%q zeroDepth=%t: got %t, want %t", name, zeroDepth, got, want)
- }
- }
- if recursion == 6 {
- return
- }
- if name != "/" {
- name += "/"
- }
- for _, c := range "_iz" {
- check(recursion+1, name+string(c))
- }
- }
- check(0, "/")
- }
- func TestMemLSLookup(t *testing.T) {
- now := time.Unix(0, 0)
- m := NewMemLS().(*memLS)
- badToken := m.nextToken()
- t.Logf("badToken=%q", badToken)
- for _, name := range lockTestNames {
- token, err := m.Create(now, LockDetails{
- Root: name,
- Duration: infiniteTimeout,
- ZeroDepth: lockTestZeroDepth(name),
- })
- if err != nil {
- t.Fatalf("creating lock for %q: %v", name, err)
- }
- t.Logf("%-15q -> node=%p token=%q", name, m.byName[name], token)
- }
- baseNames := append([]string{"/a", "/b/c"}, lockTestNames...)
- for _, baseName := range baseNames {
- for _, suffix := range []string{"", "/0", "/1/2/3"} {
- name := baseName + suffix
- goodToken := ""
- base := m.byName[baseName]
- if base != nil && (suffix == "" || !lockTestZeroDepth(baseName)) {
- goodToken = base.token
- }
- for _, token := range []string{badToken, goodToken} {
- if token == "" {
- continue
- }
- got := m.lookup(name, Condition{Token: token})
- want := base
- if token == badToken {
- want = nil
- }
- if got != want {
- t.Errorf("name=%-20qtoken=%q (bad=%t): got %p, want %p",
- name, token, token == badToken, got, want)
- }
- }
- }
- }
- }
- func TestMemLSConfirm(t *testing.T) {
- now := time.Unix(0, 0)
- m := NewMemLS().(*memLS)
- alice, err := m.Create(now, LockDetails{
- Root: "/alice",
- Duration: infiniteTimeout,
- ZeroDepth: false,
- })
- if err != nil {
- t.Fatalf("Create: %v", err)
- }
- tweedle, err := m.Create(now, LockDetails{
- Root: "/tweedle",
- Duration: infiniteTimeout,
- ZeroDepth: false,
- })
- if err != nil {
- t.Fatalf("Create: %v", err)
- }
- if err := m.consistent(); err != nil {
- t.Fatalf("Create: inconsistent state: %v", err)
- }
- // Test a mismatch between name and condition.
- _, err = m.Confirm(now, "/tweedle/dee", "", Condition{Token: alice})
- if err != ErrConfirmationFailed {
- t.Fatalf("Confirm (mismatch): got %v, want ErrConfirmationFailed", err)
- }
- if err := m.consistent(); err != nil {
- t.Fatalf("Confirm (mismatch): inconsistent state: %v", err)
- }
- // Test two names (that fall under the same lock) in the one Confirm call.
- release, err := m.Confirm(now, "/tweedle/dee", "/tweedle/dum", Condition{Token: tweedle})
- if err != nil {
- t.Fatalf("Confirm (twins): %v", err)
- }
- if err := m.consistent(); err != nil {
- t.Fatalf("Confirm (twins): inconsistent state: %v", err)
- }
- release()
- if err := m.consistent(); err != nil {
- t.Fatalf("release (twins): inconsistent state: %v", err)
- }
- // Test the same two names in overlapping Confirm / release calls.
- releaseDee, err := m.Confirm(now, "/tweedle/dee", "", Condition{Token: tweedle})
- if err != nil {
- t.Fatalf("Confirm (sequence #0): %v", err)
- }
- if err := m.consistent(); err != nil {
- t.Fatalf("Confirm (sequence #0): inconsistent state: %v", err)
- }
- _, err = m.Confirm(now, "/tweedle/dum", "", Condition{Token: tweedle})
- if err != ErrConfirmationFailed {
- t.Fatalf("Confirm (sequence #1): got %v, want ErrConfirmationFailed", err)
- }
- if err := m.consistent(); err != nil {
- t.Fatalf("Confirm (sequence #1): inconsistent state: %v", err)
- }
- releaseDee()
- if err := m.consistent(); err != nil {
- t.Fatalf("release (sequence #2): inconsistent state: %v", err)
- }
- releaseDum, err := m.Confirm(now, "/tweedle/dum", "", Condition{Token: tweedle})
- if err != nil {
- t.Fatalf("Confirm (sequence #3): %v", err)
- }
- if err := m.consistent(); err != nil {
- t.Fatalf("Confirm (sequence #3): inconsistent state: %v", err)
- }
- // Test that you can't unlock a held lock.
- err = m.Unlock(now, tweedle)
- if err != ErrLocked {
- t.Fatalf("Unlock (sequence #4): got %v, want ErrLocked", err)
- }
- releaseDum()
- if err := m.consistent(); err != nil {
- t.Fatalf("release (sequence #5): inconsistent state: %v", err)
- }
- err = m.Unlock(now, tweedle)
- if err != nil {
- t.Fatalf("Unlock (sequence #6): %v", err)
- }
- if err := m.consistent(); err != nil {
- t.Fatalf("Unlock (sequence #6): inconsistent state: %v", err)
- }
- }
- func TestMemLSNonCanonicalRoot(t *testing.T) {
- now := time.Unix(0, 0)
- m := NewMemLS().(*memLS)
- token, err := m.Create(now, LockDetails{
- Root: "/foo/./bar//",
- Duration: 1 * time.Second,
- })
- if err != nil {
- t.Fatalf("Create: %v", err)
- }
- if err := m.consistent(); err != nil {
- t.Fatalf("Create: inconsistent state: %v", err)
- }
- if err := m.Unlock(now, token); err != nil {
- t.Fatalf("Unlock: %v", err)
- }
- if err := m.consistent(); err != nil {
- t.Fatalf("Unlock: inconsistent state: %v", err)
- }
- }
- func TestMemLSExpiry(t *testing.T) {
- m := NewMemLS().(*memLS)
- testCases := []string{
- "setNow 0",
- "create /a.5",
- "want /a.5",
- "create /c.6",
- "want /a.5 /c.6",
- "create /a/b.7",
- "want /a.5 /a/b.7 /c.6",
- "setNow 4",
- "want /a.5 /a/b.7 /c.6",
- "setNow 5",
- "want /a/b.7 /c.6",
- "setNow 6",
- "want /a/b.7",
- "setNow 7",
- "want ",
- "setNow 8",
- "want ",
- "create /a.12",
- "create /b.13",
- "create /c.15",
- "create /a/d.16",
- "want /a.12 /a/d.16 /b.13 /c.15",
- "refresh /a.14",
- "want /a.14 /a/d.16 /b.13 /c.15",
- "setNow 12",
- "want /a.14 /a/d.16 /b.13 /c.15",
- "setNow 13",
- "want /a.14 /a/d.16 /c.15",
- "setNow 14",
- "want /a/d.16 /c.15",
- "refresh /a/d.20",
- "refresh /c.20",
- "want /a/d.20 /c.20",
- "setNow 20",
- "want ",
- }
- tokens := map[string]string{}
- zTime := time.Unix(0, 0)
- now := zTime
- for i, tc := range testCases {
- j := strings.IndexByte(tc, ' ')
- if j < 0 {
- t.Fatalf("test case #%d %q: invalid command", i, tc)
- }
- op, arg := tc[:j], tc[j+1:]
- switch op {
- default:
- t.Fatalf("test case #%d %q: invalid operation %q", i, tc, op)
- case "create", "refresh":
- parts := strings.Split(arg, ".")
- if len(parts) != 2 {
- t.Fatalf("test case #%d %q: invalid create", i, tc)
- }
- root := parts[0]
- d, err := strconv.Atoi(parts[1])
- if err != nil {
- t.Fatalf("test case #%d %q: invalid duration", i, tc)
- }
- dur := time.Unix(0, 0).Add(time.Duration(d) * time.Second).Sub(now)
- switch op {
- case "create":
- token, err := m.Create(now, LockDetails{
- Root: root,
- Duration: dur,
- ZeroDepth: true,
- })
- if err != nil {
- t.Fatalf("test case #%d %q: Create: %v", i, tc, err)
- }
- tokens[root] = token
- case "refresh":
- token := tokens[root]
- if token == "" {
- t.Fatalf("test case #%d %q: no token for %q", i, tc, root)
- }
- got, err := m.Refresh(now, token, dur)
- if err != nil {
- t.Fatalf("test case #%d %q: Refresh: %v", i, tc, err)
- }
- want := LockDetails{
- Root: root,
- Duration: dur,
- ZeroDepth: true,
- }
- if got != want {
- t.Fatalf("test case #%d %q:\ngot %v\nwant %v", i, tc, got, want)
- }
- }
- case "setNow":
- d, err := strconv.Atoi(arg)
- if err != nil {
- t.Fatalf("test case #%d %q: invalid duration", i, tc)
- }
- now = time.Unix(0, 0).Add(time.Duration(d) * time.Second)
- case "want":
- m.mu.Lock()
- m.collectExpiredNodes(now)
- got := make([]string, 0, len(m.byToken))
- for _, n := range m.byToken {
- got = append(got, fmt.Sprintf("%s.%d",
- n.details.Root, n.expiry.Sub(zTime)/time.Second))
- }
- m.mu.Unlock()
- sort.Strings(got)
- want := []string{}
- if arg != "" {
- want = strings.Split(arg, " ")
- }
- if !reflect.DeepEqual(got, want) {
- t.Fatalf("test case #%d %q:\ngot %q\nwant %q", i, tc, got, want)
- }
- }
- if err := m.consistent(); err != nil {
- t.Fatalf("test case #%d %q: inconsistent state: %v", i, tc, err)
- }
- }
- }
- func TestMemLS(t *testing.T) {
- now := time.Unix(0, 0)
- m := NewMemLS().(*memLS)
- rng := rand.New(rand.NewSource(0))
- tokens := map[string]string{}
- nConfirm, nCreate, nRefresh, nUnlock := 0, 0, 0, 0
- const N = 2000
- for i := 0; i < N; i++ {
- name := lockTestNames[rng.Intn(len(lockTestNames))]
- duration := lockTestDurations[rng.Intn(len(lockTestDurations))]
- confirmed, unlocked := false, false
- // If the name was already locked, we randomly confirm/release, refresh
- // or unlock it. Otherwise, we create a lock.
- token := tokens[name]
- if token != "" {
- switch rng.Intn(3) {
- case 0:
- confirmed = true
- nConfirm++
- release, err := m.Confirm(now, name, "", Condition{Token: token})
- if err != nil {
- t.Fatalf("iteration #%d: Confirm %q: %v", i, name, err)
- }
- if err := m.consistent(); err != nil {
- t.Fatalf("iteration #%d: inconsistent state: %v", i, err)
- }
- release()
- case 1:
- nRefresh++
- if _, err := m.Refresh(now, token, duration); err != nil {
- t.Fatalf("iteration #%d: Refresh %q: %v", i, name, err)
- }
- case 2:
- unlocked = true
- nUnlock++
- if err := m.Unlock(now, token); err != nil {
- t.Fatalf("iteration #%d: Unlock %q: %v", i, name, err)
- }
- }
- } else {
- nCreate++
- var err error
- token, err = m.Create(now, LockDetails{
- Root: name,
- Duration: duration,
- ZeroDepth: lockTestZeroDepth(name),
- })
- if err != nil {
- t.Fatalf("iteration #%d: Create %q: %v", i, name, err)
- }
- }
- if !confirmed {
- if duration == 0 || unlocked {
- // A zero-duration lock should expire immediately and is
- // effectively equivalent to being unlocked.
- tokens[name] = ""
- } else {
- tokens[name] = token
- }
- }
- if err := m.consistent(); err != nil {
- t.Fatalf("iteration #%d: inconsistent state: %v", i, err)
- }
- }
- if nConfirm < N/10 {
- t.Fatalf("too few Confirm calls: got %d, want >= %d", nConfirm, N/10)
- }
- if nCreate < N/10 {
- t.Fatalf("too few Create calls: got %d, want >= %d", nCreate, N/10)
- }
- if nRefresh < N/10 {
- t.Fatalf("too few Refresh calls: got %d, want >= %d", nRefresh, N/10)
- }
- if nUnlock < N/10 {
- t.Fatalf("too few Unlock calls: got %d, want >= %d", nUnlock, N/10)
- }
- }
- func (m *memLS) consistent() error {
- m.mu.Lock()
- defer m.mu.Unlock()
- // If m.byName is non-empty, then it must contain an entry for the root "/",
- // and its refCount should equal the number of locked nodes.
- if len(m.byName) > 0 {
- n := m.byName["/"]
- if n == nil {
- return fmt.Errorf(`non-empty m.byName does not contain the root "/"`)
- }
- if n.refCount != len(m.byToken) {
- return fmt.Errorf("root node refCount=%d, differs from len(m.byToken)=%d", n.refCount, len(m.byToken))
- }
- }
- for name, n := range m.byName {
- // The map keys should be consistent with the node's copy of the key.
- if n.details.Root != name {
- return fmt.Errorf("node name %q != byName map key %q", n.details.Root, name)
- }
- // A name must be clean, and start with a "/".
- if len(name) == 0 || name[0] != '/' {
- return fmt.Errorf(`node name %q does not start with "/"`, name)
- }
- if name != path.Clean(name) {
- return fmt.Errorf(`node name %q is not clean`, name)
- }
- // A node's refCount should be positive.
- if n.refCount <= 0 {
- return fmt.Errorf("non-positive refCount for node at name %q", name)
- }
- // A node's refCount should be the number of self-or-descendents that
- // are locked (i.e. have a non-empty token).
- var list []string
- for name0, n0 := range m.byName {
- // All of lockTestNames' name fragments are one byte long: '_', 'i' or 'z',
- // so strings.HasPrefix is equivalent to self-or-descendent name match.
- // We don't have to worry about "/foo/bar" being a false positive match
- // for "/foo/b".
- if strings.HasPrefix(name0, name) && n0.token != "" {
- list = append(list, name0)
- }
- }
- if n.refCount != len(list) {
- sort.Strings(list)
- return fmt.Errorf("node at name %q has refCount %d but locked self-or-descendents are %q (len=%d)",
- name, n.refCount, list, len(list))
- }
- // A node n is in m.byToken if it has a non-empty token.
- if n.token != "" {
- if _, ok := m.byToken[n.token]; !ok {
- return fmt.Errorf("node at name %q has token %q but not in m.byToken", name, n.token)
- }
- }
- // A node n is in m.byExpiry if it has a non-negative byExpiryIndex.
- if n.byExpiryIndex >= 0 {
- if n.byExpiryIndex >= len(m.byExpiry) {
- return fmt.Errorf("node at name %q has byExpiryIndex %d but m.byExpiry has length %d", name, n.byExpiryIndex, len(m.byExpiry))
- }
- if n != m.byExpiry[n.byExpiryIndex] {
- return fmt.Errorf("node at name %q has byExpiryIndex %d but that indexes a different node", name, n.byExpiryIndex)
- }
- }
- }
- for token, n := range m.byToken {
- // The map keys should be consistent with the node's copy of the key.
- if n.token != token {
- return fmt.Errorf("node token %q != byToken map key %q", n.token, token)
- }
- // Every node in m.byToken is in m.byName.
- if _, ok := m.byName[n.details.Root]; !ok {
- return fmt.Errorf("node at name %q in m.byToken but not in m.byName", n.details.Root)
- }
- }
- for i, n := range m.byExpiry {
- // The slice indices should be consistent with the node's copy of the index.
- if n.byExpiryIndex != i {
- return fmt.Errorf("node byExpiryIndex %d != byExpiry slice index %d", n.byExpiryIndex, i)
- }
- // Every node in m.byExpiry is in m.byName.
- if _, ok := m.byName[n.details.Root]; !ok {
- return fmt.Errorf("node at name %q in m.byExpiry but not in m.byName", n.details.Root)
- }
- // No node in m.byExpiry should be held.
- if n.held {
- return fmt.Errorf("node at name %q in m.byExpiry is held", n.details.Root)
- }
- }
- return nil
- }
- func TestParseTimeout(t *testing.T) {
- testCases := []struct {
- s string
- want time.Duration
- wantErr error
- }{{
- "",
- infiniteTimeout,
- nil,
- }, {
- "Infinite",
- infiniteTimeout,
- nil,
- }, {
- "Infinitesimal",
- 0,
- errInvalidTimeout,
- }, {
- "infinite",
- 0,
- errInvalidTimeout,
- }, {
- "Second-0",
- 0 * time.Second,
- nil,
- }, {
- "Second-123",
- 123 * time.Second,
- nil,
- }, {
- " Second-456 ",
- 456 * time.Second,
- nil,
- }, {
- "Second-4100000000",
- 4100000000 * time.Second,
- nil,
- }, {
- "junk",
- 0,
- errInvalidTimeout,
- }, {
- "Second-",
- 0,
- errInvalidTimeout,
- }, {
- "Second--1",
- 0,
- errInvalidTimeout,
- }, {
- "Second--123",
- 0,
- errInvalidTimeout,
- }, {
- "Second-+123",
- 0,
- errInvalidTimeout,
- }, {
- "Second-0x123",
- 0,
- errInvalidTimeout,
- }, {
- "second-123",
- 0,
- errInvalidTimeout,
- }, {
- "Second-4294967295",
- 4294967295 * time.Second,
- nil,
- }, {
- // Section 10.7 says that "The timeout value for TimeType "Second"
- // must not be greater than 2^32-1."
- "Second-4294967296",
- 0,
- errInvalidTimeout,
- }, {
- // This test case comes from section 9.10.9 of the spec. It says,
- //
- // "In this request, the client has specified that it desires an
- // infinite-length lock, if available, otherwise a timeout of 4.1
- // billion seconds, if available."
- //
- // The Go WebDAV package always supports infinite length locks,
- // and ignores the fallback after the comma.
- "Infinite, Second-4100000000",
- infiniteTimeout,
- nil,
- }}
- for _, tc := range testCases {
- got, gotErr := parseTimeout(tc.s)
- if got != tc.want || gotErr != tc.wantErr {
- t.Errorf("parsing %q:\ngot %v, %v\nwant %v, %v", tc.s, got, gotErr, tc.want, tc.wantErr)
- }
- }
- }
|