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