1
2
3
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
191
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
234
235
236
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
277
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
491
492
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
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 = io.SeekStart
715 case "cur":
716 whence = io.SeekCurrent
717 case "end":
718 whence = io.SeekEnd
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
795
796
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
818
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
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 }
1184
View as plain text