diff --git a/gno.land/cmd/gnoland/start.go b/gno.land/cmd/gnoland/start.go index 4bcd2742627..e2a2b2e58d7 100644 --- a/gno.land/cmd/gnoland/start.go +++ b/gno.land/cmd/gnoland/start.go @@ -234,7 +234,7 @@ func makeGenesisDoc( for _, pkg := range nonDraftPkgs { // open files in directory as MemPackage. - memPkg := gno.ReadMemPackage(pkg.Path(), pkg.Name()) + memPkg := gno.ReadMemPackage(pkg.Dir, pkg.Name) var tx std.Tx tx.Msgs = []std.Msg{ vmm.MsgAddPackage{ diff --git a/gnovm/cmd/gno/test.go b/gnovm/cmd/gno/test.go index b99645df0b4..d96693ae993 100644 --- a/gnovm/cmd/gno/test.go +++ b/gnovm/cmd/gno/test.go @@ -136,9 +136,13 @@ func execTest(cfg *testCfg, args []string, io *commands.IO) error { cfg.rootDir = guessRootDir() } - pkgPaths, err := gnoPackagesFromArgs(args) + paths, err := gnoPackagesFromArgs(args) if err != nil { - return fmt.Errorf("list packages from args: %w", err) + return fmt.Errorf("list package paths from args: %w", err) + } + if len(paths) == 0 { + io.ErrPrintln("no packages to test") + return nil } if cfg.timeout > 0 { @@ -148,21 +152,26 @@ func execTest(cfg *testCfg, args []string, io *commands.IO) error { }() } + subPkgs, err := gnomod.SubPkgsFromPaths(paths) + if err != nil { + return fmt.Errorf("list sub packages: %w", err) + } + buildErrCount := 0 testErrCount := 0 - for _, pkgPath := range pkgPaths { + for _, pkg := range subPkgs { if cfg.precompile { if verbose { - io.ErrPrintfln("=== PREC %s", pkgPath) + io.ErrPrintfln("=== PREC %s", pkg.Dir) } precompileOpts := newPrecompileOptions(&precompileCfg{ output: tempdirRoot, }) - err := precompilePkg(importPath(pkgPath), precompileOpts) + err := precompilePkg(importPath(pkg.Dir), precompileOpts) if err != nil { io.ErrPrintln(err) io.ErrPrintln("FAIL") - io.ErrPrintfln("FAIL %s", pkgPath) + io.ErrPrintfln("FAIL %s", pkg.Dir) io.ErrPrintln("FAIL") buildErrCount++ @@ -170,9 +179,9 @@ func execTest(cfg *testCfg, args []string, io *commands.IO) error { } if verbose { - io.ErrPrintfln("=== BUILD %s", pkgPath) + io.ErrPrintfln("=== BUILD %s", pkg.Dir) } - tempDir, err := ResolvePath(tempdirRoot, importPath(pkgPath)) + tempDir, err := ResolvePath(tempdirRoot, importPath(pkg.Dir)) if err != nil { return errors.New("cannot resolve build dir") } @@ -180,7 +189,7 @@ func execTest(cfg *testCfg, args []string, io *commands.IO) error { if err != nil { io.ErrPrintln(err) io.ErrPrintln("FAIL") - io.ErrPrintfln("FAIL %s", pkgPath) + io.ErrPrintfln("FAIL %s", pkg.Dir) io.ErrPrintln("FAIL") buildErrCount++ @@ -188,35 +197,27 @@ func execTest(cfg *testCfg, args []string, io *commands.IO) error { } } - unittestFiles, err := filepath.Glob(filepath.Join(pkgPath, "*_test.gno")) - if err != nil { - log.Fatal(err) - } - filetestFiles, err := filepath.Glob(filepath.Join(pkgPath, "*_filetest.gno")) - if err != nil { - log.Fatal(err) - } - if len(unittestFiles) == 0 && len(filetestFiles) == 0 { - io.ErrPrintfln("? %s \t[no test files]", pkgPath) + if len(pkg.TestGnoFiles) == 0 && len(pkg.FiletestGnoFiles) == 0 { + io.ErrPrintfln("? %s \t[no test files]", pkg.Dir) continue } - sort.Strings(unittestFiles) - sort.Strings(filetestFiles) + sort.Strings(pkg.TestGnoFiles) + sort.Strings(pkg.FiletestGnoFiles) startedAt := time.Now() - err = gnoTestPkg(pkgPath, unittestFiles, filetestFiles, cfg, io) + err = gnoTestPkg(pkg.Dir, pkg.TestGnoFiles, pkg.FiletestGnoFiles, cfg, io) duration := time.Since(startedAt) dstr := fmtDuration(duration) if err != nil { - io.ErrPrintfln("%s: test pkg: %v", pkgPath, err) + io.ErrPrintfln("%s: test pkg: %v", pkg.Dir, err) io.ErrPrintfln("FAIL") - io.ErrPrintfln("FAIL %s \t%s", pkgPath, dstr) + io.ErrPrintfln("FAIL %s \t%s", pkg.Dir, dstr) io.ErrPrintfln("FAIL") testErrCount++ } else { - io.ErrPrintfln("ok %s \t%s", pkgPath, dstr) + io.ErrPrintfln("ok %s \t%s", pkg.Dir, dstr) } } if testErrCount > 0 || buildErrCount > 0 { diff --git a/gnovm/cmd/gno/test_test.go b/gnovm/cmd/gno/test_test.go index 67df26facd7..856f63a9053 100644 --- a/gnovm/cmd/gno/test_test.go +++ b/gnovm/cmd/gno/test_test.go @@ -17,7 +17,8 @@ func TestTest(t *testing.T) { errShouldContain: "no such file or directory", }, { - args: []string{"test", "../../tests/integ/empty-dir"}, + args: []string{"test", "../../tests/integ/empty-dir"}, + stderrShouldContain: "no packages to test", }, { // FIXME: should have an output @@ -98,6 +99,11 @@ func TestTest(t *testing.T) { stdoutShouldContain: "RUN TestSprintf", stderrShouldContain: "ok ./../../../examples/gno.land/p/demo/ufmt", }, + { + args: []string{"test", "../../../examples/gno.land/p/demo/ufmt/ufmt_test.gno"}, + stdoutShouldContain: "RUN TestSprintf", + stderrShouldContain: "ok ../../../examples/gno.land/p/demo/ufmt", + }, { args: []string{"test", "--verbose", "../../../examples/gno.land/p/demo/ufmt"}, stdoutShouldContain: "RUN TestSprintf", diff --git a/gnovm/cmd/gno/util.go b/gnovm/cmd/gno/util.go index 3de742d2a5a..8288539c97b 100644 --- a/gnovm/cmd/gno/util.go +++ b/gnovm/cmd/gno/util.go @@ -86,9 +86,14 @@ func gnoPackagesFromArgs(args []string) ([]string, error) { } visited[parentDir] = true - // cannot use path.Join or filepath.Join, because we need - // to ensure that ./ is the prefix to pass to go build. - pkg := "./" + parentDir + pkg := parentDir + if !filepath.IsAbs(parentDir) { + // cannot use path.Join or filepath.Join, because we need + // to ensure that ./ is the prefix to pass to go build. + // if not absolute. + pkg = "./" + parentDir + } + paths = append(paths, pkg) return nil }) diff --git a/gnovm/pkg/gnomod/pkg.go b/gnovm/pkg/gnomod/pkg.go index f74b92608c8..f6fe7f60301 100644 --- a/gnovm/pkg/gnomod/pkg.go +++ b/gnovm/pkg/gnomod/pkg.go @@ -5,13 +5,25 @@ import ( "io/fs" "os" "path/filepath" + "strings" ) type Pkg struct { - name string - path string - draft bool - requires []string + Dir string // absolute path to package dir + Name string // package name + Requires []string // dependencies + Draft bool // whether the package is a draft +} + +type SubPkg struct { + Dir string // absolute path to package dir + ImportPath string // import path of package + Root string // Root dir containing this package, i.e dir containing gno.mod file + Imports []string // imports used by this package + + GnoFiles []string // .gno source files (excluding TestGnoFiles, FiletestGnoFiles) + TestGnoFiles []string // _test.gno source files + FiletestGnoFiles []string // _filetest.gno source files } type ( @@ -19,26 +31,6 @@ type ( SortedPkgList []Pkg ) -// Name returns the name of the package. -func (p Pkg) Name() string { - return p.name -} - -// Path returns the path of the package. -func (p Pkg) Path() string { - return p.path -} - -// Draft returns whether the package is a draft. -func (p Pkg) Draft() bool { - return p.draft -} - -// Requires returns the required packages of the package. -func (p Pkg) Requires() []string { - return p.requires -} - // sortPkgs sorts the given packages by their dependencies. func (pl PkgList) Sort() (SortedPkgList, error) { visited := make(map[string]bool) @@ -57,21 +49,21 @@ func (pl PkgList) Sort() (SortedPkgList, error) { // visitNode visits a package's and its dependencies dependencies and adds them to the sorted list. func visitPackage(pkg Pkg, pkgs []Pkg, visited, onStack map[string]bool, sortedPkgs *[]Pkg) error { - if onStack[pkg.name] { - return fmt.Errorf("cycle detected: %s", pkg.name) + if onStack[pkg.Name] { + return fmt.Errorf("cycle detected: %s", pkg.Name) } - if visited[pkg.name] { + if visited[pkg.Name] { return nil } - visited[pkg.name] = true - onStack[pkg.name] = true + visited[pkg.Name] = true + onStack[pkg.Name] = true // Visit package's dependencies - for _, req := range pkg.requires { + for _, req := range pkg.Requires { found := false for _, p := range pkgs { - if p.name != req { + if p.Name != req { continue } if err := visitPackage(p, pkgs, visited, onStack, sortedPkgs); err != nil { @@ -81,11 +73,11 @@ func visitPackage(pkg Pkg, pkgs []Pkg, visited, onStack map[string]bool, sortedP break } if !found { - return fmt.Errorf("missing dependency '%s' for package '%s'", req, pkg.name) + return fmt.Errorf("missing dependency '%s' for package '%s'", req, pkg.Name) } } - onStack[pkg.name] = false + onStack[pkg.Name] = false *sortedPkgs = append(*sortedPkgs, pkg) return nil } @@ -120,10 +112,10 @@ func ListPkgs(root string) (PkgList, error) { } pkgs = append(pkgs, Pkg{ - name: gnoMod.Module.Mod.Path, - path: path, - draft: gnoMod.Draft, - requires: func() []string { + Dir: path, + Name: gnoMod.Module.Mod.Path, + Draft: gnoMod.Draft, + Requires: func() []string { var reqs []string for _, req := range gnoMod.Require { reqs = append(reqs, req.Mod.Path) @@ -147,15 +139,15 @@ func (sp SortedPkgList) GetNonDraftPkgs() SortedPkgList { draft := make(map[string]bool) for _, pkg := range sp { - if pkg.draft { - draft[pkg.name] = true + if pkg.Draft { + draft[pkg.Name] = true continue } dependsOnDraft := false - for _, req := range pkg.requires { + for _, req := range pkg.Requires { if draft[req] { dependsOnDraft = true - draft[pkg.name] = true + draft[pkg.Name] = true break } } @@ -165,3 +157,94 @@ func (sp SortedPkgList) GetNonDraftPkgs() SortedPkgList { } return res } + +// SubPkgsFromPaths returns a list of subpackages from the given paths. +func SubPkgsFromPaths(paths []string) ([]*SubPkg, error) { + for _, path := range paths { + fi, err := os.Stat(path) + if err != nil { + return nil, err + } + if fi.IsDir() { + continue + } + if filepath.Ext(path) != ".gno" { + return nil, fmt.Errorf("files must be .gno files: %s", path) + } + + subPkg, err := GnoFileSubPkg(paths) + if err != nil { + return nil, err + } + return []*SubPkg{subPkg}, nil + } + + subPkgs := make([]*SubPkg, 0, len(paths)) + for _, path := range paths { + subPkg := SubPkg{} + + matches, err := filepath.Glob(filepath.Join(path, "*.gno")) + if err != nil { + return nil, fmt.Errorf("failed to match pattern: %w", err) + } + + subPkg.Dir = path + for _, match := range matches { + if strings.HasSuffix(match, "_test.gno") { + subPkg.TestGnoFiles = append(subPkg.TestGnoFiles, match) + continue + } + + if strings.HasSuffix(match, "_filetest.gno") { + subPkg.FiletestGnoFiles = append(subPkg.FiletestGnoFiles, match) + continue + } + subPkg.GnoFiles = append(subPkg.GnoFiles, match) + } + + subPkgs = append(subPkgs, &subPkg) + } + + return subPkgs, nil +} + +// GnoFileSubPkg returns a subpackage from the given .gno files. +func GnoFileSubPkg(files []string) (*SubPkg, error) { + subPkg := SubPkg{} + firstDir := "" + for _, file := range files { + if filepath.Ext(file) != ".gno" { + return nil, fmt.Errorf("files must be .gno files: %s", file) + } + + fi, err := os.Stat(file) + if err != nil { + return nil, err + } + if fi.IsDir() { + return nil, fmt.Errorf("%s is a directory, should be a Gno file", file) + } + + dir := filepath.Dir(file) + if firstDir == "" { + firstDir = dir + } + if dir != firstDir { + return nil, fmt.Errorf("all files must be in one directory; have %s and %s", firstDir, dir) + } + + if strings.HasSuffix(file, "_test.gno") { + subPkg.TestGnoFiles = append(subPkg.TestGnoFiles, file) + continue + } + + if strings.HasSuffix(file, "_filetest.gno") { + subPkg.FiletestGnoFiles = append(subPkg.FiletestGnoFiles, file) + continue + } + subPkg.GnoFiles = append(subPkg.GnoFiles, file) + } + subPkg.Dir = firstDir + + return &subPkg, nil +} diff --git a/gnovm/pkg/gnomod/pkg_test.go b/gnovm/pkg/gnomod/pkg_test.go index e69c4f0351a..587a0bb8f81 100644 --- a/gnovm/pkg/gnomod/pkg_test.go +++ b/gnovm/pkg/gnomod/pkg_test.go @@ -197,7 +197,7 @@ func TestListAndNonDraftPkgs(t *testing.T) { require.NoError(t, err) assert.Equal(t, len(tc.outListPkgs), len(pkgs)) for _, p := range pkgs { - assert.Contains(t, tc.outListPkgs, p.name) + assert.Contains(t, tc.outListPkgs, p.Name) } // Sort packages @@ -208,7 +208,7 @@ func TestListAndNonDraftPkgs(t *testing.T) { nonDraft := sorted.GetNonDraftPkgs() assert.Equal(t, len(tc.outNonDraftPkgs), len(nonDraft)) for _, p := range nonDraft { - assert.Contains(t, tc.outNonDraftPkgs, p.name) + assert.Contains(t, tc.outNonDraftPkgs, p.Name) } }) } @@ -240,30 +240,30 @@ func TestSortPkgs(t *testing.T) { }, { desc: "no_dependencies", in: []Pkg{ - {name: "pkg1", path: "/path/to/pkg1", requires: []string{}}, - {name: "pkg2", path: "/path/to/pkg2", requires: []string{}}, - {name: "pkg3", path: "/path/to/pkg3", requires: []string{}}, + {Name: "pkg1", Dir: "/path/to/pkg1", Requires: []string{}}, + {Name: "pkg2", Dir: "/path/to/pkg2", Requires: []string{}}, + {Name: "pkg3", Dir: "/path/to/pkg3", Requires: []string{}}, }, expected: []string{"pkg1", "pkg2", "pkg3"}, }, { desc: "circular_dependencies", in: []Pkg{ - {name: "pkg1", path: "/path/to/pkg1", requires: []string{"pkg2"}}, - {name: "pkg2", path: "/path/to/pkg2", requires: []string{"pkg1"}}, + {Name: "pkg1", Dir: "/path/to/pkg1", Requires: []string{"pkg2"}}, + {Name: "pkg2", Dir: "/path/to/pkg2", Requires: []string{"pkg1"}}, }, shouldErr: true, }, { desc: "missing_dependencies", in: []Pkg{ - {name: "pkg1", path: "/path/to/pkg1", requires: []string{"pkg2"}}, + {Name: "pkg1", Dir: "/path/to/pkg1", Requires: []string{"pkg2"}}, }, shouldErr: true, }, { desc: "valid_dependencies", in: []Pkg{ - {name: "pkg1", path: "/path/to/pkg1", requires: []string{"pkg2"}}, - {name: "pkg2", path: "/path/to/pkg2", requires: []string{"pkg3"}}, - {name: "pkg3", path: "/path/to/pkg3", requires: []string{}}, + {Name: "pkg1", Dir: "/path/to/pkg1", Requires: []string{"pkg2"}}, + {Name: "pkg2", Dir: "/path/to/pkg2", Requires: []string{"pkg3"}}, + {Name: "pkg3", Dir: "/path/to/pkg3", Requires: []string{}}, }, expected: []string{"pkg3", "pkg2", "pkg1"}, }, @@ -275,7 +275,7 @@ func TestSortPkgs(t *testing.T) { } else { require.NoError(t, err) for i := range tc.expected { - assert.Equal(t, tc.expected[i], sorted[i].name) + assert.Equal(t, tc.expected[i], sorted[i].Name) } } })