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