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 }