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