Repository: goplus/gop
Pull request #1416 (2023-09-21 13:17:06)

gop/parser/fsx


Patch:
diff --git a/parser/fsx/fsys.go b/parser/fsx/fsys.go
new file mode 100644
index 00000000..02b3e301
--- /dev/null
+++ b/parser/fsx/fsys.go
@@ -0,0 +1,36 @@
+package fsx
+
+import (
+	"io/fs"
+	"os"
+	"path/filepath"
+)
+
+// -----------------------------------------------------------------------------
+
+// FileSystem represents a file system.
+type FileSystem interface {
+	ReadDir(dirname string) ([]fs.DirEntry, error)
+	ReadFile(filename string) ([]byte, error)
+	Join(elem ...string) string
+}
+
+// -----------------------------------------------------------------------------
+
+type localFS struct{}
+
+func (p localFS) ReadDir(dirname string) ([]fs.DirEntry, error) {
+	return os.ReadDir(dirname)
+}
+
+func (p localFS) ReadFile(filename string) ([]byte, error) {
+	return os.ReadFile(filename)
+}
+
+func (p localFS) Join(elem ...string) string {
+	return filepath.Join(elem...)
+}
+
+var Local FileSystem = localFS{}
+
+// -----------------------------------------------------------------------------
diff --git a/x/build/fs.go b/parser/fsx/memfs/fs.go
similarity index 56%
rename from x/build/fs.go
rename to parser/fsx/memfs/fs.go
index 29f6c465..d16cd42f 100644
--- a/x/build/fs.go
+++ b/parser/fsx/memfs/fs.go
@@ -14,85 +14,66 @@
  * limitations under the License.
  */
 
-package build
+package memfs
 
 import (
 	"bytes"
 	"io"
 	"io/fs"
-	"io/ioutil"
 	"os"
 	"path/filepath"
-	"time"
 )
 
