github.com/bananabytelabs/wazero@v0.0.0-20240105073314-54b22a776da8/internal/fstest/fstest.go (about) 1 // Package fstest defines filesystem test cases that help validate host 2 // functions implementing WASI and `GOOS=js GOARCH=wasm`. Tests are defined 3 // here to reduce duplication and drift. 4 // 5 // Here's an example using this inside code that compiles to wasm. 6 // 7 // if err := fstest.WriteTestFiles(tmpDir); err != nil { 8 // log.Panicln(err) 9 // } 10 // if err := fstest.TestFS(os.DirFS(tmpDir)); err != nil { 11 // log.Panicln(err) 12 // } 13 // 14 // Failures found here should result in new tests in the appropriate package, 15 // for example, gojs, sysfs or wasi_snapshot_preview1. 16 // 17 // This package must have no dependencies. Otherwise, compiling this with 18 // TinyGo or `GOOS=js GOARCH=wasm` can become bloated or complicated. 19 package fstest 20 21 import ( 22 "io/fs" 23 "os" 24 "path" 25 "path/filepath" 26 "runtime" 27 "testing/fstest" 28 "time" 29 ) 30 31 var files = []struct { 32 name string 33 file *fstest.MapFile 34 }{ 35 { 36 name: ".", // is defined only for the sake of assigning mtim. 37 file: &fstest.MapFile{ 38 Mode: fs.ModeDir | 0o755, 39 ModTime: time.Unix(1609459200, 0), 40 }, 41 }, 42 {name: "empty.txt", file: &fstest.MapFile{Mode: 0o600}}, 43 {name: "emptydir", file: &fstest.MapFile{Mode: fs.ModeDir | 0o755}}, 44 {name: "animals.txt", file: &fstest.MapFile{Data: []byte(`bear 45 cat 46 shark 47 dinosaur 48 human 49 `), Mode: 0o644, ModTime: time.Unix(1667482413, 0)}}, 50 {name: "sub", file: &fstest.MapFile{ 51 Mode: fs.ModeDir | 0o755, 52 ModTime: time.Unix(1640995200, 0), 53 }}, 54 {name: "sub/test.txt", file: &fstest.MapFile{ 55 Data: []byte("greet sub dir\n"), 56 Mode: 0o444, 57 ModTime: time.Unix(1672531200, 0), 58 }}, 59 {name: "dir", file: &fstest.MapFile{Mode: fs.ModeDir | 0o755}}, // for readDir tests... 60 {name: "dir/-", file: &fstest.MapFile{Mode: 0o400}}, // len = 24+1 = 25 61 {name: "dir/a-", file: &fstest.MapFile{Mode: fs.ModeDir | 0o755}}, // len = 24+2 = 26 62 {name: "dir/ab-", file: &fstest.MapFile{Mode: 0o400}}, // len = 24+3 = 27 63 } 64 65 // FS includes all test files. 66 var FS = func() fstest.MapFS { 67 testFS := make(fstest.MapFS, len(files)) 68 for _, nf := range files { 69 testFS[nf.name] = nf.file 70 } 71 return testFS 72 }() 73 74 // WriteTestFiles writes files defined in FS to the given directory. 75 // This is used for implementations like os.DirFS. 76 func WriteTestFiles(tmpDir string) (err error) { 77 // Don't use a map as the iteration order is inconsistent and can result in 78 // files created prior to their directories. 79 for _, nf := range files { 80 if err = writeTestFile(tmpDir, nf.name, nf.file); err != nil { 81 return 82 } 83 } 84 85 // The below is similar to code in os_test.go. In summary, Windows mtime 86 // can be inconsistent between DirEntry.Info and File.Stat. The latter is 87 // authoritative (via GetFileInformationByHandle), but the former can be 88 // out of sync (via FindFirstFile). Explicitly calling os.Chtimes syncs 89 // these. See golang.org/issues/42637. 90 if runtime.GOOS == "windows" { 91 return filepath.WalkDir(tmpDir, func(path string, d fs.DirEntry, err error) error { 92 if err != nil { 93 return err 94 } 95 info, err := d.Info() 96 if err != nil { 97 return err 98 } 99 100 // os.Stat uses GetFileInformationByHandle internally. 101 st, err := os.Stat(path) 102 if err != nil { 103 return err 104 } else if st.ModTime() == info.ModTime() { 105 return nil // synced! 106 } 107 108 // Otherwise, we need to sync the timestamps. 109 atimeNsec, mtimeNsec := timesFromFileInfo(st) 110 return os.Chtimes(path, time.Unix(0, atimeNsec), time.Unix(0, mtimeNsec)) 111 }) 112 } 113 return 114 } 115 116 // TestFS runs fstest.TestFS on the given input which is either FS or includes 117 // files written by WriteTestFiles. 118 func TestFS(testfs fs.FS) error { 119 expected := make([]string, 0, len(files)) 120 for _, nf := range files[1:] { // skip "." 121 expected = append(expected, nf.name) 122 } 123 return fstest.TestFS(testfs, expected...) 124 } 125 126 var defaultTime = time.Unix(1577836800, 0) 127 128 func writeTestFile(tmpDir, name string, file *fstest.MapFile) (err error) { 129 fullPath := path.Join(tmpDir, name) 130 if mode := file.Mode; mode&fs.ModeDir != 0 { 131 if name != "." { 132 err = os.Mkdir(fullPath, mode) 133 } 134 } else { 135 err = os.WriteFile(fullPath, file.Data, mode) 136 } 137 138 if err != nil { 139 return 140 } 141 142 mtim := file.ModTime 143 if mtim.Unix() == 0 { 144 mtim = defaultTime 145 } 146 return os.Chtimes(fullPath, mtim, mtim) 147 }