It will help for us to rename earlier vs later, and syscallfs will be laborious, especially after we introduce an FSConfig type and need to declare a method name that differentiates from normal fs.FS. e.g. WithFS vs WithSysFS reads nicer than WithSyscallFS, and meanwhile sys is already a public package. Signed-off-by: Adrian Cole <adrian@tetrate.io>
151 lines
4.2 KiB
Go
151 lines
4.2 KiB
Go
// Package fstest defines filesystem test cases that help validate host
|
|
// functions implementing WASI and `GOARCH=wasm GOOS=js`. Tests are defined
|
|
// here to reduce duplication and drift.
|
|
//
|
|
// Here's an example using this inside code that compiles to wasm.
|
|
//
|
|
// if err := fstest.WriteTestFiles(tmpDir); err != nil {
|
|
// log.Panicln(err)
|
|
// }
|
|
// if err := fstest.TestFS(os.DirFS(tmpDir)); err != nil {
|
|
// log.Panicln(err)
|
|
// }
|
|
//
|
|
// Failures found here should result in new tests in the appropriate package,
|
|
// for example, gojs, sysfs or wasi_snapshot_preview1.
|
|
//
|
|
// This package must have no dependencies. Otherwise, compiling this with
|
|
// TinyGo or `GOARCH=wasm GOOS=js` can become bloated or complicated.
|
|
package fstest
|
|
|
|
import (
|
|
"io/fs"
|
|
"os"
|
|
"path"
|
|
"path/filepath"
|
|
"runtime"
|
|
"testing/fstest"
|
|
"time"
|
|
|
|
"github.com/tetratelabs/wazero/internal/platform"
|
|
)
|
|
|
|
var files = []struct {
|
|
name string
|
|
file *fstest.MapFile
|
|
}{
|
|
{
|
|
name: ".", // is defined only for the sake of assigning mtim.
|
|
file: &fstest.MapFile{
|
|
Mode: fs.ModeDir | 0o755,
|
|
ModTime: time.Unix(1609459200, 0),
|
|
},
|
|
},
|
|
{name: "empty.txt", file: &fstest.MapFile{Mode: 0o600}},
|
|
{name: "emptydir", file: &fstest.MapFile{Mode: fs.ModeDir | 0o755}},
|
|
{name: "animals.txt", file: &fstest.MapFile{Data: []byte(`bear
|
|
cat
|
|
shark
|
|
dinosaur
|
|
human
|
|
`), Mode: 0o644, ModTime: time.Unix(1667482413, 0)}},
|
|
{name: "sub", file: &fstest.MapFile{
|
|
Mode: fs.ModeDir | 0o755,
|
|
ModTime: time.Unix(1640995200, 0),
|
|
}},
|
|
{name: "sub/test.txt", file: &fstest.MapFile{
|
|
Data: []byte("greet sub dir\n"),
|
|
Mode: 0o444,
|
|
ModTime: time.Unix(1672531200, 0),
|
|
}},
|
|
{name: "dir", file: &fstest.MapFile{Mode: fs.ModeDir | 0o755}}, // for readDir tests...
|
|
{name: "dir/-", file: &fstest.MapFile{Mode: 0o400}}, // len = 24+1 = 25
|
|
{name: "dir/a-", file: &fstest.MapFile{Mode: fs.ModeDir | 0o755}}, // len = 24+2 = 26
|
|
{name: "dir/ab-", file: &fstest.MapFile{Mode: 0o400}}, // len = 24+3 = 27
|
|
}
|
|
|
|
// FS includes all test files.
|
|
var FS = func() fstest.MapFS {
|
|
testFS := make(fstest.MapFS, len(files))
|
|
for _, nf := range files {
|
|
testFS[nf.name] = nf.file
|
|
}
|
|
return testFS
|
|
}()
|
|
|
|
// WriteTestFiles writes files defined in FS to the given directory.
|
|
// This is used for implementations like os.DirFS.
|
|
func WriteTestFiles(tmpDir string) (err error) {
|
|
// Don't use a map as the iteration order is inconsistent and can result in
|
|
// files created prior to their directories.
|
|
for _, nf := range files {
|
|
if err = writeTestFile(tmpDir, nf.name, nf.file); err != nil {
|
|
return
|
|
}
|
|
}
|
|
|
|
// The below is similar to code in os_test.go. In summary, Windows mtime
|
|
// can be inconsistent between DirEntry.Info and File.Stat. The latter is
|
|
// authoritative (via GetFileInformationByHandle), but the former can be
|
|
// out of sync (via FindFirstFile). Explicitly calling os.Chtimes syncs
|
|
// these. See golang.org/issues/42637.
|
|
if runtime.GOOS == "windows" {
|
|
return filepath.WalkDir(tmpDir, func(path string, d fs.DirEntry, err error) error {
|
|
if err != nil {
|
|
return err
|
|
}
|
|
info, err := d.Info()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// os.Stat uses GetFileInformationByHandle internally.
|
|
stat, err := os.Stat(path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if stat.ModTime() == info.ModTime() {
|
|
return nil // synced!
|
|
}
|
|
|
|
// Otherwise, we need to sync the timestamps.
|
|
atimeNsec, mtimeNsec, _ := platform.StatTimes(stat)
|
|
return os.Chtimes(path, time.Unix(0, atimeNsec), time.Unix(0, mtimeNsec))
|
|
})
|
|
}
|
|
return
|
|
}
|
|
|
|
// TestFS runs fstest.TestFS on the given input which is either FS or includes
|
|
// files written by WriteTestFiles.
|
|
func TestFS(testfs fs.FS) error {
|
|
expected := make([]string, 0, len(files))
|
|
for _, nf := range files[1:] { // skip "."
|
|
expected = append(expected, nf.name)
|
|
}
|
|
return fstest.TestFS(testfs, expected...)
|
|
}
|
|
|
|
var defaultTime = time.Unix(1577836800, 0)
|
|
|
|
func writeTestFile(tmpDir, name string, file *fstest.MapFile) (err error) {
|
|
fullPath := path.Join(tmpDir, name)
|
|
if mode := file.Mode; mode&fs.ModeDir != 0 {
|
|
if name != "." {
|
|
err = os.Mkdir(fullPath, mode)
|
|
}
|
|
} else {
|
|
err = os.WriteFile(fullPath, file.Data, mode)
|
|
}
|
|
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
mtim := file.ModTime
|
|
if mtim.Unix() == 0 {
|
|
mtim = defaultTime
|
|
}
|
|
return os.Chtimes(fullPath, mtim, mtim)
|
|
}
|