-type memFileInfo struct {
-	name string
-}
-
-func (p *memFileInfo) Name() string {
-	return p.name
-}
-
-func (p *memFileInfo) Size() int64 {
-	return 0
-}
-
-func (p *memFileInfo) Mode() os.FileMode {
-	return 0
-}
-
-func (p *memFileInfo) ModTime() (t time.Time) {
-	return
+type FileFS struct {
+	filename string
+	data     []byte
+	info     fs.DirEntry
 }
 
-func (p *memFileInfo) IsDir() bool {
-	return false
+type dirEntry struct {
+	fs.FileInfo
 }
 
-func (p *memFileInfo) Sys() interface{} {
-	return nil
+func (p *dirEntry) Type() fs.FileMode {
+	return p.FileInfo.Mode().Type()
 }
 
-type fileFS struct {
-	dir      string
-	filename string
-	data     []byte
-	info     fs.FileInfo
+func (p *dirEntry) Info() (fs.FileInfo, error) {
+	return p.FileInfo, nil
 }
 
-func newFileFS(filename string, src interface{}) (f *fileFS, err error) {
-	dir, name := filepath.Split(filename)
+func File(filename string, src interface{}) (f *FileFS, err error) {
 	var data []byte
-	var info fs.FileInfo
+	var info fs.DirEntry
 	if src != nil {
 		data, err = readSource(src)
 		if err != nil {
 			return
 		}
-		info = &memFileInfo{name: name}
+		info = &memFileInfo{name: filename, size: len(data)}
 	} else {
-		info, err = os.Stat(filename)
-		if err != nil {
-			return
+		fi, e := os.Stat(filename)
+		if e != nil {
+			return nil, e
 		}
 		data, err = os.ReadFile(filename)
 		if err != nil {
 			return
 		}
+		info = &dirEntry{fi}
 	}
-	return &fileFS{dir: dir, filename: filename, data: data, info: info}, nil
+	return &FileFS{filename: filename, data: data, info: info}, nil
 }
 
-func (p fileFS) ReadDir(dirname string) ([]fs.FileInfo, error) {
-	return []fs.FileInfo{p.info}, nil
+func (p FileFS) ReadDir(dirname string) ([]fs.DirEntry, error) {
+	return []fs.DirEntry{p.info}, nil
 }
 
-func (p fileFS) ReadFile(filename string) ([]byte, error) {
+func (p FileFS) ReadFile(filename string) ([]byte, error) {
 	return p.data, nil
 }
 
-func (p fileFS) Join(elem ...string) string {
+func (p FileFS) Join(elem ...string) string {
 	return filepath.Join(elem...)
 }
 
@@ -108,7 +89,7 @@ func readSource(src interface{}) ([]byte, error) {
 			return s.Bytes(), nil
 		}
 	case io.Reader:
-		return ioutil.ReadAll(s)
+		return io.ReadAll(s)
 	}
 	return nil, os.ErrInvalid
 }
diff --git a/parser/parser_gop.go b/parser/parser_gop.go
index ddd820b4..372c880b 100644
--- a/parser/parser_gop.go
+++ b/parser/parser_gop.go
@@ -22,8 +22,6 @@ import (
 	"fmt"
 	"io"
 	"io/fs"
-	"io/ioutil"
-	"os"
 	"path"
 	"path/filepath"
 	"strings"
@@ -32,6 +30,7 @@ import (
 	goparser "go/parser"
 
 	"github.com/goplus/gop/ast"
+	"github.com/goplus/gop/parser/fsx"
 	"github.com/goplus/gop/token"
 )
 
@@ -51,30 +50,9 @@ func SetDebug(dbgFlags int) {
 	debugParseError = (dbgFlags & DbgFlagParseError) != 0
 }
 
-// -----------------------------------------------------------------------------
-
-// FileSystem represents a file system.
-type FileSystem interface {
-	ReadDir(dirname string) ([]fs.FileInfo, error)
-	ReadFile(filename string) ([]byte, error)
-	Join(elem ...string) string
-}
-
-type localFS struct{}
+type FileSystem = fsx.FileSystem
 
-func (p localFS) ReadDir(dirname string) ([]fs.FileInfo, error) {
-	return ioutil.ReadDir(dirname)
-}
-
-func (p localFS) ReadFile(filename string) ([]byte, error) {
-	return os.ReadFile(filename)
-}
-
-func (p localFS) Join(elem ...string) string {
-	return filepath.Join(elem...)
-}
-
-var local FileSystem = localFS{}
+// -----------------------------------------------------------------------------
 
 // Parse parses a single Go+ source file. The target specifies the Go+ source file.
 // If the file couldn't be read, a nil map and the respective error are returned.
@@ -102,7 +80,7 @@ func astFileToPkg(file *ast.File, fileName string) (pkg *ast.Package) {
 
 // ParseDir calls ParseFSDir by passing a local filesystem.
 func ParseDir(fset *token.FileSet, path string, filter func(fs.FileInfo) bool, mode Mode) (pkgs map[string]*ast.Package, first error) {
-	return ParseFSDir(fset, local, path, Config{Filter: filter, Mode: mode})
+	return ParseFSDir(fset, fsx.Local, path, Config{Filter: filter, Mode: mode})
 }
 
 type Config struct {
@@ -113,7 +91,7 @@ type Config struct {
 
 // ParseDirEx calls ParseFSDir by passing a local filesystem.
 func ParseDirEx(fset *token.FileSet, path string, conf Config) (pkgs map[string]*ast.Package, first error) {
-	return ParseFSDir(fset, local, path, conf)
+	return ParseFSDir(fset, fsx.Local, path, conf)
 }
 
 // ClassFileExt returns the classfile extension
@@ -186,7 +164,7 @@ func ParseFSDir(fset *token.FileSet, fs FileSystem, path string, conf Config) (p
 		if isClass {
 			mode |= ParseGoPlusClass
 		}
-		if !strings.HasPrefix(fname, "_") && (conf.Filter == nil || conf.Filter(d)) {
+		if !strings.HasPrefix(fname, "_") && (conf.Filter == nil || filter(conf.Filter, d)) {
 			filename := fs.Join(path, fname)
 			if useGoParser {
 				if filedata, err := fs.ReadFile(filename); err == nil {
@@ -215,6 +193,11 @@ func ParseFSDir(fset *token.FileSet, fs FileSystem, path string, conf Config) (p
 	return
 }
 
+func filter(fn func(fs.FileInfo) bool, d fs.DirEntry) bool {
+	fi, err := d.Info()
+	return err != nil || fn(fi)
+}
+
 func reqPkg(pkgs map[string]*ast.Package, name string) *ast.Package {
 	pkg, found := pkgs[name]
 	if !found {
@@ -241,7 +224,7 @@ func defaultClassKind(fname string) (isProj bool, ok bool) {
 // -----------------------------------------------------------------------------
 
 func ParseFiles(fset *token.FileSet, files []string, mode Mode) (map[string]*ast.Package, error) {
-	return ParseFSFiles(fset, local, files, mode)
+	return ParseFSFiles(fset, fsx.Local, files, mode)
 }
 
 func ParseFSFiles(fset *token.FileSet, fs FileSystem, files []string, mode Mode) (map[string]*ast.Package, error) {
@@ -266,7 +249,7 @@ func ParseFSFiles(fset *token.FileSet, fs FileSystem, files []string, mode Mode)
 
 // ParseFile parses the source code of a single Go+ source file and returns the corresponding ast.File node.
 func ParseFile(fset *token.FileSet, filename string, src interface{}, mode Mode) (f *ast.File, err error) {
-	return ParseFSFile(fset, local, filename, src, mode)
+	return ParseFSFile(fset, fsx.Local, filename, src, mode)
 }
 
 // ParseFSFile parses the source code of a single Go+ source file and returns the corresponding ast.File node.
@@ -299,7 +282,7 @@ func readSource(src interface{}) ([]byte, error) {
 			return s.Bytes(), nil
 		}
 	case io.Reader:
-		return ioutil.ReadAll(s)
+		return io.ReadAll(s)
 	}
 	return nil, errInvalidSource
 }
diff --git a/x/build/build.go b/x/build/build.go
index db7086dd..9373c616 100644
--- a/x/build/build.go
+++ b/x/build/build.go
@@ -22,10 +22,12 @@ import (
 	goast "go/ast"
 	"go/types"
 	"path"
+	"path/filepath"
 
 	"github.com/goplus/gop/ast"
 	"github.com/goplus/gop/cl"
 	"github.com/goplus/gop/parser"
+	"github.com/goplus/gop/parser/fsx/memfs"
 	"github.com/goplus/gop/token"
 	"github.com/goplus/gox"
 	"github.com/goplus/gox/packages"
@@ -139,12 +141,12 @@ func (c *Context) ParseFSDir(fs parser.FileSystem, dir string) (*Package, error)
 	return c.loadPackage(dir, pkgs)
 }
 
-func (c *Context) ParseFile(fname string, src interface{}) (*Package, error) {
-	fs, err := newFileFS(fname, src)
+func (c *Context) ParseFile(file string, src interface{}) (*Package, error) {
+	fs, err := memfs.File(file, src)
 	if err != nil {
 		return nil, err
 	}
-	return c.ParseFSDir(fs, fs.dir)
+	return c.ParseFSDir(fs, filepath.Dir(file))
 }
 
 func (c *Context) loadPackage(srcDir string, pkgs map[string]*ast.Package) (*Package, error) {


Test patch:
diff --git a/ast/goptest/gopq.go b/ast/goptest/gopq.go
index b51182b1..c567dda2 100644
--- a/ast/goptest/gopq.go
+++ b/ast/goptest/gopq.go
@@ -20,7 +20,7 @@ import (
 	"go/token"
 
 	"github.com/goplus/gop/ast/gopq"
-	"github.com/goplus/gop/parser/parsertest"
+	"github.com/goplus/gop/parser/fsx/memfs"
 )
 
 // -----------------------------------------------------------------------------
@@ -28,7 +28,7 @@ import (
 // New creates a nodeset object that represents a Go+ dom tree.
 func New(script string) (gopq.NodeSet, error) {
 	fset := token.NewFileSet()
-	fs := parsertest.NewSingleFileFS("/foo", "bar.gop", script)
+	fs := memfs.SingleFile("/foo", "bar.gop", script)
 	return gopq.NewSourceFrom(fset, fs, "/foo", nil, 0)
 }
 
diff --git a/cl/compile_spx_test.go b/cl/compile_spx_test.go
index fc25f66f..7ceb8a59 100644
--- a/cl/compile_spx_test.go
+++ b/cl/compile_spx_test.go
@@ -24,7 +24,7 @@ import (
 
 	"github.com/goplus/gop/cl"
 	"github.com/goplus/gop/parser"
-	"github.com/goplus/gop/parser/parsertest"
+	"github.com/goplus/gop/parser/fsx/memfs"
 	"github.com/goplus/gop/scanner"
 	"github.com/goplus/mod/modfile"
 )
@@ -77,7 +77,7 @@ func gopSpxTestExConf(t *testing.T, name string, conf *cl.Config, gmx, spxcode,
 		cl.SetDisableRecover(true)
 		defer cl.SetDisableRecover(false)
 
-		fs := parsertest.NewTwoFilesFS("/foo", spxfile, spxcode, gmxfile, gmx)
+		fs := memfs.TwoFiles("/foo", spxfile, spxcode, gmxfile, gmx)
 		pkgs, err := parser.ParseFSDir(gblFset, fs, "/foo", spxParserConf())
 		if err != nil {
 			scanner.PrintError(os.Stderr, err)
@@ -101,7 +101,7 @@ func gopSpxTestExConf(t *testing.T, name string, conf *cl.Config, gmx, spxcode,
 }
 
 func gopSpxErrorTestEx(t *testing.T, msg, gmx, spxcode, gmxfile, spxfile string) {
-	fs := parsertest.NewTwoFilesFS("/foo", spxfile, spxcode, gmxfile, gmx)
+	fs := memfs.TwoFiles("/foo", spxfile, spxcode, gmxfile, gmx)
 	pkgs, err := parser.ParseFSDir(gblFset, fs, "/foo", spxParserConf())
 	if err != nil {
 		scanner.PrintError(os.Stderr, err)
diff --git a/cl/compile_test.go b/cl/compile_test.go
index 8721668f..21e2d561 100644
--- a/cl/compile_test.go
+++ b/cl/compile_test.go
@@ -26,7 +26,7 @@ import (
 	"github.com/goplus/gop"
 	"github.com/goplus/gop/cl"
 	"github.com/goplus/gop/parser"
-	"github.com/goplus/gop/parser/parsertest"
+	"github.com/goplus/gop/parser/fsx/memfs"
 	"github.com/goplus/gop/scanner"
 	"github.com/goplus/gop/token"
 	"github.com/goplus/gox"
@@ -69,17 +69,17 @@ func gopClTest(t *testing.T, gopcode, expected string) {
 }
 
 func gopClTestFile(t *testing.T, gopcode, expected string, fname string) {
-	fs := parsertest.NewSingleFileFS("/foo", fname, gopcode)
+	fs := memfs.SingleFile("/foo", fname, gopcode)
 	gopClTestFS(t, gblConf, fs, "main", expected)
 }
 
 func gopClTestEx(t *testing.T, conf *cl.Config, pkgname, gopcode, expected string) {
-	fs := parsertest.NewSingleFileFS("/foo", "bar.gop", gopcode)
+	fs := memfs.SingleFile("/foo", "bar.gop", gopcode)
 	gopClTestFS(t, conf, fs, pkgname, expected)
 }
 
 func gopMixedClTest(t *testing.T, pkgname, gocode, gopcode, expected string) {
-	fs := parsertest.NewTwoFilesFS("/foo", "a.go", gocode, "b.gop", gopcode)
+	fs := memfs.TwoFiles("/foo", "a.go", gocode, "b.gop", gopcode)
 	gopClTestFS(t, gblConf, fs, pkgname, expected)
 }
 
diff --git a/cl/error_msg_test.go b/cl/error_msg_test.go
index 28e92643..1a1f049a 100644
--- a/cl/error_msg_test.go
+++ b/cl/error_msg_test.go
@@ -25,7 +25,7 @@ import (
 
 	"github.com/goplus/gop/cl"
 	"github.com/goplus/gop/parser"
-	"github.com/goplus/gop/parser/parsertest"
+	"github.com/goplus/gop/parser/fsx/memfs"
 	"github.com/goplus/gop/scanner"
 )
 
@@ -34,7 +34,7 @@ func codeErrorTest(t *testing.T, msg, src string) {
 }
 
 func codeErrorTestEx(t *testing.T, pkgname, filename, msg, src string) {
-	fs := parsertest.NewSingleFileFS("/foo", filename, src)
+	fs := memfs.SingleFile("/foo", filename, src)
 	pkgs, err := parser.ParseFSDir(gblFset, fs, "/foo", parser.Config{})
 	if err != nil {
 		scanner.PrintError(os.Stderr, err)
diff --git a/cl/run_test.go b/cl/run_test.go
index e05b025d..7f31b1b7 100644
--- a/cl/run_test.go
+++ b/cl/run_test.go
@@ -13,7 +13,7 @@ import (
 
 	"github.com/goplus/gop/cl"
 	"github.com/goplus/gop/parser"
-	"github.com/goplus/gop/parser/parsertest"
+	"github.com/goplus/gop/parser/fsx/memfs"
 	"github.com/goplus/gop/scanner"
 )
 
@@ -83,7 +83,7 @@ func genGo(t *testing.T, conf *cl.Config, gopcode string) []byte {
 	cl.SetDisableRecover(true)
 	defer cl.SetDisableRecover(false)
 
-	fs := parsertest.NewSingleFileFS("/foo", "bar.gop", gopcode)
+	fs := memfs.SingleFile("/foo", "bar.gop", gopcode)
 	pkgs, err := parser.ParseFSDir(gblFset, fs, "/foo", parser.Config{Mode: parser.ParseComments})
 	if err != nil {
 		scanner.PrintError(os.Stderr, err)
diff --git a/cl/typeparams_test.go b/cl/typeparams_test.go
index 64adabba..10463024 100644
--- a/cl/typeparams_test.go
+++ b/cl/typeparams_test.go
@@ -11,7 +11,7 @@ import (
 
 	"github.com/goplus/gop/cl"
 	"github.com/goplus/gop/parser"
-	"github.com/goplus/gop/parser/parsertest"
+	"github.com/goplus/gop/parser/fsx/memfs"
 )
 
 func TestTypeParamsFunc(t *testing.T) {
@@ -203,7 +203,7 @@ func mixedErrorTest(t *testing.T, msg, gocode, gopcode string) {
 }
 
 func mixedErrorTestEx(t *testing.T, pkgname, msg, gocode, gopcode string) {
-	fs := parsertest.NewTwoFilesFS("/foo", "a.go", gocode, "b.gop", gopcode)
+	fs := memfs.TwoFiles("/foo", "a.go", gocode, "b.gop", gopcode)
 	pkgs, err := parser.ParseFSDir(gblFset, fs, "/foo", parser.Config{})
 	if err != nil {
 		scanner.PrintError(os.Stderr, err)
diff --git a/parser/parsertest/memfs.go b/parser/fsx/memfs/memfs.go
similarity index 69%
rename from parser/parsertest/memfs.go
rename to parser/fsx/memfs/memfs.go
index 09e7b9ef..82efae4a 100644
--- a/parser/parsertest/memfs.go
+++ b/parser/fsx/memfs/memfs.go
@@ -14,12 +14,12 @@
  * limitations under the License.
  */
 
-package parsertest
+package memfs
 
 import (
 	"io/fs"
-	"os"
 	"path"
+	"path/filepath"
 	"syscall"
 	"time"
 )
@@ -28,22 +28,27 @@ import (
 
 type memFileInfo struct {
 	name string
+	size int
 }
 
 func (p *memFileInfo) Name() string {
-	return p.name
+	return filepath.Base(p.name)
 }
 
 func (p *memFileInfo) Size() int64 {
+	return int64(p.size)
+}
+
+func (p *memFileInfo) Mode() fs.FileMode {
 	return 0
 }
 
-func (p *memFileInfo) Mode() os.FileMode {
+func (p *memFileInfo) Type() fs.FileMode {
 	return 0
 }
 
 func (p *memFileInfo) ModTime() (t time.Time) {
-	return
+	return time.Now()
 }
 
 func (p *memFileInfo) IsDir() bool {
@@ -54,22 +59,28 @@ func (p *memFileInfo) Sys() interface{} {
 	return nil
 }
 
-// MemFS represents a file system in memory.
-type MemFS struct {
+func (p *memFileInfo) Info() (fs.FileInfo, error) {
+	return p, nil
+}
+
+// -----------------------------------------------------------------------------
+
+// FS represents a file system in memory.
+type FS struct {
 	dirs  map[string][]string
 	files map[string]string
 }
 
-// NewMemFS creates a MemFS instance.
-func NewMemFS(dirs map[string][]string, files map[string]string) *MemFS {
-	return &MemFS{dirs: dirs, files: files}
+// New creates a file system instance.
+func New(dirs map[string][]string, files map[string]string) *FS {
+	return &FS{dirs: dirs, files: files}
 }
 
 // ReadDir reads the directory named by dirname and returns
 // a list of directory entries sorted by filename.
-func (p *MemFS) ReadDir(dirname string) ([]fs.FileInfo, error) {
+func (p *FS) ReadDir(dirname string) ([]fs.DirEntry, error) {
 	if items, ok := p.dirs[dirname]; ok {
-		fis := make([]fs.FileInfo, len(items))
+		fis := make([]fs.DirEntry, len(items))
 		for i, item := range items {
 			fis[i] = &memFileInfo{name: item}
 		}
@@ -82,7 +93,7 @@ func (p *MemFS) ReadDir(dirname string) ([]fs.FileInfo, error) {
 // A successful call returns err == nil, not err == EOF. Because ReadFile
 // reads the whole file, it does not treat an EOF from Read as an error
 // to be reported.
-func (p *MemFS) ReadFile(filename string) ([]byte, error) {
+func (p *FS) ReadFile(filename string) ([]byte, error) {
 	if data, ok := p.files[filename]; ok {
 		return []byte(data), nil
 	}
@@ -94,24 +105,24 @@ func (p *MemFS) ReadFile(filename string) ([]byte, error) {
 // The result is Cleaned. However, if the argument list is
 // empty or all its elements are empty, Join returns
 // an empty string.
-func (p *MemFS) Join(elem ...string) string {
+func (p *FS) Join(elem ...string) string {
 	return path.Join(elem...)
 }
 
 // -----------------------------------------------------------------------------
 
-// NewSingleFileFS creates a file system that only contains a single file.
-func NewSingleFileFS(dir string, fname string, data string) *MemFS {
-	return NewMemFS(map[string][]string{
+// SingleFile creates a file system that only contains a single file.
+func SingleFile(dir string, fname string, data string) *FS {
+	return New(map[string][]string{
 		dir: {fname},
 	}, map[string]string{
 		path.Join(dir, fname): data,
 	})
 }
 
-// NewTwoFilesFS creates a file system that contains two files.
-func NewTwoFilesFS(dir string, fname1, data1, fname2, data2 string) *MemFS {
-	return NewMemFS(map[string][]string{
+// TwoFiles creates a file system that contains two files.
+func TwoFiles(dir string, fname1, data1, fname2, data2 string) *FS {
+	return New(map[string][]string{
 		dir: {fname1, fname2},
 	}, map[string]string{
 		path.Join(dir, fname1): data1,
diff --git a/parser/parserdir_test.go b/parser/parserdir_test.go
index bb84de9f..10fcc025 100644
--- a/parser/parserdir_test.go
+++ b/parser/parserdir_test.go
@@ -26,6 +26,7 @@ import (
 	"syscall"
 	"testing"
 
+	"github.com/goplus/gop/parser/fsx/memfs"
 	"github.com/goplus/gop/parser/parsertest"
 	"github.com/goplus/gop/scanner"
 	"github.com/goplus/gop/token"
@@ -132,7 +133,7 @@ func TestParseGoFiles(t *testing.T) {
 
 func TestGopAutoGen(t *testing.T) {
 	fset := token.NewFileSet()
-	fs := parsertest.NewSingleFileFS("/foo", "gop_autogen.go", ``)
+	fs := memfs.SingleFile("/foo", "gop_autogen.go", ``)
 	pkgs, err := ParseFSDir(fset, fs, "/foo", Config{})
 	if err != nil {
 		t.Fatal("ParseFSDir:", err)
@@ -144,7 +145,7 @@ func TestGopAutoGen(t *testing.T) {
 
 func TestGoFile(t *testing.T) {
 	fset := token.NewFileSet()
-	fs := parsertest.NewSingleFileFS("/foo", "test.go", `package foo`)
+	fs := memfs.SingleFile("/foo", "test.go", `package foo`)
 	pkgs, err := ParseFSDir(fset, fs, "/foo", Config{})
 	if err != nil {
 		t.Fatal("ParseFSDir:", err)
@@ -156,25 +157,25 @@ func TestGoFile(t *testing.T) {
 
 func TestErrParse(t *testing.T) {
 	fset := token.NewFileSet()
-	fs := parsertest.NewSingleFileFS("/foo", "test.go", `package foo bar`)
+	fs := memfs.SingleFile("/foo", "test.go", `package foo bar`)
 	_, err := ParseFSDir(fset, fs, "/foo", Config{})
 	if err == nil {
 		t.Fatal("ParseFSDir test.go: no error?")
 	}
 
-	fs = parsertest.NewSingleFileFS("/foo", "test.gop", `package foo bar`)
+	fs = memfs.SingleFile("/foo", "test.gop", `package foo bar`)
 	_, err = ParseFSDir(fset, fs, "/foo", Config{})
 	if err == nil {
 		t.Fatal("ParseFSDir test.gop: no error?")
 	}
 
-	fs = parsertest.NewMemFS(map[string][]string{"/foo": {"test.go"}}, map[string]string{})
+	fs = memfs.New(map[string][]string{"/foo": {"test.go"}}, map[string]string{})
 	_, err = ParseFSDir(fset, fs, "/foo", Config{})
 	if err != syscall.ENOENT {
 		t.Fatal("ParseFSDir:", err)
 	}
 
-	fs = parsertest.NewSingleFileFS("/foo", "test.abc.gox", `package foo`)
+	fs = memfs.SingleFile("/foo", "test.abc.gox", `package foo`)
 	_, err = ParseFSDir(fset, fs, "/foo", Config{})
 	if err == nil {
 		t.Fatal("ParseFSDir test.gop: no error?")
diff --git a/x/build/build_test.go b/x/build/build_test.go
index b16abf6c..72e46f14 100644
--- a/x/build/build_test.go
+++ b/x/build/build_test.go
@@ -21,7 +21,6 @@ import (
 	"fmt"
 	"go/printer"
 	"go/types"
-	"io/fs"
 	"io/ioutil"
 	"os"
 	"path"
@@ -30,6 +29,7 @@ import (
 	"testing"
 
 	"github.com/goplus/gop/cl"
+	"github.com/goplus/gop/parser/fsx"
 	"github.com/goplus/gop/x/build"
 )
 
@@ -427,8 +427,8 @@ func testFrom(t *testing.T, name, dir string) {
 	if err != nil {
 		t.Fatal("BuildDir failed:", err)
 	}
-	if chk, err := ioutil.ReadFile(filepath.Join(dir, name+".expect")); err == nil {
-		if bytes.Compare(data, chk) != 0 {
+	if chk, err := os.ReadFile(filepath.Join(dir, name+".expect")); err == nil {
+		if !bytes.Equal(data, chk) {
 			t.Fatalf("-- %v output check error --\n%v\n--\n%v", name, string(data), string(chk))
 		}
 	}
@@ -438,20 +438,6 @@ func TestFromTestdata(t *testing.T) {
 	testFromDir(t, "./_testdata")
 }
 
-type localFS struct{}
-
-func (p localFS) ReadDir(dirname string) ([]fs.FileInfo, error) {
-	return ioutil.ReadDir(dirname)
-}
-
-func (p localFS) ReadFile(filename string) ([]byte, error) {
-	return os.ReadFile(filename)
-}
-
-func (p localFS) Join(elem ...string) string {
-	return filepath.Join(elem...)
-}
-
 func TestFS(t *testing.T) {
 	var expect = []byte(`package main
 
@@ -461,11 +447,11 @@ func main() {
 	fmt.Println("Go+")
 }
 `)
-	data, err := ctx.BuildFSDir(localFS{}, "./_testdata/hello")
+	data, err := ctx.BuildFSDir(fsx.Local, "./_testdata/hello")
 	if err != nil {
 		t.Fatal("build fs dir failed", err)
 	}
-	if bytes.Compare(data, expect) != 0 {
+	if !bytes.Equal(data, expect) {
 		t.Fatal("build fs data failed", string(data))
 	}
 }
@@ -479,7 +465,7 @@ func main() {
 	fmt.Println("Go+")
 }
 `)
-	pkg, err := ctx.ParseFSDir(localFS{}, "./_testdata/hello")
+	pkg, err := ctx.ParseFSDir(fsx.Local, "./_testdata/hello")
 	if err != nil {
 		t.Fatal("parser fs dir failed", err)
 	}
@@ -488,7 +474,7 @@ func main() {
 	if err != nil {
 		t.Fatal("fprint ast error", err)
 	}
-	if bytes.Compare(buf.Bytes(), expect) != 0 {
+	if !bytes.Equal(buf.Bytes(), expect) {
 		t.Fatal("build ast data failed", buf.String())
 	}
 }
@@ -502,7 +488,7 @@ func TestError(t *testing.T) {
 	if err == nil {
 		t.Fatal("BuildDir: no error?")
 	}
-	_, err = ctx.BuildFSDir(localFS{}, "./testdata/nofound")
+	_, err = ctx.BuildFSDir(fsx.Local, "./testdata/nofound")
 	if err == nil {
 		t.Fatal("BuildDir: no error?")
 	}