mirror of https://github.com/veypi/OneAuth.git
				
				
				
			
			You cannot select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
	
	
		
			736 lines
		
	
	
		
			18 KiB
		
	
	
	
		
			Go
		
	
		
		
			
		
	
	
			736 lines
		
	
	
		
			18 KiB
		
	
	
	
		
			Go
		
	
| 
											1 year ago
										 | // 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) | ||
|  | 		} | ||
|  | 	} | ||
|  | } |