OSDN Git Service

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