github.com/graybobo/golang.org-package-offline-cache@v0.0.0-20200626051047-6608995c132f/x/net/webdav/file_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  	"io"
    10  	"io/ioutil"
    11  	"os"
    12  	"path"
    13  	"path/filepath"
    14  	"reflect"
    15  	"runtime"
    16  	"sort"
    17  	"strconv"
    18  	"strings"
    19  	"testing"
    20  
    21  	"golang.org/x/net/webdav/internal/xml"
    22  )
    23  
    24  func TestSlashClean(t *testing.T) {
    25  	testCases := []string{
    26  		"",
    27  		".",
    28  		"/",
    29  		"/./",
    30  		"//",
    31  		"//.",
    32  		"//a",
    33  		"/a",
    34  		"/a/b/c",
    35  		"/a//b/./../c/d/",
    36  		"a",
    37  		"a/b/c",
    38  	}
    39  	for _, tc := range testCases {
    40  		got := slashClean(tc)
    41  		want := path.Clean("/" + tc)
    42  		if got != want {
    43  			t.Errorf("tc=%q: got %q, want %q", tc, got, want)
    44  		}
    45  	}
    46  }
    47  
    48  func TestDirResolve(t *testing.T) {
    49  	testCases := []struct {
    50  		dir, name, want string
    51  	}{
    52  		{"/", "", "/"},
    53  		{"/", "/", "/"},
    54  		{"/", ".", "/"},
    55  		{"/", "./a", "/a"},
    56  		{"/", "..", "/"},
    57  		{"/", "..", "/"},
    58  		{"/", "../", "/"},
    59  		{"/", "../.", "/"},
    60  		{"/", "../a", "/a"},
    61  		{"/", "../..", "/"},
    62  		{"/", "../bar/a", "/bar/a"},
    63  		{"/", "../baz/a", "/baz/a"},
    64  		{"/", "...", "/..."},
    65  		{"/", ".../a", "/.../a"},
    66  		{"/", ".../..", "/"},
    67  		{"/", "a", "/a"},
    68  		{"/", "a/./b", "/a/b"},
    69  		{"/", "a/../../b", "/b"},
    70  		{"/", "a/../b", "/b"},
    71  		{"/", "a/b", "/a/b"},
    72  		{"/", "a/b/c/../../d", "/a/d"},
    73  		{"/", "a/b/c/../../../d", "/d"},
    74  		{"/", "a/b/c/../../../../d", "/d"},
    75  		{"/", "a/b/c/d", "/a/b/c/d"},
    76  
    77  		{"/foo/bar", "", "/foo/bar"},
    78  		{"/foo/bar", "/", "/foo/bar"},
    79  		{"/foo/bar", ".", "/foo/bar"},
    80  		{"/foo/bar", "./a", "/foo/bar/a"},
    81  		{"/foo/bar", "..", "/foo/bar"},
    82  		{"/foo/bar", "../", "/foo/bar"},
    83  		{"/foo/bar", "../.", "/foo/bar"},
    84  		{"/foo/bar", "../a", "/foo/bar/a"},
    85  		{"/foo/bar", "../..", "/foo/bar"},
    86  		{"/foo/bar", "../bar/a", "/foo/bar/bar/a"},
    87  		{"/foo/bar", "../baz/a", "/foo/bar/baz/a"},
    88  		{"/foo/bar", "...", "/foo/bar/..."},
    89  		{"/foo/bar", ".../a", "/foo/bar/.../a"},
    90  		{"/foo/bar", ".../..", "/foo/bar"},
    91  		{"/foo/bar", "a", "/foo/bar/a"},
    92  		{"/foo/bar", "a/./b", "/foo/bar/a/b"},
    93  		{"/foo/bar", "a/../../b", "/foo/bar/b"},
    94  		{"/foo/bar", "a/../b", "/foo/bar/b"},
    95  		{"/foo/bar", "a/b", "/foo/bar/a/b"},
    96  		{"/foo/bar", "a/b/c/../../d", "/foo/bar/a/d"},
    97  		{"/foo/bar", "a/b/c/../../../d", "/foo/bar/d"},
    98  		{"/foo/bar", "a/b/c/../../../../d", "/foo/bar/d"},
    99  		{"/foo/bar", "a/b/c/d", "/foo/bar/a/b/c/d"},
   100  
   101  		{"/foo/bar/", "", "/foo/bar"},
   102  		{"/foo/bar/", "/", "/foo/bar"},
   103  		{"/foo/bar/", ".", "/foo/bar"},
   104  		{"/foo/bar/", "./a", "/foo/bar/a"},
   105  		{"/foo/bar/", "..", "/foo/bar"},
   106  
   107  		{"/foo//bar///", "", "/foo/bar"},
   108  		{"/foo//bar///", "/", "/foo/bar"},
   109  		{"/foo//bar///", ".", "/foo/bar"},
   110  		{"/foo//bar///", "./a", "/foo/bar/a"},
   111  		{"/foo//bar///", "..", "/foo/bar"},
   112  
   113  		{"/x/y/z", "ab/c\x00d/ef", ""},
   114  
   115  		{".", "", "."},
   116  		{".", "/", "."},
   117  		{".", ".", "."},
   118  		{".", "./a", "a"},
   119  		{".", "..", "."},
   120  		{".", "..", "."},
   121  		{".", "../", "."},
   122  		{".", "../.", "."},
   123  		{".", "../a", "a"},
   124  		{".", "../..", "."},
   125  		{".", "../bar/a", "bar/a"},
   126  		{".", "../baz/a", "baz/a"},
   127  		{".", "...", "..."},
   128  		{".", ".../a", ".../a"},
   129  		{".", ".../..", "."},
   130  		{".", "a", "a"},
   131  		{".", "a/./b", "a/b"},
   132  		{".", "a/../../b", "b"},
   133  		{".", "a/../b", "b"},
   134  		{".", "a/b", "a/b"},
   135  		{".", "a/b/c/../../d", "a/d"},
   136  		{".", "a/b/c/../../../d", "d"},
   137  		{".", "a/b/c/../../../../d", "d"},
   138  		{".", "a/b/c/d", "a/b/c/d"},
   139  
   140  		{"", "", "."},
   141  		{"", "/", "."},
   142  		{"", ".", "."},
   143  		{"", "./a", "a"},
   144  		{"", "..", "."},
   145  	}
   146  
   147  	for _, tc := range testCases {
   148  		d := Dir(filepath.FromSlash(tc.dir))
   149  		if got := filepath.ToSlash(d.resolve(tc.name)); got != tc.want {
   150  			t.Errorf("dir=%q, name=%q: got %q, want %q", tc.dir, tc.name, got, tc.want)
   151  		}
   152  	}
   153  }
   154  
   155  func TestWalk(t *testing.T) {
   156  	type walkStep struct {
   157  		name, frag string
   158  		final      bool
   159  	}
   160  
   161  	testCases := []struct {
   162  		dir  string
   163  		want []walkStep
   164  	}{
   165  		{"", []walkStep{
   166  			{"", "", true},
   167  		}},
   168  		{"/", []walkStep{
   169  			{"", "", true},
   170  		}},
   171  		{"/a", []walkStep{
   172  			{"", "a", true},
   173  		}},
   174  		{"/a/", []walkStep{
   175  			{"", "a", true},
   176  		}},
   177  		{"/a/b", []walkStep{
   178  			{"", "a", false},
   179  			{"a", "b", true},
   180  		}},
   181  		{"/a/b/", []walkStep{
   182  			{"", "a", false},
   183  			{"a", "b", true},
   184  		}},
   185  		{"/a/b/c", []walkStep{
   186  			{"", "a", false},
   187  			{"a", "b", false},
   188  			{"b", "c", true},
   189  		}},
   190  		// The following test case is the one mentioned explicitly
   191  		// in the method description.
   192  		{"/foo/bar/x", []walkStep{
   193  			{"", "foo", false},
   194  			{"foo", "bar", false},
   195  			{"bar", "x", true},
   196  		}},
   197  	}
   198  
   199  	for _, tc := range testCases {
   200  		fs := NewMemFS().(*memFS)
   201  
   202  		parts := strings.Split(tc.dir, "/")
   203  		for p := 2; p < len(parts); p++ {
   204  			d := strings.Join(parts[:p], "/")
   205  			if err := fs.Mkdir(d, 0666); err != nil {
   206  				t.Errorf("tc.dir=%q: mkdir: %q: %v", tc.dir, d, err)
   207  			}
   208  		}
   209  
   210  		i, prevFrag := 0, ""
   211  		err := fs.walk("test", tc.dir, func(dir *memFSNode, frag string, final bool) error {
   212  			got := walkStep{
   213  				name:  prevFrag,
   214  				frag:  frag,
   215  				final: final,
   216  			}
   217  			want := tc.want[i]
   218  
   219  			if got != want {
   220  				return fmt.Errorf("got %+v, want %+v", got, want)
   221  			}
   222  			i, prevFrag = i+1, frag
   223  			return nil
   224  		})
   225  		if err != nil {
   226  			t.Errorf("tc.dir=%q: %v", tc.dir, err)
   227  		}
   228  	}
   229  }
   230  
   231  // find appends to ss the names of the named file and its children. It is
   232  // analogous to the Unix find command.
   233  //
   234  // The returned strings are not guaranteed to be in any particular order.
   235  func find(ss []string, fs FileSystem, name string) ([]string, error) {
   236  	stat, err := fs.Stat(name)
   237  	if err != nil {
   238  		return nil, err
   239  	}
   240  	ss = append(ss, name)
   241  	if stat.IsDir() {
   242  		f, err := fs.OpenFile(name, os.O_RDONLY, 0)
   243  		if err != nil {
   244  			return nil, err
   245  		}
   246  		defer f.Close()
   247  		children, err := f.Readdir(-1)
   248  		if err != nil {
   249  			return nil, err
   250  		}
   251  		for _, c := range children {
   252  			ss, err = find(ss, fs, path.Join(name, c.Name()))
   253  			if err != nil {
   254  				return nil, err
   255  			}
   256  		}
   257  	}
   258  	return ss, nil
   259  }
   260  
   261  func testFS(t *testing.T, fs FileSystem) {
   262  	errStr := func(err error) string {
   263  		switch {
   264  		case os.IsExist(err):
   265  			return "errExist"
   266  		case os.IsNotExist(err):
   267  			return "errNotExist"
   268  		case err != nil:
   269  			return "err"
   270  		}
   271  		return "ok"
   272  	}
   273  
   274  	// The non-"find" non-"stat" test cases should change the file system state. The
   275  	// indentation of the "find"s and "stat"s helps distinguish such test cases.
   276  	testCases := []string{
   277  		"  stat / want dir",
   278  		"  stat /a want errNotExist",
   279  		"  stat /d want errNotExist",
   280  		"  stat /d/e want errNotExist",
   281  		"create /a A want ok",
   282  		"  stat /a want 1",
   283  		"create /d/e EEE want errNotExist",
   284  		"mk-dir /a want errExist",
   285  		"mk-dir /d/m want errNotExist",
   286  		"mk-dir /d want ok",
   287  		"  stat /d want dir",
   288  		"create /d/e EEE want ok",
   289  		"  stat /d/e want 3",
   290  		"  find / /a /d /d/e",
   291  		"create /d/f FFFF want ok",
   292  		"create /d/g GGGGGGG want ok",
   293  		"mk-dir /d/m want ok",
   294  		"mk-dir /d/m want errExist",
   295  		"create /d/m/p PPPPP want ok",
   296  		"  stat /d/e want 3",
   297  		"  stat /d/f want 4",
   298  		"  stat /d/g want 7",
   299  		"  stat /d/h want errNotExist",
   300  		"  stat /d/m want dir",
   301  		"  stat /d/m/p want 5",
   302  		"  find / /a /d /d/e /d/f /d/g /d/m /d/m/p",
   303  		"rm-all /d want ok",
   304  		"  stat /a want 1",
   305  		"  stat /d want errNotExist",
   306  		"  stat /d/e want errNotExist",
   307  		"  stat /d/f want errNotExist",
   308  		"  stat /d/g want errNotExist",
   309  		"  stat /d/m want errNotExist",
   310  		"  stat /d/m/p want errNotExist",
   311  		"  find / /a",
   312  		"mk-dir /d/m want errNotExist",
   313  		"mk-dir /d want ok",
   314  		"create /d/f FFFF want ok",
   315  		"rm-all /d/f want ok",
   316  		"mk-dir /d/m want ok",
   317  		"rm-all /z want ok",
   318  		"rm-all / want err",
   319  		"create /b BB want ok",
   320  		"  stat / want dir",
   321  		"  stat /a want 1",
   322  		"  stat /b want 2",
   323  		"  stat /c want errNotExist",
   324  		"  stat /d want dir",
   325  		"  stat /d/m want dir",
   326  		"  find / /a /b /d /d/m",
   327  		"move__ o=F /b /c want ok",
   328  		"  stat /b want errNotExist",
   329  		"  stat /c want 2",
   330  		"  stat /d/m want dir",
   331  		"  stat /d/n want errNotExist",
   332  		"  find / /a /c /d /d/m",
   333  		"move__ o=F /d/m /d/n want ok",
   334  		"create /d/n/q QQQQ want ok",
   335  		"  stat /d/m want errNotExist",
   336  		"  stat /d/n want dir",
   337  		"  stat /d/n/q want 4",
   338  		"move__ o=F /d /d/n/z want err",
   339  		"move__ o=T /c /d/n/q want ok",
   340  		"  stat /c want errNotExist",
   341  		"  stat /d/n/q want 2",
   342  		"  find / /a /d /d/n /d/n/q",
   343  		"create /d/n/r RRRRR want ok",
   344  		"mk-dir /u want ok",
   345  		"mk-dir /u/v want ok",
   346  		"move__ o=F /d/n /u want errExist",
   347  		"create /t TTTTTT want ok",
   348  		"move__ o=F /d/n /t want errExist",
   349  		"rm-all /t want ok",
   350  		"move__ o=F /d/n /t want ok",
   351  		"  stat /d want dir",
   352  		"  stat /d/n want errNotExist",
   353  		"  stat /d/n/r want errNotExist",
   354  		"  stat /t want dir",
   355  		"  stat /t/q want 2",
   356  		"  stat /t/r want 5",
   357  		"  find / /a /d /t /t/q /t/r /u /u/v",
   358  		"move__ o=F /t / want errExist",
   359  		"move__ o=T /t /u/v want ok",
   360  		"  stat /u/v/r want 5",
   361  		"move__ o=F / /z want err",
   362  		"  find / /a /d /u /u/v /u/v/q /u/v/r",
   363  		"  stat /a want 1",
   364  		"  stat /b want errNotExist",
   365  		"  stat /c want errNotExist",
   366  		"  stat /u/v/r want 5",
   367  		"copy__ o=F d=0 /a /b want ok",
   368  		"copy__ o=T d=0 /a /c want ok",
   369  		"  stat /a want 1",
   370  		"  stat /b want 1",
   371  		"  stat /c want 1",
   372  		"  stat /u/v/r want 5",
   373  		"copy__ o=F d=0 /u/v/r /b want errExist",
   374  		"  stat /b want 1",
   375  		"copy__ o=T d=0 /u/v/r /b want ok",
   376  		"  stat /a want 1",
   377  		"  stat /b want 5",
   378  		"  stat /u/v/r want 5",
   379  		"rm-all /a want ok",
   380  		"rm-all /b want ok",
   381  		"mk-dir /u/v/w want ok",
   382  		"create /u/v/w/s SSSSSSSS want ok",
   383  		"  stat /d want dir",
   384  		"  stat /d/x want errNotExist",
   385  		"  stat /d/y want errNotExist",
   386  		"  stat /u/v/r want 5",
   387  		"  stat /u/v/w/s want 8",
   388  		"  find / /c /d /u /u/v /u/v/q /u/v/r /u/v/w /u/v/w/s",
   389  		"copy__ o=T d=0 /u/v /d/x want ok",
   390  		"copy__ o=T d=∞ /u/v /d/y want ok",
   391  		"rm-all /u want ok",
   392  		"  stat /d/x want dir",
   393  		"  stat /d/x/q want errNotExist",
   394  		"  stat /d/x/r want errNotExist",
   395  		"  stat /d/x/w want errNotExist",
   396  		"  stat /d/x/w/s want errNotExist",
   397  		"  stat /d/y want dir",
   398  		"  stat /d/y/q want 2",
   399  		"  stat /d/y/r want 5",
   400  		"  stat /d/y/w want dir",
   401  		"  stat /d/y/w/s want 8",
   402  		"  stat /u want errNotExist",
   403  		"  find / /c /d /d/x /d/y /d/y/q /d/y/r /d/y/w /d/y/w/s",
   404  		"copy__ o=F d=∞ /d/y /d/x want errExist",
   405  	}
   406  
   407  	for i, tc := range testCases {
   408  		tc = strings.TrimSpace(tc)
   409  		j := strings.IndexByte(tc, ' ')
   410  		if j < 0 {
   411  			t.Fatalf("test case #%d %q: invalid command", i, tc)
   412  		}
   413  		op, arg := tc[:j], tc[j+1:]
   414  
   415  		switch op {
   416  		default:
   417  			t.Fatalf("test case #%d %q: invalid operation %q", i, tc, op)
   418  
   419  		case "create":
   420  			parts := strings.Split(arg, " ")
   421  			if len(parts) != 4 || parts[2] != "want" {
   422  				t.Fatalf("test case #%d %q: invalid write", i, tc)
   423  			}
   424  			f, opErr := fs.OpenFile(parts[0], os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666)
   425  			if got := errStr(opErr); got != parts[3] {
   426  				t.Fatalf("test case #%d %q: OpenFile: got %q (%v), want %q", i, tc, got, opErr, parts[3])
   427  			}
   428  			if f != nil {
   429  				if _, err := f.Write([]byte(parts[1])); err != nil {
   430  					t.Fatalf("test case #%d %q: Write: %v", i, tc, err)
   431  				}
   432  				if err := f.Close(); err != nil {
   433  					t.Fatalf("test case #%d %q: Close: %v", i, tc, err)
   434  				}
   435  			}
   436  
   437  		case "find":
   438  			got, err := find(nil, fs, "/")
   439  			if err != nil {
   440  				t.Fatalf("test case #%d %q: find: %v", i, tc, err)
   441  			}
   442  			sort.Strings(got)
   443  			want := strings.Split(arg, " ")
   444  			if !reflect.DeepEqual(got, want) {
   445  				t.Fatalf("test case #%d %q:\ngot  %s\nwant %s", i, tc, got, want)
   446  			}
   447  
   448  		case "copy__", "mk-dir", "move__", "rm-all", "stat":
   449  			nParts := 3
   450  			switch op {
   451  			case "copy__":
   452  				nParts = 6
   453  			case "move__":
   454  				nParts = 5
   455  			}
   456  			parts := strings.Split(arg, " ")
   457  			if len(parts) != nParts {
   458  				t.Fatalf("test case #%d %q: invalid %s", i, tc, op)
   459  			}
   460  
   461  			got, opErr := "", error(nil)
   462  			switch op {
   463  			case "copy__":
   464  				depth := 0
   465  				if parts[1] == "d=∞" {
   466  					depth = infiniteDepth
   467  				}
   468  				_, opErr = copyFiles(fs, parts[2], parts[3], parts[0] == "o=T", depth, 0)
   469  			case "mk-dir":
   470  				opErr = fs.Mkdir(parts[0], 0777)
   471  			case "move__":
   472  				_, opErr = moveFiles(fs, parts[1], parts[2], parts[0] == "o=T")
   473  			case "rm-all":
   474  				opErr = fs.RemoveAll(parts[0])
   475  			case "stat":
   476  				var stat os.FileInfo
   477  				fileName := parts[0]
   478  				if stat, opErr = fs.Stat(fileName); opErr == nil {
   479  					if stat.IsDir() {
   480  						got = "dir"
   481  					} else {
   482  						got = strconv.Itoa(int(stat.Size()))
   483  					}
   484  
   485  					if fileName == "/" {
   486  						// For a Dir FileSystem, the virtual file system root maps to a
   487  						// real file system name like "/tmp/webdav-test012345", which does
   488  						// not end with "/". We skip such cases.
   489  					} else if statName := stat.Name(); path.Base(fileName) != statName {
   490  						t.Fatalf("test case #%d %q: file name %q inconsistent with stat name %q",
   491  							i, tc, fileName, statName)
   492  					}
   493  				}
   494  			}
   495  			if got == "" {
   496  				got = errStr(opErr)
   497  			}
   498  
   499  			if parts[len(parts)-2] != "want" {
   500  				t.Fatalf("test case #%d %q: invalid %s", i, tc, op)
   501  			}
   502  			if want := parts[len(parts)-1]; got != want {
   503  				t.Fatalf("test case #%d %q: got %q (%v), want %q", i, tc, got, opErr, want)
   504  			}
   505  		}
   506  	}
   507  }
   508  
   509  func TestDir(t *testing.T) {
   510  	switch runtime.GOOS {
   511  	case "nacl":
   512  		t.Skip("see golang.org/issue/12004")
   513  	case "plan9":
   514  		t.Skip("see golang.org/issue/11453")
   515  	}
   516  
   517  	td, err := ioutil.TempDir("", "webdav-test")
   518  	if err != nil {
   519  		t.Fatal(err)
   520  	}
   521  	defer os.RemoveAll(td)
   522  	testFS(t, Dir(td))
   523  }
   524  
   525  func TestMemFS(t *testing.T) {
   526  	testFS(t, NewMemFS())
   527  }
   528  
   529  func TestMemFSRoot(t *testing.T) {
   530  	fs := NewMemFS()
   531  	for i := 0; i < 5; i++ {
   532  		stat, err := fs.Stat("/")
   533  		if err != nil {
   534  			t.Fatalf("i=%d: Stat: %v", i, err)
   535  		}
   536  		if !stat.IsDir() {
   537  			t.Fatalf("i=%d: Stat.IsDir is false, want true", i)
   538  		}
   539  
   540  		f, err := fs.OpenFile("/", os.O_RDONLY, 0)
   541  		if err != nil {
   542  			t.Fatalf("i=%d: OpenFile: %v", i, err)
   543  		}
   544  		defer f.Close()
   545  		children, err := f.Readdir(-1)
   546  		if err != nil {
   547  			t.Fatalf("i=%d: Readdir: %v", i, err)
   548  		}
   549  		if len(children) != i {
   550  			t.Fatalf("i=%d: got %d children, want %d", i, len(children), i)
   551  		}
   552  
   553  		if _, err := f.Write(make([]byte, 1)); err == nil {
   554  			t.Fatalf("i=%d: Write: got nil error, want non-nil", i)
   555  		}
   556  
   557  		if err := fs.Mkdir(fmt.Sprintf("/dir%d", i), 0777); err != nil {
   558  			t.Fatalf("i=%d: Mkdir: %v", i, err)
   559  		}
   560  	}
   561  }
   562  
   563  func TestMemFileReaddir(t *testing.T) {
   564  	fs := NewMemFS()
   565  	if err := fs.Mkdir("/foo", 0777); err != nil {
   566  		t.Fatalf("Mkdir: %v", err)
   567  	}
   568  	readdir := func(count int) ([]os.FileInfo, error) {
   569  		f, err := fs.OpenFile("/foo", os.O_RDONLY, 0)
   570  		if err != nil {
   571  			t.Fatalf("OpenFile: %v", err)
   572  		}
   573  		defer f.Close()
   574  		return f.Readdir(count)
   575  	}
   576  	if got, err := readdir(-1); len(got) != 0 || err != nil {
   577  		t.Fatalf("readdir(-1): got %d fileInfos with err=%v, want 0, <nil>", len(got), err)
   578  	}
   579  	if got, err := readdir(+1); len(got) != 0 || err != io.EOF {
   580  		t.Fatalf("readdir(+1): got %d fileInfos with err=%v, want 0, EOF", len(got), err)
   581  	}
   582  }
   583  
   584  func TestMemFile(t *testing.T) {
   585  	testCases := []string{
   586  		"wantData ",
   587  		"wantSize 0",
   588  		"write abc",
   589  		"wantData abc",
   590  		"write de",
   591  		"wantData abcde",
   592  		"wantSize 5",
   593  		"write 5*x",
   594  		"write 4*y+2*z",
   595  		"write 3*st",
   596  		"wantData abcdexxxxxyyyyzzststst",
   597  		"wantSize 22",
   598  		"seek set 4 want 4",
   599  		"write EFG",
   600  		"wantData abcdEFGxxxyyyyzzststst",
   601  		"wantSize 22",
   602  		"seek set 2 want 2",
   603  		"read cdEF",
   604  		"read Gx",
   605  		"seek cur 0 want 8",
   606  		"seek cur 2 want 10",
   607  		"seek cur -1 want 9",
   608  		"write J",
   609  		"wantData abcdEFGxxJyyyyzzststst",
   610  		"wantSize 22",
   611  		"seek cur -4 want 6",
   612  		"write ghijk",
   613  		"wantData abcdEFghijkyyyzzststst",
   614  		"wantSize 22",
   615  		"read yyyz",
   616  		"seek cur 0 want 15",
   617  		"write ",
   618  		"seek cur 0 want 15",
   619  		"read ",
   620  		"seek cur 0 want 15",
   621  		"seek end -3 want 19",
   622  		"write ZZ",
   623  		"wantData abcdEFghijkyyyzzstsZZt",
   624  		"wantSize 22",
   625  		"write 4*A",
   626  		"wantData abcdEFghijkyyyzzstsZZAAAA",
   627  		"wantSize 25",
   628  		"seek end 0 want 25",
   629  		"seek end -5 want 20",
   630  		"read Z+4*A",
   631  		"write 5*B",
   632  		"wantData abcdEFghijkyyyzzstsZZAAAABBBBB",
   633  		"wantSize 30",
   634  		"seek end 10 want 40",
   635  		"write C",
   636  		"wantData abcdEFghijkyyyzzstsZZAAAABBBBB..........C",
   637  		"wantSize 41",
   638  		"write D",
   639  		"wantData abcdEFghijkyyyzzstsZZAAAABBBBB..........CD",
   640  		"wantSize 42",
   641  		"seek set 43 want 43",
   642  		"write E",
   643  		"wantData abcdEFghijkyyyzzstsZZAAAABBBBB..........CD.E",
   644  		"wantSize 44",
   645  		"seek set 0 want 0",
   646  		"write 5*123456789_",
   647  		"wantData 123456789_123456789_123456789_123456789_123456789_",
   648  		"wantSize 50",
   649  		"seek cur 0 want 50",
   650  		"seek cur -99 want err",
   651  	}
   652  
   653  	const filename = "/foo"
   654  	fs := NewMemFS()
   655  	f, err := fs.OpenFile(filename, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666)
   656  	if err != nil {
   657  		t.Fatalf("OpenFile: %v", err)
   658  	}
   659  	defer f.Close()
   660  
   661  	for i, tc := range testCases {
   662  		j := strings.IndexByte(tc, ' ')
   663  		if j < 0 {
   664  			t.Fatalf("test case #%d %q: invalid command", i, tc)
   665  		}
   666  		op, arg := tc[:j], tc[j+1:]
   667  
   668  		// Expand an arg like "3*a+2*b" to "aaabb".
   669  		parts := strings.Split(arg, "+")
   670  		for j, part := range parts {
   671  			if k := strings.IndexByte(part, '*'); k >= 0 {
   672  				repeatCount, repeatStr := part[:k], part[k+1:]
   673  				n, err := strconv.Atoi(repeatCount)
   674  				if err != nil {
   675  					t.Fatalf("test case #%d %q: invalid repeat count %q", i, tc, repeatCount)
   676  				}
   677  				parts[j] = strings.Repeat(repeatStr, n)
   678  			}
   679  		}
   680  		arg = strings.Join(parts, "")
   681  
   682  		switch op {
   683  		default:
   684  			t.Fatalf("test case #%d %q: invalid operation %q", i, tc, op)
   685  
   686  		case "read":
   687  			buf := make([]byte, len(arg))
   688  			if _, err := io.ReadFull(f, buf); err != nil {
   689  				t.Fatalf("test case #%d %q: ReadFull: %v", i, tc, err)
   690  			}
   691  			if got := string(buf); got != arg {
   692  				t.Fatalf("test case #%d %q:\ngot  %q\nwant %q", i, tc, got, arg)
   693  			}
   694  
   695  		case "seek":
   696  			parts := strings.Split(arg, " ")
   697  			if len(parts) != 4 {
   698  				t.Fatalf("test case #%d %q: invalid seek", i, tc)
   699  			}
   700  
   701  			whence := 0
   702  			switch parts[0] {
   703  			default:
   704  				t.Fatalf("test case #%d %q: invalid seek whence", i, tc)
   705  			case "set":
   706  				whence = os.SEEK_SET
   707  			case "cur":
   708  				whence = os.SEEK_CUR
   709  			case "end":
   710  				whence = os.SEEK_END
   711  			}
   712  			offset, err := strconv.Atoi(parts[1])
   713  			if err != nil {
   714  				t.Fatalf("test case #%d %q: invalid offset %q", i, tc, parts[1])
   715  			}
   716  
   717  			if parts[2] != "want" {
   718  				t.Fatalf("test case #%d %q: invalid seek", i, tc)
   719  			}
   720  			if parts[3] == "err" {
   721  				_, err := f.Seek(int64(offset), whence)
   722  				if err == nil {
   723  					t.Fatalf("test case #%d %q: Seek returned nil error, want non-nil", i, tc)
   724  				}
   725  			} else {
   726  				got, err := f.Seek(int64(offset), whence)
   727  				if err != nil {
   728  					t.Fatalf("test case #%d %q: Seek: %v", i, tc, err)
   729  				}
   730  				want, err := strconv.Atoi(parts[3])
   731  				if err != nil {
   732  					t.Fatalf("test case #%d %q: invalid want %q", i, tc, parts[3])
   733  				}
   734  				if got != int64(want) {
   735  					t.Fatalf("test case #%d %q: got %d, want %d", i, tc, got, want)
   736  				}
   737  			}
   738  
   739  		case "write":
   740  			n, err := f.Write([]byte(arg))
   741  			if err != nil {
   742  				t.Fatalf("test case #%d %q: write: %v", i, tc, err)
   743  			}
   744  			if n != len(arg) {
   745  				t.Fatalf("test case #%d %q: write returned %d bytes, want %d", i, tc, n, len(arg))
   746  			}
   747  
   748  		case "wantData":
   749  			g, err := fs.OpenFile(filename, os.O_RDONLY, 0666)
   750  			if err != nil {
   751  				t.Fatalf("test case #%d %q: OpenFile: %v", i, tc, err)
   752  			}
   753  			gotBytes, err := ioutil.ReadAll(g)
   754  			if err != nil {
   755  				t.Fatalf("test case #%d %q: ReadAll: %v", i, tc, err)
   756  			}
   757  			for i, c := range gotBytes {
   758  				if c == '\x00' {
   759  					gotBytes[i] = '.'
   760  				}
   761  			}
   762  			got := string(gotBytes)
   763  			if got != arg {
   764  				t.Fatalf("test case #%d %q:\ngot  %q\nwant %q", i, tc, got, arg)
   765  			}
   766  			if err := g.Close(); err != nil {
   767  				t.Fatalf("test case #%d %q: Close: %v", i, tc, err)
   768  			}
   769  
   770  		case "wantSize":
   771  			n, err := strconv.Atoi(arg)
   772  			if err != nil {
   773  				t.Fatalf("test case #%d %q: invalid size %q", i, tc, arg)
   774  			}
   775  			fi, err := fs.Stat(filename)
   776  			if err != nil {
   777  				t.Fatalf("test case #%d %q: Stat: %v", i, tc, err)
   778  			}
   779  			if got, want := fi.Size(), int64(n); got != want {
   780  				t.Fatalf("test case #%d %q: got %d, want %d", i, tc, got, want)
   781  			}
   782  		}
   783  	}
   784  }
   785  
   786  // TestMemFileWriteAllocs tests that writing N consecutive 1KiB chunks to a
   787  // memFile doesn't allocate a new buffer for each of those N times. Otherwise,
   788  // calling io.Copy(aMemFile, src) is likely to have quadratic complexity.
   789  func TestMemFileWriteAllocs(t *testing.T) {
   790  	fs := NewMemFS()
   791  	f, err := fs.OpenFile("/xxx", os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666)
   792  	if err != nil {
   793  		t.Fatalf("OpenFile: %v", err)
   794  	}
   795  	defer f.Close()
   796  
   797  	xxx := make([]byte, 1024)
   798  	for i := range xxx {
   799  		xxx[i] = 'x'
   800  	}
   801  
   802  	a := testing.AllocsPerRun(100, func() {
   803  		f.Write(xxx)
   804  	})
   805  	// AllocsPerRun returns an integral value, so we compare the rounded-down
   806  	// number to zero.
   807  	if a > 0 {
   808  		t.Fatalf("%v allocs per run, want 0", a)
   809  	}
   810  }
   811  
   812  func BenchmarkMemFileWrite(b *testing.B) {
   813  	fs := NewMemFS()
   814  	xxx := make([]byte, 1024)
   815  	for i := range xxx {
   816  		xxx[i] = 'x'
   817  	}
   818  
   819  	b.ResetTimer()
   820  	for i := 0; i < b.N; i++ {
   821  		f, err := fs.OpenFile("/xxx", os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666)
   822  		if err != nil {
   823  			b.Fatalf("OpenFile: %v", err)
   824  		}
   825  		for j := 0; j < 100; j++ {
   826  			f.Write(xxx)
   827  		}
   828  		if err := f.Close(); err != nil {
   829  			b.Fatalf("Close: %v", err)
   830  		}
   831  		if err := fs.RemoveAll("/xxx"); err != nil {
   832  			b.Fatalf("RemoveAll: %v", err)
   833  		}
   834  	}
   835  }
   836  
   837  func TestCopyMoveProps(t *testing.T) {
   838  	fs := NewMemFS()
   839  	create := func(name string) error {
   840  		f, err := fs.OpenFile(name, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666)
   841  		if err != nil {
   842  			return err
   843  		}
   844  		_, wErr := f.Write([]byte("contents"))
   845  		cErr := f.Close()
   846  		if wErr != nil {
   847  			return wErr
   848  		}
   849  		return cErr
   850  	}
   851  	patch := func(name string, patches ...Proppatch) error {
   852  		f, err := fs.OpenFile(name, os.O_RDWR, 0666)
   853  		if err != nil {
   854  			return err
   855  		}
   856  		_, pErr := f.(DeadPropsHolder).Patch(patches)
   857  		cErr := f.Close()
   858  		if pErr != nil {
   859  			return pErr
   860  		}
   861  		return cErr
   862  	}
   863  	props := func(name string) (map[xml.Name]Property, error) {
   864  		f, err := fs.OpenFile(name, os.O_RDWR, 0666)
   865  		if err != nil {
   866  			return nil, err
   867  		}
   868  		m, pErr := f.(DeadPropsHolder).DeadProps()
   869  		cErr := f.Close()
   870  		if pErr != nil {
   871  			return nil, pErr
   872  		}
   873  		if cErr != nil {
   874  			return nil, cErr
   875  		}
   876  		return m, nil
   877  	}
   878  
   879  	p0 := Property{
   880  		XMLName:  xml.Name{Space: "x:", Local: "boat"},
   881  		InnerXML: []byte("pea-green"),
   882  	}
   883  	p1 := Property{
   884  		XMLName:  xml.Name{Space: "x:", Local: "ring"},
   885  		InnerXML: []byte("1 shilling"),
   886  	}
   887  	p2 := Property{
   888  		XMLName:  xml.Name{Space: "x:", Local: "spoon"},
   889  		InnerXML: []byte("runcible"),
   890  	}
   891  	p3 := Property{
   892  		XMLName:  xml.Name{Space: "x:", Local: "moon"},
   893  		InnerXML: []byte("light"),
   894  	}
   895  
   896  	if err := create("/src"); err != nil {
   897  		t.Fatalf("create /src: %v", err)
   898  	}
   899  	if err := patch("/src", Proppatch{Props: []Property{p0, p1}}); err != nil {
   900  		t.Fatalf("patch /src +p0 +p1: %v", err)
   901  	}
   902  	if _, err := copyFiles(fs, "/src", "/tmp", true, infiniteDepth, 0); err != nil {
   903  		t.Fatalf("copyFiles /src /tmp: %v", err)
   904  	}
   905  	if _, err := moveFiles(fs, "/tmp", "/dst", true); err != nil {
   906  		t.Fatalf("moveFiles /tmp /dst: %v", err)
   907  	}
   908  	if err := patch("/src", Proppatch{Props: []Property{p0}, Remove: true}); err != nil {
   909  		t.Fatalf("patch /src -p0: %v", err)
   910  	}
   911  	if err := patch("/src", Proppatch{Props: []Property{p2}}); err != nil {
   912  		t.Fatalf("patch /src +p2: %v", err)
   913  	}
   914  	if err := patch("/dst", Proppatch{Props: []Property{p1}, Remove: true}); err != nil {
   915  		t.Fatalf("patch /dst -p1: %v", err)
   916  	}
   917  	if err := patch("/dst", Proppatch{Props: []Property{p3}}); err != nil {
   918  		t.Fatalf("patch /dst +p3: %v", err)
   919  	}
   920  
   921  	gotSrc, err := props("/src")
   922  	if err != nil {
   923  		t.Fatalf("props /src: %v", err)
   924  	}
   925  	wantSrc := map[xml.Name]Property{
   926  		p1.XMLName: p1,
   927  		p2.XMLName: p2,
   928  	}
   929  	if !reflect.DeepEqual(gotSrc, wantSrc) {
   930  		t.Fatalf("props /src:\ngot  %v\nwant %v", gotSrc, wantSrc)
   931  	}
   932  
   933  	gotDst, err := props("/dst")
   934  	if err != nil {
   935  		t.Fatalf("props /dst: %v", err)
   936  	}
   937  	wantDst := map[xml.Name]Property{
   938  		p0.XMLName: p0,
   939  		p3.XMLName: p3,
   940  	}
   941  	if !reflect.DeepEqual(gotDst, wantDst) {
   942  		t.Fatalf("props /dst:\ngot  %v\nwant %v", gotDst, wantDst)
   943  	}
   944  }
   945  
   946  func TestWalkFS(t *testing.T) {
   947  	testCases := []struct {
   948  		desc    string
   949  		buildfs []string
   950  		startAt string
   951  		depth   int
   952  		walkFn  filepath.WalkFunc
   953  		want    []string
   954  	}{{
   955  		"just root",
   956  		[]string{},
   957  		"/",
   958  		infiniteDepth,
   959  		nil,
   960  		[]string{
   961  			"/",
   962  		},
   963  	}, {
   964  		"infinite walk from root",
   965  		[]string{
   966  			"mkdir /a",
   967  			"mkdir /a/b",
   968  			"touch /a/b/c",
   969  			"mkdir /a/d",
   970  			"mkdir /e",
   971  			"touch /f",
   972  		},
   973  		"/",
   974  		infiniteDepth,
   975  		nil,
   976  		[]string{
   977  			"/",
   978  			"/a",
   979  			"/a/b",
   980  			"/a/b/c",
   981  			"/a/d",
   982  			"/e",
   983  			"/f",
   984  		},
   985  	}, {
   986  		"infinite walk from subdir",
   987  		[]string{
   988  			"mkdir /a",
   989  			"mkdir /a/b",
   990  			"touch /a/b/c",
   991  			"mkdir /a/d",
   992  			"mkdir /e",
   993  			"touch /f",
   994  		},
   995  		"/a",
   996  		infiniteDepth,
   997  		nil,
   998  		[]string{
   999  			"/a",
  1000  			"/a/b",
  1001  			"/a/b/c",
  1002  			"/a/d",
  1003  		},
  1004  	}, {
  1005  		"depth 1 walk from root",
  1006  		[]string{
  1007  			"mkdir /a",
  1008  			"mkdir /a/b",
  1009  			"touch /a/b/c",
  1010  			"mkdir /a/d",
  1011  			"mkdir /e",
  1012  			"touch /f",
  1013  		},
  1014  		"/",
  1015  		1,
  1016  		nil,
  1017  		[]string{
  1018  			"/",
  1019  			"/a",
  1020  			"/e",
  1021  			"/f",
  1022  		},
  1023  	}, {
  1024  		"depth 1 walk from subdir",
  1025  		[]string{
  1026  			"mkdir /a",
  1027  			"mkdir /a/b",
  1028  			"touch /a/b/c",
  1029  			"mkdir /a/b/g",
  1030  			"mkdir /a/b/g/h",
  1031  			"touch /a/b/g/i",
  1032  			"touch /a/b/g/h/j",
  1033  		},
  1034  		"/a/b",
  1035  		1,
  1036  		nil,
  1037  		[]string{
  1038  			"/a/b",
  1039  			"/a/b/c",
  1040  			"/a/b/g",
  1041  		},
  1042  	}, {
  1043  		"depth 0 walk from subdir",
  1044  		[]string{
  1045  			"mkdir /a",
  1046  			"mkdir /a/b",
  1047  			"touch /a/b/c",
  1048  			"mkdir /a/b/g",
  1049  			"mkdir /a/b/g/h",
  1050  			"touch /a/b/g/i",
  1051  			"touch /a/b/g/h/j",
  1052  		},
  1053  		"/a/b",
  1054  		0,
  1055  		nil,
  1056  		[]string{
  1057  			"/a/b",
  1058  		},
  1059  	}, {
  1060  		"infinite walk from file",
  1061  		[]string{
  1062  			"mkdir /a",
  1063  			"touch /a/b",
  1064  			"touch /a/c",
  1065  		},
  1066  		"/a/b",
  1067  		0,
  1068  		nil,
  1069  		[]string{
  1070  			"/a/b",
  1071  		},
  1072  	}, {
  1073  		"infinite walk with skipped subdir",
  1074  		[]string{
  1075  			"mkdir /a",
  1076  			"mkdir /a/b",
  1077  			"touch /a/b/c",
  1078  			"mkdir /a/b/g",
  1079  			"mkdir /a/b/g/h",
  1080  			"touch /a/b/g/i",
  1081  			"touch /a/b/g/h/j",
  1082  			"touch /a/b/z",
  1083  		},
  1084  		"/",
  1085  		infiniteDepth,
  1086  		func(path string, info os.FileInfo, err error) error {
  1087  			if path == "/a/b/g" {
  1088  				return filepath.SkipDir
  1089  			}
  1090  			return nil
  1091  		},
  1092  		[]string{
  1093  			"/",
  1094  			"/a",
  1095  			"/a/b",
  1096  			"/a/b/c",
  1097  			"/a/b/z",
  1098  		},
  1099  	}}
  1100  	for _, tc := range testCases {
  1101  		fs, err := buildTestFS(tc.buildfs)
  1102  		if err != nil {
  1103  			t.Fatalf("%s: cannot create test filesystem: %v", tc.desc, err)
  1104  		}
  1105  		var got []string
  1106  		traceFn := func(path string, info os.FileInfo, err error) error {
  1107  			if tc.walkFn != nil {
  1108  				err = tc.walkFn(path, info, err)
  1109  				if err != nil {
  1110  					return err
  1111  				}
  1112  			}
  1113  			got = append(got, path)
  1114  			return nil
  1115  		}
  1116  		fi, err := fs.Stat(tc.startAt)
  1117  		if err != nil {
  1118  			t.Fatalf("%s: cannot stat: %v", tc.desc, err)
  1119  		}
  1120  		err = walkFS(fs, tc.depth, tc.startAt, fi, traceFn)
  1121  		if err != nil {
  1122  			t.Errorf("%s:\ngot error %v, want nil", tc.desc, err)
  1123  			continue
  1124  		}
  1125  		sort.Strings(got)
  1126  		sort.Strings(tc.want)
  1127  		if !reflect.DeepEqual(got, tc.want) {
  1128  			t.Errorf("%s:\ngot  %q\nwant %q", tc.desc, got, tc.want)
  1129  			continue
  1130  		}
  1131  	}
  1132  }
  1133  
  1134  func buildTestFS(buildfs []string) (FileSystem, error) {
  1135  	// TODO: Could this be merged with the build logic in TestFS?
  1136  
  1137  	fs := NewMemFS()
  1138  	for _, b := range buildfs {
  1139  		op := strings.Split(b, " ")
  1140  		switch op[0] {
  1141  		case "mkdir":
  1142  			err := fs.Mkdir(op[1], os.ModeDir|0777)
  1143  			if err != nil {
  1144  				return nil, err
  1145  			}
  1146  		case "touch":
  1147  			f, err := fs.OpenFile(op[1], os.O_RDWR|os.O_CREATE, 0666)
  1148  			if err != nil {
  1149  				return nil, err
  1150  			}
  1151  			f.Close()
  1152  		case "write":
  1153  			f, err := fs.OpenFile(op[1], os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666)
  1154  			if err != nil {
  1155  				return nil, err
  1156  			}
  1157  			_, err = f.Write([]byte(op[2]))
  1158  			f.Close()
  1159  			if err != nil {
  1160  				return nil, err
  1161  			}
  1162  		default:
  1163  			return nil, fmt.Errorf("unknown file operation %q", op[0])
  1164  		}
  1165  	}
  1166  	return fs, nil
  1167  }