1
2
3
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
190
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
233
234
235
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
276
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
490
491
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
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
794
795
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
817
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
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