src.elv.sh@v0.21.0-dev.0.20240515223629-06979efb9a2a/pkg/testutil/testdir_test.go (about)

     1  package testutil
     2  
     3  import (
     4  	"io"
     5  	"io/fs"
     6  	"os"
     7  	"path/filepath"
     8  	"testing"
     9  
    10  	"github.com/google/go-cmp/cmp"
    11  	"src.elv.sh/pkg/must"
    12  	"src.elv.sh/pkg/tt"
    13  )
    14  
    15  func TestTempDir_DirIsValid(t *testing.T) {
    16  	dir := TempDir(t)
    17  
    18  	stat, err := os.Stat(dir)
    19  	if err != nil {
    20  		t.Errorf("TestDir returns %q which cannot be stated", dir)
    21  	}
    22  	if !stat.IsDir() {
    23  		t.Errorf("TestDir returns %q which is not a dir", dir)
    24  	}
    25  }
    26  
    27  func TestTempDir_DirHasSymlinksResolved(t *testing.T) {
    28  	dir := TempDir(t)
    29  
    30  	resolved, err := filepath.EvalSymlinks(dir)
    31  	if err != nil {
    32  		panic(err)
    33  	}
    34  	if dir != resolved {
    35  		t.Errorf("TestDir returns %q, but it resolves to %q", dir, resolved)
    36  	}
    37  }
    38  
    39  func TestTempDir_CleanupRemovesDirRecursively(t *testing.T) {
    40  	c := &cleanuper{}
    41  	dir := TempDir(c)
    42  
    43  	err := os.WriteFile(filepath.Join(dir, "a"), []byte("test"), 0600)
    44  	if err != nil {
    45  		panic(err)
    46  	}
    47  
    48  	c.runCleanups()
    49  	if _, err := os.Stat(dir); err == nil {
    50  		t.Errorf("Dir %q still exists after cleanup", dir)
    51  	}
    52  }
    53  
    54  func TestChdir(t *testing.T) {
    55  	dir := TempDir(t)
    56  	original := getWd()
    57  
    58  	c := &cleanuper{}
    59  	Chdir(c, dir)
    60  
    61  	after := getWd()
    62  	if after != dir {
    63  		t.Errorf("pwd is now %q, want %q", after, dir)
    64  	}
    65  
    66  	c.runCleanups()
    67  	restored := getWd()
    68  	if restored != original {
    69  		t.Errorf("pwd restored to %q, want %q", restored, original)
    70  	}
    71  }
    72  
    73  func TestApplyDir_CreatesFiles(t *testing.T) {
    74  	InTempDir(t)
    75  
    76  	ApplyDir(Dir{
    77  		"a": "a content",
    78  		"b": "b content",
    79  	})
    80  
    81  	testFileContent(t, "a", "a content")
    82  	testFileContent(t, "b", "b content")
    83  }
    84  
    85  func TestApplyDir_CreatesDirectories(t *testing.T) {
    86  	InTempDir(t)
    87  
    88  	ApplyDir(Dir{
    89  		"d": Dir{
    90  			"d1": "d1 content",
    91  			"d2": "d2 content",
    92  			"dd": Dir{
    93  				"dd1": "dd1 content",
    94  			},
    95  		},
    96  	})
    97  
    98  	testFileContent(t, "d/d1", "d1 content")
    99  	testFileContent(t, "d/d2", "d2 content")
   100  	testFileContent(t, "d/dd/dd1", "dd1 content")
   101  }
   102  
   103  func TestApplyDir_AllowsExistingDirectories(t *testing.T) {
   104  	InTempDir(t)
   105  
   106  	ApplyDir(Dir{"d": Dir{}})
   107  	ApplyDir(Dir{"d": Dir{"a": "content"}})
   108  
   109  	testFileContent(t, "d/a", "content")
   110  }
   111  
   112  var It = tt.It
   113  
   114  func TestDirAsFS(t *testing.T) {
   115  	dir := Dir{
   116  		"d": Dir{
   117  			"x": "this is file d/x",
   118  			"y": "this is file d/y",
   119  		},
   120  		"a": "this is file a",
   121  		"b": File{Perm: 0o777, Content: "this is file b"},
   122  	}
   123  
   124  	// fs.WalkDir exercises a large subset of the fs.FS API.
   125  	entries := make(map[string]string)
   126  	fs.WalkDir(dir, ".", func(path string, d fs.DirEntry, err error) error {
   127  		must.OK(err)
   128  		entries[path] = fs.FormatFileInfo(must.OK1(d.Info()))
   129  		return nil
   130  	})
   131  	wantEntries := map[string]string{
   132  		".":   "drwxr-xr-x 0 1970-01-01 00:00:00 ./",
   133  		"a":   "-rw-r--r-- 14 1970-01-01 00:00:00 a",
   134  		"b":   "-rwxrwxrwx 14 1970-01-01 00:00:00 b",
   135  		"d":   "drwxr-xr-x 0 1970-01-01 00:00:00 d/",
   136  		"d/x": "-rw-r--r-- 16 1970-01-01 00:00:00 x",
   137  		"d/y": "-rw-r--r-- 16 1970-01-01 00:00:00 y",
   138  	}
   139  	if diff := cmp.Diff(wantEntries, entries); diff != "" {
   140  		t.Errorf("DirEntry map from walking the FS: (-want +got):\n%s", diff)
   141  	}
   142  
   143  	// Direct file access is not exercised by fs.WalkDir (other than to "."), so
   144  	// test those too.
   145  	readFile := func(name string) (string, error) {
   146  		bs, err := fs.ReadFile(dir, name)
   147  		return string(bs), err
   148  	}
   149  	tt.Test(t, tt.Fn(readFile).Named("readFile"),
   150  		It("supports accessing file in root").
   151  			Args("a").
   152  			Rets("this is file a", error(nil)),
   153  		It("supports accessing file backed by a File struct").
   154  			Args("b").
   155  			Rets("this is file b", error(nil)),
   156  		It("supports accessing file in subdirectory").
   157  			Args("d/x").
   158  			Rets("this is file d/x", error(nil)),
   159  		It("errors if file doesn't exist").
   160  			Args("d/bad").
   161  			Rets("", &fs.PathError{Op: "open", Path: "d/bad", Err: fs.ErrNotExist}),
   162  		It("errors if a directory component of the path doesn't exist").
   163  			Args("badd/x").
   164  			Rets("", &fs.PathError{Op: "open", Path: "badd/x", Err: fs.ErrNotExist}),
   165  		It("errors if a directory component of the path is a file").
   166  			Args("a/x").
   167  			Rets("", &fs.PathError{Op: "open", Path: "a/x", Err: fs.ErrNotExist}),
   168  		It("can open but not read a directory").
   169  			Args("d").
   170  			Rets("", &fs.PathError{Op: "read", Path: "d", Err: errIsDir}),
   171  		It("errors if path is invalid").
   172  			Args("/d").
   173  			Rets("", &fs.PathError{Op: "open", Path: "/d", Err: fs.ErrInvalid}),
   174  	)
   175  
   176  	// fs.WalkDir calls ReadDir with -1. Also exercise the code for reading
   177  	// piece by piece.
   178  	file := must.OK1(dir.Open(".")).(fs.ReadDirFile)
   179  	rootEntries := make(map[string]string)
   180  	for {
   181  		es, err := file.ReadDir(1)
   182  		if err != nil {
   183  			if err == io.EOF {
   184  				break
   185  			}
   186  			panic(err)
   187  		}
   188  		rootEntries[es[0].Name()] = fs.FormatFileInfo(must.OK1(es[0].Info()))
   189  	}
   190  	wantRootEntries := map[string]string{
   191  		"a": "-rw-r--r-- 14 1970-01-01 00:00:00 a",
   192  		"b": "-rwxrwxrwx 14 1970-01-01 00:00:00 b",
   193  		"d": "drwxr-xr-x 0 1970-01-01 00:00:00 d/",
   194  	}
   195  	if diff := cmp.Diff(wantRootEntries, rootEntries); diff != "" {
   196  		t.Errorf("DirEntry map from reading the root piece by piece: (-want +got):\n%s", diff)
   197  	}
   198  
   199  	// Cover the Sys method of the two FileInfo implementations.
   200  	must.OK1(must.OK1(dir.Open("d")).Stat()).Sys()
   201  	must.OK1(must.OK1(dir.Open("a")).Stat()).Sys()
   202  }
   203  
   204  func getWd() string {
   205  	dir, err := os.Getwd()
   206  	if err != nil {
   207  		panic(err)
   208  	}
   209  	dir, err = filepath.EvalSymlinks(dir)
   210  	if err != nil {
   211  		panic(err)
   212  	}
   213  	return dir
   214  }
   215  
   216  func testFileContent(t *testing.T, filename string, wantContent string) {
   217  	t.Helper()
   218  	content, err := os.ReadFile(filename)
   219  	if err != nil {
   220  		t.Errorf("Could not read %v: %v", filename, err)
   221  		return
   222  	}
   223  	if string(content) != wantContent {
   224  		t.Errorf("File %v is %q, want %q", filename, content, wantContent)
   225  	}
   226  }