Black Lives Matter. Support the Equal Justice Initiative.

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

Documentation: cmd/go/internal/fsys

     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  // initOverlay resets the overlay state to reflect the config.
    18  // config should be a text archive string. The comment is the overlay config
    19  // json, and the files, in the archive are laid out in a temp directory
    20  // that cwd is set to.
    21  func initOverlay(t *testing.T, config string) {
    22  	t.Helper()
    23  
    24  	// Create a temporary directory and chdir to it.
    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  		// subdir1 is only on disk
    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  		// subdir2 is only in overlay
   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  		// subdir3 has files on disk and in overlay
   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  		// subdir4 is overlaid with a file
   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  		// subdir5 doesn't exist, and is overlaid with a "delete" entry
   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  		// subdir6 does exist, and is overlaid with a "delete" entry
   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  				// no subdir5.
   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}, // entry is returned even if destination file doesn't exist
   279  			},
   280  		},
   281  		{
   282  			// check that read dir actually redirects files that already exist
   283  			// the original this_file_is_overlaid.txt is empty
   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  		// Sorted diff of want and infos.
   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", // regular file on disk
   335  		"subdir2/file2.txt", // regular file in overlay
   336  		"subdir4",           // directory overlaid with regular file
   337  		"subdir5",           // directory deleted in overlay
   338  		"parentoverwritten/subdir1/subdir2/subdir3", // parentoverwritten/subdir1 overlaid with regular file
   339  		"parentoverwritten/subdir1/subdir2",         // parentoverwritten/subdir1 overlaid with regular file
   340  		"subdir11",                                  // directory with deleted child, overlaid with regular file
   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  		// OverlayPath returns false for directories
   454  		{"subdir2", "subdir2", false},
   455  		{"subdir2/file2.txt", filepath.Join(cwd, "overlayfiles/subdir2_file2.txt"), true},
   456  		// OverlayPath doesn't stat a file to see if it exists, so it happily returns
   457  		// the 'to' path and true even if the 'to' path doesn't exist on disk.
   458  		{"subdir3/doesntexist", filepath.Join(cwd, "this_file_doesnt_exist_anywhere"), true},
   459  		// Like the subdir2/file2.txt case above, but subdir4 exists on disk, but subdir2 does not.
   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  	// The root of the walk must be a name with an actual basename, not just ".".
   612  	// Walk uses Lstat to obtain the name of the root, and Lstat on platforms
   613  	// other than Plan 9 reports the name "." instead of the actual base name of
   614  	// the directory. (See https://golang.org/issue/42115.)
   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 // don't check size for directories
   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  	// Create symlink
   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  		// ensure Walk doesn't walk into the directory pointed to by the symlink
   806  		// (because it's supposed to use Lstat instead of Stat).
   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 // mode & (fs.ModeDir|0x700): only check 'user' permissions
   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 // don't check size for directories
   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 // mode & (os.ModeDir|0x700): only check 'user' permissions
   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 // don't check size for directories
  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  	// Create symlink
  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