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

View as plain text