mirror of https://github.com/Xhofe/alist
				
				
				
			
		
			
				
	
	
		
			736 lines
		
	
	
		
			18 KiB
		
	
	
	
		
			Go
		
	
	
			
		
		
	
	
			736 lines
		
	
	
		
			18 KiB
		
	
	
	
		
			Go
		
	
	
// 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)
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 |