...

Source file src/golang.org/x/net/webdav/file_test.go

Documentation: golang.org/x/net/webdav

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

View as plain text