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