1
2
3
4
5 package fsys
6
7 import (
8 "encoding/json"
9 "errors"
10 "internal/testenv"
11 "internal/txtar"
12 "io"
13 "io/fs"
14 "os"
15 "path/filepath"
16 "reflect"
17 "testing"
18 )
19
20
21
22
23
24 func initOverlay(t *testing.T, config string) {
25 t.Helper()
26
27
28 cwd = filepath.Join(t.TempDir(), "root")
29 if err := os.Mkdir(cwd, 0777); err != nil {
30 t.Fatal(err)
31 }
32 t.Chdir(cwd)
33
34 a := txtar.Parse([]byte(config))
35 for _, f := range a.Files {
36 name := filepath.Join(cwd, f.Name)
37 if err := os.MkdirAll(filepath.Dir(name), 0777); err != nil {
38 t.Fatal(err)
39 }
40 if err := os.WriteFile(name, f.Data, 0666); err != nil {
41 t.Fatal(err)
42 }
43 }
44
45 var overlayJSON OverlayJSON
46 if err := json.Unmarshal(a.Comment, &overlayJSON); err != nil {
47 t.Fatal("parsing overlay JSON:", err)
48 }
49
50 if err := initFromJSON(overlayJSON); err != nil {
51 t.Fatal(err)
52 }
53 t.Cleanup(func() { overlay = nil })
54 }
55
56 func TestIsDir(t *testing.T) {
57 initOverlay(t, `
58 {
59 "Replace": {
60 "subdir2/file2.txt": "overlayfiles/subdir2_file2.txt",
61 "subdir4": "overlayfiles/subdir4",
62 "subdir3/file3b.txt": "overlayfiles/subdir3_file3b.txt",
63 "subdir5": "",
64 "subdir6": ""
65 }
66 }
67 -- subdir1/file1.txt --
68
69 -- subdir3/file3a.txt --
70 33
71 -- subdir4/file4.txt --
72 444
73 -- overlayfiles/subdir2_file2.txt --
74 2
75 -- overlayfiles/subdir3_file3b.txt --
76 66666
77 -- overlayfiles/subdir4 --
78 x
79 -- subdir6/file6.txt --
80 six
81 `)
82
83 testCases := []struct {
84 path string
85 want, wantErr bool
86 }{
87 {"", true, true},
88 {".", true, false},
89 {cwd, true, false},
90 {cwd + string(filepath.Separator), true, false},
91
92 {filepath.Join(cwd, "subdir1"), true, false},
93 {"subdir1", true, false},
94 {"subdir1" + string(filepath.Separator), true, false},
95 {"subdir1/file1.txt", false, false},
96 {"subdir1/doesntexist.txt", false, true},
97 {"doesntexist", false, true},
98
99 {filepath.Join(cwd, "subdir2"), true, false},
100 {"subdir2", true, false},
101 {"subdir2" + string(filepath.Separator), true, false},
102 {"subdir2/file2.txt", false, false},
103 {"subdir2/doesntexist.txt", false, true},
104
105 {filepath.Join(cwd, "subdir3"), true, false},
106 {"subdir3", true, false},
107 {"subdir3" + string(filepath.Separator), true, false},
108 {"subdir3/file3a.txt", false, false},
109 {"subdir3/file3b.txt", false, false},
110 {"subdir3/doesntexist.txt", false, true},
111
112 {filepath.Join(cwd, "subdir4"), false, false},
113 {"subdir4", false, false},
114 {"subdir4" + string(filepath.Separator), false, false},
115 {"subdir4/file4.txt", false, false},
116 {"subdir4/doesntexist.txt", false, false},
117
118 {filepath.Join(cwd, "subdir5"), false, false},
119 {"subdir5", false, false},
120 {"subdir5" + string(filepath.Separator), false, false},
121 {"subdir5/file5.txt", false, false},
122 {"subdir5/doesntexist.txt", false, false},
123
124 {filepath.Join(cwd, "subdir6"), false, false},
125 {"subdir6", false, false},
126 {"subdir6" + string(filepath.Separator), false, false},
127 {"subdir6/file6.txt", false, false},
128 {"subdir6/doesntexist.txt", false, false},
129 }
130
131 for _, tc := range testCases {
132 got, err := IsDir(tc.path)
133 if err != nil {
134 if !tc.wantErr {
135 t.Errorf("IsDir(%q): got error with string %q, want no error", tc.path, err.Error())
136 }
137 continue
138 }
139 if tc.wantErr {
140 t.Errorf("IsDir(%q): got no error, want error", tc.path)
141 }
142 if tc.want != got {
143 t.Errorf("IsDir(%q) = %v, want %v", tc.path, got, tc.want)
144 }
145 }
146 }
147
148 const readDirOverlay = `
149 {
150 "Replace": {
151 "subdir2/file2.txt": "overlayfiles/subdir2_file2.txt",
152 "subdir4": "overlayfiles/subdir4",
153 "subdir3/file3b.txt": "overlayfiles/subdir3_file3b.txt",
154 "subdir5": "",
155 "subdir6/asubsubdir/afile.txt": "overlayfiles/subdir6_asubsubdir_afile.txt",
156 "subdir6/asubsubdir/zfile.txt": "overlayfiles/subdir6_asubsubdir_zfile.txt",
157 "subdir6/zsubsubdir/file.txt": "overlayfiles/subdir6_zsubsubdir_file.txt",
158 "subdir7/asubsubdir/file.txt": "overlayfiles/subdir7_asubsubdir_file.txt",
159 "subdir7/zsubsubdir/file.txt": "overlayfiles/subdir7_zsubsubdir_file.txt",
160 "subdir8/doesntexist": "this_file_doesnt_exist_anywhere",
161 "other/pointstodir": "overlayfiles/this_is_a_directory",
162 "parentoverwritten/subdir1": "overlayfiles/parentoverwritten_subdir1",
163 "subdir9/this_file_is_overlaid.txt": "overlayfiles/subdir9_this_file_is_overlaid.txt",
164 "subdir10/only_deleted_file.txt": "",
165 "subdir11/deleted.txt": "",
166 "subdir11": "overlayfiles/subdir11",
167 "textfile.txt/file.go": "overlayfiles/textfile_txt_file.go"
168 }
169 }
170 -- subdir1/file1.txt --
171
172 -- subdir3/file3a.txt --
173 33
174 -- subdir4/file4.txt --
175 444
176 -- subdir6/file.txt --
177 -- subdir6/asubsubdir/file.txt --
178 -- subdir6/anothersubsubdir/file.txt --
179 -- subdir9/this_file_is_overlaid.txt --
180 -- subdir10/only_deleted_file.txt --
181 this will be deleted in overlay
182 -- subdir11/deleted.txt --
183 -- parentoverwritten/subdir1/subdir2/subdir3/file.txt --
184 -- textfile.txt --
185 this will be overridden by textfile.txt/file.go
186 -- overlayfiles/subdir2_file2.txt --
187 2
188 -- overlayfiles/subdir3_file3b.txt --
189 66666
190 -- overlayfiles/subdir4 --
191 x
192 -- overlayfiles/subdir6_asubsubdir_afile.txt --
193 -- overlayfiles/subdir6_asubsubdir_zfile.txt --
194 -- overlayfiles/subdir6_zsubsubdir_file.txt --
195 -- overlayfiles/subdir7_asubsubdir_file.txt --
196 -- overlayfiles/subdir7_zsubsubdir_file.txt --
197 -- overlayfiles/parentoverwritten_subdir1 --
198 x
199 -- overlayfiles/subdir9_this_file_is_overlaid.txt --
200 99999999
201 -- overlayfiles/subdir11 --
202 -- overlayfiles/this_is_a_directory/file.txt --
203 -- overlayfiles/textfile_txt_file.go --
204 x
205 `
206
207 func TestReadDir(t *testing.T) {
208 initOverlay(t, readDirOverlay)
209
210 type entry struct {
211 name string
212 size int64
213 isDir bool
214 }
215
216 testCases := []struct {
217 dir string
218 want []entry
219 }{
220 {
221 ".", []entry{
222 {"other", 0, true},
223 {"overlayfiles", 0, true},
224 {"parentoverwritten", 0, true},
225 {"subdir1", 0, true},
226 {"subdir10", 0, true},
227 {"subdir11", 0, false},
228 {"subdir2", 0, true},
229 {"subdir3", 0, true},
230 {"subdir4", 2, false},
231
232 {"subdir6", 0, true},
233 {"subdir7", 0, true},
234 {"subdir8", 0, true},
235 {"subdir9", 0, true},
236 {"textfile.txt", 0, true},
237 },
238 },
239 {
240 "subdir1", []entry{
241 {"file1.txt", 1, false},
242 },
243 },
244 {
245 "subdir2", []entry{
246 {"file2.txt", 2, false},
247 },
248 },
249 {
250 "subdir3", []entry{
251 {"file3a.txt", 3, false},
252 {"file3b.txt", 6, false},
253 },
254 },
255 {
256 "subdir6", []entry{
257 {"anothersubsubdir", 0, true},
258 {"asubsubdir", 0, true},
259 {"file.txt", 0, false},
260 {"zsubsubdir", 0, true},
261 },
262 },
263 {
264 "subdir6/asubsubdir", []entry{
265 {"afile.txt", 0, false},
266 {"file.txt", 0, false},
267 {"zfile.txt", 0, false},
268 },
269 },
270 {
271 "subdir8", []entry{
272 {"doesntexist", 0, false},
273 },
274 },
275 {
276
277
278 "subdir9", []entry{
279 {"this_file_is_overlaid.txt", 9, false},
280 },
281 },
282 {
283 "subdir10", []entry{},
284 },
285 {
286 "parentoverwritten", []entry{
287 {"subdir1", 2, false},
288 },
289 },
290 {
291 "textfile.txt", []entry{
292 {"file.go", 2, false},
293 },
294 },
295 }
296
297 for _, tc := range testCases {
298 dir, want := tc.dir, tc.want
299 infos, err := ReadDir(dir)
300 if err != nil {
301 t.Errorf("ReadDir(%q): %v", dir, err)
302 continue
303 }
304
305 for len(infos) > 0 || len(want) > 0 {
306 switch {
307 case len(want) == 0 || len(infos) > 0 && infos[0].Name() < want[0].name:
308 t.Errorf("ReadDir(%q): unexpected entry: %s IsDir=%v Size=%v", dir, infos[0].Name(), infos[0].IsDir(), infos[0].Size())
309 infos = infos[1:]
310 case len(infos) == 0 || len(want) > 0 && want[0].name < infos[0].Name():
311 t.Errorf("ReadDir(%q): missing entry: %s IsDir=%v Size=%v", dir, want[0].name, want[0].isDir, want[0].size)
312 want = want[1:]
313 default:
314 infoSize := infos[0].Size()
315 if want[0].isDir {
316 infoSize = 0
317 }
318 if infos[0].IsDir() != want[0].isDir || want[0].isDir && infoSize != want[0].size {
319 t.Errorf("ReadDir(%q): %s: IsDir=%v Size=%v, want IsDir=%v Size=%v", dir, want[0].name, infos[0].IsDir(), infoSize, want[0].isDir, want[0].size)
320 }
321 infos = infos[1:]
322 want = want[1:]
323 }
324 }
325 }
326
327 errCases := []string{
328 "subdir1/file1.txt",
329 "subdir2/file2.txt",
330 "subdir4",
331 "subdir5",
332 "parentoverwritten/subdir1/subdir2/subdir3",
333 "parentoverwritten/subdir1/subdir2",
334 "subdir11",
335 "other/pointstodir",
336 }
337
338 for _, dir := range errCases {
339 _, err := ReadDir(dir)
340 if _, ok := err.(*fs.PathError); !ok {
341 t.Errorf("ReadDir(%q): err = %T (%v), want fs.PathError", dir, err, err)
342 }
343 }
344 }
345
346 func TestGlob(t *testing.T) {
347 initOverlay(t, readDirOverlay)
348
349 testCases := []struct {
350 pattern string
351 match []string
352 }{
353 {
354 "*o*",
355 []string{
356 "other",
357 "overlayfiles",
358 "parentoverwritten",
359 },
360 },
361 {
362 "subdir2/file2.txt",
363 []string{
364 "subdir2/file2.txt",
365 },
366 },
367 {
368 "*/*.txt",
369 []string{
370 "overlayfiles/subdir2_file2.txt",
371 "overlayfiles/subdir3_file3b.txt",
372 "overlayfiles/subdir6_asubsubdir_afile.txt",
373 "overlayfiles/subdir6_asubsubdir_zfile.txt",
374 "overlayfiles/subdir6_zsubsubdir_file.txt",
375 "overlayfiles/subdir7_asubsubdir_file.txt",
376 "overlayfiles/subdir7_zsubsubdir_file.txt",
377 "overlayfiles/subdir9_this_file_is_overlaid.txt",
378 "subdir1/file1.txt",
379 "subdir2/file2.txt",
380 "subdir3/file3a.txt",
381 "subdir3/file3b.txt",
382 "subdir6/file.txt",
383 "subdir9/this_file_is_overlaid.txt",
384 },
385 },
386 }
387
388 for _, tc := range testCases {
389 pattern := tc.pattern
390 match, err := Glob(pattern)
391 if err != nil {
392 t.Errorf("Glob(%q): %v", pattern, err)
393 continue
394 }
395 want := tc.match
396 for i, name := range want {
397 if name != tc.pattern {
398 want[i] = filepath.FromSlash(name)
399 }
400 }
401 for len(match) > 0 || len(want) > 0 {
402 switch {
403 case len(match) == 0 || len(want) > 0 && want[0] < match[0]:
404 t.Errorf("Glob(%q): missing match: %s", pattern, want[0])
405 want = want[1:]
406 case len(want) == 0 || len(match) > 0 && match[0] < want[0]:
407 t.Errorf("Glob(%q): extra match: %s", pattern, match[0])
408 match = match[1:]
409 default:
410 want = want[1:]
411 match = match[1:]
412 }
413 }
414 }
415 }
416
417 func TestOverlayPath(t *testing.T) {
418 initOverlay(t, `
419 {
420 "Replace": {
421 "subdir2/file2.txt": "overlayfiles/subdir2_file2.txt",
422 "subdir3/doesntexist": "this_file_doesnt_exist_anywhere",
423 "subdir4/this_file_is_overlaid.txt": "overlayfiles/subdir4_this_file_is_overlaid.txt",
424 "subdir5/deleted.txt": "",
425 "parentoverwritten/subdir1": ""
426 }
427 }
428 -- subdir1/file1.txt --
429 file 1
430 -- subdir4/this_file_is_overlaid.txt --
431 these contents are replaced by the overlay
432 -- parentoverwritten/subdir1/subdir2/subdir3/file.txt --
433 -- subdir5/deleted.txt --
434 deleted
435 -- overlayfiles/subdir2_file2.txt --
436 file 2
437 -- overlayfiles/subdir4_this_file_is_overlaid.txt --
438 99999999
439 `)
440
441 testCases := []struct {
442 path string
443 wantPath string
444 wantOK bool
445 }{
446 {"subdir1/file1.txt", "subdir1/file1.txt", false},
447
448 {"subdir2", "subdir2", false},
449 {"subdir2/file2.txt", filepath.Join(cwd, "overlayfiles/subdir2_file2.txt"), true},
450
451
452 {"subdir3/doesntexist", filepath.Join(cwd, "this_file_doesnt_exist_anywhere"), true},
453
454 {"subdir4/this_file_is_overlaid.txt", filepath.Join(cwd, "overlayfiles/subdir4_this_file_is_overlaid.txt"), true},
455 {"subdir5", "subdir5", false},
456 {"subdir5/deleted.txt", "", true},
457 }
458
459 for _, tc := range testCases {
460 gotPath, gotOK := OverlayPath(tc.path)
461 if gotPath != tc.wantPath || gotOK != tc.wantOK {
462 t.Errorf("OverlayPath(%q): got %v, %v; want %v, %v",
463 tc.path, gotPath, gotOK, tc.wantPath, tc.wantOK)
464 }
465 }
466 }
467
468 func TestOpen(t *testing.T) {
469 initOverlay(t, `
470 {
471 "Replace": {
472 "subdir2/file2.txt": "overlayfiles/subdir2_file2.txt",
473 "subdir3/doesntexist": "this_file_doesnt_exist_anywhere",
474 "subdir4/this_file_is_overlaid.txt": "overlayfiles/subdir4_this_file_is_overlaid.txt",
475 "subdir5/deleted.txt": "",
476 "parentoverwritten/subdir1": "",
477 "childoverlay/subdir1.txt/child.txt": "overlayfiles/child.txt",
478 "subdir11/deleted.txt": "",
479 "subdir11": "overlayfiles/subdir11",
480 "parentdeleted": "",
481 "parentdeleted/file.txt": "overlayfiles/parentdeleted_file.txt"
482 }
483 }
484 -- subdir11/deleted.txt --
485 -- subdir1/file1.txt --
486 file 1
487 -- subdir4/this_file_is_overlaid.txt --
488 these contents are replaced by the overlay
489 -- parentoverwritten/subdir1/subdir2/subdir3/file.txt --
490 -- childoverlay/subdir1.txt --
491 this file doesn't exist because the path
492 childoverlay/subdir1.txt/child.txt is in the overlay
493 -- subdir5/deleted.txt --
494 deleted
495 -- parentdeleted --
496 this will be deleted so that parentdeleted/file.txt can exist
497 -- overlayfiles/subdir2_file2.txt --
498 file 2
499 -- overlayfiles/subdir4_this_file_is_overlaid.txt --
500 99999999
501 -- overlayfiles/child.txt --
502 -- overlayfiles/subdir11 --
503 11
504 -- overlayfiles/parentdeleted_file.txt --
505 this can exist because the parent directory is deleted
506 `)
507
508 testCases := []struct {
509 path string
510 wantContents string
511 isErr bool
512 }{
513 {"subdir1/file1.txt", "file 1\n", false},
514 {"subdir2/file2.txt", "file 2\n", false},
515 {"subdir3/doesntexist", "", true},
516 {"subdir4/this_file_is_overlaid.txt", "99999999\n", false},
517 {"subdir5/deleted.txt", "", true},
518 {"parentoverwritten/subdir1/subdir2/subdir3/file.txt", "", true},
519 {"childoverlay/subdir1.txt", "", true},
520 {"subdir11", "11\n", false},
521 {"parentdeleted/file.txt", "this can exist because the parent directory is deleted\n", false},
522 }
523
524 for _, tc := range testCases {
525 f, err := Open(tc.path)
526 if tc.isErr {
527 if err == nil {
528 f.Close()
529 t.Errorf("Open(%q): got no error, but want error", tc.path)
530 }
531 continue
532 }
533 if err != nil {
534 t.Errorf("Open(%q): got error %v, want nil", tc.path, err)
535 continue
536 }
537 contents, err := io.ReadAll(f)
538 if err != nil {
539 t.Errorf("unexpected error reading contents of file: %v", err)
540 }
541 if string(contents) != tc.wantContents {
542 t.Errorf("contents of file opened with Open(%q): got %q, want %q",
543 tc.path, contents, tc.wantContents)
544 }
545 f.Close()
546 }
547 }
548
549 func TestIsDirWithGoFiles(t *testing.T) {
550 initOverlay(t, `
551 {
552 "Replace": {
553 "goinoverlay/file.go": "dummy",
554 "directory/removed/by/file": "dummy",
555 "directory_with_go_dir/dir.go/file.txt": "dummy",
556 "otherdirectory/deleted.go": "",
557 "nonexistentdirectory/deleted.go": "",
558 "textfile.txt/file.go": "dummy"
559 }
560 }
561 -- dummy --
562 a destination file for the overlay entries to point to
563 contents don't matter for this test
564 -- nogo/file.txt --
565 -- goondisk/file.go --
566 -- goinoverlay/file.txt --
567 -- directory/removed/by/file/in/overlay/file.go --
568 -- otherdirectory/deleted.go --
569 -- textfile.txt --
570 `)
571
572 testCases := []struct {
573 dir string
574 want bool
575 wantErr bool
576 }{
577 {"nogo", false, false},
578 {"goondisk", true, false},
579 {"goinoverlay", true, false},
580 {"directory/removed/by/file/in/overlay", false, false},
581 {"directory_with_go_dir", false, false},
582 {"otherdirectory", false, false},
583 {"nonexistentdirectory", false, false},
584 {"textfile.txt", true, false},
585 }
586
587 for _, tc := range testCases {
588 got, gotErr := IsDirWithGoFiles(tc.dir)
589 if tc.wantErr {
590 if gotErr == nil {
591 t.Errorf("IsDirWithGoFiles(%q): got %v, %v; want non-nil error", tc.dir, got, gotErr)
592 }
593 continue
594 }
595 if gotErr != nil {
596 t.Errorf("IsDirWithGoFiles(%q): got %v, %v; want nil error", tc.dir, got, gotErr)
597 }
598 if got != tc.want {
599 t.Errorf("IsDirWithGoFiles(%q) = %v; want %v", tc.dir, got, tc.want)
600 }
601 }
602 }
603
604 func TestWalk(t *testing.T) {
605
606
607
608
609
610 type file struct {
611 path string
612 name string
613 size int64
614 mode fs.FileMode
615 isDir bool
616 }
617 testCases := []struct {
618 name string
619 overlay string
620 root string
621 wantFiles []file
622 }{
623 {"no overlay", `
624 {}
625 -- dir/file.txt --
626 `,
627 "dir",
628 []file{
629 {"dir", "dir", 0, fs.ModeDir | 0700, true},
630 {"dir/file.txt", "file.txt", 0, 0600, false},
631 },
632 },
633 {"overlay with different file", `
634 {
635 "Replace": {
636 "dir/file.txt": "dir/other.txt"
637 }
638 }
639 -- dir/file.txt --
640 -- dir/other.txt --
641 contents of other file
642 `,
643 "dir",
644 []file{
645 {"dir", "dir", 0, fs.ModeDir | 0500, true},
646 {"dir/file.txt", "file.txt", 23, 0600, false},
647 {"dir/other.txt", "other.txt", 23, 0600, false},
648 },
649 },
650 {"overlay with new file", `
651 {
652 "Replace": {
653 "dir/file.txt": "dir/other.txt"
654 }
655 }
656 -- dir/other.txt --
657 contents of other file
658 `,
659 "dir",
660 []file{
661 {"dir", "dir", 0, fs.ModeDir | 0500, true},
662 {"dir/file.txt", "file.txt", 23, 0600, false},
663 {"dir/other.txt", "other.txt", 23, 0600, false},
664 },
665 },
666 {"overlay with new directory", `
667 {
668 "Replace": {
669 "dir/subdir/file.txt": "dir/other.txt"
670 }
671 }
672 -- dir/other.txt --
673 contents of other file
674 `,
675 "dir",
676 []file{
677 {"dir", "dir", 0, fs.ModeDir | 0500, true},
678 {"dir/other.txt", "other.txt", 23, 0600, false},
679 {"dir/subdir", "subdir", 0, fs.ModeDir | 0500, true},
680 {"dir/subdir/file.txt", "file.txt", 23, 0600, false},
681 },
682 },
683 }
684
685 for _, tc := range testCases {
686 t.Run(tc.name, func(t *testing.T) {
687 initOverlay(t, tc.overlay)
688
689 var got []file
690 Walk(tc.root, func(path string, info fs.FileInfo, err error) error {
691 got = append(got, file{path, info.Name(), info.Size(), info.Mode(), info.IsDir()})
692 return nil
693 })
694
695 if len(got) != len(tc.wantFiles) {
696 t.Errorf("Walk: saw %#v in walk; want %#v", got, tc.wantFiles)
697 }
698 for i := 0; i < len(got) && i < len(tc.wantFiles); i++ {
699 wantPath := filepath.FromSlash(tc.wantFiles[i].path)
700 if got[i].path != wantPath {
701 t.Errorf("path of file #%v in walk, got %q, want %q", i, got[i].path, wantPath)
702 }
703 if got[i].name != tc.wantFiles[i].name {
704 t.Errorf("name of file #%v in walk, got %q, want %q", i, got[i].name, tc.wantFiles[i].name)
705 }
706 if got[i].mode&(fs.ModeDir|0700) != tc.wantFiles[i].mode {
707 t.Errorf("mode&(fs.ModeDir|0700) for mode of file #%v in walk, got %v, want %v", i, got[i].mode&(fs.ModeDir|0700), tc.wantFiles[i].mode)
708 }
709 if got[i].isDir != tc.wantFiles[i].isDir {
710 t.Errorf("isDir for file #%v in walk, got %v, want %v", i, got[i].isDir, tc.wantFiles[i].isDir)
711 }
712 if tc.wantFiles[i].isDir {
713 continue
714 }
715 if got[i].size != tc.wantFiles[i].size {
716 t.Errorf("size of file #%v in walk, got %v, want %v", i, got[i].size, tc.wantFiles[i].size)
717 }
718 }
719 })
720 }
721 }
722
723 func TestWalkSkipDir(t *testing.T) {
724 initOverlay(t, `
725 {
726 "Replace": {
727 "dir/skip/file.go": "dummy.txt",
728 "dir/dontskip/file.go": "dummy.txt",
729 "dir/dontskip/skip/file.go": "dummy.txt"
730 }
731 }
732 -- dummy.txt --
733 `)
734
735 var seen []string
736 Walk("dir", func(path string, info fs.FileInfo, err error) error {
737 seen = append(seen, filepath.ToSlash(path))
738 if info.Name() == "skip" {
739 return filepath.SkipDir
740 }
741 return nil
742 })
743
744 wantSeen := []string{"dir", "dir/dontskip", "dir/dontskip/file.go", "dir/dontskip/skip", "dir/skip"}
745
746 if len(seen) != len(wantSeen) {
747 t.Errorf("paths seen in walk: got %v entries; want %v entries", len(seen), len(wantSeen))
748 }
749
750 for i := 0; i < len(seen) && i < len(wantSeen); i++ {
751 if seen[i] != wantSeen[i] {
752 t.Errorf("path #%v seen walking tree: want %q, got %q", i, seen[i], wantSeen[i])
753 }
754 }
755 }
756
757 func TestWalkSkipAll(t *testing.T) {
758 initOverlay(t, `
759 {
760 "Replace": {
761 "dir/subdir1/foo1": "dummy.txt",
762 "dir/subdir1/foo2": "dummy.txt",
763 "dir/subdir1/foo3": "dummy.txt",
764 "dir/subdir2/foo4": "dummy.txt",
765 "dir/zzlast": "dummy.txt"
766 }
767 }
768 -- dummy.txt --
769 `)
770
771 var seen []string
772 Walk("dir", func(path string, info fs.FileInfo, err error) error {
773 seen = append(seen, filepath.ToSlash(path))
774 if info.Name() == "foo2" {
775 return filepath.SkipAll
776 }
777 return nil
778 })
779
780 wantSeen := []string{"dir", "dir/subdir1", "dir/subdir1/foo1", "dir/subdir1/foo2"}
781
782 if len(seen) != len(wantSeen) {
783 t.Errorf("paths seen in walk: got %v entries; want %v entries", len(seen), len(wantSeen))
784 }
785
786 for i := 0; i < len(seen) && i < len(wantSeen); i++ {
787 if seen[i] != wantSeen[i] {
788 t.Errorf("path %#v seen walking tree: got %q, want %q", i, seen[i], wantSeen[i])
789 }
790 }
791 }
792
793 func TestWalkError(t *testing.T) {
794 initOverlay(t, "{}")
795
796 alreadyCalled := false
797 err := Walk("foo", func(path string, info fs.FileInfo, err error) error {
798 if alreadyCalled {
799 t.Fatal("expected walk function to be called exactly once, but it was called more than once")
800 }
801 alreadyCalled = true
802 return errors.New("returned from function")
803 })
804 if !alreadyCalled {
805 t.Fatal("expected walk function to be called exactly once, but it was never called")
806
807 }
808 if err == nil {
809 t.Fatalf("Walk: got no error, want error")
810 }
811 if err.Error() != "returned from function" {
812 t.Fatalf("Walk: got error %v, want \"returned from function\" error", err)
813 }
814 }
815
816 func TestWalkSymlink(t *testing.T) {
817 testenv.MustHaveSymlink(t)
818
819 initOverlay(t, `{
820 "Replace": {"overlay_symlink/file": "symlink/file"}
821 }
822 -- dir/file --`)
823
824
825 if err := os.Symlink("dir", "symlink"); err != nil {
826 t.Error(err)
827 }
828
829 testCases := []struct {
830 name string
831 dir string
832 wantFiles []string
833 }{
834 {"control", "dir", []string{"dir", filepath.Join("dir", "file")}},
835
836
837 {"symlink_to_dir", "symlink", []string{"symlink"}},
838 {"overlay_to_symlink_to_dir", "overlay_symlink", []string{"overlay_symlink", filepath.Join("overlay_symlink", "file")}},
839
840
841 {"symlink_with_slash", "symlink" + string(filepath.Separator), []string{"symlink" + string(filepath.Separator), filepath.Join("symlink", "file")}},
842 {"overlay_to_symlink_to_dir", "overlay_symlink" + string(filepath.Separator), []string{"overlay_symlink" + string(filepath.Separator), filepath.Join("overlay_symlink", "file")}},
843 }
844
845 for _, tc := range testCases {
846 t.Run(tc.name, func(t *testing.T) {
847 var got []string
848
849 err := Walk(tc.dir, func(path string, info fs.FileInfo, err error) error {
850 t.Logf("walk %q", path)
851 got = append(got, path)
852 if err != nil {
853 t.Errorf("walkfn: got non nil err argument: %v, want nil err argument", err)
854 }
855 return nil
856 })
857 if err != nil {
858 t.Errorf("Walk: got error %q, want nil", err)
859 }
860
861 if !reflect.DeepEqual(got, tc.wantFiles) {
862 t.Errorf("files examined by walk: got %v, want %v", got, tc.wantFiles)
863 }
864 })
865 }
866
867 }
868
869 func TestLstat(t *testing.T) {
870 type file struct {
871 name string
872 size int64
873 mode fs.FileMode
874 isDir bool
875 }
876
877 testCases := []struct {
878 name string
879 overlay string
880 path string
881
882 want file
883 wantErr bool
884 }{
885 {
886 "regular_file",
887 `{}
888 -- file.txt --
889 contents`,
890 "file.txt",
891 file{"file.txt", 9, 0600, false},
892 false,
893 },
894 {
895 "new_file_in_overlay",
896 `{"Replace": {"file.txt": "dummy.txt"}}
897 -- dummy.txt --
898 contents`,
899 "file.txt",
900 file{"file.txt", 9, 0600, false},
901 false,
902 },
903 {
904 "file_replaced_in_overlay",
905 `{"Replace": {"file.txt": "dummy.txt"}}
906 -- file.txt --
907 -- dummy.txt --
908 contents`,
909 "file.txt",
910 file{"file.txt", 9, 0600, false},
911 false,
912 },
913 {
914 "file_cant_exist",
915 `{"Replace": {"deleted": "dummy.txt"}}
916 -- deleted/file.txt --
917 -- dummy.txt --
918 `,
919 "deleted/file.txt",
920 file{},
921 true,
922 },
923 {
924 "deleted",
925 `{"Replace": {"deleted": ""}}
926 -- deleted --
927 `,
928 "deleted",
929 file{},
930 true,
931 },
932 {
933 "dir_on_disk",
934 `{}
935 -- dir/foo.txt --
936 `,
937 "dir",
938 file{"dir", 0, 0700 | fs.ModeDir, true},
939 false,
940 },
941 {
942 "dir_in_overlay",
943 `{"Replace": {"dir/file.txt": "dummy.txt"}}
944 -- dummy.txt --
945 `,
946 "dir",
947 file{"dir", 0, 0500 | fs.ModeDir, true},
948 false,
949 },
950 }
951
952 for _, tc := range testCases {
953 t.Run(tc.name, func(t *testing.T) {
954 initOverlay(t, tc.overlay)
955 got, err := Lstat(tc.path)
956 if tc.wantErr {
957 if err == nil {
958 t.Errorf("lstat(%q): got no error, want error", tc.path)
959 }
960 return
961 }
962 if err != nil {
963 t.Fatalf("lstat(%q): got error %v, want no error", tc.path, err)
964 }
965 if got.Name() != tc.want.name {
966 t.Errorf("lstat(%q).Name(): got %q, want %q", tc.path, got.Name(), tc.want.name)
967 }
968 if got.Mode()&(fs.ModeDir|0700) != tc.want.mode {
969 t.Errorf("lstat(%q).Mode()&(fs.ModeDir|0700): got %v, want %v", tc.path, got.Mode()&(fs.ModeDir|0700), tc.want.mode)
970 }
971 if got.IsDir() != tc.want.isDir {
972 t.Errorf("lstat(%q).IsDir(): got %v, want %v", tc.path, got.IsDir(), tc.want.isDir)
973 }
974 if tc.want.isDir {
975 return
976 }
977 if got.Size() != tc.want.size {
978 t.Errorf("lstat(%q).Size(): got %v, want %v", tc.path, got.Size(), tc.want.size)
979 }
980 })
981 }
982 }
983
984 func TestStat(t *testing.T) {
985 testenv.MustHaveSymlink(t)
986
987 type file struct {
988 name string
989 size int64
990 mode os.FileMode
991 isDir bool
992 }
993
994 testCases := []struct {
995 name string
996 overlay string
997 path string
998
999 want file
1000 wantErr bool
1001 }{
1002 {
1003 "regular_file",
1004 `{}
1005 -- file.txt --
1006 contents`,
1007 "file.txt",
1008 file{"file.txt", 9, 0600, false},
1009 false,
1010 },
1011 {
1012 "new_file_in_overlay",
1013 `{"Replace": {"file.txt": "dummy.txt"}}
1014 -- dummy.txt --
1015 contents`,
1016 "file.txt",
1017 file{"file.txt", 9, 0600, false},
1018 false,
1019 },
1020 {
1021 "file_replaced_in_overlay",
1022 `{"Replace": {"file.txt": "dummy.txt"}}
1023 -- file.txt --
1024 -- dummy.txt --
1025 contents`,
1026 "file.txt",
1027 file{"file.txt", 9, 0600, false},
1028 false,
1029 },
1030 {
1031 "file_cant_exist",
1032 `{"Replace": {"deleted": "dummy.txt"}}
1033 -- deleted/file.txt --
1034 -- dummy.txt --
1035 `,
1036 "deleted/file.txt",
1037 file{},
1038 true,
1039 },
1040 {
1041 "deleted",
1042 `{"Replace": {"deleted": ""}}
1043 -- deleted --
1044 `,
1045 "deleted",
1046 file{},
1047 true,
1048 },
1049 {
1050 "dir_on_disk",
1051 `{}
1052 -- dir/foo.txt --
1053 `,
1054 "dir",
1055 file{"dir", 0, 0700 | os.ModeDir, true},
1056 false,
1057 },
1058 {
1059 "dir_in_overlay",
1060 `{"Replace": {"dir/file.txt": "dummy.txt"}}
1061 -- dummy.txt --
1062 `,
1063 "dir",
1064 file{"dir", 0, 0500 | os.ModeDir, true},
1065 false,
1066 },
1067 }
1068
1069 for _, tc := range testCases {
1070 t.Run(tc.name, func(t *testing.T) {
1071 initOverlay(t, tc.overlay)
1072 got, err := Stat(tc.path)
1073 if tc.wantErr {
1074 if err == nil {
1075 t.Errorf("Stat(%q): got no error, want error", tc.path)
1076 }
1077 return
1078 }
1079 if err != nil {
1080 t.Fatalf("Stat(%q): got error %v, want no error", tc.path, err)
1081 }
1082 if got.Name() != tc.want.name {
1083 t.Errorf("Stat(%q).Name(): got %q, want %q", tc.path, got.Name(), tc.want.name)
1084 }
1085 if got.Mode()&(os.ModeDir|0700) != tc.want.mode {
1086 t.Errorf("Stat(%q).Mode()&(os.ModeDir|0700): got %v, want %v", tc.path, got.Mode()&(os.ModeDir|0700), tc.want.mode)
1087 }
1088 if got.IsDir() != tc.want.isDir {
1089 t.Errorf("Stat(%q).IsDir(): got %v, want %v", tc.path, got.IsDir(), tc.want.isDir)
1090 }
1091 if tc.want.isDir {
1092 return
1093 }
1094 if got.Size() != tc.want.size {
1095 t.Errorf("Stat(%q).Size(): got %v, want %v", tc.path, got.Size(), tc.want.size)
1096 }
1097 })
1098 }
1099 }
1100
1101 func TestStatSymlink(t *testing.T) {
1102 testenv.MustHaveSymlink(t)
1103
1104 initOverlay(t, `{
1105 "Replace": {"file.go": "symlink"}
1106 }
1107 -- to.go --
1108 0123456789
1109 `)
1110
1111
1112 if err := os.Symlink("to.go", "symlink"); err != nil {
1113 t.Error(err)
1114 }
1115
1116 f := "file.go"
1117 fi, err := Stat(f)
1118 if err != nil {
1119 t.Errorf("Stat(%q): got error %q, want nil error", f, err)
1120 }
1121
1122 if !fi.Mode().IsRegular() {
1123 t.Errorf("Stat(%q).Mode(): got %v, want regular mode", f, fi.Mode())
1124 }
1125
1126 if fi.Size() != 11 {
1127 t.Errorf("Stat(%q).Size(): got %v, want 11", f, fi.Size())
1128 }
1129 }
1130
View as plain text