Source file src/cmd/go/internal/fsys/fsys_test.go

     1  // Copyright 2020 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     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  // initOverlay resets the overlay state to reflect the config.
    21  // config should be a text archive string. The comment is the overlay config
    22  // json, and the files, in the archive are laid out in a temp directory
    23  // that cwd is set to.
    24  func initOverlay(t *testing.T, config string) {
    25  	t.Helper()
    26  
    27  	// Create a temporary directory and chdir to it.
    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  		// subdir1 is only on disk
   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  		// subdir2 is only in overlay
   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  		// subdir3 has files on disk and in overlay
   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  		// subdir4 is overlaid with a file
   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  		// subdir5 doesn't exist, and is overlaid with a "delete" entry
   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  		// subdir6 does exist, and is overlaid with a "delete" entry
   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  				// no subdir5.
   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}, // entry is returned even if destination file doesn't exist
   284  			},
   285  		},
   286  		{
   287  			// check that read dir actually redirects files that already exist
   288  			// the original this_file_is_overlaid.txt is empty
   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  		// Sorted diff of want and infos.
   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", // regular file on disk
   340  		"subdir2/file2.txt", // regular file in overlay
   341  		"subdir4",           // directory overlaid with regular file
   342  		"subdir5",           // directory deleted in overlay
   343  		"parentoverwritten/subdir1/subdir2/subdir3", // parentoverwritten/subdir1 overlaid with regular file
   344  		"parentoverwritten/subdir1/subdir2",         // parentoverwritten/subdir1 overlaid with regular file
   345  		"subdir11",                                  // directory with deleted child, overlaid with regular file
   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  		// OverlayPath returns false for directories
   459  		{"subdir2", "subdir2", false},
   460  		{"subdir2/file2.txt", filepath.Join(cwd, "overlayfiles/subdir2_file2.txt"), true},
   461  		// OverlayPath doesn't stat a file to see if it exists, so it happily returns
   462  		// the 'to' path and true even if the 'to' path doesn't exist on disk.
   463  		{"subdir3/doesntexist", filepath.Join(cwd, "this_file_doesnt_exist_anywhere"), true},
   464  		// Like the subdir2/file2.txt case above, but subdir4 exists on disk, but subdir2 does not.
   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  	// The root of the walk must be a name with an actual basename, not just ".".
   617  	// Walk uses Lstat to obtain the name of the root, and Lstat on platforms
   618  	// other than Plan 9 reports the name "." instead of the actual base name of
   619  	// the directory. (See https://golang.org/issue/42115.)
   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 // don't check size for directories
   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  	// Create symlink
   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  		// ensure Walk doesn't walk into the directory pointed to by the symlink
   847  		// (because it's supposed to use Lstat instead of Stat).
   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  		// However, adding filepath.Separator should cause the link to be resolved.
   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 // mode & (fs.ModeDir|0x700): only check 'user' permissions
   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 // don't check size for directories
   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 // mode & (os.ModeDir|0x700): only check 'user' permissions
  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 // don't check size for directories
  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  	// Create symlink
  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