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

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

View as plain text