webdav: make the memFS (not the memFSNode) the canonical source of a
file name.

This fixes inconsistent stat names after a file is renamed.

Change-Id: Ie90f8abaa31d46a87834266053b61d7770f854e2
Reviewed-on: https://go-review.googlesource.com/3051
Reviewed-by: Nick Cooper <nmvc@google.com>
Reviewed-by: Dave Cheney <dave@cheney.net>
diff --git a/webdav/file_test.go b/webdav/file_test.go
index a9bb3b7..6601fce 100644
--- a/webdav/file_test.go
+++ b/webdav/file_test.go
@@ -11,6 +11,8 @@
 	"os"
 	"path"
 	"path/filepath"
+	"reflect"
+	"sort"
 	"strconv"
 	"strings"
 	"testing"
@@ -202,10 +204,10 @@
 			}
 		}
 
-		i := 0
+		i, prevFrag := 0, ""
 		err := fs.walk("test", tc.dir, func(dir *memFSNode, frag string, final bool) error {
 			got := walkStep{
-				name:  dir.name,
+				name:  prevFrag,
 				frag:  frag,
 				final: final,
 			}
@@ -214,7 +216,7 @@
 			if got != want {
 				return fmt.Errorf("got %+v, want %+v", got, want)
 			}
-			i++
+			i, prevFrag = i+1, frag
 			return nil
 		})
 		if err != nil {
@@ -223,6 +225,36 @@
 	}
 }
 
+// find appends to ss the names of the named file and its children. It is
+// analogous to the Unix find command.
+//
+// The returned strings are not guaranteed to be in any particular order.
+func find(ss []string, fs FileSystem, name string) ([]string, error) {
+	stat, err := fs.Stat(name)
+	if err != nil {
+		return nil, err
+	}
+	ss = append(ss, name)
+	if stat.IsDir() {
+		f, err := fs.OpenFile(name, os.O_RDONLY, 0)
+		if err != nil {
+			return nil, err
+		}
+		defer f.Close()
+		children, err := f.Readdir(-1)
+		if err != nil {
+			return nil, err
+		}
+		for _, c := range children {
+			ss, err = find(ss, fs, path.Join(name, c.Name()))
+			if err != nil {
+				return nil, err
+			}
+		}
+	}
+	return ss, nil
+}
+
 func testFS(t *testing.T, fs FileSystem) {
 	errStr := func(err error) string {
 		switch {
@@ -236,8 +268,8 @@
 		return "ok"
 	}
 
-	// The non-"stat" test cases should change the file system state. The
-	// indentation of the "stat"s helps distinguish such test cases.
+	// The non-"find" non-"stat" test cases should change the file system state. The
+	// indentation of the "find"s and "stat"s helps distinguish such test cases.
 	testCases := []string{
 		"  stat / want dir",
 		"  stat /a want errNotExist",
@@ -252,6 +284,7 @@
 		"  stat /d want dir",
 		"create /d/e EEE want ok",
 		"  stat /d/e want 3",
+		"  find / /a /d /d/e",
 		"create /d/f FFFF want ok",
 		"create /d/g GGGGGGG want ok",
 		"mk-dir /d/m want ok",
@@ -263,6 +296,7 @@
 		"  stat /d/h want errNotExist",
 		"  stat /d/m want dir",
 		"  stat /d/m/p want 5",
+		"  find / /a /d /d/e /d/f /d/g /d/m /d/m/p",
 		"rm-all /d want ok",
 		"  stat /a want 1",
 		"  stat /d want errNotExist",
@@ -271,6 +305,7 @@
 		"  stat /d/g want errNotExist",
 		"  stat /d/m want errNotExist",
 		"  stat /d/m/p want errNotExist",
+		"  find / /a",
 		"mk-dir /d/m want errNotExist",
 		"mk-dir /d want ok",
 		"create /d/f FFFF want ok",
@@ -285,6 +320,7 @@
 		"  stat /c want errNotExist",
 		"  stat /d want dir",
 		"  stat /d/m want dir",
+		"  find / /a /b /d /d/m",
 		"rename /b /b want ok",
 		"  stat /b want 2",
 		"  stat /c want errNotExist",
@@ -293,6 +329,7 @@
 		"  stat /c want 2",
 		"  stat /d/m want dir",
 		"  stat /d/n want errNotExist",
+		"  find / /a /c /d /d/m",
 		"rename /d/m /d/n want ok",
 		"create /d/n/q QQQQ want ok",
 		"  stat /d/m want errNotExist",
@@ -302,6 +339,7 @@
 		"rename /c /d/n/q want ok",
 		"  stat /c want errNotExist",
 		"  stat /d/n/q want 2",
+		"  find / /a /d /d/n /d/n/q",
 		"create /d/n/r RRRRR want ok",
 		"mk-dir /u want ok",
 		"mk-dir /u/v want ok",
@@ -316,10 +354,12 @@
 		"  stat /t want dir",
 		"  stat /t/q want 2",
 		"  stat /t/r want 5",
+		"  find / /a /d /t /t/q /t/r /u /u/v",
 		"rename /t / want err",
 		"rename /t /u/v want ok",
 		"  stat /u/v/r want 5",
 		"rename / /x want err",
+		"  find / /a /d /u /u/v /u/v/q /u/v/r",
 	}
 
 	for i, tc := range testCases {
@@ -352,6 +392,17 @@
 				}
 			}
 
+		case "find":
+			got, err := find(nil, fs, "/")
+			if err != nil {
+				t.Fatalf("test case #%d %q: find: %v", i, tc, err)
+			}
+			sort.Strings(got)
+			want := strings.Split(arg, " ")
+			if !reflect.DeepEqual(got, want) {
+				t.Fatalf("test case #%d %q:\ngot  %s\nwant %s", i, tc, got, want)
+			}
+
 		case "mk-dir", "rename", "rm-all", "stat":
 			nParts := 3
 			if op == "rename" {
@@ -372,12 +423,22 @@
 				opErr = fs.RemoveAll(parts[0])
 			case "stat":
 				var stat os.FileInfo
-				if stat, opErr = fs.Stat(parts[0]); opErr == nil {
+				fileName := parts[0]
+				if stat, opErr = fs.Stat(fileName); opErr == nil {
 					if stat.IsDir() {
 						got = "dir"
 					} else {
 						got = strconv.Itoa(int(stat.Size()))
 					}
+
+					if fileName == "/" {
+						// For a Dir FileSystem, the virtual file system root maps to a
+						// real file system name like "/tmp/webdav-test012345", which does
+						// not end with "/". We skip such cases.
+					} else if statName := stat.Name(); path.Base(fileName) != statName {
+						t.Fatalf("test case #%d %q: file name %q inconsistent with stat name %q",
+							i, tc, fileName, statName)
+					}
 				}
 			}
 			if got == "" {