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