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