From 01b07197f954d025c6094286eac1f9a5a8a72398 Mon Sep 17 00:00:00 2001 From: Morgan Bazalgette Date: Sun, 26 May 2024 13:41:21 +0200 Subject: [PATCH 001/143] copy over from gnoutil pr --- gnovm/cmd/gno/lint.go | 3 +- gnovm/cmd/gno/lint_test.go | 9 +- gnovm/cmd/gno/run.go | 49 +-- gnovm/cmd/gno/test.go | 63 +-- .../cmd/gno/testdata/gno_test/empty_dir.txtar | 4 +- .../testdata/gno_test/lint_no_gnomod.txtar | 2 +- .../testdata/gno_transpile/02_empty_dir.txtar | 4 +- .../03_gno_files_parse_error.txtar | 2 +- .../gno_transpile/04_valid_gno_files.txtar | 2 +- .../gno_transpile/05_skip_fmt_flag.txtar | 2 +- .../gno_transpile/06_build_flag.txtar | 2 +- .../07_build_flag_with_build_error.txtar | 2 +- .../09_gno_files_whitelist_error.txtar | 2 +- gnovm/cmd/gno/transpile.go | 7 +- gnovm/cmd/gno/util.go | 170 -------- gnovm/cmd/gno/util_test.go | 203 ---------- gnovm/pkg/gnomod/gnomod.go | 3 +- gnovm/pkg/gnomod/pkg.go | 103 ----- gnovm/pkg/importer/definitions.go | 30 ++ gnovm/pkg/importer/importer.go | 34 ++ gnovm/pkg/importer/match.go | 383 ++++++++++++++++++ gnovm/pkg/importer/match_test.go | 376 +++++++++++++++++ .../tests/integ/invalid_module_name/main.gno | 1 + 23 files changed, 888 insertions(+), 568 deletions(-) create mode 100644 gnovm/pkg/importer/definitions.go create mode 100644 gnovm/pkg/importer/importer.go create mode 100644 gnovm/pkg/importer/match.go create mode 100644 gnovm/pkg/importer/match_test.go create mode 100644 gnovm/tests/integ/invalid_module_name/main.gno diff --git a/gnovm/cmd/gno/lint.go b/gnovm/cmd/gno/lint.go index c1974094da0..76a446618e7 100644 --- a/gnovm/cmd/gno/lint.go +++ b/gnovm/cmd/gno/lint.go @@ -14,6 +14,7 @@ import ( "github.com/gnolang/gno/gnovm/pkg/gnoenv" gno "github.com/gnolang/gno/gnovm/pkg/gnolang" + "github.com/gnolang/gno/gnovm/pkg/importer" "github.com/gnolang/gno/gnovm/tests" "github.com/gnolang/gno/tm2/pkg/commands" osm "github.com/gnolang/gno/tm2/pkg/os" @@ -64,7 +65,7 @@ func execLint(cfg *lintCfg, args []string, io commands.IO) error { rootDir = gnoenv.RootDir() } - pkgPaths, err := gnoPackagesFromArgs(args) + pkgPaths, err := importer.Match(args) if err != nil { return fmt.Errorf("list packages from args: %w", err) } diff --git a/gnovm/cmd/gno/lint_test.go b/gnovm/cmd/gno/lint_test.go index fff05726e53..9a40d8d612f 100644 --- a/gnovm/cmd/gno/lint_test.go +++ b/gnovm/cmd/gno/lint_test.go @@ -9,7 +9,7 @@ func TestLintApp(t *testing.T) { errShouldBe: "flag: help requested", }, { args: []string{"lint", "--set-exit-status=0", "../../tests/integ/run_main/"}, - stderrShouldContain: "./../../tests/integ/run_main: missing 'gno.mod' file (code=1).", + stderrShouldContain: "../../tests/integ/run_main: missing 'gno.mod' file (code=1).", }, { args: []string{"lint", "--set-exit-status=0", "../../tests/integ/undefined_variable_test/undefined_variables_test.gno"}, stderrShouldContain: "undefined_variables_test.gno:6: name toto not declared (code=2)", @@ -20,11 +20,8 @@ func TestLintApp(t *testing.T) { args: []string{"lint", "--set-exit-status=0", "../../tests/integ/several-lint-errors/main.gno"}, stderrShouldContain: "../../tests/integ/several-lint-errors/main.gno:5: expected ';', found example (code=2).\n../../tests/integ/several-lint-errors/main.gno:6", }, { - args: []string{"lint", "--set-exit-status=0", "../../tests/integ/run_main/"}, - stderrShouldContain: "./../../tests/integ/run_main: missing 'gno.mod' file (code=1).", - }, { - args: []string{"lint", "--set-exit-status=0", "../../tests/integ/minimalist_gnomod/"}, - // TODO: raise an error because there is a gno.mod, but no .gno files + args: []string{"lint", "--set-exit-status=0", "../../tests/integ/minimalist_gnomod/"}, + errShouldContain: "no valid gno files", }, { args: []string{"lint", "--set-exit-status=0", "../../tests/integ/invalid_module_name/"}, // TODO: raise an error because gno.mod is invalid diff --git a/gnovm/cmd/gno/run.go b/gnovm/cmd/gno/run.go index 65315909f72..0f41228b55e 100644 --- a/gnovm/cmd/gno/run.go +++ b/gnovm/cmd/gno/run.go @@ -7,11 +7,10 @@ import ( "fmt" "io" "os" - "path/filepath" - "strings" "github.com/gnolang/gno/gnovm/pkg/gnoenv" gno "github.com/gnolang/gno/gnovm/pkg/gnolang" + "github.com/gnolang/gno/gnovm/pkg/importer" "github.com/gnolang/gno/gnovm/tests" "github.com/gnolang/gno/tm2/pkg/commands" ) @@ -137,54 +136,24 @@ func execRun(cfg *runCfg, args []string, io commands.IO) error { } func parseFiles(fnames []string, stderr io.WriteCloser) ([]*gno.FileNode, error) { - files := make([]*gno.FileNode, 0, len(fnames)) - var hasError bool - for _, fname := range fnames { - if s, err := os.Stat(fname); err == nil && s.IsDir() { - subFns, err := listNonTestFiles(fname) - if err != nil { - return nil, err - } - subFiles, err := parseFiles(subFns, stderr) - if err != nil { - return nil, err - } - files = append(files, subFiles...) - continue - } else if err != nil { - // either not found or some other kind of error -- - // in either case not a file we can parse. - return nil, err - } + gnoFnames, err := importer.Match(fnames, importer.MatchFiles("!*_test.gno", "!*_filetest.gno")) + if err != nil { + return nil, err + } + var hasError bool + files := make([]*gno.FileNode, 0, len(gnoFnames)) + for _, fname := range gnoFnames { hasError = catchRuntimeError(fname, stderr, func() { files = append(files, gno.MustReadFile(fname)) - }) + }) || hasError } - if hasError { os.Exit(1) } return files, nil } -func listNonTestFiles(dir string) ([]string, error) { - fs, err := os.ReadDir(dir) - if err != nil { - return nil, err - } - fn := make([]string, 0, len(fs)) - for _, f := range fs { - n := f.Name() - if isGnoFile(f) && - !strings.HasSuffix(n, "_test.gno") && - !strings.HasSuffix(n, "_filetest.gno") { - fn = append(fn, filepath.Join(dir, n)) - } - } - return fn, nil -} - func runExpr(m *gno.Machine, expr string) { defer func() { if r := recover(); r != nil { diff --git a/gnovm/cmd/gno/test.go b/gnovm/cmd/gno/test.go index e09e06c0418..3bcf1903b18 100644 --- a/gnovm/cmd/gno/test.go +++ b/gnovm/cmd/gno/test.go @@ -8,9 +8,9 @@ import ( "fmt" "log" "os" + "path" "path/filepath" "runtime/debug" - "sort" "strings" "text/template" "time" @@ -20,6 +20,7 @@ import ( "github.com/gnolang/gno/gnovm/pkg/gnoenv" gno "github.com/gnolang/gno/gnovm/pkg/gnolang" "github.com/gnolang/gno/gnovm/pkg/gnomod" + "github.com/gnolang/gno/gnovm/pkg/importer" "github.com/gnolang/gno/gnovm/pkg/transpiler" "github.com/gnolang/gno/gnovm/tests" "github.com/gnolang/gno/tm2/pkg/commands" @@ -173,23 +174,24 @@ func execTest(cfg *testCfg, args []string, io commands.IO) error { } defer os.RemoveAll(tempdirRoot) - // go.mod + // Create go.mod to build the precompiled files. modPath := filepath.Join(tempdirRoot, "go.mod") err = makeTestGoMod(modPath, transpiler.ImportPrefix, "1.21") if err != nil { return fmt.Errorf("write .mod file: %w", err) } - // guess opts.RootDir + // Determine root directory. if cfg.rootDir == "" { cfg.rootDir = gnoenv.RootDir() } - paths, err := targetsFromPatterns(args) + // Find targets for test. + targets, err := importer.Match(args) if err != nil { return fmt.Errorf("list targets from patterns: %w", err) } - if len(paths) == 0 { + if len(targets) == 0 { io.ErrPrintln("no packages to test") return nil } @@ -201,26 +203,30 @@ 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 _, pkg := range subPkgs { + for _, target := range targets { + pkgFiles, err := importer.Match([]string{target}, importer.MatchFiles()) + if err != nil { + return err + } + pkgDir := target + if len(pkgFiles) == 1 && pkgFiles[0] == target { + // single-file + pkgDir = path.Dir(target) + } if cfg.transpile { if verbose { - io.ErrPrintfln("=== PREC %s", pkg.Dir) + io.ErrPrintfln("=== PREC %s", pkgDir) } transpileOpts := newTranspileOptions(&transpileCfg{ output: tempdirRoot, }) - err := transpilePkg(importPath(pkg.Dir), transpileOpts) + err := transpilePkg(importPath(pkgDir), transpileOpts) if err != nil { io.ErrPrintln(err) io.ErrPrintln("FAIL") - io.ErrPrintfln("FAIL %s", pkg.Dir) + io.ErrPrintfln("FAIL %s", pkgDir) io.ErrPrintln("FAIL") buildErrCount++ @@ -228,9 +234,9 @@ func execTest(cfg *testCfg, args []string, io commands.IO) error { } if verbose { - io.ErrPrintfln("=== BUILD %s", pkg.Dir) + io.ErrPrintfln("=== BUILD %s", pkgDir) } - tempDir, err := ResolvePath(tempdirRoot, importPath(pkg.Dir)) + tempDir, err := ResolvePath(tempdirRoot, importPath(pkgDir)) if err != nil { return errors.New("cannot resolve build dir") } @@ -238,7 +244,7 @@ func execTest(cfg *testCfg, args []string, io commands.IO) error { if err != nil { io.ErrPrintln(err) io.ErrPrintln("FAIL") - io.ErrPrintfln("FAIL %s", pkg.Dir) + io.ErrPrintfln("FAIL %s", pkgDir) io.ErrPrintln("FAIL") buildErrCount++ @@ -246,27 +252,27 @@ func execTest(cfg *testCfg, args []string, io commands.IO) error { } } - if len(pkg.TestGnoFiles) == 0 && len(pkg.FiletestGnoFiles) == 0 { - io.ErrPrintfln("? %s \t[no test files]", pkg.Dir) + testFiles := importer.Filter(pkgFiles, "*_test.gno") + ftestFiles := importer.Filter(pkgFiles, "*_filetest.gno") + + if len(testFiles) == 0 && len(ftestFiles) == 0 { + io.ErrPrintfln("? %s \t[no test files]", pkgDir) continue } - sort.Strings(pkg.TestGnoFiles) - sort.Strings(pkg.FiletestGnoFiles) - startedAt := time.Now() - err = gnoTestPkg(pkg.Dir, pkg.TestGnoFiles, pkg.FiletestGnoFiles, cfg, io) + err = gnoTestPkg(pkgDir, testFiles, ftestFiles, cfg, io) duration := time.Since(startedAt) dstr := fmtDuration(duration) if err != nil { - io.ErrPrintfln("%s: test pkg: %v", pkg.Dir, err) + io.ErrPrintfln("%s: test pkg: %v", pkgDir, err) io.ErrPrintfln("FAIL") - io.ErrPrintfln("FAIL %s \t%s", pkg.Dir, dstr) + io.ErrPrintfln("FAIL %s \t%s", pkgDir, dstr) io.ErrPrintfln("FAIL") testErrCount++ } else { - io.ErrPrintfln("ok %s \t%s", pkg.Dir, dstr) + io.ErrPrintfln("ok %s \t%s", pkgDir, dstr) } } if testErrCount > 0 || buildErrCount > 0 { @@ -639,10 +645,7 @@ func parseMemPackageTests(memPkg *std.MemPackage) (tset, itset *gno.FileSet) { tset = &gno.FileSet{} itset = &gno.FileSet{} for _, mfile := range memPkg.Files { - if !strings.HasSuffix(mfile.Name, ".gno") { - continue // skip this file. - } - if strings.HasSuffix(mfile.Name, "_filetest.gno") { + if !importer.IsGnoFile(mfile.Name, "!*_filetest.gno") { continue } n, err := gno.ParseFile(mfile.Name, mfile.Body) diff --git a/gnovm/cmd/gno/testdata/gno_test/empty_dir.txtar b/gnovm/cmd/gno/testdata/gno_test/empty_dir.txtar index ffed64ab9c7..e7cf49c5795 100644 --- a/gnovm/cmd/gno/testdata/gno_test/empty_dir.txtar +++ b/gnovm/cmd/gno/testdata/gno_test/empty_dir.txtar @@ -1,9 +1,9 @@ # Run gno test on an empty dir -gno test . +! gno test . ! stdout .+ -stderr '[no test files]' +stderr 'no valid gno files' gno test ./... diff --git a/gnovm/cmd/gno/testdata/gno_test/lint_no_gnomod.txtar b/gnovm/cmd/gno/testdata/gno_test/lint_no_gnomod.txtar index 52daa6f0e9b..700aaa354a7 100644 --- a/gnovm/cmd/gno/testdata/gno_test/lint_no_gnomod.txtar +++ b/gnovm/cmd/gno/testdata/gno_test/lint_no_gnomod.txtar @@ -16,4 +16,4 @@ func main() { -- stdout.golden -- -- stderr.golden -- -./.: missing 'gno.mod' file (code=1). +.: missing 'gno.mod' file (code=1). diff --git a/gnovm/cmd/gno/testdata/gno_transpile/02_empty_dir.txtar b/gnovm/cmd/gno/testdata/gno_transpile/02_empty_dir.txtar index 2bd1841d2b4..72752b651c6 100644 --- a/gnovm/cmd/gno/testdata/gno_transpile/02_empty_dir.txtar +++ b/gnovm/cmd/gno/testdata/gno_transpile/02_empty_dir.txtar @@ -1,6 +1,6 @@ # Run gno transpile on an empty dir -gno transpile . +! gno transpile . ! stdout .+ -! stderr .+ +stderr 'no valid gno files' diff --git a/gnovm/cmd/gno/testdata/gno_transpile/03_gno_files_parse_error.txtar b/gnovm/cmd/gno/testdata/gno_transpile/03_gno_files_parse_error.txtar index b94b86992af..d9c2c6b313f 100644 --- a/gnovm/cmd/gno/testdata/gno_transpile/03_gno_files_parse_error.txtar +++ b/gnovm/cmd/gno/testdata/gno_transpile/03_gno_files_parse_error.txtar @@ -1,6 +1,6 @@ # Run gno transpile with gno files with parse errors -! gno transpile . +! gno transpile ./... ! stdout .+ stderr '^main.gno:3:1: expected declaration, found invalid$' diff --git a/gnovm/cmd/gno/testdata/gno_transpile/04_valid_gno_files.txtar b/gnovm/cmd/gno/testdata/gno_transpile/04_valid_gno_files.txtar index 2a24423598a..fa0b191d4da 100644 --- a/gnovm/cmd/gno/testdata/gno_transpile/04_valid_gno_files.txtar +++ b/gnovm/cmd/gno/testdata/gno_transpile/04_valid_gno_files.txtar @@ -1,6 +1,6 @@ # Run gno transpile with valid gno files -gno transpile . +gno transpile ./... ! stdout .+ ! stderr .+ diff --git a/gnovm/cmd/gno/testdata/gno_transpile/05_skip_fmt_flag.txtar b/gnovm/cmd/gno/testdata/gno_transpile/05_skip_fmt_flag.txtar index c07c670f721..d6d785a8b9c 100644 --- a/gnovm/cmd/gno/testdata/gno_transpile/05_skip_fmt_flag.txtar +++ b/gnovm/cmd/gno/testdata/gno_transpile/05_skip_fmt_flag.txtar @@ -2,7 +2,7 @@ # NOTE(tb): this flag doesn't actually prevent the code format, because # `gnolang.Transpile()` calls `format.Node()`. -gno transpile -skip-fmt . +gno transpile -skip-fmt ./... ! stdout .+ ! stderr .+ diff --git a/gnovm/cmd/gno/testdata/gno_transpile/06_build_flag.txtar b/gnovm/cmd/gno/testdata/gno_transpile/06_build_flag.txtar index 110d04959c0..b8d5f8d88d1 100644 --- a/gnovm/cmd/gno/testdata/gno_transpile/06_build_flag.txtar +++ b/gnovm/cmd/gno/testdata/gno_transpile/06_build_flag.txtar @@ -1,6 +1,6 @@ # Run gno transpile with -gobuild flag -gno transpile -gobuild . +gno transpile -gobuild ./... ! stdout .+ ! stderr .+ diff --git a/gnovm/cmd/gno/testdata/gno_transpile/07_build_flag_with_build_error.txtar b/gnovm/cmd/gno/testdata/gno_transpile/07_build_flag_with_build_error.txtar index d21390f9472..8b2d2bd3179 100644 --- a/gnovm/cmd/gno/testdata/gno_transpile/07_build_flag_with_build_error.txtar +++ b/gnovm/cmd/gno/testdata/gno_transpile/07_build_flag_with_build_error.txtar @@ -1,6 +1,6 @@ # Run gno transpile with -gobuild flag -! gno transpile -gobuild . +! gno transpile -gobuild ./... ! stdout .+ stderr '^main.gno:4:6: x declared and not used$' diff --git a/gnovm/cmd/gno/testdata/gno_transpile/09_gno_files_whitelist_error.txtar b/gnovm/cmd/gno/testdata/gno_transpile/09_gno_files_whitelist_error.txtar index 79d4d6a4a2c..d0f710e628e 100644 --- a/gnovm/cmd/gno/testdata/gno_transpile/09_gno_files_whitelist_error.txtar +++ b/gnovm/cmd/gno/testdata/gno_transpile/09_gno_files_whitelist_error.txtar @@ -1,6 +1,6 @@ # Run gno transpile with gno files with whitelist errors -! gno transpile . +! gno transpile ./... ! stdout .+ stderr '^main.gno:5:2: import "xxx" is not in the whitelist$' diff --git a/gnovm/cmd/gno/transpile.go b/gnovm/cmd/gno/transpile.go index 3469304bea2..6b1f8d73b8f 100644 --- a/gnovm/cmd/gno/transpile.go +++ b/gnovm/cmd/gno/transpile.go @@ -10,6 +10,7 @@ import ( "os" "path/filepath" + "github.com/gnolang/gno/gnovm/pkg/importer" "github.com/gnolang/gno/gnovm/pkg/transpiler" "github.com/gnolang/gno/tm2/pkg/commands" ) @@ -128,7 +129,7 @@ func execTranspile(cfg *transpileCfg, args []string, io commands.IO) error { } // transpile .gno files. - paths, err := gnoFilesFromArgs(args) + paths, err := importer.Match(args, importer.MatchFiles()) if err != nil { return fmt.Errorf("list paths: %w", err) } @@ -147,7 +148,7 @@ func execTranspile(cfg *transpileCfg, args []string, io commands.IO) error { } if errlist.Len() == 0 && cfg.gobuild { - paths, err := gnoPackagesFromArgs(args) + paths, err := importer.Match(args) if err != nil { return fmt.Errorf("list packages: %w", err) } @@ -180,7 +181,7 @@ func transpilePkg(pkgPath importPath, opts *transpileOptions) error { } opts.markAsTranspiled(pkgPath) - files, err := filepath.Glob(filepath.Join(string(pkgPath), "*.gno")) + files, err := importer.Match([]string{string(pkgPath)}, importer.MatchFiles()) if err != nil { log.Fatal(err) } diff --git a/gnovm/cmd/gno/util.go b/gnovm/cmd/gno/util.go index 30d8f808d04..46d2f937054 100644 --- a/gnovm/cmd/gno/util.go +++ b/gnovm/cmd/gno/util.go @@ -4,10 +4,8 @@ import ( "fmt" "go/ast" "io" - "io/fs" "os" "path/filepath" - "regexp" "strings" "time" @@ -15,179 +13,11 @@ import ( "github.com/gnolang/gno/gnovm/pkg/transpiler" ) -func isGnoFile(f fs.DirEntry) bool { - name := f.Name() - return !strings.HasPrefix(name, ".") && strings.HasSuffix(name, ".gno") && !f.IsDir() -} - func isFileExist(path string) bool { _, err := os.Stat(path) return err == nil } -func gnoFilesFromArgs(args []string) ([]string, error) { - paths := []string{} - for _, arg := range args { - info, err := os.Stat(arg) - if err != nil { - return nil, fmt.Errorf("invalid file or package path: %w", err) - } - if !info.IsDir() { - curpath := arg - paths = append(paths, curpath) - } else { - err = filepath.WalkDir(arg, func(curpath string, f fs.DirEntry, err error) error { - if err != nil { - return fmt.Errorf("%s: walk dir: %w", arg, err) - } - - if !isGnoFile(f) { - return nil // skip - } - paths = append(paths, curpath) - return nil - }) - if err != nil { - return nil, err - } - } - } - return paths, nil -} - -func gnoPackagesFromArgs(args []string) ([]string, error) { - paths := []string{} - for _, arg := range args { - info, err := os.Stat(arg) - if err != nil { - return nil, fmt.Errorf("invalid file or package path: %w", err) - } - if !info.IsDir() { - paths = append(paths, arg) - } else { - // if the passed arg is a dir, then we'll recursively walk the dir - // and look for directories containing at least one .gno file. - - visited := map[string]bool{} // used to run the builder only once per folder. - err = filepath.WalkDir(arg, func(curpath string, f fs.DirEntry, err error) error { - if err != nil { - return fmt.Errorf("%s: walk dir: %w", arg, err) - } - if f.IsDir() { - return nil // skip - } - if !isGnoFile(f) { - return nil // skip - } - - parentDir := filepath.Dir(curpath) - if _, found := visited[parentDir]; found { - return nil - } - visited[parentDir] = true - - 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 - }) - if err != nil { - return nil, err - } - } - } - return paths, nil -} - -// targetsFromPatterns returns a list of target paths that match the patterns. -// Each pattern can represent a file or a directory, and if the pattern -// includes "/...", the "..." is treated as a wildcard, matching any string. -// Intended to be used by gno commands such as `gno test`. -func targetsFromPatterns(patterns []string) ([]string, error) { - paths := []string{} - for _, p := range patterns { - var match func(string) bool - patternLookup := false - dirToSearch := p - - // Check if the pattern includes `/...` - if strings.Contains(p, "/...") { - index := strings.Index(p, "/...") - if index != -1 { - dirToSearch = p[:index] // Extract the directory path to search - } - match = matchPattern(strings.TrimPrefix(p, "./")) - patternLookup = true - } - - info, err := os.Stat(dirToSearch) - if err != nil { - return nil, fmt.Errorf("invalid file or package path: %w", err) - } - - // If the pattern is a file or a directory - // without `/...`, add it to the list. - if !info.IsDir() || !patternLookup { - paths = append(paths, p) - continue - } - - // the pattern is a dir containing `/...`, walk the dir recursively and - // look for directories containing at least one .gno file and match pattern. - visited := map[string]bool{} // used to run the builder only once per folder. - err = filepath.WalkDir(dirToSearch, func(curpath string, f fs.DirEntry, err error) error { - if err != nil { - return fmt.Errorf("%s: walk dir: %w", dirToSearch, err) - } - // Skip directories and non ".gno" files. - if f.IsDir() || !isGnoFile(f) { - return nil - } - - parentDir := filepath.Dir(curpath) - if _, found := visited[parentDir]; found { - return nil - } - - visited[parentDir] = true - if match(parentDir) { - paths = append(paths, parentDir) - } - - return nil - }) - if err != nil { - return nil, err - } - } - return paths, nil -} - -// matchPattern(pattern)(name) reports whether -// name matches pattern. Pattern is a limited glob -// pattern in which '...' means 'any string' and there -// is no other special syntax. -// Simplified version of go source's matchPatternInternal -// (see $GOROOT/src/cmd/internal/pkgpattern) -func matchPattern(pattern string) func(name string) bool { - re := regexp.QuoteMeta(pattern) - re = strings.Replace(re, `\.\.\.`, `.*`, -1) - // Special case: foo/... matches foo too. - if strings.HasSuffix(re, `/.*`) { - re = re[:len(re)-len(`/.*`)] + `(/.*)?` - } - reg := regexp.MustCompile(`^` + re + `$`) - return func(name string) bool { - return reg.MatchString(name) - } -} - func fmtDuration(d time.Duration) string { return fmt.Sprintf("%.2fs", d.Seconds()) } diff --git a/gnovm/cmd/gno/util_test.go b/gnovm/cmd/gno/util_test.go index 9e9659bfe4f..3ea68e4c562 100644 --- a/gnovm/cmd/gno/util_test.go +++ b/gnovm/cmd/gno/util_test.go @@ -5,212 +5,9 @@ import ( "path/filepath" "testing" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) -func TestMatchPattern(t *testing.T) { - tests := []struct { - pattern string - names []string - expected []bool - }{ - { - pattern: "foo", - names: []string{"foo", "bar", "baz", "foo/bar"}, - expected: []bool{true, false, false, false}, - }, - { - pattern: "foo/...", - names: []string{"foo", "foo/bar", "foo/bar/baz", "bar", "baz"}, - expected: []bool{true, true, true, false, false}, - }, - { - pattern: "foo/bar/...", - names: []string{"foo/bar", "foo/bar/baz", "foo/baz/bar", "foo", "bar"}, - expected: []bool{true, true, false, false, false}, - }, - { - pattern: "foo/.../baz", - names: []string{"foo/bar", "foo/bar/baz", "foo/baz/bar", "foo", "bar"}, - expected: []bool{false, true, false, false, false}, - }, - { - pattern: "foo/.../baz/...", - names: []string{"foo/bar/baz", "foo/baz/bar", "foo/bar/baz/qux", "foo/baz/bar/qux"}, - expected: []bool{true, false, true, false}, - }, - { - pattern: "...", - names: []string{"foo", "bar", "baz", "foo/bar", "foo/bar/baz"}, - expected: []bool{true, true, true, true, true}, - }, - { - pattern: ".../bar", - names: []string{"foo", "bar", "baz", "foo/bar", "foo/bar/baz"}, - expected: []bool{false, false, false, true, false}, - }, - } - - for _, test := range tests { - t.Run(test.pattern, func(t *testing.T) { - matchFunc := matchPattern(test.pattern) - for i, name := range test.names { - res := matchFunc(name) - assert.Equal(t, test.expected[i], res, "Expected: %v, Got: %v", test.expected[i], res) - } - }) - } -} - -func TestTargetsFromPatterns(t *testing.T) { - tmpDir := t.TempDir() - createGnoPackages(t, tmpDir) - - for _, tc := range []struct { - desc string - in, expected []string - errorShouldContain string - }{ - { - desc: "valid1", - in: []string{ - tmpDir, - }, - expected: []string{ - tmpDir, - }, - }, - { - desc: "valid2", - in: []string{ - tmpDir + "/foo", - }, - expected: []string{ - filepath.Join(tmpDir, "foo"), - }, - }, - { - desc: "valid_recursive1", - in: []string{ - tmpDir + "/...", - }, - expected: []string{ - filepath.Join(tmpDir, "foo"), - filepath.Join(tmpDir, "bar"), - filepath.Join(tmpDir, "baz"), - filepath.Join(tmpDir, "foo", "qux"), - filepath.Join(tmpDir, "bar", "quux"), - filepath.Join(tmpDir, "foo", "qux", "corge"), - }, - }, - { - desc: "valid_recursive2", - in: []string{ - tmpDir + "/foo/...", - }, - expected: []string{ - filepath.Join(tmpDir, "foo"), - filepath.Join(tmpDir, "foo", "qux"), - filepath.Join(tmpDir, "foo", "qux", "corge"), - }, - }, - { - desc: "valid_recursive2", - in: []string{ - tmpDir + "/.../qux", - }, - expected: []string{ - filepath.Join(tmpDir, "foo", "qux"), - }, - }, - { - desc: "valid_recursive3", - in: []string{ - tmpDir + "/.../qux/...", - }, - expected: []string{ - filepath.Join(tmpDir, "foo", "qux"), - filepath.Join(tmpDir, "foo", "qux", "corge"), - }, - }, - { - desc: "multiple_input", - in: []string{ - tmpDir + "/foo", - tmpDir + "/bar", - tmpDir + "/baz", - }, - expected: []string{ - filepath.Join(tmpDir, "foo"), - filepath.Join(tmpDir, "bar"), - filepath.Join(tmpDir, "baz"), - }, - }, - { - desc: "mixed_input1", - in: []string{ - tmpDir + "/foo", - tmpDir + "/bar/...", - }, - expected: []string{ - filepath.Join(tmpDir, "foo"), - filepath.Join(tmpDir, "bar"), - filepath.Join(tmpDir, "bar", "quux"), - }, - }, - { - desc: "mixed_input2", - in: []string{ - tmpDir + "/foo", - tmpDir + "/bar/...", - tmpDir + "/baz/baz.gno", - }, - expected: []string{ - filepath.Join(tmpDir, "foo"), - filepath.Join(tmpDir, "bar"), - filepath.Join(tmpDir, "bar", "quux"), - filepath.Join(tmpDir, "baz", "baz.gno"), - }, - }, - { - desc: "not_exists1", - in: []string{ - tmpDir + "/notexists", // dir path - }, - errorShouldContain: "no such file or directory", - }, - { - desc: "not_exists2", - in: []string{ - tmpDir + "/foo/bar.gno", // file path - }, - errorShouldContain: "no such file or directory", - }, - { - desc: "not_exists3", // mixed - in: []string{ - tmpDir + "/foo", // exists - tmpDir + "/notexists", // not exists - }, - errorShouldContain: "no such file or directory", - }, - } { - t.Run(tc.desc, func(t *testing.T) { - targets, err := targetsFromPatterns(tc.in) - if tc.errorShouldContain != "" { - assert.ErrorContains(t, err, tc.errorShouldContain) - return - } - assert.NoError(t, err) - require.Equal(t, len(tc.expected), len(targets)) - for _, tr := range targets { - assert.Contains(t, tc.expected, tr) - } - }) - } -} - func createGnoPackages(t *testing.T, tmpDir string) { t.Helper() diff --git a/gnovm/pkg/gnomod/gnomod.go b/gnovm/pkg/gnomod/gnomod.go index af946f2bf39..c608b3c5636 100644 --- a/gnovm/pkg/gnomod/gnomod.go +++ b/gnovm/pkg/gnomod/gnomod.go @@ -9,6 +9,7 @@ import ( "github.com/gnolang/gno/gnovm/pkg/gnoenv" "github.com/gnolang/gno/gnovm/pkg/gnolang" + "github.com/gnolang/gno/gnovm/pkg/importer" "github.com/gnolang/gno/gnovm/pkg/transpiler" "github.com/gnolang/gno/tm2/pkg/std" "golang.org/x/mod/modfile" @@ -179,7 +180,7 @@ func CreateGnoModFile(rootDir, modPath string) error { var pkgName gnolang.Name for _, file := range files { - if file.IsDir() || !strings.HasSuffix(file.Name(), ".gno") || strings.HasSuffix(file.Name(), "_filetest.gno") { + if file.IsDir() || !importer.IsGnoFile(file.Name(), "!*_filetest.gno") { continue } diff --git a/gnovm/pkg/gnomod/pkg.go b/gnovm/pkg/gnomod/pkg.go index f6fe7f60301..27a3f105c41 100644 --- a/gnovm/pkg/gnomod/pkg.go +++ b/gnovm/pkg/gnomod/pkg.go @@ -5,7 +5,6 @@ import ( "io/fs" "os" "path/filepath" - "strings" ) type Pkg struct { @@ -15,17 +14,6 @@ type Pkg struct { 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 ( PkgList []Pkg SortedPkgList []Pkg @@ -157,94 +145,3 @@ 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/importer/definitions.go b/gnovm/pkg/importer/definitions.go new file mode 100644 index 00000000000..d3a12a2d986 --- /dev/null +++ b/gnovm/pkg/importer/definitions.go @@ -0,0 +1,30 @@ +package importer + +// This file contains "definitions"; it attempts to centralize some common +// answers to common questions like "Is this a gno file?", "What is the import +// path to the gno repository?", "Is this import path of a realm?". + +const ( + // RepoImport is the import path to the Gno repository. + RepoImport = "github.com/gnolang/gno" + + // GnolangImport is the import path to the gnolang package. + GnolangImport = RepoImport + "/gnovm/pkg/gnolang" +) + +// IsGnoFile determines whether the given files matches all of the given patterns, +// with the same matching rules as [MatchPatterns]. +// +// It is essentially a helper for MatchPatterns, implicitly adding the patterns +// "*.gno" and "!.*". +// +// IsGnoFile assumes its patterns to be syntactically well-formed; if not, it +// will panic. To test for the correctness of patterns, try passing them with +// any input to MatchPatterns. +func IsGnoFile(name string, patterns ...string) bool { + m, err := MatchPatterns(name, append(patterns, "*.gno", "!.*")...) + if err != nil { + panic(err) + } + return m +} diff --git a/gnovm/pkg/importer/importer.go b/gnovm/pkg/importer/importer.go new file mode 100644 index 00000000000..1ef2a79c39f --- /dev/null +++ b/gnovm/pkg/importer/importer.go @@ -0,0 +1,34 @@ +// Package importer allows to match an import path to a directory, and select +// a set of files within that directory which are Gno files. +package importer + +import ( + "sync" + + "github.com/gnolang/gno/gnovm/pkg/gnoenv" +) + +// Config defines the configuration for the importer. +type Config struct { + // RootDir should point to the directory of the gno repository. + // This is used to resolve import paths of packages and realms in + // "examples", and to resolve standard libraries (gnovm/stdlibs and + // gnovm/tests/stdlibs). + RootDir string + + // GnoHome is the path to the "home" directory. + // This is used to resolve imports to on-chain packages. + GnoHome string + + // If set to true, this will enable the usage of "testing standard + // libraries"; ie., some stdlibs packages will be additionally resolved + // to a second directory in gnovm/tests/stdlibs/. + Test bool +} + +var defaultConfig = sync.OnceValue(func() Config { + return Config{ + RootDir: gnoenv.RootDir(), + GnoHome: gnoenv.HomeDir(), + } +}) diff --git a/gnovm/pkg/importer/match.go b/gnovm/pkg/importer/match.go new file mode 100644 index 00000000000..0d0296cccd9 --- /dev/null +++ b/gnovm/pkg/importer/match.go @@ -0,0 +1,383 @@ +package importer + +import ( + "errors" + "fmt" + "io/fs" + "os" + "path" + "path/filepath" + "regexp" + "strings" +) + +// Filter filters the given files s with patterns, following the rules +// of [MatchPatterns]. It does not add the implicit gno patterns of [IsGnoFile]. +// +// Filter panics if any of the patterns is invalid. +func Filter(s []string, patterns ...string) []string { + ret := make([]string, 0, len(s)) + for _, v := range s { + ok, err := MatchPatterns(v, patterns...) + if err != nil { + panic(err) + } + if ok { + ret = append(ret, v) + } + } + return ret +} + +// MatchPatterns returns whether the string s matches all of the given glob-like +// patterns. +// +// - Without any modifiers, s matches the given pattern according to the rules +// of [path.Match]. +// - If a pattern begins with !, it is negated. +// - If a pattern is surrounded by forward slashes (/), it is interpreted as a +// regular expression. +// - A pattern may combine negation and regex; ie. "!/hello.*\.go/" +// - Regular expressions receive the whole path; glob patterns only receive the +// last element (path.Base). +// +// An error is returned only if the patterns have an invalid syntax. +func MatchPatterns(s string, patterns ...string) (bool, error) { + // TODO: does a regex cache make sense here? + bs := []byte(s) + for _, pattern := range patterns { + var negate bool + if strings.HasPrefix(pattern, "!") { + negate = true + pattern = pattern[1:] + } + var res bool + var err error + if len(pattern) > 1 && pattern[0] == '/' && pattern[len(pattern)-1] == '/' { + pattern = pattern[1 : len(pattern)-1] + res, err = regexp.Match(pattern, bs) + } else { + res, err = path.Match(pattern, path.Base(s)) + } + if err != nil { + return false, fmt.Errorf("pattern %q: %w", pattern, err) + } + if res == negate { + return false, nil + } + } + return true, nil +} + +type matchOptions struct { + files bool + patterns []string + noImplicit bool + disableEllipsis bool + fs fs.FS +} + +// MatchOption is an option to be passed to [Match] to modify its behavior. +type MatchOption func(c *matchOptions) + +// MatchFiles instructs [Match] to find files instead of packages. +// The file names must match the given patterns, with the same rules/format as +// [MatchPatterns]. Implicitly, all files must not start with "." and must +// end with ".gno". +func MatchFiles(patterns ...string) MatchOption { + return func(m *matchOptions) { m.files, m.patterns = true, patterns } +} + +// MatchPackages instructs [Match] to find packages instead of files. This +// is the default behaviour. A package is defined as a directory containing at +// least one file ending with ".gno" and not starting with ".gno". +// Additional requirement patterns may be specified -- these apply to filenames, +// not directory names. +func MatchPackages(patterns ...string) MatchOption { + return func(m *matchOptions) { m.files, m.patterns = false, patterns } +} + +// MatchNoImplicit removes the implicit gno filters of [Match]. +// This allows Match to be used, ie., for go files. +func MatchNoImplicit() MatchOption { + return func(m *matchOptions) { m.noImplicit = true } +} + +// MatchEllipsis sets whether to use the ellipsis syntax, as in Go, to match +// packages and files. +// +// When this is enabled, the string "/..." is treated as a wildcard and matches +// any string. +// +// The default behaviour is MatchEllipsis(true). +func MatchEllipsis(b bool) MatchOption { + return func(m *matchOptions) { m.disableEllipsis = !b } +} + +// matches ellipsis as separators. +var ( + reEllipsis = regexp.MustCompile(`(?:^|/)\.\.\.(?:/|$)`) + reEllipsisEsc = regexp.MustCompile(`(?:^|/)\\\.\\\.\\\.(/|$)`) +) + +// Match is a central function to parse a set of arguments that expand to a set of +// Gno packages or files. [MatchOptions] may be provided to customise the +// matching behaviour of Match. +// +// By default, Match returns a list of packages matching the patterns in args, +// as well as any "explicit" file passed to it. +func Match(paths []string, opts ...MatchOption) ([]string, error) { + // Determine options. + var c matchOptions + for _, opt := range opts { + opt(&c) + } + + // Set defaults for c.fs, append standard patterns. + var root string + if c.fs == nil { + c.fs = os.DirFS("/") + wd, err := os.Getwd() + if err != nil { + return nil, err + } + root = strings.ReplaceAll(wd, string(os.PathSeparator), "/") + // remove leading / + root = root[1:] + } + if !c.noImplicit { + c.patterns = append(c.patterns, "*.gno", "!.*") + } + + // found will contain the set of matched packages or files. + var found []string + + for _, arg := range paths { + // TODO: eventually we might want to support go-style arguments, + // where we can pass in a package/realm path, ie: + // go test gno.land/p/demo/avl + // for now only work on local FS + + // Normalize separator to /, clean the path, and join it with the root + // if it's relative. + clean := path.Clean(strings.ReplaceAll(arg, string(os.PathSeparator), "/")) + originalClean := clean + if path.IsAbs(clean) { + clean = clean[1:] + if clean == "" { + clean = "." + } + } else { + clean = path.Join(root, clean) + } + if !fs.ValidPath(clean) { + return nil, fmt.Errorf("invalid path: %q", arg) + } + + // Find any valid ellipsis syntax. + ellipsis := reEllipsis.FindStringIndex(clean) + if c.disableEllipsis || ellipsis == nil { + // No ellipsis, or they are disabled -- stat the path directly. + f, err := fs.Stat(c.fs, clean) + if err != nil { + // stat error will already contain path + return nil, err + } + // Explicit file. + if !f.IsDir() { + found = append(found, revertPath(root, originalClean, clean)) + continue + } + // Directory, collect all files matching our patterns. + files, err := collectMatchingFiles(c, clean) + if err != nil { + return nil, err + } + switch { + case len(files) == 0: + if c.noImplicit { + return nil, fmt.Errorf("dir %s: no matching files", arg) + } + return nil, fmt.Errorf("dir %s: no valid gno files", arg) + case c.files: + for _, file := range files { + found = append(found, revertPath(root, originalClean, file)) + } + default: + if clean == "." { + clean = "" + } + found = append(found, revertPath(root, originalClean, clean)) + } + continue + } + + // Find directory to walk. + baseDir := clean[:ellipsis[0]] + if baseDir == "" { + baseDir = "." + } + + // Use regexp for linear-time matching + // Change wildcards to be regex. They will match all directories except for hidden dirs. + // Note that the regex matches only directory names, not filenames. + reString := reEllipsisEsc.ReplaceAllString( + regexp.QuoteMeta(clean), "(?:(?:/|^)[^./][^/]*)*$1", + ) + // for single triple dot, also allow a single "." as a valid package path. + if clean == "..." { + reString = `(?:\.|` + reString + `)` + } + reString = "^" + reString + "$" + re := regexp.MustCompile(reString) + pathTrim := strings.TrimSuffix(baseDir, "/") + fi, err := fs.Stat(c.fs, path.Clean(pathTrim)) + if err != nil { + return nil, err + } + err = walkDir(c.fs, pathTrim, &statDirEntry{fi}, func(fsPath string, entry fs.DirEntry, err error) error { + // BFS guarantees that we get a dir, its files, then its subdirs. + if entry.IsDir() { + if !re.MatchString(fsPath) { + return fs.SkipDir + } + return nil + } + ok, err := MatchPatterns(fsPath, c.patterns...) + if err != nil { + return err + } + if !ok { + return nil + } + if c.files { + found = append(found, revertPath(root, originalClean, fsPath)) + return nil + } + dirPath := path.Dir(fsPath) + if dirPath == "." { + dirPath = "" + } + found = append(found, revertPath(root, originalClean, dirPath)) + // we found a gno file in this dir; so let's go look at subdirs. + return fs.SkipDir + }) + if err != nil { + return nil, err + } + } + + return found, nil +} + +// Reverts a "clean" path to be closer to its original form, helper function for Match. +// Original absolute -> add a slash to clean path. +// Original relative -> determine the number of prefixed `../` in the clean path, +// store root + n*`../` as prefix, save back as n*`../` + trimprefix(fullpath, prefix) +// cwd: user CWD for relative paths: home/user +// original: original path specified by user, cleaned: ../user2/file.gno +// clean: clean matched path, absolute but without leading slash: home/user2/file.gno +func revertPath(cwd, original, clean string) string { + if path.IsAbs(original) { + return filepath.Join(filepath.VolumeName(original), filepath.FromSlash("/"+clean)) + } + var dotdot int + for ; strings.HasPrefix(original[dotdot*3:], "../"); dotdot++ { //nolint:revive + } + + dots := original[:dotdot*3] // ../ + pref := path.Join(cwd, dots) // /home + if pref == clean { + return "." + } + res := dots + strings.TrimPrefix(clean, pref+"/") + return res +} + +type statDirEntry struct { + info fs.FileInfo +} + +func (d *statDirEntry) Name() string { return d.info.Name() } +func (d *statDirEntry) IsDir() bool { return d.info.IsDir() } +func (d *statDirEntry) Type() fs.FileMode { return d.info.Mode().Type() } +func (d *statDirEntry) Info() (fs.FileInfo, error) { return d.info, nil } + +// walkDir is mostly copied from fs.WalkDir, with a few modifications: +// +// - search is performed breadth-first instead of depth-first. +// - fs.SkipDir is not recursive, and only skips processing the current directory. +// - the argument to this (recursive) function must be a directory. +func walkDir(fsys fs.FS, name string, d fs.DirEntry, walkDirFn fs.WalkDirFunc) error { + var skipDir bool + if err := walkDirFn(name, d, nil); err != nil { + if !errors.Is(err, fs.SkipDir) { + return err + } + skipDir = true + } + + files, err := fs.ReadDir(fsys, name) + if err != nil { + // Second call, to report ReadDir error. + err = walkDirFn(name, d, err) + if err != nil { + if errors.Is(err, fs.SkipDir) { + err = nil + } + return err + } + } + + if !skipDir { + for _, file := range files { + if file.IsDir() { + continue + } + name1 := path.Join(name, file.Name()) + if err := walkDirFn(name1, file, nil); err != nil { + if errors.Is(err, fs.SkipDir) { + break + } + return err + } + } + } + + for _, dir := range files { + if !dir.IsDir() { + continue + } + name1 := path.Join(name, dir.Name()) + if err := walkDir(fsys, name1, dir, walkDirFn); err != nil { + return err + } + } + return nil +} + +func collectMatchingFiles(c matchOptions, dir string) (files []string, err error) { + des, err := fs.ReadDir(c.fs, dir) + if err != nil { + return nil, err + } + + for _, de := range des { + if de.IsDir() { + continue + } + fullPath := path.Join(dir, de.Name()) + ok, err := MatchPatterns(fullPath, c.patterns...) + if err != nil { + return nil, err + } + if ok { + files = append(files, fullPath) + // break if we're only looking for packages on the first matching file we find. + if !c.files { + break + } + } + } + return +} diff --git a/gnovm/pkg/importer/match_test.go b/gnovm/pkg/importer/match_test.go new file mode 100644 index 00000000000..7d80368035d --- /dev/null +++ b/gnovm/pkg/importer/match_test.go @@ -0,0 +1,376 @@ +package importer + +import ( + "fmt" + "io/fs" + "strings" + "testing" + "testing/fstest" + + "github.com/stretchr/testify/assert" +) + +func TestMatchPatterns(t *testing.T) { + tt := []struct { + name string + patterns []string + // if both nil, expected to error + success []string + fail []string + }{ + { + "basic", + []string{"*.gno", "!*_test.gno"}, + []string{"hello.gno", "path/to/welcome.gno", "hello/.gno", ".gno"}, + []string{"hello.go", "path/to/welcome.go", "x.gnox", ".gnox"}, + }, + { + "globsReceiveLast", + []string{"path/*.gno"}, + []string{}, + []string{"path/hello.gno", "path/hello.go", "path/hello.gno/xx.gno", "path.gno/hello.gno"}, + }, + { + "globInvalid", + []string{"[unterminated"}, + nil, nil, + }, + { + "negate", + []string{"!*.gno", "!*.go"}, + []string{"negate.sol", "noext", "///", "", ".goa", "a.goa"}, + []string{"hello.go", "path/to/welcome.go", "x.gno", ".gno"}, + }, + { + "basicRegex", + []string{"/hello/"}, + []string{"hello.gno", "x/to/hello/dir", "hello.go"}, + []string{"Hello", "he/llo", "olleh", ".hel.lo"}, + }, + { + "basicRegexNegate", + []string{"!/hello/"}, + []string{"Hello", "he/llo", "olleh", ".hel.lo"}, + []string{"hello.gno", "x/to/hello/dir", "hello.go"}, + }, + { + "regexInvalid", + []string{"/[unmatched/"}, + nil, nil, + }, + } + for _, tc := range tt { + t.Run(tc.name, func(t *testing.T) { + if tc.success == nil && tc.fail == nil { + _, err := MatchPatterns("xx", tc.patterns...) + if err == nil { + t.Errorf("%v expected to error but didn't", tc.patterns) + } + return + } + + for _, s := range tc.success { + ok, err := MatchPatterns(s, tc.patterns...) + if err != nil { + t.Fatal(err) + } + if !ok { + t.Errorf("%q: does not match %v", s, tc.patterns) + } + } + for _, s := range tc.fail { + ok, err := MatchPatterns(s, tc.patterns...) + if err != nil { + t.Fatal(err) + } + if ok { + t.Errorf("%q: matches %v", s, tc.patterns) + } + } + }) + } +} + +func TestIsGnoFile(t *testing.T) { + for _, s := range [...]string{ + "hello.gno", + "file/to/hello.gno", + "hello_test.gno", + "hello_filetest.gno", + } { + if !IsGnoFile(s) { + t.Errorf("%q marked (incorrectly) as not a gno file", s) + } + } + + for _, s := range [...]string{ + "hidden/.hidden.gno", + "onlysuffix/.gno", + "notgno/hello.go", + } { + if IsGnoFile(s) { + t.Errorf("%q marked (incorrectly) as a gno file", s) + } + } +} + +func TestFilter(t *testing.T) { + testSlice := []string{ + "README", + "LICENSE", + "gno.mod", + "file.gno", + "hello.gno", + "x_filetest.gno", + "x_test.gno", + } + assert.Equal(t, Filter(testSlice, "!*.gno"), []string{"README", "LICENSE", "gno.mod"}) + assert.Equal(t, Filter(testSlice, "gno.*"), []string{"gno.mod"}) + assert.Equal(t, Filter(testSlice, "*.gno"), []string{"file.gno", "hello.gno", "x_filetest.gno", "x_test.gno"}) + assert.Equal(t, Filter(testSlice, "*_test.gno"), []string{"x_test.gno"}) + assert.Equal(t, Filter(testSlice, "*_filetest.gno"), []string{"x_filetest.gno"}) + assert.Equal(t, Filter(testSlice, "/_(file)?test.gno$/"), []string{"x_filetest.gno", "x_test.gno"}) +} + +const matchStructure1 = `README +LICENSE +doc.gno +gno.mod +p1/hello.gno +p1/hello_test.gno +p1/hello_filetest.gno +p1/README +p2/goodbye.gno +p2/goodbye_test.gno +p2/goodbye.go +pno/invalid.go +.hidden/ +.hidden/test.gno` + +const matchStructure2 = `d1/d2/hello/d3/pkg.gno +d1/d2/world/d3/pkg.gno +d1/d2/d3/pkg.gno` + +const matchStructure3 = `foo/bar/x.gno +foo/bar/baz/x.gno +foo/baz/bar/x.gno +foo/x.gno +bar/x.gno` + +func generateFS(structure string) fs.FS { + parts := strings.Split(structure, "\n") + mfs := make(fstest.MapFS, len(parts)) + for _, part := range parts { + part = strings.TrimSpace(part) + if part == "" { + continue + } + mfs[part] = &fstest.MapFile{Data: []byte("Xxx")} + } + return mfs +} + +func Test_generateStructure(t *testing.T) { + t.Skip("Skipping (activate for debugging)") + buf := new(strings.Builder) + fs.WalkDir(generateFS(matchStructure1), ".", func(path string, d fs.DirEntry, err error) error { + fi, err := d.Info() + if err != nil { + return err + } + fmt.Fprintf(buf, "%s: %v\n", path, fi.Mode()) + return nil + }) + t.Log(buf.String()) +} + +func TestMatch(t *testing.T) { + tt := []struct { + name string + structure string + paths []string + opts []MatchOption + exp []string + errContains string + }{ + { + "basic", matchStructure1, + + []string{"./p1"}, + nil, + + []string{"p1"}, + "", + }, + { + "basicFiles", matchStructure1, + + []string{"./p1"}, + []MatchOption{MatchFiles("!*_filetest.gno")}, + + []string{"p1/hello.gno", "p1/hello_test.gno"}, + "", + }, + { + "explicit", matchStructure1, + + []string{"./p1", "./p1/hello_test.gno"}, + []MatchOption{MatchFiles("!*_test.gno", "!*_filetest.gno")}, + + []string{"p1/hello.gno", "p1/hello_test.gno"}, + "", + }, + { + "root", matchStructure1, + + []string{"/", "/p1"}, + nil, + + []string{"/", "/p1"}, + "", + }, + { + "ellipsis", matchStructure1, + + []string{"..."}, + nil, + + []string{".", "p1", "p2"}, + "", + }, + { + "onlyTestPackages", matchStructure1, + + []string{"..."}, + []MatchOption{MatchPackages("*_test.gno")}, + + []string{"p1", "p2"}, + "", + }, + { + "hidden", matchStructure1, + + []string{"...", "./.hidden"}, + nil, + + []string{".", "p1", "p2", ".hidden"}, + "", + }, + { + "subEllipsis", matchStructure2, + + []string{"/d1/d2/.../d3"}, + nil, + + []string{"/d1/d2/d3", "/d1/d2/hello/d3", "/d1/d2/world/d3"}, + "", + }, + { + "subEllipsisPackage", matchStructure2, + + []string{"/d1/d2/.../d3"}, + []MatchOption{MatchFiles()}, + + []string{"/d1/d2/d3/pkg.gno", "/d1/d2/hello/d3/pkg.gno", "/d1/d2/world/d3/pkg.gno"}, + "", + }, + { + "matchNone", matchStructure1, + + []string{"pno/..."}, + nil, + + nil, + "", + }, + { + "goMatchTest", matchStructure3, + + []string{"foo/bar/..."}, + nil, + + []string{"foo/bar", "foo/bar/baz"}, + "", + }, + { + "goMatchTest2", matchStructure3, + + []string{"foo/.../baz"}, + nil, + + []string{"foo/bar/baz"}, + "", + }, + + { + "errInvalidPath", matchStructure1, + + []string{"/", "../../../../"}, + nil, + + nil, + `invalid path: "../../`, + }, + { + "errNotFound", matchStructure1, + + []string{"not_exist"}, + nil, + + nil, + `file does not exist`, + }, + { + "errNotPackage", matchStructure1, + + []string{"p1", "pno", "p2"}, + nil, + + nil, + `dir pno: no valid gno files`, + }, + { + "errBadPattern", matchStructure1, + + []string{"p1"}, + []MatchOption{MatchFiles("[unmatched")}, + + nil, + `syntax error in pattern`, + }, + } + for _, tc := range tt { + t.Run(tc.name, func(t *testing.T) { + f := generateFS(tc.structure) + res, err := Match(tc.paths, append(tc.opts, func(c *matchOptions) { + c.fs = f + })...) + if tc.errContains == "" { + assert.Nil(t, err, "%v", err) + assert.Equal(t, res, tc.exp) + } else { + _ = assert.NotNil(t, err) && + assert.Contains(t, err.Error(), tc.errContains) + assert.Equal(t, res, tc.exp) + } + }) + } +} + +func Test_revertPath(t *testing.T) { + tt := []struct { + name string + cwd, original, clean string + result string + }{ + {"basic", "home/morgan", "file.gno", "home/morgan/file.gno", "file.gno"}, + {"sub", "home/morgan", "t1/file.gno", "home/morgan/t1/file.gno", "t1/file.gno"}, + {"dot", "home/moul", ".", "home/moul", "."}, + {"dotdot", "home/morgan", "../file.gno", "home/file.gno", "../file.gno"}, + } + for _, tc := range tt { + t.Run(tc.name, func(t *testing.T) { + assert.Equal(t, revertPath(tc.cwd, tc.original, tc.clean), tc.result, "result should match") + }) + } +} diff --git a/gnovm/tests/integ/invalid_module_name/main.gno b/gnovm/tests/integ/invalid_module_name/main.gno new file mode 100644 index 00000000000..06ab7d0f9a3 --- /dev/null +++ b/gnovm/tests/integ/invalid_module_name/main.gno @@ -0,0 +1 @@ +package main From 232834c54145d7e60bbd9584028fd45bd5c11ea8 Mon Sep 17 00:00:00 2001 From: Morgan Bazalgette Date: Sun, 26 May 2024 13:49:04 +0200 Subject: [PATCH 002/143] remove util_test --- gnovm/cmd/gno/util_test.go | 94 -------------------------------------- 1 file changed, 94 deletions(-) delete mode 100644 gnovm/cmd/gno/util_test.go diff --git a/gnovm/cmd/gno/util_test.go b/gnovm/cmd/gno/util_test.go deleted file mode 100644 index 3ea68e4c562..00000000000 --- a/gnovm/cmd/gno/util_test.go +++ /dev/null @@ -1,94 +0,0 @@ -package main - -import ( - "os" - "path/filepath" - "testing" - - "github.com/stretchr/testify/require" -) - -func createGnoPackages(t *testing.T, tmpDir string) { - t.Helper() - - type file struct { - name, data string - } - // Gno pkgs to create - pkgs := []struct { - dir string - files []file - }{ - // pkg 'foo', 'bar' and 'baz' - { - dir: filepath.Join(tmpDir, "foo"), - files: []file{ - { - name: "foo.gno", - data: `package foo`, - }, - }, - }, - { - dir: filepath.Join(tmpDir, "bar"), - files: []file{ - { - name: "bar.gno", - data: `package bar`, - }, - }, - }, - { - dir: filepath.Join(tmpDir, "baz"), - files: []file{ - { - name: "baz.gno", - data: `package baz`, - }, - }, - }, - - // pkg inside 'foo' pkg - { - dir: filepath.Join(tmpDir, "foo", "qux"), - files: []file{ - { - name: "qux.gno", - data: `package qux`, - }, - }, - }, - - // pkg inside 'bar' pkg - { - dir: filepath.Join(tmpDir, "bar", "quux"), - files: []file{ - { - name: "quux.gno", - data: `package quux`, - }, - }, - }, - - // pkg inside 'foo/qux' pkg - { - dir: filepath.Join(tmpDir, "foo", "qux", "corge"), - files: []file{ - { - name: "corge.gno", - data: `package corge`, - }, - }, - }, - } - - // Create pkgs - for _, p := range pkgs { - err := os.MkdirAll(p.dir, 0o700) - require.NoError(t, err) - for _, f := range p.files { - err = os.WriteFile(filepath.Join(p.dir, f.name), []byte(f.data), 0o644) - require.NoError(t, err) - } - } -} From 8afeacf8ae5886311851419b93ab6a278753f10e Mon Sep 17 00:00:00 2001 From: Morgan Bazalgette Date: Sun, 26 May 2024 13:59:05 +0200 Subject: [PATCH 003/143] linter fixes --- .github/workflows/examples.yml | 4 ++-- examples/Makefile | 14 +++++++------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/workflows/examples.yml b/.github/workflows/examples.yml index a01f465f38b..c1a4b4c26ba 100644 --- a/.github/workflows/examples.yml +++ b/.github/workflows/examples.yml @@ -30,7 +30,7 @@ jobs: with: go-version: ${{ matrix.goversion }} - run: go install -v ./gnovm/cmd/gno - - run: go run ./gnovm/cmd/gno transpile -v --gobuild ./examples + - run: go run ./gnovm/cmd/gno transpile -v --gobuild ./examples/... test: strategy: fail-fast: false @@ -92,4 +92,4 @@ jobs: # Find all directories containing gno.mod file find ./examples -name "gno.mod" -execdir go run "$GNO_CMD" mod tidy \; # Check if there are changes after running gno mod tidy - git diff --exit-code || (echo "Some gno.mod files are not tidy, please run 'make tidy'." && exit 1) \ No newline at end of file + git diff --exit-code || (echo "Some gno.mod files are not tidy, please run 'make tidy'." && exit 1) diff --git a/examples/Makefile b/examples/Makefile index 39a51a32112..5a01a30b641 100644 --- a/examples/Makefile +++ b/examples/Makefile @@ -23,20 +23,20 @@ GOIMPORTS_FLAGS ?= $(GOFMT_FLAGS) GOTEST_FLAGS ?= -v -p 1 -timeout=30m # Official packages (non-overridable): more reliable and tested modules, distinct from the experimentation area. -OFFICIAL_PACKAGES = ./gno.land/p -OFFICIAL_PACKAGES += ./gno.land/r/demo -OFFICIAL_PACKAGES += ./gno.land/r/gnoland -OFFICIAL_PACKAGES += ./gno.land/r/sys +OFFICIAL_PACKAGES = ./gno.land/p/... +OFFICIAL_PACKAGES += ./gno.land/r/demo/... +OFFICIAL_PACKAGES += ./gno.land/r/gnoland/... +OFFICIAL_PACKAGES += ./gno.land/r/sys/... ######################################## # Dev tools .PHONY: transpile transpile: - go run ../gnovm/cmd/gno transpile -v . + go run ../gnovm/cmd/gno transpile -v ... .PHONY: build build: - go run ../gnovm/cmd/gno transpile -v --gobuild . + go run ../gnovm/cmd/gno transpile -v --gobuild ... .PHONY: test test: @@ -48,7 +48,7 @@ lint: .PHONY: test.sync test.sync: - go run ../gnovm/cmd/gno test -v --update-golden-tests ./... + go run ../gnovm/cmd/gno test -v --update-golden-tests ... .PHONY: clean clean: From 808061d97962668917b66a11c666b6f4b04a96d6 Mon Sep 17 00:00:00 2001 From: Norman Meier Date: Mon, 7 Oct 2024 02:31:00 +0200 Subject: [PATCH 004/143] tmp: gno list Signed-off-by: Norman Meier --- gno.land/pkg/gnoclient/client_queries.go | 20 ++ gnovm/cmd/gno/lint.go | 17 +- gnovm/cmd/gno/list.go | 66 ++++ gnovm/cmd/gno/main.go | 1 + gnovm/pkg/importer/resolve.go | 380 +++++++++++++++++++++++ go.mod | 1 + go.sum | 3 + 7 files changed, 481 insertions(+), 7 deletions(-) create mode 100644 gnovm/cmd/gno/list.go create mode 100644 gnovm/pkg/importer/resolve.go diff --git a/gno.land/pkg/gnoclient/client_queries.go b/gno.land/pkg/gnoclient/client_queries.go index a6c8ea60475..40074752360 100644 --- a/gno.land/pkg/gnoclient/client_queries.go +++ b/gno.land/pkg/gnoclient/client_queries.go @@ -126,6 +126,26 @@ func (c *Client) QEval(pkgPath string, expression string) (string, *ctypes.Resul return string(qres.Response.Data), qres, nil } +// QFile ??? +func (c *Client) QFile(pkgPath string) (string, *ctypes.ResultABCIQuery, error) { + if err := c.validateRPCClient(); err != nil { + return "", nil, err + } + + path := "vm/qfile" + data := []byte(pkgPath) + + qres, err := c.RPCClient.ABCIQuery(path, data) + if err != nil { + return "", nil, errors.Wrap(err, "query qfile") + } + if qres.Response.Error != nil { + return "", nil, errors.Wrap(qres.Response.Error, "QFile failed: log:%s", qres.Response.Log) + } + + return string(qres.Response.Data), qres, nil +} + // Block gets the latest block at height, if any // Height must be larger than 0 func (c *Client) Block(height int64) (*ctypes.ResultBlock, error) { diff --git a/gnovm/cmd/gno/lint.go b/gnovm/cmd/gno/lint.go index 76a446618e7..8f5367bb36f 100644 --- a/gnovm/cmd/gno/lint.go +++ b/gnovm/cmd/gno/lint.go @@ -65,26 +65,29 @@ func execLint(cfg *lintCfg, args []string, io commands.IO) error { rootDir = gnoenv.RootDir() } - pkgPaths, err := importer.Match(args) + pkgs, err := importer.ResolvePackages(args...) if err != nil { return fmt.Errorf("list packages from args: %w", err) } hasError := false - for _, pkgPath := range pkgPaths { + for _, pkg := range pkgs { + pkgDir := pkg.Dir + pkgPath := pkg.ImportPath + if verbose { fmt.Fprintf(io.Err(), "Linting %q...\n", pkgPath) } // Check if 'gno.mod' exists - gnoModPath := filepath.Join(pkgPath, "gno.mod") + gnoModPath := filepath.Join(pkgDir, "gno.mod") if !osm.FileExists(gnoModPath) { hasError = true issue := lintIssue{ Code: lintNoGnoMod, Confidence: 1, - Location: pkgPath, + Location: pkgDir, Msg: "missing 'gno.mod' file", } fmt.Fprint(io.Err(), issue.String()+"\n") @@ -99,10 +102,10 @@ func execLint(cfg *lintCfg, args []string, io commands.IO) error { tests.ImportModeStdlibsOnly, ) - targetPath := pkgPath - info, err := os.Stat(pkgPath) + targetPath := pkgDir + info, err := os.Stat(pkgDir) if err == nil && !info.IsDir() { - targetPath = filepath.Dir(pkgPath) + targetPath = filepath.Dir(pkgDir) } memPkg := gno.ReadMemPackage(targetPath, targetPath) diff --git a/gnovm/cmd/gno/list.go b/gnovm/cmd/gno/list.go new file mode 100644 index 00000000000..a78260e97af --- /dev/null +++ b/gnovm/cmd/gno/list.go @@ -0,0 +1,66 @@ +package main + +import ( + "context" + "encoding/json" + "flag" + "fmt" + "os" + "strings" + + "github.com/gnolang/gno/gnovm/pkg/importer" + "github.com/gnolang/gno/tm2/pkg/commands" +) + +type listCfg struct { + json bool +} + +func newListCmd(io commands.IO) *commands.Command { + cfg := &listCfg{} + + return commands.NewCommand( + commands.Metadata{ + Name: "list", + ShortUsage: "list [flags] [...]", + ShortHelp: "runs the lister for the specified packages", + }, + cfg, + func(_ context.Context, args []string) error { + return execList(cfg, args, io) + }, + ) +} + +func (c *listCfg) RegisterFlags(fs *flag.FlagSet) { + fs.BoolVar(&c.json, "json", false, "verbose output when listning") +} + +func execList(cfg *listCfg, args []string, _ commands.IO) error { + if len(args) < 1 { + return flag.ErrHelp + } + + if !cfg.json { + pkgPaths, err := importer.ListPackages(args...) + if err != nil { + fmt.Println(err) + os.Exit(1) + } + fmt.Println(strings.Join(pkgPaths, "\n")) + return nil + } + + pkgs, err := importer.ResolvePackages(args...) + if err != nil { + fmt.Println(err) + os.Exit(1) + } + pkgsBytes, err := json.MarshalIndent(pkgs, "", "\t") + if err != nil { + panic(err) + } + fmt.Println(string(pkgsBytes)) + + return nil +} diff --git a/gnovm/cmd/gno/main.go b/gnovm/cmd/gno/main.go index 8b77cfd2a10..4e724923224 100644 --- a/gnovm/cmd/gno/main.go +++ b/gnovm/cmd/gno/main.go @@ -34,6 +34,7 @@ func newGnocliCmd(io commands.IO) *commands.Command { newDocCmd(io), newEnvCmd(io), newBugCmd(io), + newListCmd(io), // fmt -- gofmt // graph // vendor -- download deps from the chain in vendor/ diff --git a/gnovm/pkg/importer/resolve.go b/gnovm/pkg/importer/resolve.go new file mode 100644 index 00000000000..3d851b75689 --- /dev/null +++ b/gnovm/pkg/importer/resolve.go @@ -0,0 +1,380 @@ +package importer + +import ( + "errors" + "fmt" + "go/parser" + "go/token" + "net/url" + "os" + "path" + "path/filepath" + "strings" + + "github.com/gnolang/gno/gno.land/pkg/gnoclient" + "github.com/gnolang/gno/gnovm/pkg/gnoenv" + "github.com/gnolang/gno/tm2/pkg/bft/rpc/client" + "golang.org/x/mod/modfile" +) + +const recursiveSuffix = string(os.PathSeparator) + "..." +const modFileBaseName = "gno.mod" + +func isRecursivePath(p string) bool { + return strings.HasSuffix(p, recursiveSuffix) || p == "..." +} + +func convertRecursivePathToDir(p string) string { + if p == "..." { + return "." + } + if !strings.HasSuffix(p, recursiveSuffix) { + return p + } + return p[:len(p)-4] +} + +func findModDir(dir string) (string, error) { + dir = filepath.Clean(dir) + + potentialMod := filepath.Join(dir, modFileBaseName) + + if _, err := os.Stat(potentialMod); os.IsNotExist(err) { + parent, file := filepath.Split(dir) + if file == "" { + return "", os.ErrNotExist + } + return findModDir(parent) + } else if err != nil { + return "", err + } + + return filepath.Clean(dir), nil +} + +func ListPackages(paths ...string) ([]string, error) { + details, _, err := ListPackagesDetails(paths...) + if err != nil { + return nil, err + } + res := make([]string, len(details)) + for i, p := range details { + res[i] = p.PkgPath + } + return res, nil +} + +func ListPackagesDetails(paths ...string) ([]PackageDetails, *modfile.File, error) { + wd, err := os.Getwd() + if err != nil { + return nil, nil, fmt.Errorf("failed to get workdir: %w", err) + } + + modDir, err := findModDir(wd) + if os.IsNotExist(err) { + return nil, nil, errors.New("gno.mod file not found in current directory or any parent directory") + } else if err != nil { + return nil, nil, fmt.Errorf("failed to find parent module: %w", err) + } + modFilePath := filepath.Join(modDir, modFileBaseName) + modFileBytes, err := os.ReadFile(modFilePath) + if err != nil { + return nil, nil, fmt.Errorf("failed to read modfile: %w", err) + } + modFile, err := modfile.ParseLax(modFilePath, modFileBytes, nil) + if err != nil { + return nil, nil, fmt.Errorf("failed to parse modfile: %w", err) + } + + pkgPath := modFile.Module.Mod.Path + + toVisit := []string{} + for _, p := range paths { + toVisit = append(toVisit, filepath.Clean(p)) + } + visited := map[string]struct{}{} + packages := []PackageDetails{} + errs := []error{} + + for len(toVisit) > 0 { + p := toVisit[0] + visited[p] = struct{}{} + toVisit = toVisit[1:] + + root := convertRecursivePathToDir(p) + + if !isRecursivePath(p) { + if p != "." && strings.ContainsRune(p, '.') { + packages = append(packages, PackageDetails{ + PkgPath: p, + Remote: true, + }) + continue + } + p = path.Join(pkgPath, p) + packages = append(packages, PackageDetails{ + PkgPath: p, + Root: root, + }) + continue + } + + dirEntry, err := os.ReadDir(root) + if err != nil { + errs = append(errs, err) + continue + } + + hasGnoFiles := false + for _, entry := range dirEntry { + fileName := entry.Name() + if entry.IsDir() { + dirPath := filepath.Join(root, fileName) + recursiveSuffix + if _, ok := visited[dirPath]; !ok { + toVisit = append(toVisit, dirPath) + } + } + if !hasGnoFiles && IsGnoFile(fileName) { + hasGnoFiles = true + } + } + + if hasGnoFiles { + if _, ok := visited[root]; !ok { + toVisit = append(toVisit, root) + } + } + } + + return packages, modFile, errors.Join(errs...) +} + +type PackageDetails struct { + PkgPath string + Root string + Remote bool +} + +func ResolvePackages(paths ...string) ([]*Package, error) { + pkgs, modFile, err := ListPackagesDetails(paths...) + if err != nil { + return nil, fmt.Errorf("failed to list packages: %w", err) + } + + res := make([]*Package, len(pkgs)) + errs := []error{} + for i, meta := range pkgs { + if meta.Remote { + res[i], err = ResolveRemote(meta.PkgPath) + if err != nil { + errs = append(errs, err) + } + continue + } + + absRoot, err := filepath.Abs(meta.Root) + if err != nil { + errs = append(errs, fmt.Errorf("failed to find absolute root %q: %w", meta.Root, err)) + } + + pkgPath := meta.PkgPath + pkg, err := fillPackage(pkgPath, absRoot, modFile) + if err != nil { + return nil, fmt.Errorf("failed to fill Package %q: %w", pkgPath, err) + } + + res[i] = pkg + } + return res, errors.Join(errs...) +} + +// Does not fill deps +func ResolveRemote(pkgPath string) (*Package, error) { + modCache := filepath.Join(gnoenv.HomeDir(), "pkg", "mod") + pkgDir := filepath.Join(modCache, pkgPath) + if err := DownloadModule(pkgPath, pkgDir); err != nil { + return nil, fmt.Errorf("failed to download module %q: %w", pkgPath, err) + } + + pkg, err := fillPackage(pkgPath, pkgDir, nil) + if err != nil { + return nil, fmt.Errorf("failed to fill Package %q: %w", pkgPath, err) + } + + return pkg, nil +} + +func fillPackage(pkgPath, pkgDir string, modFile *modfile.File) (*Package, error) { + fsFiles := []string{} + modFiles := []string{} + fsTestFiles := []string{} + testFiles := []string{} + dir, err := os.ReadDir(pkgDir) + if err != nil { + return nil, fmt.Errorf("failed to read module files list: %w", err) + } + for _, entry := range dir { + if entry.IsDir() { + continue + } + + fileName := entry.Name() + if IsGnoTestFile(fileName) { + fsTestFiles = append(fsTestFiles, filepath.Join(pkgDir, fileName)) + testFiles = append(testFiles, fileName) + } else if IsGnoFile(fileName) { + fsFiles = append(fsFiles, filepath.Join(pkgDir, fileName)) + modFiles = append(modFiles, fileName) + } + } + name, imports, err := resolveNameAndImports(fsFiles) + if err != nil { + return nil, fmt.Errorf("failed to resolve name and imports for %q: %w", pkgPath, err) + } + _, testImports, err := resolveNameAndImports(fsTestFiles) + if err != nil { + return nil, fmt.Errorf("failed to resolve name test imports for %q: %w", pkgPath, err) + } + + module := Module{} + if modFile != nil { + module = Module{ + Path: modFile.Module.Mod.Path, + } + } + + return &Package{ + ImportPath: pkgPath, + Dir: pkgDir, + Name: name, + Module: module, + GnoFiles: modFiles, + Imports: imports, + TestGnoFiles: testFiles, + TestImports: testImports, + }, nil +} + +func DownloadModule(pkgPath string, dst string) error { + modFilePath := filepath.Join(dst, modFileBaseName) + if _, err := os.Stat(modFilePath); os.IsNotExist(err) { + fmt.Println("gno: downloading", pkgPath) + + // create client from pkgpath + parts := strings.Split(pkgPath, "/") + if len(parts) < 1 { + return fmt.Errorf("bad pkg path %q", pkgPath) + } + rpcURL := (&url.URL{ + Scheme: "https", + Host: "rpc." + parts[0] + ":443", + }).String() + tmClient, err := client.NewHTTPClient(rpcURL) + if err != nil { + return fmt.Errorf("failed to instantiate tm2 client with remote %q: %w", rpcURL, err) + } + client := gnoclient.Client{RPCClient: tmClient} + + // fetch files + data, _, err := client.QFile(pkgPath) + if err != nil { + return fmt.Errorf("failed to query files list for pkg %q: %w", pkgPath, err) + } + if err := os.MkdirAll(dst, 0744); err != nil { + return fmt.Errorf("failed to create cache dir for %q at %q: %w", pkgPath, dst, err) + } + files := strings.Split(string(data), "\n") + for _, file := range files { + filePath := path.Join(pkgPath, file) + data, _, err := client.QFile(filePath) + if err != nil { + return fmt.Errorf("failed to query package file %q: %w", filePath, err) + } + dst := filepath.Join(dst, file) + if err := os.WriteFile(dst, []byte(data), 0644); err != nil { + return fmt.Errorf("failed to write file at %q: %w", dst, err) + } + } + + // write gno.mod + if err := os.WriteFile(modFilePath, []byte("package "+pkgPath+"\n"), 0644); err != nil { + return fmt.Errorf("failed to write modfile at %q: %w", modFilePath, err) + } + } else if err != nil { + return fmt.Errorf("failed to stat downloaded module %q at %q: %w", pkgPath, dst, err) + } + + return nil +} + +func IsGnoTestFile(p string) bool { + if !IsGnoFile(p) { + return false + } + return strings.HasSuffix(p, "_test.gno") || strings.HasSuffix(p, "_filetest.gno") +} + +type Package struct { + Dir string `json:",omitempty"` + ImportPath string `json:",omitempty"` + Name string `json:",omitempty"` + Root string `json:",omitempty"` + Module Module `json:",omitempty"` + Match []string `json:",omitempty"` + GnoFiles []string `json:",omitempty"` + Imports []string `json:",omitempty"` + Deps []string `json:",omitempty"` + TestGnoFiles []string `json:",omitempty"` + TestImports []string `json:",omitempty"` +} + +type Module struct { + Path string `json:",omitempty"` + Main bool `json:",omitempty"` + Dir string `json:",omitempty"` + GoMod string `json:",omitempty"` + GoVersion string `json:",omitempty"` +} + +func resolveNameAndImports(gnoFiles []string) (string, []string, error) { + names := map[string]int{} + imports := map[string]struct{}{} + bestName := "" + bestNameCount := 0 + for _, srcPath := range gnoFiles { + src, err := os.ReadFile(srcPath) + if err != nil { + return "", nil, fmt.Errorf("failed to read file %q: %w", srcPath, err) + } + fset := token.NewFileSet() + f, err := parser.ParseFile(fset, srcPath, src, + // SkipObjectResolution -- unused here. + // ParseComments -- so that they show up when re-building the AST. + parser.SkipObjectResolution|parser.ImportsOnly) + if err != nil { + return "", nil, fmt.Errorf("parse: %w", err) + } + name := f.Name.String() + names[name] += 1 + count := names[name] + if count > bestNameCount { + bestName = name + bestNameCount = count + } + for _, imp := range f.Imports { + importPath := imp.Path.Value + // trim quotes + if len(importPath) >= 2 { + importPath = importPath[1 : len(importPath)-1] + } + imports[importPath] = struct{}{} + } + } + importsSlice := make([]string, len(imports)) + i := 0 + for imp := range imports { + importsSlice[i] = imp + i++ + } + return bestName, importsSlice, nil +} diff --git a/go.mod b/go.mod index 76c42f0419c..3e4c287ba41 100644 --- a/go.mod +++ b/go.mod @@ -23,6 +23,7 @@ require ( github.com/pelletier/go-toml v1.9.5 github.com/peterbourgon/ff/v3 v3.4.0 github.com/pmezard/go-difflib v1.0.0 + github.com/psanford/memfs v0.0.0-20240922203233-02fb08c0f8db github.com/rogpeppe/go-internal v1.12.0 github.com/rs/cors v1.10.1 github.com/rs/xid v1.5.0 diff --git a/go.sum b/go.sum index e4d728a106d..455248884a4 100644 --- a/go.sum +++ b/go.sum @@ -74,6 +74,7 @@ github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEW github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= @@ -131,6 +132,8 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/psanford/memfs v0.0.0-20240922203233-02fb08c0f8db h1:uwKfJcTGnZ4ziFqr0bjzNc/P1fydrAg450b9Dz8Fj8M= +github.com/psanford/memfs v0.0.0-20240922203233-02fb08c0f8db/go.mod h1:tcaRap0jS3eifrEEllL6ZMd9dg8IlDpi2S1oARrQ+NI= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.3 h1:utMvzDsuh3suAEnhH0RdHmoPbU648o6CvXxTx4SBMOw= github.com/rivo/uniseg v0.4.3/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= From 2c5d6045914be9991922ee1b03b88f337d92d82a Mon Sep 17 00:00:00 2001 From: Norman Meier Date: Tue, 8 Oct 2024 00:56:13 +0200 Subject: [PATCH 005/143] tmp Signed-off-by: Norman Meier --- gnovm/cmd/gno/lint.go | 2 +- gnovm/cmd/gno/list.go | 8 ++- gnovm/pkg/importer/resolve.go | 104 +++++++++++++++++----------------- 3 files changed, 58 insertions(+), 56 deletions(-) diff --git a/gnovm/cmd/gno/lint.go b/gnovm/cmd/gno/lint.go index 8f5367bb36f..2a4e355191c 100644 --- a/gnovm/cmd/gno/lint.go +++ b/gnovm/cmd/gno/lint.go @@ -65,7 +65,7 @@ func execLint(cfg *lintCfg, args []string, io commands.IO) error { rootDir = gnoenv.RootDir() } - pkgs, err := importer.ResolvePackages(args...) + pkgs, err := importer.Load(args...) if err != nil { return fmt.Errorf("list packages from args: %w", err) } diff --git a/gnovm/cmd/gno/list.go b/gnovm/cmd/gno/list.go index a78260e97af..ab7f0f2aedf 100644 --- a/gnovm/cmd/gno/list.go +++ b/gnovm/cmd/gno/list.go @@ -42,16 +42,20 @@ func execList(cfg *listCfg, args []string, _ commands.IO) error { } if !cfg.json { - pkgPaths, err := importer.ListPackages(args...) + pkgs, err := importer.DiscoverPackages(args...) if err != nil { fmt.Println(err) os.Exit(1) } + pkgPaths := make([]string, len(pkgs)) + for i, pkg := range pkgs { + pkgPaths[i] = pkg.PkgPath + } fmt.Println(strings.Join(pkgPaths, "\n")) return nil } - pkgs, err := importer.ResolvePackages(args...) + pkgs, err := importer.Load(args...) if err != nil { fmt.Println(err) os.Exit(1) diff --git a/gnovm/pkg/importer/resolve.go b/gnovm/pkg/importer/resolve.go index 3d851b75689..3ce200e348f 100644 --- a/gnovm/pkg/importer/resolve.go +++ b/gnovm/pkg/importer/resolve.go @@ -52,48 +52,13 @@ func findModDir(dir string) (string, error) { return filepath.Clean(dir), nil } -func ListPackages(paths ...string) ([]string, error) { - details, _, err := ListPackagesDetails(paths...) - if err != nil { - return nil, err - } - res := make([]string, len(details)) - for i, p := range details { - res[i] = p.PkgPath - } - return res, nil -} - -func ListPackagesDetails(paths ...string) ([]PackageDetails, *modfile.File, error) { - wd, err := os.Getwd() - if err != nil { - return nil, nil, fmt.Errorf("failed to get workdir: %w", err) - } - - modDir, err := findModDir(wd) - if os.IsNotExist(err) { - return nil, nil, errors.New("gno.mod file not found in current directory or any parent directory") - } else if err != nil { - return nil, nil, fmt.Errorf("failed to find parent module: %w", err) - } - modFilePath := filepath.Join(modDir, modFileBaseName) - modFileBytes, err := os.ReadFile(modFilePath) - if err != nil { - return nil, nil, fmt.Errorf("failed to read modfile: %w", err) - } - modFile, err := modfile.ParseLax(modFilePath, modFileBytes, nil) - if err != nil { - return nil, nil, fmt.Errorf("failed to parse modfile: %w", err) - } - - pkgPath := modFile.Module.Mod.Path - +func DiscoverPackages(paths ...string) ([]PackageSummary, error) { toVisit := []string{} for _, p := range paths { toVisit = append(toVisit, filepath.Clean(p)) } visited := map[string]struct{}{} - packages := []PackageDetails{} + packages := []PackageSummary{} errs := []error{} for len(toVisit) > 0 { @@ -105,16 +70,32 @@ func ListPackagesDetails(paths ...string) ([]PackageDetails, *modfile.File, erro if !isRecursivePath(p) { if p != "." && strings.ContainsRune(p, '.') { - packages = append(packages, PackageDetails{ + packages = append(packages, PackageSummary{ PkgPath: p, Remote: true, }) continue } - p = path.Join(pkgPath, p) - packages = append(packages, PackageDetails{ - PkgPath: p, + modDir, err := findModDir(root) + if os.IsNotExist(err) { + continue + } else if err != nil { + return nil, fmt.Errorf("failed to find parent module: %w", err) + } + modFilePath := filepath.Join(modDir, modFileBaseName) + modFileBytes, err := os.ReadFile(modFilePath) + if err != nil { + return nil, fmt.Errorf("failed to read modfile: %w", err) + } + modFile, err := modfile.ParseLax(modFilePath, modFileBytes, nil) + if err != nil { + return nil, fmt.Errorf("failed to parse modfile: %w", err) + } + globalPkgPath := modFile.Module.Mod.Path + packages = append(packages, PackageSummary{ + PkgPath: path.Join(globalPkgPath, p), Root: root, + ModFile: modFile, }) continue } @@ -146,26 +127,27 @@ func ListPackagesDetails(paths ...string) ([]PackageDetails, *modfile.File, erro } } - return packages, modFile, errors.Join(errs...) + return packages, errors.Join(errs...) } -type PackageDetails struct { +type PackageSummary struct { PkgPath string Root string Remote bool + ModFile *modfile.File } -func ResolvePackages(paths ...string) ([]*Package, error) { - pkgs, modFile, err := ListPackagesDetails(paths...) +func Load(paths ...string) (map[string]*Package, error) { + pkgs, err := DiscoverPackages(paths...) if err != nil { return nil, fmt.Errorf("failed to list packages: %w", err) } - res := make([]*Package, len(pkgs)) + res := make(map[string]*Package, len(pkgs)) errs := []error{} - for i, meta := range pkgs { + for _, meta := range pkgs { if meta.Remote { - res[i], err = ResolveRemote(meta.PkgPath) + res[meta.PkgPath], err = ResolveRemote(meta.PkgPath) if err != nil { errs = append(errs, err) } @@ -176,14 +158,16 @@ func ResolvePackages(paths ...string) ([]*Package, error) { if err != nil { errs = append(errs, fmt.Errorf("failed to find absolute root %q: %w", meta.Root, err)) } - pkgPath := meta.PkgPath - pkg, err := fillPackage(pkgPath, absRoot, modFile) + if meta.ModFile == nil { + return nil, errors.New("unexpected nil modfile") + } + pkg, err := fillPackage(pkgPath, absRoot, meta.ModFile) if err != nil { return nil, fmt.Errorf("failed to fill Package %q: %w", pkgPath, err) } - res[i] = pkg + res[meta.PkgPath] = pkg } return res, errors.Join(errs...) } @@ -195,8 +179,22 @@ func ResolveRemote(pkgPath string) (*Package, error) { if err := DownloadModule(pkgPath, pkgDir); err != nil { return nil, fmt.Errorf("failed to download module %q: %w", pkgPath, err) } - - pkg, err := fillPackage(pkgPath, pkgDir, nil) + modDir, err := findModDir(pkgDir) + if os.IsNotExist(err) { + return nil, errors.New("failed to clone mod") + } else if err != nil { + return nil, fmt.Errorf("failed to find parent module: %w", err) + } + modFilePath := filepath.Join(modDir, modFileBaseName) + modFileBytes, err := os.ReadFile(modFilePath) + if err != nil { + return nil, fmt.Errorf("failed to read modfile: %w", err) + } + modFile, err := modfile.ParseLax(modFilePath, modFileBytes, nil) + if err != nil { + return nil, fmt.Errorf("failed to parse modfile: %w", err) + } + pkg, err := fillPackage(pkgPath, pkgDir, modFile) if err != nil { return nil, fmt.Errorf("failed to fill Package %q: %w", pkgPath, err) } From dfc0881a603a45775e6ad68dbc0fc92b6d402bc2 Mon Sep 17 00:00:00 2001 From: Norman Meier Date: Wed, 9 Oct 2024 22:34:32 +0200 Subject: [PATCH 006/143] tmp: working Signed-off-by: Norman Meier --- gnovm/cmd/gno/list.go | 26 +++- gnovm/pkg/importer/definitions.go | 18 +++ gnovm/pkg/importer/resolve.go | 216 +++++++++++++++++++----------- gnovm/stdlibs/net/url/url.gno | 11 +- go.mod | 1 - go.sum | 3 - 6 files changed, 182 insertions(+), 93 deletions(-) diff --git a/gnovm/cmd/gno/list.go b/gnovm/cmd/gno/list.go index ab7f0f2aedf..8dc1301b316 100644 --- a/gnovm/cmd/gno/list.go +++ b/gnovm/cmd/gno/list.go @@ -14,6 +14,7 @@ import ( type listCfg struct { json bool + deps bool } func newListCmd(io commands.IO) *commands.Command { @@ -33,7 +34,16 @@ func newListCmd(io commands.IO) *commands.Command { } func (c *listCfg) RegisterFlags(fs *flag.FlagSet) { - fs.BoolVar(&c.json, "json", false, "verbose output when listning") + fs.BoolVar(&c.json, "json", false, `The -json flag causes the package data to be printed in JSON format +instead of using the template format. The JSON flag can optionally be +provided with a set of comma-separated required field names to be output. +If so, those required fields will always appear in JSON output, but +others may be omitted to save work in computing the JSON struct.`) + fs.BoolVar(&c.deps, "deps", false, `The -deps flag causes list to iterate over not just the named packages +but also all their dependencies. It visits them in a depth-first post-order +traversal, so that a package is listed only after all its dependencies. +Packages not explicitly listed on the command line will have the DepOnly +field set to true`) } func execList(cfg *listCfg, args []string, _ commands.IO) error { @@ -60,11 +70,17 @@ func execList(cfg *listCfg, args []string, _ commands.IO) error { fmt.Println(err) os.Exit(1) } - pkgsBytes, err := json.MarshalIndent(pkgs, "", "\t") - if err != nil { - panic(err) + for _, pkg := range pkgs { + if len(pkg.Match) == 0 && !cfg.deps { + continue + } + + pkgBytes, err := json.MarshalIndent(pkg, "", "\t") + if err != nil { + panic(err) + } + fmt.Println(string(pkgBytes)) } - fmt.Println(string(pkgsBytes)) return nil } diff --git a/gnovm/pkg/importer/definitions.go b/gnovm/pkg/importer/definitions.go index d3a12a2d986..96d37b69c1c 100644 --- a/gnovm/pkg/importer/definitions.go +++ b/gnovm/pkg/importer/definitions.go @@ -1,5 +1,10 @@ package importer +import ( + "os" + "strings" +) + // This file contains "definitions"; it attempts to centralize some common // answers to common questions like "Is this a gno file?", "What is the import // path to the gno repository?", "Is this import path of a realm?". @@ -10,6 +15,12 @@ const ( // GnolangImport is the import path to the gnolang package. GnolangImport = RepoImport + "/gnovm/pkg/gnolang" + + // ModfileName is the name of the module file. + ModfileName = "gno.mod" + + // RecursiveSuffix is the os-dependent suffix marking a recursive target + RecursiveSuffix = string(os.PathSeparator) + "..." ) // IsGnoFile determines whether the given files matches all of the given patterns, @@ -28,3 +39,10 @@ func IsGnoFile(name string, patterns ...string) bool { } return m } + +func IsGnoTestFile(p string) bool { + if !IsGnoFile(p) { + return false + } + return strings.HasSuffix(p, "_test.gno") || strings.HasSuffix(p, "_filetest.gno") +} diff --git a/gnovm/pkg/importer/resolve.go b/gnovm/pkg/importer/resolve.go index 3ce200e348f..6958a4f50a0 100644 --- a/gnovm/pkg/importer/resolve.go +++ b/gnovm/pkg/importer/resolve.go @@ -18,61 +18,32 @@ import ( ) const recursiveSuffix = string(os.PathSeparator) + "..." -const modFileBaseName = "gno.mod" -func isRecursivePath(p string) bool { - return strings.HasSuffix(p, recursiveSuffix) || p == "..." -} - -func convertRecursivePathToDir(p string) string { - if p == "..." { - return "." - } - if !strings.HasSuffix(p, recursiveSuffix) { - return p - } - return p[:len(p)-4] -} - -func findModDir(dir string) (string, error) { - dir = filepath.Clean(dir) - - potentialMod := filepath.Join(dir, modFileBaseName) - - if _, err := os.Stat(potentialMod); os.IsNotExist(err) { - parent, file := filepath.Split(dir) - if file == "" { - return "", os.ErrNotExist - } - return findModDir(parent) - } else if err != nil { - return "", err - } - - return filepath.Clean(dir), nil -} - -func DiscoverPackages(paths ...string) ([]PackageSummary, error) { +// TODO: match mapping +func DiscoverPackages(paths ...string) ([]*PackageSummary, error) { toVisit := []string{} for _, p := range paths { toVisit = append(toVisit, filepath.Clean(p)) } visited := map[string]struct{}{} - packages := []PackageSummary{} + packages := []*PackageSummary{} errs := []error{} for len(toVisit) > 0 { p := toVisit[0] - visited[p] = struct{}{} toVisit = toVisit[1:] + if _, ok := visited[p]; ok { + continue + } + visited[p] = struct{}{} + root := convertRecursivePathToDir(p) if !isRecursivePath(p) { if p != "." && strings.ContainsRune(p, '.') { - packages = append(packages, PackageSummary{ + packages = append(packages, &PackageSummary{ PkgPath: p, - Remote: true, }) continue } @@ -82,7 +53,7 @@ func DiscoverPackages(paths ...string) ([]PackageSummary, error) { } else if err != nil { return nil, fmt.Errorf("failed to find parent module: %w", err) } - modFilePath := filepath.Join(modDir, modFileBaseName) + modFilePath := filepath.Join(modDir, ModfileName) modFileBytes, err := os.ReadFile(modFilePath) if err != nil { return nil, fmt.Errorf("failed to read modfile: %w", err) @@ -92,9 +63,18 @@ func DiscoverPackages(paths ...string) ([]PackageSummary, error) { return nil, fmt.Errorf("failed to parse modfile: %w", err) } globalPkgPath := modFile.Module.Mod.Path - packages = append(packages, PackageSummary{ - PkgPath: path.Join(globalPkgPath, p), - Root: root, + relfpath, err := filepath.Rel(modDir, p) + if err != nil { + return nil, fmt.Errorf("failed to get pkg path relative to mod root: %w", err) + } + relpath := strings.Join(filepath.SplitList(relfpath), "/") + absroot, err := filepath.Abs(root) + if err != nil { + return nil, fmt.Errorf("failed to get absolute pkg root") + } + packages = append(packages, &PackageSummary{ + PkgPath: path.Join(globalPkgPath, relpath), + Root: absroot, ModFile: modFile, }) continue @@ -111,9 +91,7 @@ func DiscoverPackages(paths ...string) ([]PackageSummary, error) { fileName := entry.Name() if entry.IsDir() { dirPath := filepath.Join(root, fileName) + recursiveSuffix - if _, ok := visited[dirPath]; !ok { - toVisit = append(toVisit, dirPath) - } + toVisit = append(toVisit, dirPath) } if !hasGnoFiles && IsGnoFile(fileName) { hasGnoFiles = true @@ -121,9 +99,7 @@ func DiscoverPackages(paths ...string) ([]PackageSummary, error) { } if hasGnoFiles { - if _, ok := visited[root]; !ok { - toVisit = append(toVisit, root) - } + toVisit = append(toVisit, root) } } @@ -133,8 +109,8 @@ func DiscoverPackages(paths ...string) ([]PackageSummary, error) { type PackageSummary struct { PkgPath string Root string - Remote bool ModFile *modfile.File + Match []string } func Load(paths ...string) (map[string]*Package, error) { @@ -143,37 +119,94 @@ func Load(paths ...string) (map[string]*Package, error) { return nil, fmt.Errorf("failed to list packages: %w", err) } - res := make(map[string]*Package, len(pkgs)) + visited := map[string]struct{}{} + cache := make(map[string]*Package) errs := []error{} - for _, meta := range pkgs { - if meta.Remote { - res[meta.PkgPath], err = ResolveRemote(meta.PkgPath) - if err != nil { - errs = append(errs, err) - } + for pile := pkgs; len(pile) > 0; pile = pile[1:] { + pkgSum := pile[0] + if _, ok := visited[pkgSum.PkgPath]; ok { continue } + visited[pkgSum.PkgPath] = struct{}{} - absRoot, err := filepath.Abs(meta.Root) + pkg, err := resolvePackage(pkgSum) if err != nil { - errs = append(errs, fmt.Errorf("failed to find absolute root %q: %w", meta.Root, err)) - } - pkgPath := meta.PkgPath - if meta.ModFile == nil { - return nil, errors.New("unexpected nil modfile") + errs = append(errs, fmt.Errorf("failed to resolve package %q: %w", pkgSum.PkgPath, err)) + continue } - pkg, err := fillPackage(pkgPath, absRoot, meta.ModFile) - if err != nil { - return nil, fmt.Errorf("failed to fill Package %q: %w", pkgPath, err) + + // TODO: what about TestImports + for _, imp := range pkg.Imports { + pile = append(pile, &PackageSummary{ + PkgPath: imp, + }) } - res[meta.PkgPath] = pkg + cache[pkg.ImportPath] = pkg + } + + for _, pkg := range cache { + // TODO: this could be optimized + pkg.Deps = listDeps(pkg.ImportPath, cache) + } + + return cache, errors.Join(errs...) +} + +func listDeps(target string, pkgs map[string]*Package) []string { + deps := []string{} + listDepsRecursive(target, pkgs, &deps, make(map[string]struct{})) + return deps +} + +func listDepsRecursive(target string, pkgs map[string]*Package, deps *[]string, visited map[string]struct{}) { + if _, ok := visited[target]; ok { + return + } + pkg, ok := pkgs[target] + if !ok { + panic(fmt.Errorf("%s not found in cache", target)) } - return res, errors.Join(errs...) + visited[target] = struct{}{} + for _, imp := range pkg.Imports { + listDepsRecursive(imp, pkgs, deps, visited) + } + (*deps) = append(*deps, target) +} + +func resolvePackage(meta *PackageSummary) (*Package, error) { + if !strings.ContainsRune(meta.PkgPath, '.') { + return resolveStdlib(meta.PkgPath) + } + + if meta.Root == "" { + return resolveRemote(meta.PkgPath) + } + + if meta.ModFile == nil { + return nil, errors.New("unexpected nil modfile") + } + + absRoot, err := filepath.Abs(meta.Root) + if err != nil { + return nil, fmt.Errorf("failed to find absolute root %q: %w", meta.Root, err) + } + + return fillPackage(meta.PkgPath, absRoot, meta.ModFile) +} + +func resolveStdlib(pkgPath string) (*Package, error) { + gnoRoot, err := gnoenv.GuessRootDir() + if err != nil { + return nil, fmt.Errorf("failed to guess gno root dir: %w", err) + } + parts := strings.Split(pkgPath, "/") + libDir := filepath.Join(append([]string{gnoRoot, "gnovm", "stdlibs"}, parts...)...) + return fillPackage(pkgPath, libDir, nil) } // Does not fill deps -func ResolveRemote(pkgPath string) (*Package, error) { +func resolveRemote(pkgPath string) (*Package, error) { modCache := filepath.Join(gnoenv.HomeDir(), "pkg", "mod") pkgDir := filepath.Join(modCache, pkgPath) if err := DownloadModule(pkgPath, pkgDir); err != nil { @@ -185,7 +218,7 @@ func ResolveRemote(pkgPath string) (*Package, error) { } else if err != nil { return nil, fmt.Errorf("failed to find parent module: %w", err) } - modFilePath := filepath.Join(modDir, modFileBaseName) + modFilePath := filepath.Join(modDir, ModfileName) modFileBytes, err := os.ReadFile(modFilePath) if err != nil { return nil, fmt.Errorf("failed to read modfile: %w", err) @@ -254,9 +287,9 @@ func fillPackage(pkgPath, pkgDir string, modFile *modfile.File) (*Package, error } func DownloadModule(pkgPath string, dst string) error { - modFilePath := filepath.Join(dst, modFileBaseName) + modFilePath := filepath.Join(dst, ModfileName) if _, err := os.Stat(modFilePath); os.IsNotExist(err) { - fmt.Println("gno: downloading", pkgPath) + fmt.Fprintln(os.Stderr, "gno: downloading", pkgPath) // create client from pkgpath parts := strings.Split(pkgPath, "/") @@ -295,7 +328,7 @@ func DownloadModule(pkgPath string, dst string) error { } // write gno.mod - if err := os.WriteFile(modFilePath, []byte("package "+pkgPath+"\n"), 0644); err != nil { + if err := os.WriteFile(modFilePath, []byte("module "+pkgPath+"\n"), 0644); err != nil { return fmt.Errorf("failed to write modfile at %q: %w", modFilePath, err) } } else if err != nil { @@ -305,13 +338,6 @@ func DownloadModule(pkgPath string, dst string) error { return nil } -func IsGnoTestFile(p string) bool { - if !IsGnoFile(p) { - return false - } - return strings.HasSuffix(p, "_test.gno") || strings.HasSuffix(p, "_filetest.gno") -} - type Package struct { Dir string `json:",omitempty"` ImportPath string `json:",omitempty"` @@ -376,3 +402,35 @@ func resolveNameAndImports(gnoFiles []string) (string, []string, error) { } return bestName, importsSlice, nil } + +func isRecursivePath(p string) bool { + return strings.HasSuffix(p, recursiveSuffix) || p == "..." +} + +func convertRecursivePathToDir(p string) string { + if p == "..." { + return "." + } + if !strings.HasSuffix(p, recursiveSuffix) { + return p + } + return p[:len(p)-4] +} + +func findModDir(dir string) (string, error) { + dir = filepath.Clean(dir) + + potentialMod := filepath.Join(dir, ModfileName) + + if _, err := os.Stat(potentialMod); os.IsNotExist(err) { + parent, file := filepath.Split(dir) + if file == "" { + return "", os.ErrNotExist + } + return findModDir(parent) + } else if err != nil { + return "", err + } + + return filepath.Clean(dir), nil +} diff --git a/gnovm/stdlibs/net/url/url.gno b/gnovm/stdlibs/net/url/url.gno index 501b263e873..006735d366c 100644 --- a/gnovm/stdlibs/net/url/url.gno +++ b/gnovm/stdlibs/net/url/url.gno @@ -12,7 +12,6 @@ package url import ( "errors" - "fmt" "path" "sort" "strconv" @@ -27,7 +26,9 @@ type Error struct { } func (e *Error) Unwrap() error { return e.Err } -func (e *Error) Error() string { return fmt.Sprintf("%s %q: %s", e.Op, e.URL, e.Err) } +func (e *Error) Error() string { + return e.Op + " " + strconv.Quote(e.URL) + ": " + e.Err +} func (e *Error) Timeout() bool { t, ok := e.Err.(interface { @@ -623,7 +624,7 @@ func parseHost(host string) (string, error) { } colonPort := host[i+1:] if !validOptionalPort(colonPort) { - return "", fmt.Errorf("invalid port %q after host", colonPort) + return "", errors.New("invalid port " + strconv.Quote(colonPort) + " after host") } // RFC 6874 defines that %25 (%-encoded percent) introduces @@ -651,7 +652,7 @@ func parseHost(host string) (string, error) { } else if i := strings.LastIndex(host, ":"); i != -1 { colonPort := host[i:] if !validOptionalPort(colonPort) { - return "", fmt.Errorf("invalid port %q after host", colonPort) + return "", errors.New("invalid port " + strconv.Quote(colonPort) + " after host") } } @@ -934,7 +935,7 @@ func parseQuery(m Values, query string) (err error) { var key string key, query, _ = strings.Cut(query, "&") if strings.Contains(key, ";") { - err = fmt.Errorf("invalid semicolon separator in query") + err = errors.New("invalid semicolon separator in query") continue } if key == "" { diff --git a/go.mod b/go.mod index 3e4c287ba41..76c42f0419c 100644 --- a/go.mod +++ b/go.mod @@ -23,7 +23,6 @@ require ( github.com/pelletier/go-toml v1.9.5 github.com/peterbourgon/ff/v3 v3.4.0 github.com/pmezard/go-difflib v1.0.0 - github.com/psanford/memfs v0.0.0-20240922203233-02fb08c0f8db github.com/rogpeppe/go-internal v1.12.0 github.com/rs/cors v1.10.1 github.com/rs/xid v1.5.0 diff --git a/go.sum b/go.sum index 455248884a4..e4d728a106d 100644 --- a/go.sum +++ b/go.sum @@ -74,7 +74,6 @@ github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEW github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= @@ -132,8 +131,6 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/psanford/memfs v0.0.0-20240922203233-02fb08c0f8db h1:uwKfJcTGnZ4ziFqr0bjzNc/P1fydrAg450b9Dz8Fj8M= -github.com/psanford/memfs v0.0.0-20240922203233-02fb08c0f8db/go.mod h1:tcaRap0jS3eifrEEllL6ZMd9dg8IlDpi2S1oARrQ+NI= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.3 h1:utMvzDsuh3suAEnhH0RdHmoPbU648o6CvXxTx4SBMOw= github.com/rivo/uniseg v0.4.3/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= From 7c7165fd780310bc46140d15e51976101d43b7ce Mon Sep 17 00:00:00 2001 From: Norman Meier Date: Thu, 10 Oct 2024 14:47:41 +0200 Subject: [PATCH 007/143] feat: match mapping Signed-off-by: Norman Meier --- gnovm/cmd/gno/lint.go | 5 + gnovm/cmd/gno/test.go | 20 ++-- gnovm/cmd/gno/transpile.go | 5 + gnovm/pkg/importer/definitions.go | 9 +- gnovm/pkg/importer/resolve.go | 164 ++++++++++++++++++++---------- 5 files changed, 144 insertions(+), 59 deletions(-) diff --git a/gnovm/cmd/gno/lint.go b/gnovm/cmd/gno/lint.go index 0a13cdc4898..59a2c96f711 100644 --- a/gnovm/cmd/gno/lint.go +++ b/gnovm/cmd/gno/lint.go @@ -71,6 +71,11 @@ func execLint(cfg *lintCfg, args []string, io commands.IO) error { hasError := false for _, pkg := range pkgs { + // ignore deps + if len(pkg.Match) == 0 { + continue + } + pkgDir := pkg.Dir pkgPath := pkg.ImportPath diff --git a/gnovm/cmd/gno/test.go b/gnovm/cmd/gno/test.go index 91cd308d263..467d69c6318 100644 --- a/gnovm/cmd/gno/test.go +++ b/gnovm/cmd/gno/test.go @@ -4,6 +4,7 @@ import ( "bytes" "context" "encoding/json" + "errors" "flag" "fmt" "log" @@ -22,7 +23,6 @@ import ( "github.com/gnolang/gno/gnovm/pkg/importer" "github.com/gnolang/gno/gnovm/tests" "github.com/gnolang/gno/tm2/pkg/commands" - "github.com/gnolang/gno/tm2/pkg/errors" "github.com/gnolang/gno/tm2/pkg/random" "github.com/gnolang/gno/tm2/pkg/std" "github.com/gnolang/gno/tm2/pkg/testutils" @@ -181,8 +181,13 @@ func execTest(cfg *testCfg, args []string, io commands.IO) error { buildErrCount := 0 testErrCount := 0 for _, pkg := range pkgs { + // ignore deps + if len(pkg.Match) == 0 { + continue + } + if len(pkg.TestGnoFiles) == 0 && len(pkg.FiletestGnoFiles) == 0 { - io.ErrPrintfln("? %s \t[no test files]", pkg.Dir) + io.ErrPrintfln("? %s \t[no test files]", pkg.ImportPath) continue } @@ -191,14 +196,15 @@ func execTest(cfg *testCfg, args []string, io commands.IO) error { duration := time.Since(startedAt) dstr := fmtDuration(duration) + err := errors.Join(append(pkg.Errors, err)...) if err != nil { - io.ErrPrintfln("%s: test pkg: %v", pkg.Dir, err) + io.ErrPrintfln("%s: test pkg: %w", pkg.ImportPath, err) io.ErrPrintfln("FAIL") - io.ErrPrintfln("FAIL %s \t%s", pkg.Dir, dstr) + io.ErrPrintfln("FAIL %s \t%s", pkg.ImportPath, dstr) io.ErrPrintfln("FAIL") testErrCount++ } else { - io.ErrPrintfln("ok %s \t%s", pkg.Dir, dstr) + io.ErrPrintfln("ok %s \t%s", pkg.ImportPath, dstr) } } if testErrCount > 0 || buildErrCount > 0 { @@ -447,7 +453,7 @@ func runTestFiles( ret := eval[0].GetString() if ret == "" { - err := errors.New("failed to execute unit test: %q", test.Name) + err := fmt.Errorf("failed to execute unit test: %q", test.Name) errs = multierr.Append(errs, err) io.ErrPrintfln("--- FAIL: %s [internal gno testing error]", test.Name) continue @@ -463,7 +469,7 @@ func runTestFiles( } if rep.Failed { - err := errors.New("failed: %q", test.Name) + err := fmt.Errorf("failed: %q", test.Name) errs = multierr.Append(errs, err) } diff --git a/gnovm/cmd/gno/transpile.go b/gnovm/cmd/gno/transpile.go index 5f13ad4d7f0..b34075d66d1 100644 --- a/gnovm/cmd/gno/transpile.go +++ b/gnovm/cmd/gno/transpile.go @@ -143,6 +143,11 @@ func execTranspile(cfg *transpileCfg, args []string, io commands.IO) error { opts := newTranspileOptions(cfg, io) var errlist scanner.ErrorList for _, pkg := range pkgs { + // ignore deps + if len(pkg.Match) == 0 { + continue + } + st, err := os.Stat(pkg.Dir) if err != nil { return err diff --git a/gnovm/pkg/importer/definitions.go b/gnovm/pkg/importer/definitions.go index 96d37b69c1c..1f47e600f2b 100644 --- a/gnovm/pkg/importer/definitions.go +++ b/gnovm/pkg/importer/definitions.go @@ -44,5 +44,12 @@ func IsGnoTestFile(p string) bool { if !IsGnoFile(p) { return false } - return strings.HasSuffix(p, "_test.gno") || strings.HasSuffix(p, "_filetest.gno") + return strings.HasSuffix(p, "_test.gno") +} + +func IsGnoFiletestFile(p string) bool { + if !IsGnoFile(p) { + return false + } + return strings.HasSuffix(p, "_filetest.gno") } diff --git a/gnovm/pkg/importer/resolve.go b/gnovm/pkg/importer/resolve.go index 762065a0bb3..134665f2454 100644 --- a/gnovm/pkg/importer/resolve.go +++ b/gnovm/pkg/importer/resolve.go @@ -19,32 +19,57 @@ import ( const recursiveSuffix = string(os.PathSeparator) + "..." -// TODO: match mapping +type visitTarget struct { + path string + match string +} + func DiscoverPackages(paths ...string) ([]*PackageSummary, error) { - toVisit := []string{} + toVisit := []visitTarget{} for _, p := range paths { - toVisit = append(toVisit, filepath.Clean(p)) + toVisit = append(toVisit, visitTarget{path: p, match: p}) } - visited := map[string]struct{}{} + visited := map[visitTarget]struct{}{} + cache := map[string]*PackageSummary{} packages := []*PackageSummary{} errs := []error{} for len(toVisit) > 0 { - p := toVisit[0] + tgt := toVisit[0] toVisit = toVisit[1:] - if _, ok := visited[p]; ok { + if _, ok := visited[tgt]; ok { + continue + } + visited[tgt] = struct{}{} + + if tgt.path == "" { continue } - visited[p] = struct{}{} - root := convertRecursivePathToDir(p) + if tgt.path[0] == '.' { + absPath, err := filepath.Abs(tgt.path) + if err != nil { + errs = append(errs, fmt.Errorf("failed to get absolute path for %q: %w", tgt, err)) + } + toVisit = append(toVisit, visitTarget{path: absPath, match: tgt.match}) + continue + } - if !isRecursivePath(p) { - if p != "." && strings.ContainsRune(p, '.') { - packages = append(packages, &PackageSummary{ - PkgPath: p, - }) + root := convertRecursivePathToDir(tgt.path) + + if !isRecursivePath(tgt.path) { + if tgt.path[0] != '/' { + pkgPath := tgt.path + if pkg, ok := cache[pkgPath]; ok { + pkg.Match = append(pkg.Match, tgt.match) + } else { + cache[pkgPath] = &PackageSummary{ + PkgPath: pkgPath, + Match: []string{tgt.match}, + } + packages = append(packages, cache[pkgPath]) + } continue } modDir, err := findModDir(root) @@ -66,7 +91,7 @@ func DiscoverPackages(paths ...string) ([]*PackageSummary, error) { continue } globalPkgPath := modFile.Module.Mod.Path - relfpath, err := filepath.Rel(modDir, p) + relfpath, err := filepath.Rel(modDir, tgt.path) if err != nil { return nil, fmt.Errorf("failed to get pkg path relative to mod root: %w", err) } @@ -75,11 +100,18 @@ func DiscoverPackages(paths ...string) ([]*PackageSummary, error) { if err != nil { return nil, fmt.Errorf("failed to get absolute pkg root") } - packages = append(packages, &PackageSummary{ - PkgPath: path.Join(globalPkgPath, relpath), - Root: absroot, - ModFile: modFile, - }) + pkgPath := path.Join(globalPkgPath, relpath) + if pkg, ok := cache[pkgPath]; ok { + pkg.Match = append(pkg.Match, tgt.match) + } else { + cache[pkgPath] = &PackageSummary{ + PkgPath: path.Join(globalPkgPath, relpath), + Root: absroot, + ModFile: modFile, + Match: []string{tgt.match}, + } + packages = append(packages, cache[pkgPath]) + } continue } @@ -94,7 +126,7 @@ func DiscoverPackages(paths ...string) ([]*PackageSummary, error) { fileName := entry.Name() if entry.IsDir() { dirPath := filepath.Join(root, fileName) + recursiveSuffix - toVisit = append(toVisit, dirPath) + toVisit = append(toVisit, visitTarget{path: dirPath, match: tgt.match}) } if !hasGnoFiles && IsGnoFile(fileName) { hasGnoFiles = true @@ -102,7 +134,7 @@ func DiscoverPackages(paths ...string) ([]*PackageSummary, error) { } if hasGnoFiles { - toVisit = append(toVisit, root) + toVisit = append(toVisit, visitTarget{path: root, match: tgt.match}) } } @@ -116,7 +148,7 @@ type PackageSummary struct { Match []string } -func Load(paths ...string) (map[string]*Package, error) { +func Load(paths ...string) ([]*Package, error) { pkgs, err := DiscoverPackages(paths...) if err != nil { return nil, fmt.Errorf("failed to list packages: %w", err) @@ -124,6 +156,7 @@ func Load(paths ...string) (map[string]*Package, error) { visited := map[string]struct{}{} cache := make(map[string]*Package) + list := []*Package{} errs := []error{} for pile := pkgs; len(pile) > 0; pile = pile[1:] { pkgSum := pile[0] @@ -134,8 +167,12 @@ func Load(paths ...string) (map[string]*Package, error) { pkg, err := resolvePackage(pkgSum) if err != nil { - errs = append(errs, fmt.Errorf("failed to resolve package %q: %w", pkgSum.PkgPath, err)) - continue + pkg = &Package{ + ImportPath: pkgSum.PkgPath, + Dir: pkgSum.Root, + Match: pkgSum.Match, + } + pkg.Errors = append(pkg.Errors, fmt.Errorf("failed to resolve package %q: %w", pkgSum.PkgPath, err)) } // TODO: what about TestImports @@ -146,44 +183,56 @@ func Load(paths ...string) (map[string]*Package, error) { } cache[pkg.ImportPath] = pkg + list = append(list, pkg) } - for _, pkg := range cache { + for _, pkg := range list { + if len(pkg.Errors) > 0 { + continue + } // TODO: this could be optimized - pkg.Deps = listDeps(pkg.ImportPath, cache) + pkg.Deps, err = listDeps(pkg.ImportPath, cache) + if err != nil { + pkg.Errors = append(pkg.Errors, err) + } } - return cache, errors.Join(errs...) + return list, errors.Join(errs...) } -func listDeps(target string, pkgs map[string]*Package) []string { +func listDeps(target string, pkgs map[string]*Package) ([]string, error) { deps := []string{} - listDepsRecursive(target, pkgs, &deps, make(map[string]struct{})) - return deps + err := listDepsRecursive(target, pkgs, &deps, make(map[string]struct{})) + return deps, err } -func listDepsRecursive(target string, pkgs map[string]*Package, deps *[]string, visited map[string]struct{}) { +func listDepsRecursive(target string, pkgs map[string]*Package, deps *[]string, visited map[string]struct{}) error { if _, ok := visited[target]; ok { - return + return nil } pkg, ok := pkgs[target] if !ok { - panic(fmt.Errorf("%s not found in cache", target)) + return fmt.Errorf("%s not found in cache", target) } visited[target] = struct{}{} + var errs []error for _, imp := range pkg.Imports { - listDepsRecursive(imp, pkgs, deps, visited) + err := listDepsRecursive(imp, pkgs, deps, visited) + if err != nil { + errs = append(errs, err) + } } (*deps) = append(*deps, target) + return errors.Join(errs...) } func resolvePackage(meta *PackageSummary) (*Package, error) { if !strings.ContainsRune(meta.PkgPath, '.') { - return resolveStdlib(meta.PkgPath) + return resolveStdlib(meta.PkgPath, meta.Match) } if meta.Root == "" { - return resolveRemote(meta.PkgPath) + return resolveRemote(meta.PkgPath, meta.Match) } if meta.ModFile == nil { @@ -195,21 +244,21 @@ func resolvePackage(meta *PackageSummary) (*Package, error) { return nil, fmt.Errorf("failed to find absolute root %q: %w", meta.Root, err) } - return fillPackage(meta.PkgPath, absRoot, meta.ModFile) + return fillPackage(meta.PkgPath, absRoot, meta.ModFile, meta.Match) } -func resolveStdlib(pkgPath string) (*Package, error) { +func resolveStdlib(pkgPath string, match []string) (*Package, error) { gnoRoot, err := gnoenv.GuessRootDir() if err != nil { return nil, fmt.Errorf("failed to guess gno root dir: %w", err) } parts := strings.Split(pkgPath, "/") libDir := filepath.Join(append([]string{gnoRoot, "gnovm", "stdlibs"}, parts...)...) - return fillPackage(pkgPath, libDir, nil) + return fillPackage(pkgPath, libDir, nil, match) } // Does not fill deps -func resolveRemote(pkgPath string) (*Package, error) { +func resolveRemote(pkgPath string, match []string) (*Package, error) { modCache := filepath.Join(gnoenv.HomeDir(), "pkg", "mod") pkgDir := filepath.Join(modCache, pkgPath) if err := DownloadModule(pkgPath, pkgDir); err != nil { @@ -230,7 +279,7 @@ func resolveRemote(pkgPath string) (*Package, error) { if err != nil { return nil, fmt.Errorf("failed to parse modfile: %w", err) } - pkg, err := fillPackage(pkgPath, pkgDir, modFile) + pkg, err := fillPackage(pkgPath, pkgDir, modFile, match) if err != nil { return nil, fmt.Errorf("failed to fill Package %q: %w", pkgPath, err) } @@ -238,11 +287,13 @@ func resolveRemote(pkgPath string) (*Package, error) { return pkg, nil } -func fillPackage(pkgPath, pkgDir string, modFile *modfile.File) (*Package, error) { +func fillPackage(pkgPath, pkgDir string, modFile *modfile.File, match []string) (*Package, error) { fsFiles := []string{} modFiles := []string{} fsTestFiles := []string{} testFiles := []string{} + fsFiletestFiles := []string{} + filetestFiles := []string{} dir, err := os.ReadDir(pkgDir) if err != nil { return nil, fmt.Errorf("failed to read module files list: %w", err) @@ -256,6 +307,9 @@ func fillPackage(pkgPath, pkgDir string, modFile *modfile.File) (*Package, error if IsGnoTestFile(fileName) { fsTestFiles = append(fsTestFiles, filepath.Join(pkgDir, fileName)) testFiles = append(testFiles, fileName) + } else if IsGnoFiletestFile(fileName) { + fsTestFiles = append(fsFiletestFiles, filepath.Join(pkgDir, fileName)) + testFiles = append(filetestFiles, fileName) } else if IsGnoFile(fileName) { fsFiles = append(fsFiles, filepath.Join(pkgDir, fileName)) modFiles = append(modFiles, fileName) @@ -267,7 +321,11 @@ func fillPackage(pkgPath, pkgDir string, modFile *modfile.File) (*Package, error } _, testImports, err := resolveNameAndImports(fsTestFiles) if err != nil { - return nil, fmt.Errorf("failed to resolve name test imports for %q: %w", pkgPath, err) + return nil, fmt.Errorf("failed to resolve test name and imports for %q: %w", pkgPath, err) + } + _, filetestImports, err := resolveNameAndImports(filetestFiles) + if err != nil { + return nil, fmt.Errorf("failed to resolve filetest name and imports for %q: %w", pkgPath, err) } module := Module{} @@ -278,14 +336,17 @@ func fillPackage(pkgPath, pkgDir string, modFile *modfile.File) (*Package, error } return &Package{ - ImportPath: pkgPath, - Dir: pkgDir, - Name: name, - Module: module, - GnoFiles: modFiles, - Imports: imports, - TestGnoFiles: testFiles, - TestImports: testImports, + ImportPath: pkgPath, + Dir: pkgDir, + Name: name, + Module: module, + Match: match, + GnoFiles: modFiles, + Imports: imports, + TestGnoFiles: testFiles, + TestImports: testImports, + FiletestGnoFiles: filetestFiles, + FiletestImports: filetestImports, }, nil } @@ -355,6 +416,7 @@ type Package struct { TestImports []string `json:",omitempty"` FiletestGnoFiles []string `json:",omitempty"` FiletestImports []string `json:",omitempty"` + Errors []error `json:",omitempty"` } type Module struct { From 1c3a83045daabbc71b2ddc082c7f12478a996273 Mon Sep 17 00:00:00 2001 From: Norman Meier Date: Thu, 10 Oct 2024 15:13:08 +0200 Subject: [PATCH 008/143] fix: test and lint Signed-off-by: Norman Meier --- gnovm/cmd/gno/lint.go | 13 +++----- gnovm/pkg/importer/resolve.go | 61 +++++++++++++++++++++++------------ 2 files changed, 44 insertions(+), 30 deletions(-) diff --git a/gnovm/cmd/gno/lint.go b/gnovm/cmd/gno/lint.go index 59a2c96f711..388ab9ae14a 100644 --- a/gnovm/cmd/gno/lint.go +++ b/gnovm/cmd/gno/lint.go @@ -17,7 +17,6 @@ import ( "github.com/gnolang/gno/gnovm/pkg/importer" "github.com/gnolang/gno/gnovm/tests" "github.com/gnolang/gno/tm2/pkg/commands" - osm "github.com/gnolang/gno/tm2/pkg/os" ) type lintCfg struct { @@ -84,8 +83,7 @@ func execLint(cfg *lintCfg, args []string, io commands.IO) error { } // Check if 'gno.mod' exists - gnoModPath := filepath.Join(pkgDir, "gno.mod") - if !osm.FileExists(gnoModPath) { + if pkg.Module.Path == "" { hasError = true issue := lintIssue{ Code: lintNoGnoMod, @@ -105,13 +103,10 @@ func execLint(cfg *lintCfg, args []string, io commands.IO) error { tests.ImportModeStdlibsOnly, ) - targetPath := pkgDir - info, err := os.Stat(pkgDir) - if err == nil && !info.IsDir() { - targetPath = filepath.Dir(pkgDir) + memPkg, err := pkg.MemPkg() + if err != nil { + panic(fmt.Errorf("failed to convert pkg to mempkg: %w", err)) } - - memPkg := gno.ReadMemPackage(targetPath, targetPath) tm := tests.TestMachine(testStore, stdout, memPkg.Name) // Check package diff --git a/gnovm/pkg/importer/resolve.go b/gnovm/pkg/importer/resolve.go index 134665f2454..82f30143a96 100644 --- a/gnovm/pkg/importer/resolve.go +++ b/gnovm/pkg/importer/resolve.go @@ -14,6 +14,7 @@ import ( "github.com/gnolang/gno/gno.land/pkg/gnoclient" "github.com/gnolang/gno/gnovm/pkg/gnoenv" "github.com/gnolang/gno/tm2/pkg/bft/rpc/client" + "github.com/gnolang/gno/tm2/pkg/std" "golang.org/x/mod/modfile" ) @@ -202,11 +203,11 @@ func Load(paths ...string) ([]*Package, error) { func listDeps(target string, pkgs map[string]*Package) ([]string, error) { deps := []string{} - err := listDepsRecursive(target, pkgs, &deps, make(map[string]struct{})) + err := listDepsRecursive(target, target, pkgs, &deps, make(map[string]struct{})) return deps, err } -func listDepsRecursive(target string, pkgs map[string]*Package, deps *[]string, visited map[string]struct{}) error { +func listDepsRecursive(rootTarget string, target string, pkgs map[string]*Package, deps *[]string, visited map[string]struct{}) error { if _, ok := visited[target]; ok { return nil } @@ -217,34 +218,31 @@ func listDepsRecursive(target string, pkgs map[string]*Package, deps *[]string, visited[target] = struct{}{} var errs []error for _, imp := range pkg.Imports { - err := listDepsRecursive(imp, pkgs, deps, visited) + err := listDepsRecursive(rootTarget, imp, pkgs, deps, visited) if err != nil { errs = append(errs, err) } } - (*deps) = append(*deps, target) + if target != rootTarget { + (*deps) = append(*deps, target) + } return errors.Join(errs...) } func resolvePackage(meta *PackageSummary) (*Package, error) { - if !strings.ContainsRune(meta.PkgPath, '.') { - return resolveStdlib(meta.PkgPath, meta.Match) - } - if meta.Root == "" { - return resolveRemote(meta.PkgPath, meta.Match) + if !strings.ContainsRune(meta.PkgPath, '.') { + return resolveStdlib(meta.PkgPath, meta.Match) + } else { + return resolveRemote(meta.PkgPath, meta.Match) + } } if meta.ModFile == nil { return nil, errors.New("unexpected nil modfile") } - absRoot, err := filepath.Abs(meta.Root) - if err != nil { - return nil, fmt.Errorf("failed to find absolute root %q: %w", meta.Root, err) - } - - return fillPackage(meta.PkgPath, absRoot, meta.ModFile, meta.Match) + return fillPackage(meta.PkgPath, meta.Root, meta.ModFile, meta.Match) } func resolveStdlib(pkgPath string, match []string) (*Package, error) { @@ -289,7 +287,7 @@ func resolveRemote(pkgPath string, match []string) (*Package, error) { func fillPackage(pkgPath, pkgDir string, modFile *modfile.File, match []string) (*Package, error) { fsFiles := []string{} - modFiles := []string{} + files := []string{} fsTestFiles := []string{} testFiles := []string{} fsFiletestFiles := []string{} @@ -308,11 +306,11 @@ func fillPackage(pkgPath, pkgDir string, modFile *modfile.File, match []string) fsTestFiles = append(fsTestFiles, filepath.Join(pkgDir, fileName)) testFiles = append(testFiles, fileName) } else if IsGnoFiletestFile(fileName) { - fsTestFiles = append(fsFiletestFiles, filepath.Join(pkgDir, fileName)) - testFiles = append(filetestFiles, fileName) + fsFiletestFiles = append(fsFiletestFiles, filepath.Join(pkgDir, fileName)) + filetestFiles = append(filetestFiles, fileName) } else if IsGnoFile(fileName) { fsFiles = append(fsFiles, filepath.Join(pkgDir, fileName)) - modFiles = append(modFiles, fileName) + files = append(files, fileName) } } name, imports, err := resolveNameAndImports(fsFiles) @@ -323,7 +321,7 @@ func fillPackage(pkgPath, pkgDir string, modFile *modfile.File, match []string) if err != nil { return nil, fmt.Errorf("failed to resolve test name and imports for %q: %w", pkgPath, err) } - _, filetestImports, err := resolveNameAndImports(filetestFiles) + _, filetestImports, err := resolveNameAndImports(fsFiletestFiles) if err != nil { return nil, fmt.Errorf("failed to resolve filetest name and imports for %q: %w", pkgPath, err) } @@ -341,7 +339,7 @@ func fillPackage(pkgPath, pkgDir string, modFile *modfile.File, match []string) Name: name, Module: module, Match: match, - GnoFiles: modFiles, + GnoFiles: files, Imports: imports, TestGnoFiles: testFiles, TestImports: testImports, @@ -419,6 +417,27 @@ type Package struct { Errors []error `json:",omitempty"` } +func (p *Package) MemPkg() (*std.MemPackage, error) { + allFiles := append(p.GnoFiles, p.TestGnoFiles...) + allFiles = append(allFiles, p.FiletestGnoFiles...) + files := make([]*std.MemFile, len(allFiles)) + for i, f := range allFiles { + body, err := os.ReadFile(filepath.Join(p.Dir, f)) + if err != nil { + return nil, err + } + files[i] = &std.MemFile{ + Name: f, + Body: string(body), + } + } + return &std.MemPackage{ + Name: p.Name, + Path: p.ImportPath, + Files: files, + }, nil +} + type Module struct { Path string `json:",omitempty"` Main bool `json:",omitempty"` From f95e4bb1e8bb59a28e8791e97231af3fe8995c55 Mon Sep 17 00:00:00 2001 From: Norman Meier Date: Thu, 10 Oct 2024 15:36:07 +0200 Subject: [PATCH 009/143] fix: partially fix transpile Signed-off-by: Norman Meier --- gnovm/cmd/gno/transpile.go | 49 +++++++++++++++++------------------ gnovm/pkg/importer/resolve.go | 1 + 2 files changed, 25 insertions(+), 25 deletions(-) diff --git a/gnovm/cmd/gno/transpile.go b/gnovm/cmd/gno/transpile.go index b34075d66d1..7e12b16c015 100644 --- a/gnovm/cmd/gno/transpile.go +++ b/gnovm/cmd/gno/transpile.go @@ -140,6 +140,11 @@ func execTranspile(cfg *transpileCfg, args []string, io commands.IO) error { return fmt.Errorf("load pkgs: %w", err) } + pkgsMap := map[string]*importer.Package{} + for _, pkg := range pkgs { + pkgsMap[pkg.ImportPath] = pkg + } + opts := newTranspileOptions(cfg, io) var errlist scanner.ErrorList for _, pkg := range pkgs { @@ -153,19 +158,21 @@ func execTranspile(cfg *transpileCfg, args []string, io commands.IO) error { return err } if st.IsDir() { - err = transpilePkg(pkg, opts) + err = transpilePkg(pkg, pkgsMap, opts) } else { + panic("should ot try to transpile file yet") + if opts.cfg.verbose { io.ErrPrintln(filepath.Clean(pkg.Dir)) } - err = transpileFile(pkg.Dir, opts) + err = transpileFile(pkg.Dir, pkgsMap, opts) } if err != nil { var fileErrlist scanner.ErrorList if !errors.As(err, &fileErrlist) { // Not an scanner.ErrorList: return immediately. - return fmt.Errorf("%s: transpile: %w", pkg, err) + return fmt.Errorf("%s: transpile: %w", pkg.ImportPath, err) } errlist = append(errlist, fileErrlist...) } @@ -181,7 +188,7 @@ func execTranspile(cfg *transpileCfg, args []string, io commands.IO) error { var fileErrlist scanner.ErrorList if !errors.As(err, &fileErrlist) { // Not an scanner.ErrorList: return immediately. - return fmt.Errorf("%s: build: %w", pkg, err) + return fmt.Errorf("%s: build: %w", pkg.ImportPath, err) } errlist = append(errlist, fileErrlist...) } @@ -200,7 +207,7 @@ func execTranspile(cfg *transpileCfg, args []string, io commands.IO) error { // transpilePkg transpiles all non-test files at the given location. // Additionally, it checks the gno.mod in said location, and skips it if it is // a draft module -func transpilePkg(pkg *importer.Package, opts *transpileOptions) error { +func transpilePkg(pkg *importer.Package, pkgs map[string]*importer.Package, opts *transpileOptions) error { dirPath := pkg.Dir if opts.isTranspiled(dirPath) { return nil @@ -227,7 +234,8 @@ func transpilePkg(pkg *importer.Package, opts *transpileOptions) error { opts.io.ErrPrintln(filepath.Clean(dirPath)) } for _, file := range pkg.GnoFiles { - if err := transpileFile(file, opts); err != nil { + fpath := filepath.Join(pkg.Dir, file) + if err := transpileFile(fpath, pkgs, opts); err != nil { return fmt.Errorf("%s: %w", file, err) } } @@ -235,7 +243,7 @@ func transpilePkg(pkg *importer.Package, opts *transpileOptions) error { return nil } -func transpileFile(srcPath string, opts *transpileOptions) error { +func transpileFile(srcPath string, pkgs map[string]*importer.Package, opts *transpileOptions) error { flags := opts.getFlags() // parse .gno. @@ -274,27 +282,18 @@ func transpileFile(srcPath string, opts *transpileOptions) error { // transpile imported packages, if `SkipImports` sets to false if !flags.skipImports && !strings.HasSuffix(srcPath, "_filetest.gno") && !strings.HasSuffix(srcPath, "_test.gno") { - dirPaths, err := getPathsFromImportSpec(opts.cfg.rootDir, transpileRes.Imports) - if err != nil { - return err - } - deps, err := importer.Load(dirPaths...) - if err != nil { - return err - } - for _, path := range dirPaths { - dep := (*importer.Package)(nil) - for _, elem := range deps { - abspath, _ := filepath.Abs(path) - if abspath == elem.Dir { - dep = elem - break - } + for _, imp := range transpileRes.Imports { + pkgPath, err := strconv.Unquote(imp.Path.Value) + if err != nil { + return fmt.Errorf("failed to unquote pkg path %v", imp.Path.Value) } + pkgPath = strings.TrimPrefix(pkgPath, "github.com/gnolang/gno/gnovm/stdlibs/") + pkgPath = strings.TrimPrefix(pkgPath, "github.com/gnolang/gno/examples/") + dep := pkgs[pkgPath] if dep == nil { - return fmt.Errorf("failed to find matching package for %q", path) + return fmt.Errorf("failed to find matching package for %q", pkgPath) } - if err := transpilePkg(dep, opts); err != nil { + if err := transpilePkg(dep, pkgs, opts); err != nil { return err } } diff --git a/gnovm/pkg/importer/resolve.go b/gnovm/pkg/importer/resolve.go index 82f30143a96..f792ae3e9bb 100644 --- a/gnovm/pkg/importer/resolve.go +++ b/gnovm/pkg/importer/resolve.go @@ -149,6 +149,7 @@ type PackageSummary struct { Match []string } +// FIXME: support files func Load(paths ...string) ([]*Package, error) { pkgs, err := DiscoverPackages(paths...) if err != nil { From 2d990e5e6c49905d6885a8c695539bf61db07179 Mon Sep 17 00:00:00 2001 From: Norman Meier Date: Thu, 10 Oct 2024 16:10:38 +0200 Subject: [PATCH 010/143] tmp: support loaded pkgs in test store Signed-off-by: Norman Meier --- gnovm/cmd/gno/lint.go | 7 ++- gnovm/cmd/gno/run.go | 2 +- gnovm/cmd/gno/test.go | 132 ++++++++++++++++++++++------------------- gnovm/pkg/repl/repl.go | 2 +- gnovm/tests/file.go | 2 +- gnovm/tests/imports.go | 20 ++++++- 6 files changed, 98 insertions(+), 67 deletions(-) diff --git a/gnovm/cmd/gno/lint.go b/gnovm/cmd/gno/lint.go index 388ab9ae14a..d67fd4b2c6c 100644 --- a/gnovm/cmd/gno/lint.go +++ b/gnovm/cmd/gno/lint.go @@ -67,6 +67,11 @@ func execLint(cfg *lintCfg, args []string, io commands.IO) error { return fmt.Errorf("list packages from args: %w", err) } + pkgsMap := map[string]*importer.Package{} + for _, pkg := range pkgs { + pkgsMap[pkg.ImportPath] = pkg + } + hasError := false for _, pkg := range pkgs { @@ -98,7 +103,7 @@ func execLint(cfg *lintCfg, args []string, io commands.IO) error { hasError = catchRuntimeError(pkgPath, io.Err(), func() { stdout, stdin, stderr := io.Out(), io.In(), io.Err() testStore := tests.TestStore( - rootDir, "", + rootDir, "", pkgsMap, stdin, stdout, stderr, tests.ImportModeStdlibsOnly, ) diff --git a/gnovm/cmd/gno/run.go b/gnovm/cmd/gno/run.go index dab84d6abad..a05277692e7 100644 --- a/gnovm/cmd/gno/run.go +++ b/gnovm/cmd/gno/run.go @@ -91,7 +91,7 @@ func execRun(cfg *runCfg, args []string, io commands.IO) error { // init store and machine testStore := tests.TestStore(cfg.rootDir, - "", stdin, stdout, stderr, + "", nil, stdin, stdout, stderr, tests.ImportModeStdlibsPreferred) if cfg.verbose { testStore.SetLogStoreOps(true) diff --git a/gnovm/cmd/gno/test.go b/gnovm/cmd/gno/test.go index 467d69c6318..daa12236a89 100644 --- a/gnovm/cmd/gno/test.go +++ b/gnovm/cmd/gno/test.go @@ -4,11 +4,9 @@ import ( "bytes" "context" "encoding/json" - "errors" "flag" "fmt" "log" - "os" "path/filepath" "runtime/debug" "strings" @@ -19,13 +17,10 @@ import ( "github.com/gnolang/gno/gnovm/pkg/gnoenv" gno "github.com/gnolang/gno/gnovm/pkg/gnolang" - "github.com/gnolang/gno/gnovm/pkg/gnomod" "github.com/gnolang/gno/gnovm/pkg/importer" "github.com/gnolang/gno/gnovm/tests" "github.com/gnolang/gno/tm2/pkg/commands" - "github.com/gnolang/gno/tm2/pkg/random" "github.com/gnolang/gno/tm2/pkg/std" - "github.com/gnolang/gno/tm2/pkg/testutils" ) type testCfg struct { @@ -192,11 +187,11 @@ func execTest(cfg *testCfg, args []string, io commands.IO) error { } startedAt := time.Now() - err = gnoTestPkg(pkg.Dir, pkg.TestGnoFiles, pkg.FiletestGnoFiles, cfg, io) + err = gnoTestPkg(pkg, pkgs, cfg, io) duration := time.Since(startedAt) dstr := fmtDuration(duration) - err := errors.Join(append(pkg.Errors, err)...) + err := multierr.Combine(append(pkg.Errors, err)...) if err != nil { io.ErrPrintfln("%s: test pkg: %w", pkg.ImportPath, err) io.ErrPrintfln("FAIL") @@ -216,9 +211,8 @@ func execTest(cfg *testCfg, args []string, io commands.IO) error { } func gnoTestPkg( - pkgPath string, - unittestFiles, - filetestFiles []string, + pkg *importer.Package, + loadedPkgs []*importer.Package, cfg *testCfg, io commands.IO, ) error { @@ -245,27 +239,23 @@ func gnoTestPkg( stdout = commands.WriteNopCloser(mockOut) } + pkgsMap := map[string]*importer.Package{} + for _, pkg := range loadedPkgs { + pkgsMap[pkg.ImportPath] = pkg + } + // testing with *_test.gno - if len(unittestFiles) > 0 { + if len(pkg.TestGnoFiles) > 0 { // Determine gnoPkgPath by reading gno.mod - var gnoPkgPath string - modfile, err := gnomod.ParseAt(pkgPath) - if err == nil { - gnoPkgPath = modfile.Module.Mod.Path - } else { - gnoPkgPath = pkgPathFromRootDir(pkgPath, rootDir) - if gnoPkgPath == "" { - // unable to read pkgPath from gno.mod, generate a random realm path - io.ErrPrintfln("--- WARNING: unable to read package path from gno.mod or gno root directory; try creating a gno.mod file") - gnoPkgPath = gno.RealmPathPrefix + random.RandStr(8) - } + memPkg, err := pkg.MemPkg() + if err != nil { + errs = multierr.Append(errs, fmt.Errorf("failed to convert pkg %q to mem pkg: %w", pkg.Dir, err)) } - memPkg := gno.ReadMemPackage(pkgPath, gnoPkgPath) // tfiles, ifiles := gno.ParseMemPackageTests(memPkg) var tfiles, ifiles *gno.FileSet - hasError := catchRuntimeError(gnoPkgPath, stderr, func() { + hasError := catchRuntimeError(pkg.ImportPath, stderr, func() { tfiles, ifiles = parseMemPackageTests(memPkg) }) @@ -277,15 +267,23 @@ func gnoTestPkg( // run test files in pkg if len(tfiles.Files) > 0 { testStore := tests.TestStore( - rootDir, "", + rootDir, "", pkgsMap, stdin, stdout, stderr, mode, ) + for _, pkg := range loadedPkgs { + memPkg, err := pkg.MemPkg() + if err != nil { + errs = multierr.Append(errs, fmt.Errorf("failed to convert pkg %q to mem pkg: %w", pkg.Dir, err)) + continue + } + testStore.AddMemPackage(memPkg) + } if verbose { testStore.SetLogStoreOps(true) } - m := tests.TestMachine(testStore, stdout, gnoPkgPath) + m := tests.TestMachine(testStore, stdout, pkg.ImportPath) if printRuntimeMetrics { // from tm2/pkg/sdk/vm/keeper.go // XXX: make maxAllocTx configurable. @@ -303,10 +301,18 @@ func gnoTestPkg( // test xxx_test pkg if len(ifiles.Files) > 0 { testStore := tests.TestStore( - rootDir, "", + rootDir, "", pkgsMap, stdin, stdout, stderr, mode, ) + for _, pkg := range loadedPkgs { + memPkg, err := pkg.MemPkg() + if err != nil { + errs = multierr.Append(errs, fmt.Errorf("failed to convert pkg %q to mem pkg: %w", pkg.Dir, err)) + continue + } + testStore.AddMemPackage(memPkg) + } if verbose { testStore.SetLogStoreOps(true) } @@ -336,49 +342,51 @@ func gnoTestPkg( } // testing with *_filetest.gno - { - filter := splitRegexp(runFlag) - for _, testFile := range filetestFiles { - testFileName := filepath.Base(testFile) - testName := "file/" + testFileName - if !shouldRun(filter, testName) { - continue - } - - startedAt := time.Now() - if verbose { - io.ErrPrintfln("=== RUN %s", testName) - } + /* + { + filter := splitRegexp(runFlag) + for _, testFile := range pkg.FiletestGnoFiles { + testFileName := filepath.Base(testFile) + testName := "file/" + testFileName + if !shouldRun(filter, testName) { + continue + } - var closer func() (string, error) - if !verbose { - closer = testutils.CaptureStdoutAndStderr() - } + startedAt := time.Now() + if verbose { + io.ErrPrintfln("=== RUN %s", testName) + } - testFilePath := filepath.Join(pkgPath, testFileName) - err := tests.RunFileTest(rootDir, testFilePath, tests.WithSyncWanted(cfg.updateGoldenTests)) - duration := time.Since(startedAt) - dstr := fmtDuration(duration) + var closer func() (string, error) + if !verbose { + closer = testutils.CaptureStdoutAndStderr() + } - if err != nil { - errs = multierr.Append(errs, err) - io.ErrPrintfln("--- FAIL: %s (%s)", testName, dstr) - if verbose { - stdouterr, err := closer() - if err != nil { - panic(err) + testFilePath := filepath.Join(pkg.Dir, testFileName) + err := tests.RunFileTest(rootDir, testFilePath, tests.WithSyncWanted(cfg.updateGoldenTests)) + duration := time.Since(startedAt) + dstr := fmtDuration(duration) + + if err != nil { + errs = multierr.Append(errs, err) + io.ErrPrintfln("--- FAIL: %s (%s)", testName, dstr) + if verbose { + stdouterr, err := closer() + if err != nil { + panic(err) + } + fmt.Fprintln(os.Stderr, stdouterr) } - fmt.Fprintln(os.Stderr, stdouterr) + continue } - continue - } - if verbose { - io.ErrPrintfln("--- PASS: %s (%s)", testName, dstr) + if verbose { + io.ErrPrintfln("--- PASS: %s (%s)", testName, dstr) + } + // XXX: add per-test metrics } - // XXX: add per-test metrics } - } + */ return errs } diff --git a/gnovm/pkg/repl/repl.go b/gnovm/pkg/repl/repl.go index e7b5ecea96f..c55b9d47fdf 100644 --- a/gnovm/pkg/repl/repl.go +++ b/gnovm/pkg/repl/repl.go @@ -124,7 +124,7 @@ func NewRepl(opts ...ReplOption) *Repl { r.stderr = &b r.storeFunc = func() gno.Store { - return tests.TestStore("teststore", "", r.stdin, r.stdout, r.stderr, tests.ImportModeStdlibsOnly) + return tests.TestStore("teststore", "", nil, r.stdin, r.stdout, r.stderr, tests.ImportModeStdlibsOnly) } for _, o := range opts { diff --git a/gnovm/tests/file.go b/gnovm/tests/file.go index f6bd789f1bf..1e53f80e5d0 100644 --- a/gnovm/tests/file.go +++ b/gnovm/tests/file.go @@ -122,7 +122,7 @@ func RunFileTest(rootDir string, path string, opts ...RunFileTestOption) error { if f.nativeLibs { mode = ImportModeNativePreferred } - store := TestStore(rootDir, "./files", stdin, stdout, stderr, mode) + store := TestStore(rootDir, "./files", nil, stdin, stdout, stderr, mode) store.SetLogStoreOps(true) m := testMachineCustom(store, pkgPath, stdout, maxAlloc, send) checkMachineIsEmpty := true diff --git a/gnovm/tests/imports.go b/gnovm/tests/imports.go index 30f410fa8d5..7de76b75f44 100644 --- a/gnovm/tests/imports.go +++ b/gnovm/tests/imports.go @@ -38,6 +38,7 @@ import ( "unicode/utf8" gno "github.com/gnolang/gno/gnovm/pkg/gnolang" + "github.com/gnolang/gno/gnovm/pkg/importer" teststdlibs "github.com/gnolang/gno/gnovm/tests/stdlibs" teststd "github.com/gnolang/gno/gnovm/tests/stdlibs/std" "github.com/gnolang/gno/tm2/pkg/db/memdb" @@ -61,7 +62,7 @@ const ( ) // NOTE: this isn't safe, should only be used for testing. -func TestStore(rootDir, filesPath string, stdin io.Reader, stdout, stderr io.Writer, mode importMode) (resStore gno.Store) { +func TestStore(rootDir, filesPath string, pkgs map[string]*importer.Package, stdin io.Reader, stdout, stderr io.Writer, mode importMode) (resStore gno.Store) { getPackage := func(pkgPath string, store gno.Store) (pn *gno.PackageNode, pv *gno.PackageValue) { if pkgPath == "" { panic(fmt.Sprintf("invalid zero package path in testStore().pkgGetter")) @@ -72,6 +73,23 @@ func TestStore(rootDir, filesPath string, stdin io.Reader, stdout, stderr io.Wri panic(fmt.Sprintf("unrecognized import mode")) } + if pkg, ok := pkgs[pkgPath]; ok { + memPkg, err := pkg.MemPkg() + if err != nil { + panic(fmt.Errorf("failed to convert imported pkg to mem pkg: %w", err)) + } + send := std.Coins{} + ctx := TestContext(pkgPath, send) + m2 := gno.NewMachineWithOptions(gno.MachineOptions{ + PkgPath: "test", + Output: stdout, + Store: store, + Context: ctx, + }) + pn, pv = m2.RunMemPackage(memPkg, true) + return + } + if filesPath != "" { // if _test package... const testPath = "github.com/gnolang/gno/_test/" From 69954858194c7ec13485c279c7241bd3734fda64 Mon Sep 17 00:00:00 2001 From: Norman Meier Date: Fri, 11 Oct 2024 12:59:50 +0200 Subject: [PATCH 011/143] tmp: better Module info Signed-off-by: Norman Meier --- gnovm/pkg/importer/resolve.go | 73 +++++++++++++++++++++-------------- 1 file changed, 43 insertions(+), 30 deletions(-) diff --git a/gnovm/pkg/importer/resolve.go b/gnovm/pkg/importer/resolve.go index f792ae3e9bb..0c5d2909285 100644 --- a/gnovm/pkg/importer/resolve.go +++ b/gnovm/pkg/importer/resolve.go @@ -108,8 +108,12 @@ func DiscoverPackages(paths ...string) ([]*PackageSummary, error) { cache[pkgPath] = &PackageSummary{ PkgPath: path.Join(globalPkgPath, relpath), Root: absroot, - ModFile: modFile, - Match: []string{tgt.match}, + Module: &Module{ + Path: globalPkgPath, + Dir: modDir, + GnoMod: modFilePath, + }, + Match: []string{tgt.match}, } packages = append(packages, cache[pkgPath]) } @@ -145,7 +149,7 @@ func DiscoverPackages(paths ...string) ([]*PackageSummary, error) { type PackageSummary struct { PkgPath string Root string - ModFile *modfile.File + Module *Module Match []string } @@ -233,37 +237,40 @@ func listDepsRecursive(rootTarget string, target string, pkgs map[string]*Packag func resolvePackage(meta *PackageSummary) (*Package, error) { if meta.Root == "" { if !strings.ContainsRune(meta.PkgPath, '.') { - return resolveStdlib(meta.PkgPath, meta.Match) + return resolveStdlib(meta) } else { - return resolveRemote(meta.PkgPath, meta.Match) + return resolveRemote(meta) } } - if meta.ModFile == nil { - return nil, errors.New("unexpected nil modfile") + if meta.Module == nil { + return nil, errors.New("unexpected nil module") } - return fillPackage(meta.PkgPath, meta.Root, meta.ModFile, meta.Match) + return fillPackage(meta) } -func resolveStdlib(pkgPath string, match []string) (*Package, error) { +func resolveStdlib(ometa *PackageSummary) (*Package, error) { + meta := *ometa gnoRoot, err := gnoenv.GuessRootDir() if err != nil { return nil, fmt.Errorf("failed to guess gno root dir: %w", err) } - parts := strings.Split(pkgPath, "/") - libDir := filepath.Join(append([]string{gnoRoot, "gnovm", "stdlibs"}, parts...)...) - return fillPackage(pkgPath, libDir, nil, match) + parts := strings.Split(meta.PkgPath, "/") + meta.Root = filepath.Join(append([]string{gnoRoot, "gnovm", "stdlibs"}, parts...)...) + return fillPackage(&meta) } // Does not fill deps -func resolveRemote(pkgPath string, match []string) (*Package, error) { +func resolveRemote(ometa *PackageSummary) (*Package, error) { + meta := *ometa + modCache := filepath.Join(gnoenv.HomeDir(), "pkg", "mod") - pkgDir := filepath.Join(modCache, pkgPath) - if err := DownloadModule(pkgPath, pkgDir); err != nil { - return nil, fmt.Errorf("failed to download module %q: %w", pkgPath, err) + meta.Root = filepath.Join(modCache, meta.PkgPath) + if err := DownloadModule(meta.PkgPath, meta.Root); err != nil { + return nil, fmt.Errorf("failed to download module %q: %w", meta.PkgPath, err) } - modDir, err := findModDir(pkgDir) + modDir, err := findModDir(meta.Root) if os.IsNotExist(err) { return nil, errors.New("failed to clone mod") } else if err != nil { @@ -278,21 +285,31 @@ func resolveRemote(pkgPath string, match []string) (*Package, error) { if err != nil { return nil, fmt.Errorf("failed to parse modfile: %w", err) } - pkg, err := fillPackage(pkgPath, pkgDir, modFile, match) + meta.Module = &Module{ + Path: modFile.Module.Mod.Path, + Dir: modDir, + GnoMod: modFilePath, + } + + pkg, err := fillPackage(&meta) if err != nil { - return nil, fmt.Errorf("failed to fill Package %q: %w", pkgPath, err) + return nil, fmt.Errorf("failed to fill Package %q: %w", meta.PkgPath, err) } return pkg, nil } -func fillPackage(pkgPath, pkgDir string, modFile *modfile.File, match []string) (*Package, error) { +func fillPackage(meta *PackageSummary) (*Package, error) { fsFiles := []string{} files := []string{} fsTestFiles := []string{} testFiles := []string{} fsFiletestFiles := []string{} filetestFiles := []string{} + + pkgDir := meta.Root + pkgPath := meta.PkgPath + dir, err := os.ReadDir(pkgDir) if err != nil { return nil, fmt.Errorf("failed to read module files list: %w", err) @@ -328,10 +345,8 @@ func fillPackage(pkgPath, pkgDir string, modFile *modfile.File, match []string) } module := Module{} - if modFile != nil { - module = Module{ - Path: modFile.Module.Mod.Path, - } + if meta.Module != nil { + module = *meta.Module } return &Package{ @@ -339,7 +354,7 @@ func fillPackage(pkgPath, pkgDir string, modFile *modfile.File, match []string) Dir: pkgDir, Name: name, Module: module, - Match: match, + Match: meta.Match, GnoFiles: files, Imports: imports, TestGnoFiles: testFiles, @@ -440,11 +455,9 @@ func (p *Package) MemPkg() (*std.MemPackage, error) { } type Module struct { - Path string `json:",omitempty"` - Main bool `json:",omitempty"` - Dir string `json:",omitempty"` - GoMod string `json:",omitempty"` - GoVersion string `json:",omitempty"` + Path string `json:",omitempty"` + Dir string `json:",omitempty"` + GnoMod string `json:",omitempty"` } func resolveNameAndImports(gnoFiles []string) (string, []string, error) { From fb8d518e3996dcd853bc9862d3035031794aacb0 Mon Sep 17 00:00:00 2001 From: Norman Meier Date: Fri, 11 Oct 2024 13:43:33 +0200 Subject: [PATCH 012/143] tmp: follow go resolver logic Signed-off-by: Norman Meier --- examples/gno.land/gno.mod | 1 + examples/gno.land/p/archives/bank/gno.mod | 1 - examples/gno.land/p/demo/acl/gno.mod | 8 -------- examples/gno.land/p/demo/avl/gno.mod | 1 - examples/gno.land/p/demo/avlhelpers/gno.mod | 3 --- examples/gno.land/p/demo/bf/gno.mod | 1 - examples/gno.land/p/demo/blog/gno.mod | 7 ------- examples/gno.land/p/demo/cford32/gno.mod | 1 - examples/gno.land/p/demo/context/gno.mod | 1 - examples/gno.land/p/demo/diff/gno.mod | 1 - examples/gno.land/p/demo/dom/gno.mod | 3 --- examples/gno.land/p/demo/entropy/gno.mod | 1 - examples/gno.land/p/demo/flow/gno.mod | 1 - examples/gno.land/p/demo/fqname/gno.mod | 3 --- examples/gno.land/p/demo/gnode/gno.mod | 1 - examples/gno.land/p/demo/gnorkle/agent/gno.mod | 6 ------ examples/gno.land/p/demo/gnorkle/feed/gno.mod | 1 - .../gno.land/p/demo/gnorkle/feeds/static/gno.mod | 13 ------------- examples/gno.land/p/demo/gnorkle/gnorkle/gno.mod | 9 --------- examples/gno.land/p/demo/gnorkle/ingester/gno.mod | 1 - .../p/demo/gnorkle/ingesters/single/gno.mod | 8 -------- examples/gno.land/p/demo/gnorkle/message/gno.mod | 3 --- examples/gno.land/p/demo/gnorkle/storage/gno.mod | 1 - .../gno.land/p/demo/gnorkle/storage/simple/gno.mod | 9 --------- examples/gno.land/p/demo/grc/exts/gno.mod | 1 - examples/gno.land/p/demo/grc/grc1155/gno.mod | 7 ------- examples/gno.land/p/demo/grc/grc20/gno.mod | 9 --------- examples/gno.land/p/demo/grc/grc721/gno.mod | 8 -------- examples/gno.land/p/demo/grc/grc777/gno.mod | 3 --- examples/gno.land/p/demo/groups/gno.mod | 6 ------ examples/gno.land/p/demo/int256/gno.mod | 3 --- examples/gno.land/p/demo/json/eisel_lemire/gno.mod | 1 - examples/gno.land/p/demo/json/gno.mod | 7 ------- examples/gno.land/p/demo/json/ryu/gno.mod | 1 - examples/gno.land/p/demo/math_eval/int32/gno.mod | 3 --- examples/gno.land/p/demo/memeland/gno.mod | 10 ---------- examples/gno.land/p/demo/merkle/gno.mod | 1 - examples/gno.land/p/demo/microblog/gno.mod | 6 ------ examples/gno.land/p/demo/mux/gno.mod | 1 - examples/gno.land/p/demo/nestedpkg/gno.mod | 1 - .../p/demo/ownable/exts/authorizable/gno.mod | 9 --------- examples/gno.land/p/demo/ownable/gno.mod | 6 ------ examples/gno.land/p/demo/pausable/gno.mod | 6 ------ examples/gno.land/p/demo/rat/gno.mod | 1 - examples/gno.land/p/demo/releases/gno.mod | 1 - examples/gno.land/p/demo/seqid/gno.mod | 3 --- examples/gno.land/p/demo/stack/gno.mod | 1 - examples/gno.land/p/demo/subscription/gno.mod | 1 - .../gno.land/p/demo/subscription/lifetime/gno.mod | 8 -------- .../gno.land/p/demo/subscription/recurring/gno.mod | 8 -------- examples/gno.land/p/demo/svg/gno.mod | 3 --- examples/gno.land/p/demo/tamagotchi/gno.mod | 3 --- examples/gno.land/p/demo/tests/gno.mod | 7 ------- examples/gno.land/p/demo/tests/p_crossrealm/gno.mod | 1 - examples/gno.land/p/demo/tests/subtests/gno.mod | 4 ---- examples/gno.land/p/demo/testutils/gno.mod | 1 - examples/gno.land/p/demo/todolist/gno.mod | 6 ------ examples/gno.land/p/demo/uassert/gno.mod | 3 --- examples/gno.land/p/demo/ufmt/gno.mod | 1 - examples/gno.land/p/demo/ui/gno.mod | 1 - examples/gno.land/p/demo/uint256/gno.mod | 1 - examples/gno.land/p/demo/urequire/gno.mod | 3 --- examples/gno.land/p/demo/users/gno.mod | 1 - examples/gno.land/p/demo/watchdog/gno.mod | 3 --- examples/gno.land/p/gov/proposal/gno.mod | 7 ------- examples/gno.land/p/moul/printfdebugging/gno.mod | 3 --- examples/gno.land/p/nt/poa/gno.mod | 10 ---------- examples/gno.land/p/sys/validators/gno.mod | 1 - examples/gno.land/r/demo/art/gnoface/gno.mod | 7 ------- examples/gno.land/r/demo/art/millipede/gno.mod | 6 ------ examples/gno.land/r/demo/banktest/gno.mod | 1 - examples/gno.land/r/demo/bar20/gno.mod | 8 -------- examples/gno.land/r/demo/boards/gno.mod | 6 ------ examples/gno.land/r/demo/counter/gno.mod | 1 - examples/gno.land/r/demo/deep/very/deep/gno.mod | 1 - examples/gno.land/r/demo/disperse/gno.mod | 3 --- examples/gno.land/r/demo/echo/gno.mod | 3 --- examples/gno.land/r/demo/event/gno.mod | 1 - examples/gno.land/r/demo/foo1155/gno.mod | 8 -------- examples/gno.land/r/demo/foo20/gno.mod | 11 ----------- examples/gno.land/r/demo/foo721/gno.mod | 8 -------- examples/gno.land/r/demo/games/dice_roller/gno.mod | 11 ----------- examples/gno.land/r/demo/games/shifumi/gno.mod | 7 ------- examples/gno.land/r/demo/grc20factory/gno.mod | 9 --------- examples/gno.land/r/demo/groups/gno.mod | 6 ------ examples/gno.land/r/demo/keystore/gno.mod | 8 -------- examples/gno.land/r/demo/markdown_test/gno.mod | 1 - examples/gno.land/r/demo/math_eval/gno.mod | 6 ------ examples/gno.land/r/demo/memeland/gno.mod | 3 --- examples/gno.land/r/demo/microblog/gno.mod | 9 --------- examples/gno.land/r/demo/nft/gno.mod | 6 ------ examples/gno.land/r/demo/profile/gno.mod | 9 --------- examples/gno.land/r/demo/releases_example/gno.mod | 3 --- examples/gno.land/r/demo/tamagotchi/gno.mod | 6 ------ examples/gno.land/r/demo/tests/crossrealm/gno.mod | 6 ------ examples/gno.land/r/demo/tests/gno.mod | 6 ------ examples/gno.land/r/demo/tests/subtests/gno.mod | 4 ---- examples/gno.land/r/demo/tests_foo/gno.mod | 3 --- examples/gno.land/r/demo/todolist/gno.mod | 9 --------- examples/gno.land/r/demo/types/gno.mod | 3 --- examples/gno.land/r/demo/ui/gno.mod | 6 ------ examples/gno.land/r/demo/userbook/gno.mod | 8 -------- examples/gno.land/r/demo/users/gno.mod | 8 -------- examples/gno.land/r/demo/wugnot/gno.mod | 8 -------- examples/gno.land/r/gnoland/blog/gno.mod | 8 -------- examples/gno.land/r/gnoland/events/gno.mod | 9 --------- examples/gno.land/r/gnoland/faucet/gno.mod | 7 ------- examples/gno.land/r/gnoland/ghverify/gno.mod | 9 --------- examples/gno.land/r/gnoland/home/gno.mod | 9 --------- examples/gno.land/r/gnoland/monit/gno.mod | 8 -------- examples/gno.land/r/gnoland/pages/gno.mod | 6 ------ examples/gno.land/r/gnoland/valopers/gno.mod | 11 ----------- examples/gno.land/r/gov/dao/gno.mod | 8 -------- examples/gno.land/r/leon/config/gno.mod | 1 - examples/gno.land/r/leon/home/gno.mod | 8 -------- examples/gno.land/r/manfred/config/gno.mod | 1 - examples/gno.land/r/manfred/home/gno.mod | 3 --- examples/gno.land/r/manfred/present/gno.mod | 6 ------ examples/gno.land/r/morgan/guestbook/gno.mod | 7 ------- examples/gno.land/r/morgan/home/gno.mod | 1 - examples/gno.land/r/sys/rewards/gno.mod | 1 - examples/gno.land/r/sys/users/gno.mod | 6 ------ examples/gno.land/r/sys/validators/gno.mod | 12 ------------ examples/gno.land/r/x/manfred_outfmt/gno.mod | 5 ----- .../gno.land/r/x/manfred_upgrade_patterns/gno.mod | 3 --- 125 files changed, 1 insertion(+), 593 deletions(-) create mode 100644 examples/gno.land/gno.mod delete mode 100644 examples/gno.land/p/archives/bank/gno.mod delete mode 100644 examples/gno.land/p/demo/acl/gno.mod delete mode 100644 examples/gno.land/p/demo/avl/gno.mod delete mode 100644 examples/gno.land/p/demo/avlhelpers/gno.mod delete mode 100644 examples/gno.land/p/demo/bf/gno.mod delete mode 100644 examples/gno.land/p/demo/blog/gno.mod delete mode 100644 examples/gno.land/p/demo/cford32/gno.mod delete mode 100644 examples/gno.land/p/demo/context/gno.mod delete mode 100644 examples/gno.land/p/demo/diff/gno.mod delete mode 100644 examples/gno.land/p/demo/dom/gno.mod delete mode 100644 examples/gno.land/p/demo/entropy/gno.mod delete mode 100644 examples/gno.land/p/demo/flow/gno.mod delete mode 100644 examples/gno.land/p/demo/fqname/gno.mod delete mode 100644 examples/gno.land/p/demo/gnode/gno.mod delete mode 100644 examples/gno.land/p/demo/gnorkle/agent/gno.mod delete mode 100644 examples/gno.land/p/demo/gnorkle/feed/gno.mod delete mode 100644 examples/gno.land/p/demo/gnorkle/feeds/static/gno.mod delete mode 100644 examples/gno.land/p/demo/gnorkle/gnorkle/gno.mod delete mode 100644 examples/gno.land/p/demo/gnorkle/ingester/gno.mod delete mode 100644 examples/gno.land/p/demo/gnorkle/ingesters/single/gno.mod delete mode 100644 examples/gno.land/p/demo/gnorkle/message/gno.mod delete mode 100644 examples/gno.land/p/demo/gnorkle/storage/gno.mod delete mode 100644 examples/gno.land/p/demo/gnorkle/storage/simple/gno.mod delete mode 100644 examples/gno.land/p/demo/grc/exts/gno.mod delete mode 100644 examples/gno.land/p/demo/grc/grc1155/gno.mod delete mode 100644 examples/gno.land/p/demo/grc/grc20/gno.mod delete mode 100644 examples/gno.land/p/demo/grc/grc721/gno.mod delete mode 100644 examples/gno.land/p/demo/grc/grc777/gno.mod delete mode 100644 examples/gno.land/p/demo/groups/gno.mod delete mode 100644 examples/gno.land/p/demo/int256/gno.mod delete mode 100644 examples/gno.land/p/demo/json/eisel_lemire/gno.mod delete mode 100644 examples/gno.land/p/demo/json/gno.mod delete mode 100644 examples/gno.land/p/demo/json/ryu/gno.mod delete mode 100644 examples/gno.land/p/demo/math_eval/int32/gno.mod delete mode 100644 examples/gno.land/p/demo/memeland/gno.mod delete mode 100644 examples/gno.land/p/demo/merkle/gno.mod delete mode 100644 examples/gno.land/p/demo/microblog/gno.mod delete mode 100644 examples/gno.land/p/demo/mux/gno.mod delete mode 100644 examples/gno.land/p/demo/nestedpkg/gno.mod delete mode 100644 examples/gno.land/p/demo/ownable/exts/authorizable/gno.mod delete mode 100644 examples/gno.land/p/demo/ownable/gno.mod delete mode 100644 examples/gno.land/p/demo/pausable/gno.mod delete mode 100644 examples/gno.land/p/demo/rat/gno.mod delete mode 100644 examples/gno.land/p/demo/releases/gno.mod delete mode 100644 examples/gno.land/p/demo/seqid/gno.mod delete mode 100644 examples/gno.land/p/demo/stack/gno.mod delete mode 100644 examples/gno.land/p/demo/subscription/gno.mod delete mode 100644 examples/gno.land/p/demo/subscription/lifetime/gno.mod delete mode 100644 examples/gno.land/p/demo/subscription/recurring/gno.mod delete mode 100644 examples/gno.land/p/demo/svg/gno.mod delete mode 100644 examples/gno.land/p/demo/tamagotchi/gno.mod delete mode 100644 examples/gno.land/p/demo/tests/gno.mod delete mode 100644 examples/gno.land/p/demo/tests/p_crossrealm/gno.mod delete mode 100644 examples/gno.land/p/demo/tests/subtests/gno.mod delete mode 100644 examples/gno.land/p/demo/testutils/gno.mod delete mode 100644 examples/gno.land/p/demo/todolist/gno.mod delete mode 100644 examples/gno.land/p/demo/uassert/gno.mod delete mode 100644 examples/gno.land/p/demo/ufmt/gno.mod delete mode 100644 examples/gno.land/p/demo/ui/gno.mod delete mode 100644 examples/gno.land/p/demo/uint256/gno.mod delete mode 100644 examples/gno.land/p/demo/urequire/gno.mod delete mode 100644 examples/gno.land/p/demo/users/gno.mod delete mode 100644 examples/gno.land/p/demo/watchdog/gno.mod delete mode 100644 examples/gno.land/p/gov/proposal/gno.mod delete mode 100644 examples/gno.land/p/moul/printfdebugging/gno.mod delete mode 100644 examples/gno.land/p/nt/poa/gno.mod delete mode 100644 examples/gno.land/p/sys/validators/gno.mod delete mode 100644 examples/gno.land/r/demo/art/gnoface/gno.mod delete mode 100644 examples/gno.land/r/demo/art/millipede/gno.mod delete mode 100644 examples/gno.land/r/demo/banktest/gno.mod delete mode 100644 examples/gno.land/r/demo/bar20/gno.mod delete mode 100644 examples/gno.land/r/demo/boards/gno.mod delete mode 100644 examples/gno.land/r/demo/counter/gno.mod delete mode 100644 examples/gno.land/r/demo/deep/very/deep/gno.mod delete mode 100644 examples/gno.land/r/demo/disperse/gno.mod delete mode 100644 examples/gno.land/r/demo/echo/gno.mod delete mode 100644 examples/gno.land/r/demo/event/gno.mod delete mode 100644 examples/gno.land/r/demo/foo1155/gno.mod delete mode 100644 examples/gno.land/r/demo/foo20/gno.mod delete mode 100644 examples/gno.land/r/demo/foo721/gno.mod delete mode 100644 examples/gno.land/r/demo/games/dice_roller/gno.mod delete mode 100644 examples/gno.land/r/demo/games/shifumi/gno.mod delete mode 100644 examples/gno.land/r/demo/grc20factory/gno.mod delete mode 100644 examples/gno.land/r/demo/groups/gno.mod delete mode 100644 examples/gno.land/r/demo/keystore/gno.mod delete mode 100644 examples/gno.land/r/demo/markdown_test/gno.mod delete mode 100644 examples/gno.land/r/demo/math_eval/gno.mod delete mode 100644 examples/gno.land/r/demo/memeland/gno.mod delete mode 100644 examples/gno.land/r/demo/microblog/gno.mod delete mode 100644 examples/gno.land/r/demo/nft/gno.mod delete mode 100644 examples/gno.land/r/demo/profile/gno.mod delete mode 100644 examples/gno.land/r/demo/releases_example/gno.mod delete mode 100644 examples/gno.land/r/demo/tamagotchi/gno.mod delete mode 100644 examples/gno.land/r/demo/tests/crossrealm/gno.mod delete mode 100644 examples/gno.land/r/demo/tests/gno.mod delete mode 100644 examples/gno.land/r/demo/tests/subtests/gno.mod delete mode 100644 examples/gno.land/r/demo/tests_foo/gno.mod delete mode 100644 examples/gno.land/r/demo/todolist/gno.mod delete mode 100644 examples/gno.land/r/demo/types/gno.mod delete mode 100644 examples/gno.land/r/demo/ui/gno.mod delete mode 100644 examples/gno.land/r/demo/userbook/gno.mod delete mode 100644 examples/gno.land/r/demo/users/gno.mod delete mode 100644 examples/gno.land/r/demo/wugnot/gno.mod delete mode 100644 examples/gno.land/r/gnoland/blog/gno.mod delete mode 100644 examples/gno.land/r/gnoland/events/gno.mod delete mode 100644 examples/gno.land/r/gnoland/faucet/gno.mod delete mode 100644 examples/gno.land/r/gnoland/ghverify/gno.mod delete mode 100644 examples/gno.land/r/gnoland/home/gno.mod delete mode 100644 examples/gno.land/r/gnoland/monit/gno.mod delete mode 100644 examples/gno.land/r/gnoland/pages/gno.mod delete mode 100644 examples/gno.land/r/gnoland/valopers/gno.mod delete mode 100644 examples/gno.land/r/gov/dao/gno.mod delete mode 100644 examples/gno.land/r/leon/config/gno.mod delete mode 100644 examples/gno.land/r/leon/home/gno.mod delete mode 100644 examples/gno.land/r/manfred/config/gno.mod delete mode 100644 examples/gno.land/r/manfred/home/gno.mod delete mode 100644 examples/gno.land/r/manfred/present/gno.mod delete mode 100644 examples/gno.land/r/morgan/guestbook/gno.mod delete mode 100644 examples/gno.land/r/morgan/home/gno.mod delete mode 100644 examples/gno.land/r/sys/rewards/gno.mod delete mode 100644 examples/gno.land/r/sys/users/gno.mod delete mode 100644 examples/gno.land/r/sys/validators/gno.mod delete mode 100644 examples/gno.land/r/x/manfred_outfmt/gno.mod delete mode 100644 examples/gno.land/r/x/manfred_upgrade_patterns/gno.mod diff --git a/examples/gno.land/gno.mod b/examples/gno.land/gno.mod new file mode 100644 index 00000000000..8563e9e5da1 --- /dev/null +++ b/examples/gno.land/gno.mod @@ -0,0 +1 @@ +module gno.land \ No newline at end of file diff --git a/examples/gno.land/p/archives/bank/gno.mod b/examples/gno.land/p/archives/bank/gno.mod deleted file mode 100644 index 810731aed04..00000000000 --- a/examples/gno.land/p/archives/bank/gno.mod +++ /dev/null @@ -1 +0,0 @@ -module gno.land/p/demo/bank diff --git a/examples/gno.land/p/demo/acl/gno.mod b/examples/gno.land/p/demo/acl/gno.mod deleted file mode 100644 index 15d9f078048..00000000000 --- a/examples/gno.land/p/demo/acl/gno.mod +++ /dev/null @@ -1,8 +0,0 @@ -module gno.land/p/demo/acl - -require ( - gno.land/p/demo/avl v0.0.0-latest - gno.land/p/demo/testutils v0.0.0-latest - gno.land/p/demo/uassert v0.0.0-latest - gno.land/p/demo/ufmt v0.0.0-latest -) diff --git a/examples/gno.land/p/demo/avl/gno.mod b/examples/gno.land/p/demo/avl/gno.mod deleted file mode 100644 index a6a2a1362e3..00000000000 --- a/examples/gno.land/p/demo/avl/gno.mod +++ /dev/null @@ -1 +0,0 @@ -module gno.land/p/demo/avl diff --git a/examples/gno.land/p/demo/avlhelpers/gno.mod b/examples/gno.land/p/demo/avlhelpers/gno.mod deleted file mode 100644 index 559f60975cf..00000000000 --- a/examples/gno.land/p/demo/avlhelpers/gno.mod +++ /dev/null @@ -1,3 +0,0 @@ -module gno.land/p/demo/avlhelpers - -require gno.land/p/demo/avl v0.0.0-latest diff --git a/examples/gno.land/p/demo/bf/gno.mod b/examples/gno.land/p/demo/bf/gno.mod deleted file mode 100644 index b887582196c..00000000000 --- a/examples/gno.land/p/demo/bf/gno.mod +++ /dev/null @@ -1 +0,0 @@ -module gno.land/p/demo/bf diff --git a/examples/gno.land/p/demo/blog/gno.mod b/examples/gno.land/p/demo/blog/gno.mod deleted file mode 100644 index 65f58e7a0f6..00000000000 --- a/examples/gno.land/p/demo/blog/gno.mod +++ /dev/null @@ -1,7 +0,0 @@ -module gno.land/p/demo/blog - -require ( - gno.land/p/demo/avl v0.0.0-latest - gno.land/p/demo/mux v0.0.0-latest - gno.land/p/demo/ufmt v0.0.0-latest -) diff --git a/examples/gno.land/p/demo/cford32/gno.mod b/examples/gno.land/p/demo/cford32/gno.mod deleted file mode 100644 index 20b99c65e4c..00000000000 --- a/examples/gno.land/p/demo/cford32/gno.mod +++ /dev/null @@ -1 +0,0 @@ -module gno.land/p/demo/cford32 diff --git a/examples/gno.land/p/demo/context/gno.mod b/examples/gno.land/p/demo/context/gno.mod deleted file mode 100644 index a04ae1f91f8..00000000000 --- a/examples/gno.land/p/demo/context/gno.mod +++ /dev/null @@ -1 +0,0 @@ -module gno.land/p/demo/context diff --git a/examples/gno.land/p/demo/diff/gno.mod b/examples/gno.land/p/demo/diff/gno.mod deleted file mode 100644 index 3041b5f62f1..00000000000 --- a/examples/gno.land/p/demo/diff/gno.mod +++ /dev/null @@ -1 +0,0 @@ -module gno.land/p/demo/diff diff --git a/examples/gno.land/p/demo/dom/gno.mod b/examples/gno.land/p/demo/dom/gno.mod deleted file mode 100644 index 83ca827cf66..00000000000 --- a/examples/gno.land/p/demo/dom/gno.mod +++ /dev/null @@ -1,3 +0,0 @@ -module gno.land/p/demo/dom - -require gno.land/p/demo/avl v0.0.0-latest diff --git a/examples/gno.land/p/demo/entropy/gno.mod b/examples/gno.land/p/demo/entropy/gno.mod deleted file mode 100644 index 9a6db8f5b61..00000000000 --- a/examples/gno.land/p/demo/entropy/gno.mod +++ /dev/null @@ -1 +0,0 @@ -module gno.land/p/demo/entropy diff --git a/examples/gno.land/p/demo/flow/gno.mod b/examples/gno.land/p/demo/flow/gno.mod deleted file mode 100644 index 5adddbfe021..00000000000 --- a/examples/gno.land/p/demo/flow/gno.mod +++ /dev/null @@ -1 +0,0 @@ -module gno.land/p/demo/flow diff --git a/examples/gno.land/p/demo/fqname/gno.mod b/examples/gno.land/p/demo/fqname/gno.mod deleted file mode 100644 index 1282e262303..00000000000 --- a/examples/gno.land/p/demo/fqname/gno.mod +++ /dev/null @@ -1,3 +0,0 @@ -module gno.land/p/demo/fqname - -require gno.land/p/demo/uassert v0.0.0-latest diff --git a/examples/gno.land/p/demo/gnode/gno.mod b/examples/gno.land/p/demo/gnode/gno.mod deleted file mode 100644 index a93c2051830..00000000000 --- a/examples/gno.land/p/demo/gnode/gno.mod +++ /dev/null @@ -1 +0,0 @@ -module gno.land/p/demo/gnode diff --git a/examples/gno.land/p/demo/gnorkle/agent/gno.mod b/examples/gno.land/p/demo/gnorkle/agent/gno.mod deleted file mode 100644 index 093ca9cf38e..00000000000 --- a/examples/gno.land/p/demo/gnorkle/agent/gno.mod +++ /dev/null @@ -1,6 +0,0 @@ -module gno.land/p/demo/gnorkle/agent - -require ( - gno.land/p/demo/avl v0.0.0-latest - gno.land/p/demo/uassert v0.0.0-latest -) diff --git a/examples/gno.land/p/demo/gnorkle/feed/gno.mod b/examples/gno.land/p/demo/gnorkle/feed/gno.mod deleted file mode 100644 index 65e1ffa5897..00000000000 --- a/examples/gno.land/p/demo/gnorkle/feed/gno.mod +++ /dev/null @@ -1 +0,0 @@ -module gno.land/p/demo/gnorkle/feed diff --git a/examples/gno.land/p/demo/gnorkle/feeds/static/gno.mod b/examples/gno.land/p/demo/gnorkle/feeds/static/gno.mod deleted file mode 100644 index c651c62cb1b..00000000000 --- a/examples/gno.land/p/demo/gnorkle/feeds/static/gno.mod +++ /dev/null @@ -1,13 +0,0 @@ -module gno.land/p/demo/gnorkle/feeds/static - -require ( - gno.land/p/demo/gnorkle/feed v0.0.0-latest - gno.land/p/demo/gnorkle/gnorkle v0.0.0-latest - gno.land/p/demo/gnorkle/ingester v0.0.0-latest - gno.land/p/demo/gnorkle/ingesters/single v0.0.0-latest - gno.land/p/demo/gnorkle/message v0.0.0-latest - gno.land/p/demo/gnorkle/storage/simple v0.0.0-latest - gno.land/p/demo/uassert v0.0.0-latest - gno.land/p/demo/ufmt v0.0.0-latest - gno.land/p/demo/urequire v0.0.0-latest -) diff --git a/examples/gno.land/p/demo/gnorkle/gnorkle/gno.mod b/examples/gno.land/p/demo/gnorkle/gnorkle/gno.mod deleted file mode 100644 index 88fb202863f..00000000000 --- a/examples/gno.land/p/demo/gnorkle/gnorkle/gno.mod +++ /dev/null @@ -1,9 +0,0 @@ -module gno.land/p/demo/gnorkle/gnorkle - -require ( - gno.land/p/demo/avl v0.0.0-latest - gno.land/p/demo/gnorkle/agent v0.0.0-latest - gno.land/p/demo/gnorkle/feed v0.0.0-latest - gno.land/p/demo/gnorkle/ingester v0.0.0-latest - gno.land/p/demo/gnorkle/message v0.0.0-latest -) diff --git a/examples/gno.land/p/demo/gnorkle/ingester/gno.mod b/examples/gno.land/p/demo/gnorkle/ingester/gno.mod deleted file mode 100644 index 66fa3abf6ad..00000000000 --- a/examples/gno.land/p/demo/gnorkle/ingester/gno.mod +++ /dev/null @@ -1 +0,0 @@ -module gno.land/p/demo/gnorkle/ingester diff --git a/examples/gno.land/p/demo/gnorkle/ingesters/single/gno.mod b/examples/gno.land/p/demo/gnorkle/ingesters/single/gno.mod deleted file mode 100644 index 71120966a0c..00000000000 --- a/examples/gno.land/p/demo/gnorkle/ingesters/single/gno.mod +++ /dev/null @@ -1,8 +0,0 @@ -module gno.land/p/demo/gnorkle/ingesters/single - -require ( - gno.land/p/demo/gnorkle/gnorkle v0.0.0-latest - gno.land/p/demo/gnorkle/ingester v0.0.0-latest - gno.land/p/demo/gnorkle/storage/simple v0.0.0-latest - gno.land/p/demo/uassert v0.0.0-latest -) diff --git a/examples/gno.land/p/demo/gnorkle/message/gno.mod b/examples/gno.land/p/demo/gnorkle/message/gno.mod deleted file mode 100644 index 4baad40ef86..00000000000 --- a/examples/gno.land/p/demo/gnorkle/message/gno.mod +++ /dev/null @@ -1,3 +0,0 @@ -module gno.land/p/demo/gnorkle/message - -require gno.land/p/demo/uassert v0.0.0-latest diff --git a/examples/gno.land/p/demo/gnorkle/storage/gno.mod b/examples/gno.land/p/demo/gnorkle/storage/gno.mod deleted file mode 100644 index d98962cb771..00000000000 --- a/examples/gno.land/p/demo/gnorkle/storage/gno.mod +++ /dev/null @@ -1 +0,0 @@ -module gno.land/p/demo/gnorkle/storage diff --git a/examples/gno.land/p/demo/gnorkle/storage/simple/gno.mod b/examples/gno.land/p/demo/gnorkle/storage/simple/gno.mod deleted file mode 100644 index cd673a8771c..00000000000 --- a/examples/gno.land/p/demo/gnorkle/storage/simple/gno.mod +++ /dev/null @@ -1,9 +0,0 @@ -module gno.land/p/demo/gnorkle/storage/simple - -require ( - gno.land/p/demo/gnorkle/feed v0.0.0-latest - gno.land/p/demo/gnorkle/storage v0.0.0-latest - gno.land/p/demo/uassert v0.0.0-latest - gno.land/p/demo/ufmt v0.0.0-latest - gno.land/p/demo/urequire v0.0.0-latest -) diff --git a/examples/gno.land/p/demo/grc/exts/gno.mod b/examples/gno.land/p/demo/grc/exts/gno.mod deleted file mode 100644 index 001c4bf1df0..00000000000 --- a/examples/gno.land/p/demo/grc/exts/gno.mod +++ /dev/null @@ -1 +0,0 @@ -module gno.land/p/demo/grc/exts diff --git a/examples/gno.land/p/demo/grc/grc1155/gno.mod b/examples/gno.land/p/demo/grc/grc1155/gno.mod deleted file mode 100644 index d6db0700146..00000000000 --- a/examples/gno.land/p/demo/grc/grc1155/gno.mod +++ /dev/null @@ -1,7 +0,0 @@ -module gno.land/p/demo/grc/grc1155 - -require ( - gno.land/p/demo/avl v0.0.0-latest - gno.land/p/demo/uassert v0.0.0-latest - gno.land/p/demo/ufmt v0.0.0-latest -) diff --git a/examples/gno.land/p/demo/grc/grc20/gno.mod b/examples/gno.land/p/demo/grc/grc20/gno.mod deleted file mode 100644 index e872d80ec12..00000000000 --- a/examples/gno.land/p/demo/grc/grc20/gno.mod +++ /dev/null @@ -1,9 +0,0 @@ -module gno.land/p/demo/grc/grc20 - -require ( - gno.land/p/demo/avl v0.0.0-latest - gno.land/p/demo/grc/exts v0.0.0-latest - gno.land/p/demo/testutils v0.0.0-latest - gno.land/p/demo/ufmt v0.0.0-latest - gno.land/p/demo/urequire v0.0.0-latest -) diff --git a/examples/gno.land/p/demo/grc/grc721/gno.mod b/examples/gno.land/p/demo/grc/grc721/gno.mod deleted file mode 100644 index 9e1d6f56ffc..00000000000 --- a/examples/gno.land/p/demo/grc/grc721/gno.mod +++ /dev/null @@ -1,8 +0,0 @@ -module gno.land/p/demo/grc/grc721 - -require ( - gno.land/p/demo/avl v0.0.0-latest - gno.land/p/demo/testutils v0.0.0-latest - gno.land/p/demo/uassert v0.0.0-latest - gno.land/p/demo/ufmt v0.0.0-latest -) diff --git a/examples/gno.land/p/demo/grc/grc777/gno.mod b/examples/gno.land/p/demo/grc/grc777/gno.mod deleted file mode 100644 index 9fbf2f2b7cd..00000000000 --- a/examples/gno.land/p/demo/grc/grc777/gno.mod +++ /dev/null @@ -1,3 +0,0 @@ -module gno.land/p/demo/grc/grc777 - -require gno.land/p/demo/grc/exts v0.0.0-latest diff --git a/examples/gno.land/p/demo/groups/gno.mod b/examples/gno.land/p/demo/groups/gno.mod deleted file mode 100644 index f0749e3f411..00000000000 --- a/examples/gno.land/p/demo/groups/gno.mod +++ /dev/null @@ -1,6 +0,0 @@ -module gno.land/p/demo/groups - -require ( - gno.land/p/demo/rat v0.0.0-latest - gno.land/r/demo/boards v0.0.0-latest -) diff --git a/examples/gno.land/p/demo/int256/gno.mod b/examples/gno.land/p/demo/int256/gno.mod deleted file mode 100644 index ef906c83c93..00000000000 --- a/examples/gno.land/p/demo/int256/gno.mod +++ /dev/null @@ -1,3 +0,0 @@ -module gno.land/p/demo/int256 - -require gno.land/p/demo/uint256 v0.0.0-latest diff --git a/examples/gno.land/p/demo/json/eisel_lemire/gno.mod b/examples/gno.land/p/demo/json/eisel_lemire/gno.mod deleted file mode 100644 index d6670de82e2..00000000000 --- a/examples/gno.land/p/demo/json/eisel_lemire/gno.mod +++ /dev/null @@ -1 +0,0 @@ -module gno.land/p/demo/json/eisel_lemire diff --git a/examples/gno.land/p/demo/json/gno.mod b/examples/gno.land/p/demo/json/gno.mod deleted file mode 100644 index 8a380644acc..00000000000 --- a/examples/gno.land/p/demo/json/gno.mod +++ /dev/null @@ -1,7 +0,0 @@ -module gno.land/p/demo/json - -require ( - gno.land/p/demo/json/eisel_lemire v0.0.0-latest - gno.land/p/demo/json/ryu v0.0.0-latest - gno.land/p/demo/ufmt v0.0.0-latest -) diff --git a/examples/gno.land/p/demo/json/ryu/gno.mod b/examples/gno.land/p/demo/json/ryu/gno.mod deleted file mode 100644 index 86a1988b052..00000000000 --- a/examples/gno.land/p/demo/json/ryu/gno.mod +++ /dev/null @@ -1 +0,0 @@ -module gno.land/p/demo/json/ryu diff --git a/examples/gno.land/p/demo/math_eval/int32/gno.mod b/examples/gno.land/p/demo/math_eval/int32/gno.mod deleted file mode 100644 index de57497a699..00000000000 --- a/examples/gno.land/p/demo/math_eval/int32/gno.mod +++ /dev/null @@ -1,3 +0,0 @@ -module gno.land/p/demo/math_eval/int32 - -require gno.land/p/demo/ufmt v0.0.0-latest diff --git a/examples/gno.land/p/demo/memeland/gno.mod b/examples/gno.land/p/demo/memeland/gno.mod deleted file mode 100644 index 66f22d1ccee..00000000000 --- a/examples/gno.land/p/demo/memeland/gno.mod +++ /dev/null @@ -1,10 +0,0 @@ -module gno.land/p/demo/memeland - -require ( - gno.land/p/demo/avl v0.0.0-latest - gno.land/p/demo/ownable v0.0.0-latest - gno.land/p/demo/seqid v0.0.0-latest - gno.land/p/demo/testutils v0.0.0-latest - gno.land/p/demo/uassert v0.0.0-latest - gno.land/p/demo/ufmt v0.0.0-latest -) diff --git a/examples/gno.land/p/demo/merkle/gno.mod b/examples/gno.land/p/demo/merkle/gno.mod deleted file mode 100644 index ae70400aa6d..00000000000 --- a/examples/gno.land/p/demo/merkle/gno.mod +++ /dev/null @@ -1 +0,0 @@ -module gno.land/p/demo/merkle diff --git a/examples/gno.land/p/demo/microblog/gno.mod b/examples/gno.land/p/demo/microblog/gno.mod deleted file mode 100644 index 9bbcfa19e31..00000000000 --- a/examples/gno.land/p/demo/microblog/gno.mod +++ /dev/null @@ -1,6 +0,0 @@ -module gno.land/p/demo/microblog - -require ( - gno.land/p/demo/avl v0.0.0-latest - gno.land/p/demo/ufmt v0.0.0-latest -) diff --git a/examples/gno.land/p/demo/mux/gno.mod b/examples/gno.land/p/demo/mux/gno.mod deleted file mode 100644 index 972a531e14c..00000000000 --- a/examples/gno.land/p/demo/mux/gno.mod +++ /dev/null @@ -1 +0,0 @@ -module gno.land/p/demo/mux diff --git a/examples/gno.land/p/demo/nestedpkg/gno.mod b/examples/gno.land/p/demo/nestedpkg/gno.mod deleted file mode 100644 index 24e16fdeb74..00000000000 --- a/examples/gno.land/p/demo/nestedpkg/gno.mod +++ /dev/null @@ -1 +0,0 @@ -module gno.land/p/demo/nestedpkg diff --git a/examples/gno.land/p/demo/ownable/exts/authorizable/gno.mod b/examples/gno.land/p/demo/ownable/exts/authorizable/gno.mod deleted file mode 100644 index f36823f3f71..00000000000 --- a/examples/gno.land/p/demo/ownable/exts/authorizable/gno.mod +++ /dev/null @@ -1,9 +0,0 @@ -module gno.land/p/demo/ownable/exts/authorizable - -require ( - gno.land/p/demo/avl v0.0.0-latest - gno.land/p/demo/ownable v0.0.0-latest - gno.land/p/demo/testutils v0.0.0-latest - gno.land/p/demo/uassert v0.0.0-latest - gno.land/p/demo/ufmt v0.0.0-latest -) diff --git a/examples/gno.land/p/demo/ownable/gno.mod b/examples/gno.land/p/demo/ownable/gno.mod deleted file mode 100644 index 00f7812f6f5..00000000000 --- a/examples/gno.land/p/demo/ownable/gno.mod +++ /dev/null @@ -1,6 +0,0 @@ -module gno.land/p/demo/ownable - -require ( - gno.land/p/demo/testutils v0.0.0-latest - gno.land/p/demo/uassert v0.0.0-latest -) diff --git a/examples/gno.land/p/demo/pausable/gno.mod b/examples/gno.land/p/demo/pausable/gno.mod deleted file mode 100644 index 156875f7d85..00000000000 --- a/examples/gno.land/p/demo/pausable/gno.mod +++ /dev/null @@ -1,6 +0,0 @@ -module gno.land/p/demo/pausable - -require ( - gno.land/p/demo/ownable v0.0.0-latest - gno.land/p/demo/urequire v0.0.0-latest -) diff --git a/examples/gno.land/p/demo/rat/gno.mod b/examples/gno.land/p/demo/rat/gno.mod deleted file mode 100644 index c20136fe9bb..00000000000 --- a/examples/gno.land/p/demo/rat/gno.mod +++ /dev/null @@ -1 +0,0 @@ -module gno.land/p/demo/rat diff --git a/examples/gno.land/p/demo/releases/gno.mod b/examples/gno.land/p/demo/releases/gno.mod deleted file mode 100644 index 93214b9bc08..00000000000 --- a/examples/gno.land/p/demo/releases/gno.mod +++ /dev/null @@ -1 +0,0 @@ -module gno.land/p/demo/releases diff --git a/examples/gno.land/p/demo/seqid/gno.mod b/examples/gno.land/p/demo/seqid/gno.mod deleted file mode 100644 index d1390012c3c..00000000000 --- a/examples/gno.land/p/demo/seqid/gno.mod +++ /dev/null @@ -1,3 +0,0 @@ -module gno.land/p/demo/seqid - -require gno.land/p/demo/cford32 v0.0.0-latest diff --git a/examples/gno.land/p/demo/stack/gno.mod b/examples/gno.land/p/demo/stack/gno.mod deleted file mode 100644 index e39ec7b8f28..00000000000 --- a/examples/gno.land/p/demo/stack/gno.mod +++ /dev/null @@ -1 +0,0 @@ -module gno.land/p/demo/stack diff --git a/examples/gno.land/p/demo/subscription/gno.mod b/examples/gno.land/p/demo/subscription/gno.mod deleted file mode 100644 index ea60a4c628a..00000000000 --- a/examples/gno.land/p/demo/subscription/gno.mod +++ /dev/null @@ -1 +0,0 @@ -module gno.land/p/demo/subscription diff --git a/examples/gno.land/p/demo/subscription/lifetime/gno.mod b/examples/gno.land/p/demo/subscription/lifetime/gno.mod deleted file mode 100644 index 0084aa714c5..00000000000 --- a/examples/gno.land/p/demo/subscription/lifetime/gno.mod +++ /dev/null @@ -1,8 +0,0 @@ -module gno.land/p/demo/subscription/lifetime - -require ( - gno.land/p/demo/avl v0.0.0-latest - gno.land/p/demo/ownable v0.0.0-latest - gno.land/p/demo/testutils v0.0.0-latest - gno.land/p/demo/uassert v0.0.0-latest -) diff --git a/examples/gno.land/p/demo/subscription/recurring/gno.mod b/examples/gno.land/p/demo/subscription/recurring/gno.mod deleted file mode 100644 index d3cf8a044f8..00000000000 --- a/examples/gno.land/p/demo/subscription/recurring/gno.mod +++ /dev/null @@ -1,8 +0,0 @@ -module gno.land/p/demo/subscription/recurring - -require ( - gno.land/p/demo/avl v0.0.0-latest - gno.land/p/demo/ownable v0.0.0-latest - gno.land/p/demo/testutils v0.0.0-latest - gno.land/p/demo/uassert v0.0.0-latest -) diff --git a/examples/gno.land/p/demo/svg/gno.mod b/examples/gno.land/p/demo/svg/gno.mod deleted file mode 100644 index 0af7ba0636d..00000000000 --- a/examples/gno.land/p/demo/svg/gno.mod +++ /dev/null @@ -1,3 +0,0 @@ -module gno.land/p/demo/svg - -require gno.land/p/demo/ufmt v0.0.0-latest diff --git a/examples/gno.land/p/demo/tamagotchi/gno.mod b/examples/gno.land/p/demo/tamagotchi/gno.mod deleted file mode 100644 index 58441284a6b..00000000000 --- a/examples/gno.land/p/demo/tamagotchi/gno.mod +++ /dev/null @@ -1,3 +0,0 @@ -module gno.land/p/demo/tamagotchi - -require gno.land/p/demo/ufmt v0.0.0-latest diff --git a/examples/gno.land/p/demo/tests/gno.mod b/examples/gno.land/p/demo/tests/gno.mod deleted file mode 100644 index d3d796f76f8..00000000000 --- a/examples/gno.land/p/demo/tests/gno.mod +++ /dev/null @@ -1,7 +0,0 @@ -module gno.land/p/demo/tests - -require ( - gno.land/p/demo/tests/subtests v0.0.0-latest - gno.land/p/demo/uassert v0.0.0-latest - gno.land/r/demo/tests v0.0.0-latest -) diff --git a/examples/gno.land/p/demo/tests/p_crossrealm/gno.mod b/examples/gno.land/p/demo/tests/p_crossrealm/gno.mod deleted file mode 100644 index 8585cfd9c8d..00000000000 --- a/examples/gno.land/p/demo/tests/p_crossrealm/gno.mod +++ /dev/null @@ -1 +0,0 @@ -module gno.land/p/demo/tests/p_crossrealm diff --git a/examples/gno.land/p/demo/tests/subtests/gno.mod b/examples/gno.land/p/demo/tests/subtests/gno.mod deleted file mode 100644 index c8333722809..00000000000 --- a/examples/gno.land/p/demo/tests/subtests/gno.mod +++ /dev/null @@ -1,4 +0,0 @@ -module gno.land/p/demo/tests/subtests - -// TODO: this file should not exist. -// This is a temporary workaround. Until https://github.com/gnolang/gno/issues/852 diff --git a/examples/gno.land/p/demo/testutils/gno.mod b/examples/gno.land/p/demo/testutils/gno.mod deleted file mode 100644 index 0c97bf4b367..00000000000 --- a/examples/gno.land/p/demo/testutils/gno.mod +++ /dev/null @@ -1 +0,0 @@ -module gno.land/p/demo/testutils diff --git a/examples/gno.land/p/demo/todolist/gno.mod b/examples/gno.land/p/demo/todolist/gno.mod deleted file mode 100644 index bbccf357e3b..00000000000 --- a/examples/gno.land/p/demo/todolist/gno.mod +++ /dev/null @@ -1,6 +0,0 @@ -module gno.land/p/demo/todolist - -require ( - gno.land/p/demo/avl v0.0.0-latest - gno.land/p/demo/uassert v0.0.0-latest -) diff --git a/examples/gno.land/p/demo/uassert/gno.mod b/examples/gno.land/p/demo/uassert/gno.mod deleted file mode 100644 index f22276564bf..00000000000 --- a/examples/gno.land/p/demo/uassert/gno.mod +++ /dev/null @@ -1,3 +0,0 @@ -module gno.land/p/demo/uassert - -require gno.land/p/demo/diff v0.0.0-latest diff --git a/examples/gno.land/p/demo/ufmt/gno.mod b/examples/gno.land/p/demo/ufmt/gno.mod deleted file mode 100644 index 61b52b09fed..00000000000 --- a/examples/gno.land/p/demo/ufmt/gno.mod +++ /dev/null @@ -1 +0,0 @@ -module gno.land/p/demo/ufmt diff --git a/examples/gno.land/p/demo/ui/gno.mod b/examples/gno.land/p/demo/ui/gno.mod deleted file mode 100644 index 41f5cb78d83..00000000000 --- a/examples/gno.land/p/demo/ui/gno.mod +++ /dev/null @@ -1 +0,0 @@ -module gno.land/p/demo/ui diff --git a/examples/gno.land/p/demo/uint256/gno.mod b/examples/gno.land/p/demo/uint256/gno.mod deleted file mode 100644 index 71e5050c831..00000000000 --- a/examples/gno.land/p/demo/uint256/gno.mod +++ /dev/null @@ -1 +0,0 @@ -module gno.land/p/demo/uint256 diff --git a/examples/gno.land/p/demo/urequire/gno.mod b/examples/gno.land/p/demo/urequire/gno.mod deleted file mode 100644 index 9689a2222ac..00000000000 --- a/examples/gno.land/p/demo/urequire/gno.mod +++ /dev/null @@ -1,3 +0,0 @@ -module gno.land/p/demo/urequire - -require gno.land/p/demo/uassert v0.0.0-latest diff --git a/examples/gno.land/p/demo/users/gno.mod b/examples/gno.land/p/demo/users/gno.mod deleted file mode 100644 index ad652803fb8..00000000000 --- a/examples/gno.land/p/demo/users/gno.mod +++ /dev/null @@ -1 +0,0 @@ -module gno.land/p/demo/users diff --git a/examples/gno.land/p/demo/watchdog/gno.mod b/examples/gno.land/p/demo/watchdog/gno.mod deleted file mode 100644 index 29005441401..00000000000 --- a/examples/gno.land/p/demo/watchdog/gno.mod +++ /dev/null @@ -1,3 +0,0 @@ -module gno.land/p/demo/watchdog - -require gno.land/p/demo/uassert v0.0.0-latest diff --git a/examples/gno.land/p/gov/proposal/gno.mod b/examples/gno.land/p/gov/proposal/gno.mod deleted file mode 100644 index 3f6ef34a759..00000000000 --- a/examples/gno.land/p/gov/proposal/gno.mod +++ /dev/null @@ -1,7 +0,0 @@ -module gno.land/p/gov/proposal - -require ( - gno.land/p/demo/context v0.0.0-latest - gno.land/p/demo/uassert v0.0.0-latest - gno.land/p/demo/urequire v0.0.0-latest -) diff --git a/examples/gno.land/p/moul/printfdebugging/gno.mod b/examples/gno.land/p/moul/printfdebugging/gno.mod deleted file mode 100644 index 2cf6aa09e61..00000000000 --- a/examples/gno.land/p/moul/printfdebugging/gno.mod +++ /dev/null @@ -1,3 +0,0 @@ -module gno.land/p/demo/printfdebugging - -require gno.land/p/demo/ufmt v0.0.0-latest diff --git a/examples/gno.land/p/nt/poa/gno.mod b/examples/gno.land/p/nt/poa/gno.mod deleted file mode 100644 index 5c1b75eb05a..00000000000 --- a/examples/gno.land/p/nt/poa/gno.mod +++ /dev/null @@ -1,10 +0,0 @@ -module gno.land/p/nt/poa - -require ( - gno.land/p/demo/avl v0.0.0-latest - gno.land/p/demo/testutils v0.0.0-latest - gno.land/p/demo/uassert v0.0.0-latest - gno.land/p/demo/ufmt v0.0.0-latest - gno.land/p/demo/urequire v0.0.0-latest - gno.land/p/sys/validators v0.0.0-latest -) diff --git a/examples/gno.land/p/sys/validators/gno.mod b/examples/gno.land/p/sys/validators/gno.mod deleted file mode 100644 index 9c7a38aada0..00000000000 --- a/examples/gno.land/p/sys/validators/gno.mod +++ /dev/null @@ -1 +0,0 @@ -module gno.land/p/sys/validators diff --git a/examples/gno.land/r/demo/art/gnoface/gno.mod b/examples/gno.land/r/demo/art/gnoface/gno.mod deleted file mode 100644 index 072c98f3bd6..00000000000 --- a/examples/gno.land/r/demo/art/gnoface/gno.mod +++ /dev/null @@ -1,7 +0,0 @@ -module gno.land/r/demo/art/gnoface - -require ( - gno.land/p/demo/entropy v0.0.0-latest - gno.land/p/demo/uassert v0.0.0-latest - gno.land/p/demo/ufmt v0.0.0-latest -) diff --git a/examples/gno.land/r/demo/art/millipede/gno.mod b/examples/gno.land/r/demo/art/millipede/gno.mod deleted file mode 100644 index 7cd604206fa..00000000000 --- a/examples/gno.land/r/demo/art/millipede/gno.mod +++ /dev/null @@ -1,6 +0,0 @@ -module gno.land/r/demo/art/millipede - -require ( - gno.land/p/demo/uassert v0.0.0-latest - gno.land/p/demo/ufmt v0.0.0-latest -) diff --git a/examples/gno.land/r/demo/banktest/gno.mod b/examples/gno.land/r/demo/banktest/gno.mod deleted file mode 100644 index 7660f338c13..00000000000 --- a/examples/gno.land/r/demo/banktest/gno.mod +++ /dev/null @@ -1 +0,0 @@ -module gno.land/r/demo/banktest diff --git a/examples/gno.land/r/demo/bar20/gno.mod b/examples/gno.land/r/demo/bar20/gno.mod deleted file mode 100644 index 2ec82d7be0b..00000000000 --- a/examples/gno.land/r/demo/bar20/gno.mod +++ /dev/null @@ -1,8 +0,0 @@ -module gno.land/r/demo/bar20 - -require ( - gno.land/p/demo/grc/grc20 v0.0.0-latest - gno.land/p/demo/testutils v0.0.0-latest - gno.land/p/demo/ufmt v0.0.0-latest - gno.land/p/demo/urequire v0.0.0-latest -) diff --git a/examples/gno.land/r/demo/boards/gno.mod b/examples/gno.land/r/demo/boards/gno.mod deleted file mode 100644 index 434ad019883..00000000000 --- a/examples/gno.land/r/demo/boards/gno.mod +++ /dev/null @@ -1,6 +0,0 @@ -module gno.land/r/demo/boards - -require ( - gno.land/p/demo/avl v0.0.0-latest - gno.land/r/demo/users v0.0.0-latest -) diff --git a/examples/gno.land/r/demo/counter/gno.mod b/examples/gno.land/r/demo/counter/gno.mod deleted file mode 100644 index 332d4e6da6a..00000000000 --- a/examples/gno.land/r/demo/counter/gno.mod +++ /dev/null @@ -1 +0,0 @@ -module gno.land/r/demo/counter diff --git a/examples/gno.land/r/demo/deep/very/deep/gno.mod b/examples/gno.land/r/demo/deep/very/deep/gno.mod deleted file mode 100644 index ab5a9ef8536..00000000000 --- a/examples/gno.land/r/demo/deep/very/deep/gno.mod +++ /dev/null @@ -1 +0,0 @@ -module gno.land/r/demo/deep/very/deep diff --git a/examples/gno.land/r/demo/disperse/gno.mod b/examples/gno.land/r/demo/disperse/gno.mod deleted file mode 100644 index 0ba9c88810a..00000000000 --- a/examples/gno.land/r/demo/disperse/gno.mod +++ /dev/null @@ -1,3 +0,0 @@ -module gno.land/r/demo/disperse - -require gno.land/r/demo/grc20factory v0.0.0-latest diff --git a/examples/gno.land/r/demo/echo/gno.mod b/examples/gno.land/r/demo/echo/gno.mod deleted file mode 100644 index 4ca5ccab6e0..00000000000 --- a/examples/gno.land/r/demo/echo/gno.mod +++ /dev/null @@ -1,3 +0,0 @@ -module gno.land/r/demo/echo - -require gno.land/p/demo/urequire v0.0.0-latest diff --git a/examples/gno.land/r/demo/event/gno.mod b/examples/gno.land/r/demo/event/gno.mod deleted file mode 100644 index 64987d43d79..00000000000 --- a/examples/gno.land/r/demo/event/gno.mod +++ /dev/null @@ -1 +0,0 @@ -module gno.land/r/demo/event diff --git a/examples/gno.land/r/demo/foo1155/gno.mod b/examples/gno.land/r/demo/foo1155/gno.mod deleted file mode 100644 index 0a405c5b4a2..00000000000 --- a/examples/gno.land/r/demo/foo1155/gno.mod +++ /dev/null @@ -1,8 +0,0 @@ -module gno.land/r/demo/foo1155 - -require ( - gno.land/p/demo/grc/grc1155 v0.0.0-latest - gno.land/p/demo/ufmt v0.0.0-latest - gno.land/p/demo/users v0.0.0-latest - gno.land/r/demo/users v0.0.0-latest -) diff --git a/examples/gno.land/r/demo/foo20/gno.mod b/examples/gno.land/r/demo/foo20/gno.mod deleted file mode 100644 index 4035f9b1200..00000000000 --- a/examples/gno.land/r/demo/foo20/gno.mod +++ /dev/null @@ -1,11 +0,0 @@ -module gno.land/r/demo/foo20 - -require ( - gno.land/p/demo/grc/grc20 v0.0.0-latest - gno.land/p/demo/ownable v0.0.0-latest - gno.land/p/demo/testutils v0.0.0-latest - gno.land/p/demo/uassert v0.0.0-latest - gno.land/p/demo/ufmt v0.0.0-latest - gno.land/p/demo/users v0.0.0-latest - gno.land/r/demo/users v0.0.0-latest -) diff --git a/examples/gno.land/r/demo/foo721/gno.mod b/examples/gno.land/r/demo/foo721/gno.mod deleted file mode 100644 index e013677379d..00000000000 --- a/examples/gno.land/r/demo/foo721/gno.mod +++ /dev/null @@ -1,8 +0,0 @@ -module gno.land/r/demo/foo721 - -require ( - gno.land/p/demo/grc/grc721 v0.0.0-latest - gno.land/p/demo/ufmt v0.0.0-latest - gno.land/p/demo/users v0.0.0-latest - gno.land/r/demo/users v0.0.0-latest -) diff --git a/examples/gno.land/r/demo/games/dice_roller/gno.mod b/examples/gno.land/r/demo/games/dice_roller/gno.mod deleted file mode 100644 index 75c6473fa3e..00000000000 --- a/examples/gno.land/r/demo/games/dice_roller/gno.mod +++ /dev/null @@ -1,11 +0,0 @@ -module gno.land/r/demo/games/dice_roller - -require ( - gno.land/p/demo/avl v0.0.0-latest - gno.land/p/demo/entropy v0.0.0-latest - gno.land/p/demo/seqid v0.0.0-latest - gno.land/p/demo/testutils v0.0.0-latest - gno.land/p/demo/ufmt v0.0.0-latest - gno.land/p/demo/urequire v0.0.0-latest - gno.land/r/demo/users v0.0.0-latest -) diff --git a/examples/gno.land/r/demo/games/shifumi/gno.mod b/examples/gno.land/r/demo/games/shifumi/gno.mod deleted file mode 100644 index 7a4fc173d3d..00000000000 --- a/examples/gno.land/r/demo/games/shifumi/gno.mod +++ /dev/null @@ -1,7 +0,0 @@ -module gno.land/r/demo/games/shifumi - -require ( - gno.land/p/demo/avl v0.0.0-latest - gno.land/p/demo/seqid v0.0.0-latest - gno.land/r/demo/users v0.0.0-latest -) diff --git a/examples/gno.land/r/demo/grc20factory/gno.mod b/examples/gno.land/r/demo/grc20factory/gno.mod deleted file mode 100644 index 8d0fbd0c46b..00000000000 --- a/examples/gno.land/r/demo/grc20factory/gno.mod +++ /dev/null @@ -1,9 +0,0 @@ -module gno.land/r/demo/grc20factory - -require ( - gno.land/p/demo/avl v0.0.0-latest - gno.land/p/demo/grc/grc20 v0.0.0-latest - gno.land/p/demo/ownable v0.0.0-latest - gno.land/p/demo/uassert v0.0.0-latest - gno.land/p/demo/ufmt v0.0.0-latest -) diff --git a/examples/gno.land/r/demo/groups/gno.mod b/examples/gno.land/r/demo/groups/gno.mod deleted file mode 100644 index fc6756e13e2..00000000000 --- a/examples/gno.land/r/demo/groups/gno.mod +++ /dev/null @@ -1,6 +0,0 @@ -module gno.land/r/demo/groups - -require ( - gno.land/p/demo/avl v0.0.0-latest - gno.land/r/demo/users v0.0.0-latest -) diff --git a/examples/gno.land/r/demo/keystore/gno.mod b/examples/gno.land/r/demo/keystore/gno.mod deleted file mode 100644 index 49b0f3494a4..00000000000 --- a/examples/gno.land/r/demo/keystore/gno.mod +++ /dev/null @@ -1,8 +0,0 @@ -module gno.land/r/demo/keystore - -require ( - gno.land/p/demo/avl v0.0.0-latest - gno.land/p/demo/testutils v0.0.0-latest - gno.land/p/demo/uassert v0.0.0-latest - gno.land/p/demo/ufmt v0.0.0-latest -) diff --git a/examples/gno.land/r/demo/markdown_test/gno.mod b/examples/gno.land/r/demo/markdown_test/gno.mod deleted file mode 100644 index 19c0d1dcfb3..00000000000 --- a/examples/gno.land/r/demo/markdown_test/gno.mod +++ /dev/null @@ -1 +0,0 @@ -module gno.land/r/demo/markdown_test diff --git a/examples/gno.land/r/demo/math_eval/gno.mod b/examples/gno.land/r/demo/math_eval/gno.mod deleted file mode 100644 index 0e3fcfe6e9b..00000000000 --- a/examples/gno.land/r/demo/math_eval/gno.mod +++ /dev/null @@ -1,6 +0,0 @@ -module gno.land/r/demo/math_eval - -require ( - gno.land/p/demo/math_eval/int32 v0.0.0-latest - gno.land/p/demo/ufmt v0.0.0-latest -) diff --git a/examples/gno.land/r/demo/memeland/gno.mod b/examples/gno.land/r/demo/memeland/gno.mod deleted file mode 100644 index 5c73379519b..00000000000 --- a/examples/gno.land/r/demo/memeland/gno.mod +++ /dev/null @@ -1,3 +0,0 @@ -module gno.land/r/demo/memeland - -require gno.land/p/demo/memeland v0.0.0-latest diff --git a/examples/gno.land/r/demo/microblog/gno.mod b/examples/gno.land/r/demo/microblog/gno.mod deleted file mode 100644 index 26349e481d4..00000000000 --- a/examples/gno.land/r/demo/microblog/gno.mod +++ /dev/null @@ -1,9 +0,0 @@ -module gno.land/r/demo/microblog - -require ( - gno.land/p/demo/microblog v0.0.0-latest - gno.land/p/demo/testutils v0.0.0-latest - gno.land/p/demo/ufmt v0.0.0-latest - gno.land/p/demo/urequire v0.0.0-latest - gno.land/r/demo/users v0.0.0-latest -) diff --git a/examples/gno.land/r/demo/nft/gno.mod b/examples/gno.land/r/demo/nft/gno.mod deleted file mode 100644 index 89e0055be51..00000000000 --- a/examples/gno.land/r/demo/nft/gno.mod +++ /dev/null @@ -1,6 +0,0 @@ -module gno.land/r/demo/nft - -require ( - gno.land/p/demo/avl v0.0.0-latest - gno.land/p/demo/grc/grc721 v0.0.0-latest -) diff --git a/examples/gno.land/r/demo/profile/gno.mod b/examples/gno.land/r/demo/profile/gno.mod deleted file mode 100644 index e7feac5d680..00000000000 --- a/examples/gno.land/r/demo/profile/gno.mod +++ /dev/null @@ -1,9 +0,0 @@ -module gno.land/r/demo/profile - -require ( - gno.land/p/demo/avl v0.0.0-latest - gno.land/p/demo/mux v0.0.0-latest - gno.land/p/demo/testutils v0.0.0-latest - gno.land/p/demo/uassert v0.0.0-latest - gno.land/p/demo/ufmt v0.0.0-latest -) diff --git a/examples/gno.land/r/demo/releases_example/gno.mod b/examples/gno.land/r/demo/releases_example/gno.mod deleted file mode 100644 index 22f640fe797..00000000000 --- a/examples/gno.land/r/demo/releases_example/gno.mod +++ /dev/null @@ -1,3 +0,0 @@ -module gno.land/r/demo/releases_example - -require gno.land/p/demo/releases v0.0.0-latest diff --git a/examples/gno.land/r/demo/tamagotchi/gno.mod b/examples/gno.land/r/demo/tamagotchi/gno.mod deleted file mode 100644 index b7a2deea2c2..00000000000 --- a/examples/gno.land/r/demo/tamagotchi/gno.mod +++ /dev/null @@ -1,6 +0,0 @@ -module gno.land/r/demo/tamagotchi - -require ( - gno.land/p/demo/tamagotchi v0.0.0-latest - gno.land/p/demo/ufmt v0.0.0-latest -) diff --git a/examples/gno.land/r/demo/tests/crossrealm/gno.mod b/examples/gno.land/r/demo/tests/crossrealm/gno.mod deleted file mode 100644 index 71a89ec2ec5..00000000000 --- a/examples/gno.land/r/demo/tests/crossrealm/gno.mod +++ /dev/null @@ -1,6 +0,0 @@ -module gno.land/r/demo/tests/crossrealm - -require ( - gno.land/p/demo/tests/p_crossrealm v0.0.0-latest - gno.land/p/demo/ufmt v0.0.0-latest -) diff --git a/examples/gno.land/r/demo/tests/gno.mod b/examples/gno.land/r/demo/tests/gno.mod deleted file mode 100644 index c51571e7d04..00000000000 --- a/examples/gno.land/r/demo/tests/gno.mod +++ /dev/null @@ -1,6 +0,0 @@ -module gno.land/r/demo/tests - -require ( - gno.land/p/demo/nestedpkg v0.0.0-latest - gno.land/r/demo/tests/subtests v0.0.0-latest -) diff --git a/examples/gno.land/r/demo/tests/subtests/gno.mod b/examples/gno.land/r/demo/tests/subtests/gno.mod deleted file mode 100644 index 9f466ff77b9..00000000000 --- a/examples/gno.land/r/demo/tests/subtests/gno.mod +++ /dev/null @@ -1,4 +0,0 @@ -module gno.land/r/demo/tests/subtests - -// TODO: this file should not exist. -// This is a temporary workaround. Until https://github.com/gnolang/gno/issues/852 diff --git a/examples/gno.land/r/demo/tests_foo/gno.mod b/examples/gno.land/r/demo/tests_foo/gno.mod deleted file mode 100644 index 226271ae4b0..00000000000 --- a/examples/gno.land/r/demo/tests_foo/gno.mod +++ /dev/null @@ -1,3 +0,0 @@ -module gno.land/r/demo/tests_foo - -require gno.land/r/demo/tests v0.0.0-latest diff --git a/examples/gno.land/r/demo/todolist/gno.mod b/examples/gno.land/r/demo/todolist/gno.mod deleted file mode 100644 index 36909859a6f..00000000000 --- a/examples/gno.land/r/demo/todolist/gno.mod +++ /dev/null @@ -1,9 +0,0 @@ -module gno.land/r/demo/todolist - -require ( - gno.land/p/demo/avl v0.0.0-latest - gno.land/p/demo/seqid v0.0.0-latest - gno.land/p/demo/todolist v0.0.0-latest - gno.land/p/demo/uassert v0.0.0-latest - gno.land/p/demo/ufmt v0.0.0-latest -) diff --git a/examples/gno.land/r/demo/types/gno.mod b/examples/gno.land/r/demo/types/gno.mod deleted file mode 100644 index 0e86e5d5676..00000000000 --- a/examples/gno.land/r/demo/types/gno.mod +++ /dev/null @@ -1,3 +0,0 @@ -module gno.land/r/demo/types - -require gno.land/p/demo/avl v0.0.0-latest diff --git a/examples/gno.land/r/demo/ui/gno.mod b/examples/gno.land/r/demo/ui/gno.mod deleted file mode 100644 index 0ef5d9dd40e..00000000000 --- a/examples/gno.land/r/demo/ui/gno.mod +++ /dev/null @@ -1,6 +0,0 @@ -module gno.land/r/demo/ui - -require ( - gno.land/p/demo/uassert v0.0.0-latest - gno.land/p/demo/ui v0.0.0-latest -) diff --git a/examples/gno.land/r/demo/userbook/gno.mod b/examples/gno.land/r/demo/userbook/gno.mod deleted file mode 100644 index 213586d12ee..00000000000 --- a/examples/gno.land/r/demo/userbook/gno.mod +++ /dev/null @@ -1,8 +0,0 @@ -module gno.land/r/demo/userbook - -require ( - gno.land/p/demo/avl v0.0.0-latest - gno.land/p/demo/mux v0.0.0-latest - gno.land/p/demo/testutils v0.0.0-latest - gno.land/p/demo/ufmt v0.0.0-latest -) diff --git a/examples/gno.land/r/demo/users/gno.mod b/examples/gno.land/r/demo/users/gno.mod deleted file mode 100644 index cdef52b6952..00000000000 --- a/examples/gno.land/r/demo/users/gno.mod +++ /dev/null @@ -1,8 +0,0 @@ -module gno.land/r/demo/users - -require ( - gno.land/p/demo/avl v0.0.0-latest - gno.land/p/demo/avlhelpers v0.0.0-latest - gno.land/p/demo/uassert v0.0.0-latest - gno.land/p/demo/users v0.0.0-latest -) diff --git a/examples/gno.land/r/demo/wugnot/gno.mod b/examples/gno.land/r/demo/wugnot/gno.mod deleted file mode 100644 index f076e90e068..00000000000 --- a/examples/gno.land/r/demo/wugnot/gno.mod +++ /dev/null @@ -1,8 +0,0 @@ -module gno.land/r/demo/wugnot - -require ( - gno.land/p/demo/grc/grc20 v0.0.0-latest - gno.land/p/demo/ufmt v0.0.0-latest - gno.land/p/demo/users v0.0.0-latest - gno.land/r/demo/users v0.0.0-latest -) diff --git a/examples/gno.land/r/gnoland/blog/gno.mod b/examples/gno.land/r/gnoland/blog/gno.mod deleted file mode 100644 index 17c17e0cfa6..00000000000 --- a/examples/gno.land/r/gnoland/blog/gno.mod +++ /dev/null @@ -1,8 +0,0 @@ -module gno.land/r/gnoland/blog - -require ( - gno.land/p/demo/avl v0.0.0-latest - gno.land/p/demo/blog v0.0.0-latest - gno.land/p/demo/context v0.0.0-latest - gno.land/p/gov/proposal v0.0.0-latest -) diff --git a/examples/gno.land/r/gnoland/events/gno.mod b/examples/gno.land/r/gnoland/events/gno.mod deleted file mode 100644 index bd3e4652b04..00000000000 --- a/examples/gno.land/r/gnoland/events/gno.mod +++ /dev/null @@ -1,9 +0,0 @@ -module gno.land/r/gnoland/events - -require ( - gno.land/p/demo/ownable/exts/authorizable v0.0.0-latest - gno.land/p/demo/seqid v0.0.0-latest - gno.land/p/demo/uassert v0.0.0-latest - gno.land/p/demo/ufmt v0.0.0-latest - gno.land/p/demo/urequire v0.0.0-latest -) diff --git a/examples/gno.land/r/gnoland/faucet/gno.mod b/examples/gno.land/r/gnoland/faucet/gno.mod deleted file mode 100644 index 693b0e795cf..00000000000 --- a/examples/gno.land/r/gnoland/faucet/gno.mod +++ /dev/null @@ -1,7 +0,0 @@ -module gno.land/r/gnoland/faucet - -require ( - gno.land/p/demo/avl v0.0.0-latest - gno.land/p/demo/testutils v0.0.0-latest - gno.land/p/demo/ufmt v0.0.0-latest -) diff --git a/examples/gno.land/r/gnoland/ghverify/gno.mod b/examples/gno.land/r/gnoland/ghverify/gno.mod deleted file mode 100644 index 386bd9293d2..00000000000 --- a/examples/gno.land/r/gnoland/ghverify/gno.mod +++ /dev/null @@ -1,9 +0,0 @@ -module gno.land/r/gnoland/ghverify - -require ( - gno.land/p/demo/avl v0.0.0-latest - gno.land/p/demo/gnorkle/feeds/static v0.0.0-latest - gno.land/p/demo/gnorkle/gnorkle v0.0.0-latest - gno.land/p/demo/gnorkle/message v0.0.0-latest - gno.land/p/demo/testutils v0.0.0-latest -) diff --git a/examples/gno.land/r/gnoland/home/gno.mod b/examples/gno.land/r/gnoland/home/gno.mod deleted file mode 100644 index c208ad421c9..00000000000 --- a/examples/gno.land/r/gnoland/home/gno.mod +++ /dev/null @@ -1,9 +0,0 @@ -module gno.land/r/gnoland/home - -require ( - gno.land/p/demo/ownable v0.0.0-latest - gno.land/p/demo/ufmt v0.0.0-latest - gno.land/p/demo/ui v0.0.0-latest - gno.land/r/gnoland/blog v0.0.0-latest - gno.land/r/gnoland/events v0.0.0-latest -) diff --git a/examples/gno.land/r/gnoland/monit/gno.mod b/examples/gno.land/r/gnoland/monit/gno.mod deleted file mode 100644 index e67fdaa7d71..00000000000 --- a/examples/gno.land/r/gnoland/monit/gno.mod +++ /dev/null @@ -1,8 +0,0 @@ -module gno.land/r/gnoland/monit - -require ( - gno.land/p/demo/ownable v0.0.0-latest - gno.land/p/demo/uassert v0.0.0-latest - gno.land/p/demo/ufmt v0.0.0-latest - gno.land/p/demo/watchdog v0.0.0-latest -) diff --git a/examples/gno.land/r/gnoland/pages/gno.mod b/examples/gno.land/r/gnoland/pages/gno.mod deleted file mode 100644 index 31e9ad2c85b..00000000000 --- a/examples/gno.land/r/gnoland/pages/gno.mod +++ /dev/null @@ -1,6 +0,0 @@ -module gno.land/r/gnoland/pages - -require ( - gno.land/p/demo/avl v0.0.0-latest - gno.land/p/demo/blog v0.0.0-latest -) diff --git a/examples/gno.land/r/gnoland/valopers/gno.mod b/examples/gno.land/r/gnoland/valopers/gno.mod deleted file mode 100644 index 2d24fb27952..00000000000 --- a/examples/gno.land/r/gnoland/valopers/gno.mod +++ /dev/null @@ -1,11 +0,0 @@ -module gno.land/r/gnoland/valopers - -require ( - gno.land/p/demo/avl v0.0.0-latest - gno.land/p/demo/testutils v0.0.0-latest - gno.land/p/demo/uassert v0.0.0-latest - gno.land/p/demo/ufmt v0.0.0-latest - gno.land/p/sys/validators v0.0.0-latest - gno.land/r/gov/dao v0.0.0-latest - gno.land/r/sys/validators v0.0.0-latest -) diff --git a/examples/gno.land/r/gov/dao/gno.mod b/examples/gno.land/r/gov/dao/gno.mod deleted file mode 100644 index f3c0bae990e..00000000000 --- a/examples/gno.land/r/gov/dao/gno.mod +++ /dev/null @@ -1,8 +0,0 @@ -module gno.land/r/gov/dao - -require ( - gno.land/p/demo/testutils v0.0.0-latest - gno.land/p/demo/ufmt v0.0.0-latest - gno.land/p/demo/urequire v0.0.0-latest - gno.land/p/gov/proposal v0.0.0-latest -) diff --git a/examples/gno.land/r/leon/config/gno.mod b/examples/gno.land/r/leon/config/gno.mod deleted file mode 100644 index e8cd5cd85b7..00000000000 --- a/examples/gno.land/r/leon/config/gno.mod +++ /dev/null @@ -1 +0,0 @@ -module gno.land/r/leon/config diff --git a/examples/gno.land/r/leon/home/gno.mod b/examples/gno.land/r/leon/home/gno.mod deleted file mode 100644 index 48cf64a9d0a..00000000000 --- a/examples/gno.land/r/leon/home/gno.mod +++ /dev/null @@ -1,8 +0,0 @@ -module gno.land/r/leon/home - -require ( - gno.land/p/demo/ufmt v0.0.0-latest - gno.land/r/demo/art/gnoface v0.0.0-latest - gno.land/r/demo/art/millipede v0.0.0-latest - gno.land/r/leon/config v0.0.0-latest -) diff --git a/examples/gno.land/r/manfred/config/gno.mod b/examples/gno.land/r/manfred/config/gno.mod deleted file mode 100644 index 516bf38528e..00000000000 --- a/examples/gno.land/r/manfred/config/gno.mod +++ /dev/null @@ -1 +0,0 @@ -module gno.land/r/manfred/config diff --git a/examples/gno.land/r/manfred/home/gno.mod b/examples/gno.land/r/manfred/home/gno.mod deleted file mode 100644 index 6e7aac70cc7..00000000000 --- a/examples/gno.land/r/manfred/home/gno.mod +++ /dev/null @@ -1,3 +0,0 @@ -module gno.land/r/manfred/home - -require gno.land/r/manfred/config v0.0.0-latest diff --git a/examples/gno.land/r/manfred/present/gno.mod b/examples/gno.land/r/manfred/present/gno.mod deleted file mode 100644 index 5d50447e0e0..00000000000 --- a/examples/gno.land/r/manfred/present/gno.mod +++ /dev/null @@ -1,6 +0,0 @@ -module gno.land/r/manfred/present - -require ( - gno.land/p/demo/avl v0.0.0-latest - gno.land/p/demo/blog v0.0.0-latest -) diff --git a/examples/gno.land/r/morgan/guestbook/gno.mod b/examples/gno.land/r/morgan/guestbook/gno.mod deleted file mode 100644 index 2591643d33d..00000000000 --- a/examples/gno.land/r/morgan/guestbook/gno.mod +++ /dev/null @@ -1,7 +0,0 @@ -module gno.land/r/morgan/guestbook - -require ( - gno.land/p/demo/avl v0.0.0-latest - gno.land/p/demo/ownable v0.0.0-latest - gno.land/p/demo/seqid v0.0.0-latest -) diff --git a/examples/gno.land/r/morgan/home/gno.mod b/examples/gno.land/r/morgan/home/gno.mod deleted file mode 100644 index 573a7e139e7..00000000000 --- a/examples/gno.land/r/morgan/home/gno.mod +++ /dev/null @@ -1 +0,0 @@ -module gno.land/r/morgan/home diff --git a/examples/gno.land/r/sys/rewards/gno.mod b/examples/gno.land/r/sys/rewards/gno.mod deleted file mode 100644 index f615998a01f..00000000000 --- a/examples/gno.land/r/sys/rewards/gno.mod +++ /dev/null @@ -1 +0,0 @@ -module gno.land/r/sys/rewards diff --git a/examples/gno.land/r/sys/users/gno.mod b/examples/gno.land/r/sys/users/gno.mod deleted file mode 100644 index 774a364a272..00000000000 --- a/examples/gno.land/r/sys/users/gno.mod +++ /dev/null @@ -1,6 +0,0 @@ -module gno.land/r/sys/users - -require ( - gno.land/p/demo/ownable v0.0.0-latest - gno.land/r/demo/users v0.0.0-latest -) diff --git a/examples/gno.land/r/sys/validators/gno.mod b/examples/gno.land/r/sys/validators/gno.mod deleted file mode 100644 index d9d129dd543..00000000000 --- a/examples/gno.land/r/sys/validators/gno.mod +++ /dev/null @@ -1,12 +0,0 @@ -module gno.land/r/sys/validators - -require ( - gno.land/p/demo/avl v0.0.0-latest - gno.land/p/demo/seqid v0.0.0-latest - gno.land/p/demo/testutils v0.0.0-latest - gno.land/p/demo/uassert v0.0.0-latest - gno.land/p/demo/ufmt v0.0.0-latest - gno.land/p/gov/proposal v0.0.0-latest - gno.land/p/nt/poa v0.0.0-latest - gno.land/p/sys/validators v0.0.0-latest -) diff --git a/examples/gno.land/r/x/manfred_outfmt/gno.mod b/examples/gno.land/r/x/manfred_outfmt/gno.mod deleted file mode 100644 index 7044f0f72b3..00000000000 --- a/examples/gno.land/r/x/manfred_outfmt/gno.mod +++ /dev/null @@ -1,5 +0,0 @@ -// Draft - -module gno.land/r/x/manfred_outfmt - -require gno.land/p/demo/ufmt v0.0.0-latest diff --git a/examples/gno.land/r/x/manfred_upgrade_patterns/gno.mod b/examples/gno.land/r/x/manfred_upgrade_patterns/gno.mod deleted file mode 100644 index d6e97dc39b9..00000000000 --- a/examples/gno.land/r/x/manfred_upgrade_patterns/gno.mod +++ /dev/null @@ -1,3 +0,0 @@ -// Draft - -module gno.land/r/x/manfred_upgrade_patterns From 1c51509b483bfae5a362f24de340e5b72e9c792e Mon Sep 17 00:00:00 2001 From: Norman Meier Date: Fri, 11 Oct 2024 14:09:02 +0200 Subject: [PATCH 013/143] fix: resolver usage in test Signed-off-by: Norman Meier --- .github/workflows/examples.yml | 10 ++--- gnovm/cmd/gno/test.go | 72 +++++++++++++++++----------------- gnovm/tests/imports.go | 35 +++++++++-------- 3 files changed, 59 insertions(+), 58 deletions(-) diff --git a/.github/workflows/examples.yml b/.github/workflows/examples.yml index 7a54802112f..b9c24984e65 100644 --- a/.github/workflows/examples.yml +++ b/.github/workflows/examples.yml @@ -3,7 +3,7 @@ name: examples on: pull_request: push: - branches: [ "master" ] + branches: ["master"] concurrency: group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} @@ -24,7 +24,7 @@ jobs: with: go-version: ${{ matrix.goversion }} - run: go install -v ./gnovm/cmd/gno - - run: go run ./gnovm/cmd/gno transpile -v --gobuild ./examples/... + - run: go run ./gnovm/cmd/gno transpile -v --gobuild ./examples/gno.land/... test: strategy: fail-fast: false @@ -47,7 +47,7 @@ jobs: echo "LOG_LEVEL=debug" >> $GITHUB_ENV echo "LOG_PATH_DIR=$LOG_PATH_DIR" >> $GITHUB_ENV - run: go install -v ./gnovm/cmd/gno - - run: go run ./gnovm/cmd/gno test -v ./examples/... + - run: go run ./gnovm/cmd/gno test -v ./examples/gno.land/... lint: strategy: fail-fast: false @@ -70,7 +70,7 @@ jobs: strategy: fail-fast: false matrix: - goversion: [ "1.22.x" ] + goversion: ["1.22.x"] runs-on: ubuntu-latest timeout-minutes: 10 steps: @@ -86,7 +86,7 @@ jobs: strategy: fail-fast: false matrix: - go-version: [ "1.22.x" ] + go-version: ["1.22.x"] # unittests: TODO: matrix with contracts runs-on: ubuntu-latest timeout-minutes: 10 diff --git a/gnovm/cmd/gno/test.go b/gnovm/cmd/gno/test.go index daa12236a89..2427a940ea2 100644 --- a/gnovm/cmd/gno/test.go +++ b/gnovm/cmd/gno/test.go @@ -7,6 +7,7 @@ import ( "flag" "fmt" "log" + "os" "path/filepath" "runtime/debug" "strings" @@ -21,6 +22,7 @@ import ( "github.com/gnolang/gno/gnovm/tests" "github.com/gnolang/gno/tm2/pkg/commands" "github.com/gnolang/gno/tm2/pkg/std" + "github.com/gnolang/gno/tm2/pkg/testutils" ) type testCfg struct { @@ -342,51 +344,49 @@ func gnoTestPkg( } // testing with *_filetest.gno - /* - { - filter := splitRegexp(runFlag) - for _, testFile := range pkg.FiletestGnoFiles { - testFileName := filepath.Base(testFile) - testName := "file/" + testFileName - if !shouldRun(filter, testName) { - continue - } + { + filter := splitRegexp(runFlag) + for _, testFile := range pkg.FiletestGnoFiles { + testFileName := filepath.Base(testFile) + testName := "file/" + testFileName + if !shouldRun(filter, testName) { + continue + } - startedAt := time.Now() - if verbose { - io.ErrPrintfln("=== RUN %s", testName) - } + startedAt := time.Now() + if verbose { + io.ErrPrintfln("=== RUN %s", testName) + } - var closer func() (string, error) - if !verbose { - closer = testutils.CaptureStdoutAndStderr() - } + var closer func() (string, error) + if !verbose { + closer = testutils.CaptureStdoutAndStderr() + } - testFilePath := filepath.Join(pkg.Dir, testFileName) - err := tests.RunFileTest(rootDir, testFilePath, tests.WithSyncWanted(cfg.updateGoldenTests)) - duration := time.Since(startedAt) - dstr := fmtDuration(duration) + testFilePath := filepath.Join(pkg.Dir, testFileName) + err := tests.RunFileTest(rootDir, testFilePath, tests.WithSyncWanted(cfg.updateGoldenTests)) + duration := time.Since(startedAt) + dstr := fmtDuration(duration) - if err != nil { - errs = multierr.Append(errs, err) - io.ErrPrintfln("--- FAIL: %s (%s)", testName, dstr) - if verbose { - stdouterr, err := closer() - if err != nil { - panic(err) - } - fmt.Fprintln(os.Stderr, stdouterr) + if err != nil { + errs = multierr.Append(errs, err) + io.ErrPrintfln("--- FAIL: %s (%s)", testName, dstr) + if verbose { + stdouterr, err := closer() + if err != nil { + panic(err) } - continue + fmt.Fprintln(os.Stderr, stdouterr) } + continue + } - if verbose { - io.ErrPrintfln("--- PASS: %s (%s)", testName, dstr) - } - // XXX: add per-test metrics + if verbose { + io.ErrPrintfln("--- PASS: %s (%s)", testName, dstr) } + // XXX: add per-test metrics } - */ + } return errs } diff --git a/gnovm/tests/imports.go b/gnovm/tests/imports.go index 7de76b75f44..6f61b3e066c 100644 --- a/gnovm/tests/imports.go +++ b/gnovm/tests/imports.go @@ -73,23 +73,6 @@ func TestStore(rootDir, filesPath string, pkgs map[string]*importer.Package, std panic(fmt.Sprintf("unrecognized import mode")) } - if pkg, ok := pkgs[pkgPath]; ok { - memPkg, err := pkg.MemPkg() - if err != nil { - panic(fmt.Errorf("failed to convert imported pkg to mem pkg: %w", err)) - } - send := std.Coins{} - ctx := TestContext(pkgPath, send) - m2 := gno.NewMachineWithOptions(gno.MachineOptions{ - PkgPath: "test", - Output: stdout, - Store: store, - Context: ctx, - }) - pn, pv = m2.RunMemPackage(memPkg, true) - return - } - if filesPath != "" { // if _test package... const testPath = "github.com/gnolang/gno/_test/" @@ -408,6 +391,24 @@ func TestStore(rootDir, filesPath string, pkgs map[string]*importer.Package, std } } + // packages from resolver + if pkg, ok := pkgs[pkgPath]; ok { + memPkg, err := pkg.MemPkg() + if err != nil { + panic(fmt.Errorf("failed to convert imported pkg to mem pkg: %w", err)) + } + send := std.Coins{} + ctx := TestContext(pkgPath, send) + m2 := gno.NewMachineWithOptions(gno.MachineOptions{ + PkgPath: "test", + Output: stdout, + Store: store, + Context: ctx, + }) + pn, pv = m2.RunMemPackage(memPkg, true) + return + } + // if examples package... examplePath := filepath.Join(rootDir, "examples", pkgPath) if osm.DirExists(examplePath) { From da6541777269786d173c16b48b726110d0ea4b29 Mon Sep 17 00:00:00 2001 From: Norman Meier Date: Fri, 11 Oct 2024 14:16:24 +0200 Subject: [PATCH 014/143] chore: mod tidy Signed-off-by: Norman Meier --- examples/gno.land/gno.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/gno.land/gno.mod b/examples/gno.land/gno.mod index 8563e9e5da1..fb7d383a4ed 100644 --- a/examples/gno.land/gno.mod +++ b/examples/gno.land/gno.mod @@ -1 +1 @@ -module gno.land \ No newline at end of file +module gno.land From 4a8401a066b481b88f511b918efc85b19a087db8 Mon Sep 17 00:00:00 2001 From: Norman Meier Date: Fri, 11 Oct 2024 14:17:19 +0200 Subject: [PATCH 015/143] chore: ignore draft Signed-off-by: Norman Meier --- examples/gno.land/r/x/manfred_outfmt/gno.mod | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 examples/gno.land/r/x/manfred_outfmt/gno.mod diff --git a/examples/gno.land/r/x/manfred_outfmt/gno.mod b/examples/gno.land/r/x/manfred_outfmt/gno.mod new file mode 100644 index 00000000000..7044f0f72b3 --- /dev/null +++ b/examples/gno.land/r/x/manfred_outfmt/gno.mod @@ -0,0 +1,5 @@ +// Draft + +module gno.land/r/x/manfred_outfmt + +require gno.land/p/demo/ufmt v0.0.0-latest From e792771efbed51600994164d2792d6ee8e33e8f7 Mon Sep 17 00:00:00 2001 From: Norman Meier Date: Fri, 11 Oct 2024 14:32:54 +0200 Subject: [PATCH 016/143] chore: rename importer -> packages like go, break import cycle and move download to own file Signed-off-by: Norman Meier --- gno.land/pkg/gnoclient/client_queries.go | 20 ----- gnovm/cmd/gno/lint.go | 6 +- gnovm/cmd/gno/list.go | 6 +- gnovm/cmd/gno/run.go | 4 +- gnovm/cmd/gno/test.go | 12 +-- gnovm/cmd/gno/transpile.go | 10 +-- gnovm/cmd/gno/util.go | 12 +-- gnovm/pkg/doc/pkg.go | 4 +- gnovm/pkg/gnolang/debugger_test.go | 2 +- gnovm/pkg/gnolang/go2gno_test.go | 2 +- gnovm/pkg/gnomod/gnomod.go | 4 +- .../pkg/{importer => packages}/definitions.go | 2 +- gnovm/pkg/packages/download.go | 83 +++++++++++++++++++ gnovm/pkg/{importer => packages}/importer.go | 6 +- gnovm/pkg/{importer => packages}/match.go | 2 +- .../pkg/{importer => packages}/match_test.go | 2 +- gnovm/pkg/{importer => packages}/resolve.go | 57 +------------ gnovm/pkg/transpiler/transpiler.go | 2 +- gnovm/tests/imports.go | 4 +- gnovm/tests/machine_test.go | 2 +- gnovm/tests/package_test.go | 2 +- misc/genstd/mapping.go | 2 +- 22 files changed, 127 insertions(+), 119 deletions(-) rename gnovm/pkg/{importer => packages}/definitions.go (98%) create mode 100644 gnovm/pkg/packages/download.go rename gnovm/pkg/{importer => packages}/importer.go (86%) rename gnovm/pkg/{importer => packages}/match.go (99%) rename gnovm/pkg/{importer => packages}/match_test.go (99%) rename gnovm/pkg/{importer => packages}/resolve.go (87%) diff --git a/gno.land/pkg/gnoclient/client_queries.go b/gno.land/pkg/gnoclient/client_queries.go index e1dd7283c3a..9d9d7305116 100644 --- a/gno.land/pkg/gnoclient/client_queries.go +++ b/gno.land/pkg/gnoclient/client_queries.go @@ -126,26 +126,6 @@ func (c *Client) QEval(pkgPath string, expression string) (string, *ctypes.Resul return string(qres.Response.Data), qres, nil } -// QFile ??? -func (c *Client) QFile(pkgPath string) (string, *ctypes.ResultABCIQuery, error) { - if err := c.validateRPCClient(); err != nil { - return "", nil, err - } - - path := "vm/qfile" - data := []byte(pkgPath) - - qres, err := c.RPCClient.ABCIQuery(path, data) - if err != nil { - return "", nil, errors.Wrap(err, "query qfile") - } - if qres.Response.Error != nil { - return "", nil, errors.Wrap(qres.Response.Error, "QFile failed: log:%s", qres.Response.Log) - } - - return string(qres.Response.Data), qres, nil -} - // Block gets the latest block at height, if any // Height must be larger than 0 func (c *Client) Block(height int64) (*ctypes.ResultBlock, error) { diff --git a/gnovm/cmd/gno/lint.go b/gnovm/cmd/gno/lint.go index d67fd4b2c6c..e9b9735e1cc 100644 --- a/gnovm/cmd/gno/lint.go +++ b/gnovm/cmd/gno/lint.go @@ -14,7 +14,7 @@ import ( "github.com/gnolang/gno/gnovm/pkg/gnoenv" gno "github.com/gnolang/gno/gnovm/pkg/gnolang" - "github.com/gnolang/gno/gnovm/pkg/importer" + "github.com/gnolang/gno/gnovm/pkg/packages" "github.com/gnolang/gno/gnovm/tests" "github.com/gnolang/gno/tm2/pkg/commands" ) @@ -62,12 +62,12 @@ func execLint(cfg *lintCfg, args []string, io commands.IO) error { rootDir = gnoenv.RootDir() } - pkgs, err := importer.Load(args...) + pkgs, err := packages.Load(args...) if err != nil { return fmt.Errorf("list packages from args: %w", err) } - pkgsMap := map[string]*importer.Package{} + pkgsMap := map[string]*packages.Package{} for _, pkg := range pkgs { pkgsMap[pkg.ImportPath] = pkg } diff --git a/gnovm/cmd/gno/list.go b/gnovm/cmd/gno/list.go index 8dc1301b316..4bc082d4864 100644 --- a/gnovm/cmd/gno/list.go +++ b/gnovm/cmd/gno/list.go @@ -8,7 +8,7 @@ import ( "os" "strings" - "github.com/gnolang/gno/gnovm/pkg/importer" + "github.com/gnolang/gno/gnovm/pkg/packages" "github.com/gnolang/gno/tm2/pkg/commands" ) @@ -52,7 +52,7 @@ func execList(cfg *listCfg, args []string, _ commands.IO) error { } if !cfg.json { - pkgs, err := importer.DiscoverPackages(args...) + pkgs, err := packages.DiscoverPackages(args...) if err != nil { fmt.Println(err) os.Exit(1) @@ -65,7 +65,7 @@ func execList(cfg *listCfg, args []string, _ commands.IO) error { return nil } - pkgs, err := importer.Load(args...) + pkgs, err := packages.Load(args...) if err != nil { fmt.Println(err) os.Exit(1) diff --git a/gnovm/cmd/gno/run.go b/gnovm/cmd/gno/run.go index a05277692e7..5d57d244e17 100644 --- a/gnovm/cmd/gno/run.go +++ b/gnovm/cmd/gno/run.go @@ -9,7 +9,7 @@ import ( "github.com/gnolang/gno/gnovm/pkg/gnoenv" gno "github.com/gnolang/gno/gnovm/pkg/gnolang" - "github.com/gnolang/gno/gnovm/pkg/importer" + "github.com/gnolang/gno/gnovm/pkg/packages" "github.com/gnolang/gno/gnovm/tests" "github.com/gnolang/gno/tm2/pkg/commands" "github.com/gnolang/gno/tm2/pkg/std" @@ -140,7 +140,7 @@ func execRun(cfg *runCfg, args []string, io commands.IO) error { } func parseFiles(fnames []string, stderr io.WriteCloser) ([]*gno.FileNode, error) { - gnoFnames, err := importer.Match(fnames, importer.MatchFiles("!*_test.gno", "!*_filetest.gno")) + gnoFnames, err := packages.Match(fnames, packages.MatchFiles("!*_test.gno", "!*_filetest.gno")) if err != nil { return nil, err } diff --git a/gnovm/cmd/gno/test.go b/gnovm/cmd/gno/test.go index 2427a940ea2..6c283248e4a 100644 --- a/gnovm/cmd/gno/test.go +++ b/gnovm/cmd/gno/test.go @@ -18,7 +18,7 @@ import ( "github.com/gnolang/gno/gnovm/pkg/gnoenv" gno "github.com/gnolang/gno/gnovm/pkg/gnolang" - "github.com/gnolang/gno/gnovm/pkg/importer" + "github.com/gnolang/gno/gnovm/pkg/packages" "github.com/gnolang/gno/gnovm/tests" "github.com/gnolang/gno/tm2/pkg/commands" "github.com/gnolang/gno/tm2/pkg/std" @@ -159,7 +159,7 @@ func execTest(cfg *testCfg, args []string, io commands.IO) error { } // Find targets for test. - pkgs, err := importer.Load(args...) + pkgs, err := packages.Load(args...) if err != nil { return fmt.Errorf("list targets from patterns: %w", err) } @@ -213,8 +213,8 @@ func execTest(cfg *testCfg, args []string, io commands.IO) error { } func gnoTestPkg( - pkg *importer.Package, - loadedPkgs []*importer.Package, + pkg *packages.Package, + loadedPkgs []*packages.Package, cfg *testCfg, io commands.IO, ) error { @@ -241,7 +241,7 @@ func gnoTestPkg( stdout = commands.WriteNopCloser(mockOut) } - pkgsMap := map[string]*importer.Package{} + pkgsMap := map[string]*packages.Package{} for _, pkg := range loadedPkgs { pkgsMap[pkg.ImportPath] = pkg } @@ -585,7 +585,7 @@ func parseMemPackageTests(memPkg *std.MemPackage) (tset, itset *gno.FileSet) { tset = &gno.FileSet{} itset = &gno.FileSet{} for _, mfile := range memPkg.Files { - if !importer.IsGnoFile(mfile.Name, "!*_filetest.gno") { + if !packages.IsGnoFile(mfile.Name, "!*_filetest.gno") { continue } n, err := gno.ParseFile(mfile.Name, mfile.Body) diff --git a/gnovm/cmd/gno/transpile.go b/gnovm/cmd/gno/transpile.go index 7e12b16c015..62dfcb50bb4 100644 --- a/gnovm/cmd/gno/transpile.go +++ b/gnovm/cmd/gno/transpile.go @@ -18,7 +18,7 @@ import ( "github.com/gnolang/gno/gnovm/pkg/gnoenv" "github.com/gnolang/gno/gnovm/pkg/gnomod" - "github.com/gnolang/gno/gnovm/pkg/importer" + "github.com/gnolang/gno/gnovm/pkg/packages" "github.com/gnolang/gno/gnovm/pkg/transpiler" "github.com/gnolang/gno/tm2/pkg/commands" ) @@ -135,12 +135,12 @@ func execTranspile(cfg *transpileCfg, args []string, io commands.IO) error { } // transpile .gno packages and files. - pkgs, err := importer.Load(args...) + pkgs, err := packages.Load(args...) if err != nil { return fmt.Errorf("load pkgs: %w", err) } - pkgsMap := map[string]*importer.Package{} + pkgsMap := map[string]*packages.Package{} for _, pkg := range pkgs { pkgsMap[pkg.ImportPath] = pkg } @@ -207,7 +207,7 @@ func execTranspile(cfg *transpileCfg, args []string, io commands.IO) error { // transpilePkg transpiles all non-test files at the given location. // Additionally, it checks the gno.mod in said location, and skips it if it is // a draft module -func transpilePkg(pkg *importer.Package, pkgs map[string]*importer.Package, opts *transpileOptions) error { +func transpilePkg(pkg *packages.Package, pkgs map[string]*packages.Package, opts *transpileOptions) error { dirPath := pkg.Dir if opts.isTranspiled(dirPath) { return nil @@ -243,7 +243,7 @@ func transpilePkg(pkg *importer.Package, pkgs map[string]*importer.Package, opts return nil } -func transpileFile(srcPath string, pkgs map[string]*importer.Package, opts *transpileOptions) error { +func transpileFile(srcPath string, pkgs map[string]*packages.Package, opts *transpileOptions) error { flags := opts.getFlags() // parse .gno. diff --git a/gnovm/cmd/gno/util.go b/gnovm/cmd/gno/util.go index b16d8eb6a79..738ab003497 100644 --- a/gnovm/cmd/gno/util.go +++ b/gnovm/cmd/gno/util.go @@ -9,7 +9,7 @@ import ( "regexp" "strings" - "github.com/gnolang/gno/gnovm/pkg/importer" + "github.com/gnolang/gno/gnovm/pkg/packages" ) func isFileExist(path string) bool { @@ -28,7 +28,7 @@ func gnoFilesFromArgsRecursively(args []string) ([]string, error) { if !info.IsDir() { path := filepath.Join(argPath, fs.FileInfoToDirEntry(info).Name()) - if importer.IsGnoFile(path) { + if packages.IsGnoFile(path) { paths = append(paths, ensurePathPrefix(argPath)) } @@ -58,7 +58,7 @@ func gnoFilesFromArgs(args []string) ([]string, error) { if !info.IsDir() { path := filepath.Join(argPath, fs.FileInfoToDirEntry(info).Name()) - if importer.IsGnoFile(path) { + if packages.IsGnoFile(path) { paths = append(paths, ensurePathPrefix(argPath)) } continue @@ -70,7 +70,7 @@ func gnoFilesFromArgs(args []string) ([]string, error) { } for _, f := range files { path := filepath.Join(argPath, f.Name()) - if importer.IsGnoFile(path) { + if packages.IsGnoFile(path) { paths = append(paths, ensurePathPrefix(path)) } } @@ -99,7 +99,7 @@ func walkDirForGnoFiles(root string, addPath func(path string)) error { } path := filepath.Join(currPath, f.Name()) - if f.IsDir() || !importer.IsGnoFile(path) { + if f.IsDir() || !packages.IsGnoFile(path) { return nil } @@ -187,7 +187,7 @@ func targetsFromPatterns(patterns []string) ([]string, error) { } // Skip directories and non ".gno" files. path := filepath.Join(dirToSearch, curpath) - if f.IsDir() || !importer.IsGnoFile(path) { + if f.IsDir() || !packages.IsGnoFile(path) { return nil } diff --git a/gnovm/pkg/doc/pkg.go b/gnovm/pkg/doc/pkg.go index 71e1a50f299..618af5af708 100644 --- a/gnovm/pkg/doc/pkg.go +++ b/gnovm/pkg/doc/pkg.go @@ -210,14 +210,14 @@ func (pkg *pkgData) docPackage(opts *WriteDocumentationOptions) (*ast.Package, * // Compute package documentation. // Assign to blank to ignore errors that can happen due to unresolved identifiers. - astpkg, _ := ast.NewPackage(pkg.fset, fileMap, simpleImporter, nil) + astpkg, _ := ast.NewPackage(pkg.fset, fileMap, simplepackages, nil) p := doc.New(astpkg, pkg.dir.importPath, mode) // TODO: classifyExamples(p, Examples(testGoFiles...)) return astpkg, p, nil } -func simpleImporter(imports map[string]*ast.Object, path string) (*ast.Object, error) { +func simplepackages(imports map[string]*ast.Object, path string) (*ast.Object, error) { pkg := imports[path] if pkg == nil { // note that strings.LastIndex returns -1 if there is no "/" diff --git a/gnovm/pkg/gnolang/debugger_test.go b/gnovm/pkg/gnolang/debugger_test.go index fe059ba9f56..bd9ef347447 100644 --- a/gnovm/pkg/gnolang/debugger_test.go +++ b/gnovm/pkg/gnolang/debugger_test.go @@ -44,7 +44,7 @@ func evalTest(debugAddr, in, file string) (out, err, stacktrace string) { err = strings.TrimSpace(strings.ReplaceAll(err, "../../tests/files/", "files/")) }() - testStore := tests.TestStore(gnoenv.RootDir(), "../../tests/files", stdin, stdout, stderr, mode) + testStore := tests.TestStore(gnoenv.RootDir(), "../../tests/files", nil, stdin, stdout, stderr, mode) f := gnolang.MustReadFile(file) diff --git a/gnovm/pkg/gnolang/go2gno_test.go b/gnovm/pkg/gnolang/go2gno_test.go index d85c142ca52..93afbe046b6 100644 --- a/gnovm/pkg/gnolang/go2gno_test.go +++ b/gnovm/pkg/gnolang/go2gno_test.go @@ -294,7 +294,7 @@ func TestTypeCheckMemPackage(t *testing.T) { tt = append(tt, testCase{ "ImportWithCache", - // This test will make use of the importer's internal cache for package `std`. + // This test will make use of the packages's internal cache for package `std`. &std.MemPackage{ Name: "hello", Path: "gno.land/p/demo/hello", diff --git a/gnovm/pkg/gnomod/gnomod.go b/gnovm/pkg/gnomod/gnomod.go index 202949bd44b..d86ee606d95 100644 --- a/gnovm/pkg/gnomod/gnomod.go +++ b/gnovm/pkg/gnomod/gnomod.go @@ -11,7 +11,7 @@ import ( "github.com/gnolang/gno/gnovm/pkg/gnoenv" "github.com/gnolang/gno/gnovm/pkg/gnolang" - "github.com/gnolang/gno/gnovm/pkg/importer" + "github.com/gnolang/gno/gnovm/pkg/packages" "github.com/gnolang/gno/gnovm/pkg/transpiler" "github.com/gnolang/gno/tm2/pkg/std" "golang.org/x/mod/modfile" @@ -171,7 +171,7 @@ func CreateGnoModFile(rootDir, modPath string) error { var pkgName gnolang.Name for _, file := range files { - if file.IsDir() || !importer.IsGnoFile(file.Name(), "!*_filetest.gno") { + if file.IsDir() || !packages.IsGnoFile(file.Name(), "!*_filetest.gno") { continue } diff --git a/gnovm/pkg/importer/definitions.go b/gnovm/pkg/packages/definitions.go similarity index 98% rename from gnovm/pkg/importer/definitions.go rename to gnovm/pkg/packages/definitions.go index 1f47e600f2b..ad4954578a8 100644 --- a/gnovm/pkg/importer/definitions.go +++ b/gnovm/pkg/packages/definitions.go @@ -1,4 +1,4 @@ -package importer +package packages import ( "os" diff --git a/gnovm/pkg/packages/download.go b/gnovm/pkg/packages/download.go new file mode 100644 index 00000000000..040cc768e89 --- /dev/null +++ b/gnovm/pkg/packages/download.go @@ -0,0 +1,83 @@ +package packages + +import ( + "fmt" + "net/url" + "os" + "path" + "path/filepath" + "strings" + + "github.com/gnolang/gno/tm2/pkg/bft/rpc/client" + "github.com/gnolang/gno/tm2/pkg/errors" +) + +func DownloadModule(pkgPath string, dst string) error { + modFilePath := filepath.Join(dst, ModfileName) + if _, err := os.Stat(modFilePath); os.IsNotExist(err) { + fmt.Fprintln(os.Stderr, "gno: downloading", pkgPath) + + // create client from pkgpath + parts := strings.Split(pkgPath, "/") + if len(parts) < 1 { + return fmt.Errorf("bad pkg path %q", pkgPath) + } + rpcURL := (&url.URL{ + Scheme: "https", + Host: "rpc." + parts[0] + ":443", + }).String() + tmClient, err := client.NewHTTPClient(rpcURL) + if err != nil { + return fmt.Errorf("failed to instantiate tm2 client with remote %q: %w", rpcURL, err) + } + defer tmClient.Close() + + // fetch files + data, err := qfile(tmClient, pkgPath) + if err != nil { + return fmt.Errorf("failed to query files list for pkg %q: %w", pkgPath, err) + } + if err := os.MkdirAll(dst, 0744); err != nil { + return fmt.Errorf("failed to create cache dir for %q at %q: %w", pkgPath, dst, err) + } + files := strings.Split(string(data), "\n") + for _, file := range files { + filePath := path.Join(pkgPath, file) + data, err := qfile(tmClient, filePath) + if err != nil { + return fmt.Errorf("failed to query package file %q: %w", filePath, err) + } + dst := filepath.Join(dst, file) + if err := os.WriteFile(dst, data, 0644); err != nil { + return fmt.Errorf("failed to write file at %q: %w", dst, err) + } + } + + // write gno.mod + if err := os.WriteFile(modFilePath, []byte("module "+pkgPath+"\n"), 0644); err != nil { + return fmt.Errorf("failed to write modfile at %q: %w", modFilePath, err) + } + } else if err != nil { + return fmt.Errorf("failed to stat downloaded module %q at %q: %w", pkgPath, dst, err) + } + + // modfile exists in modcache, do nothing + + return nil +} + +// not using gno client due to cyclic dep +func qfile(tmClient client.Client, pkgPath string) ([]byte, error) { + path := "vm/qfile" + data := []byte(pkgPath) + + qres, err := tmClient.ABCIQuery(path, data) + if err != nil { + return nil, errors.Wrap(err, "query qfile") + } + if qres.Response.Error != nil { + return nil, errors.Wrap(qres.Response.Error, "QFile failed: log:%s", qres.Response.Log) + } + + return qres.Response.Data, nil +} diff --git a/gnovm/pkg/importer/importer.go b/gnovm/pkg/packages/importer.go similarity index 86% rename from gnovm/pkg/importer/importer.go rename to gnovm/pkg/packages/importer.go index 1ef2a79c39f..c542a1f4bf1 100644 --- a/gnovm/pkg/importer/importer.go +++ b/gnovm/pkg/packages/importer.go @@ -1,6 +1,6 @@ -// Package importer allows to match an import path to a directory, and select +// Package packages allows to match an import path to a directory, and select // a set of files within that directory which are Gno files. -package importer +package packages import ( "sync" @@ -8,7 +8,7 @@ import ( "github.com/gnolang/gno/gnovm/pkg/gnoenv" ) -// Config defines the configuration for the importer. +// Config defines the configuration for the packages. type Config struct { // RootDir should point to the directory of the gno repository. // This is used to resolve import paths of packages and realms in diff --git a/gnovm/pkg/importer/match.go b/gnovm/pkg/packages/match.go similarity index 99% rename from gnovm/pkg/importer/match.go rename to gnovm/pkg/packages/match.go index 0d0296cccd9..24d10d3b685 100644 --- a/gnovm/pkg/importer/match.go +++ b/gnovm/pkg/packages/match.go @@ -1,4 +1,4 @@ -package importer +package packages import ( "errors" diff --git a/gnovm/pkg/importer/match_test.go b/gnovm/pkg/packages/match_test.go similarity index 99% rename from gnovm/pkg/importer/match_test.go rename to gnovm/pkg/packages/match_test.go index 7d80368035d..4119ee11abb 100644 --- a/gnovm/pkg/importer/match_test.go +++ b/gnovm/pkg/packages/match_test.go @@ -1,4 +1,4 @@ -package importer +package packages import ( "fmt" diff --git a/gnovm/pkg/importer/resolve.go b/gnovm/pkg/packages/resolve.go similarity index 87% rename from gnovm/pkg/importer/resolve.go rename to gnovm/pkg/packages/resolve.go index 0c5d2909285..87cf0cdcb06 100644 --- a/gnovm/pkg/importer/resolve.go +++ b/gnovm/pkg/packages/resolve.go @@ -1,19 +1,16 @@ -package importer +package packages import ( "errors" "fmt" "go/parser" "go/token" - "net/url" "os" "path" "path/filepath" "strings" - "github.com/gnolang/gno/gno.land/pkg/gnoclient" "github.com/gnolang/gno/gnovm/pkg/gnoenv" - "github.com/gnolang/gno/tm2/pkg/bft/rpc/client" "github.com/gnolang/gno/tm2/pkg/std" "golang.org/x/mod/modfile" ) @@ -364,58 +361,6 @@ func fillPackage(meta *PackageSummary) (*Package, error) { }, nil } -func DownloadModule(pkgPath string, dst string) error { - modFilePath := filepath.Join(dst, ModfileName) - if _, err := os.Stat(modFilePath); os.IsNotExist(err) { - fmt.Fprintln(os.Stderr, "gno: downloading", pkgPath) - - // create client from pkgpath - parts := strings.Split(pkgPath, "/") - if len(parts) < 1 { - return fmt.Errorf("bad pkg path %q", pkgPath) - } - rpcURL := (&url.URL{ - Scheme: "https", - Host: "rpc." + parts[0] + ":443", - }).String() - tmClient, err := client.NewHTTPClient(rpcURL) - if err != nil { - return fmt.Errorf("failed to instantiate tm2 client with remote %q: %w", rpcURL, err) - } - client := gnoclient.Client{RPCClient: tmClient} - - // fetch files - data, _, err := client.QFile(pkgPath) - if err != nil { - return fmt.Errorf("failed to query files list for pkg %q: %w", pkgPath, err) - } - if err := os.MkdirAll(dst, 0744); err != nil { - return fmt.Errorf("failed to create cache dir for %q at %q: %w", pkgPath, dst, err) - } - files := strings.Split(string(data), "\n") - for _, file := range files { - filePath := path.Join(pkgPath, file) - data, _, err := client.QFile(filePath) - if err != nil { - return fmt.Errorf("failed to query package file %q: %w", filePath, err) - } - dst := filepath.Join(dst, file) - if err := os.WriteFile(dst, []byte(data), 0644); err != nil { - return fmt.Errorf("failed to write file at %q: %w", dst, err) - } - } - - // write gno.mod - if err := os.WriteFile(modFilePath, []byte("module "+pkgPath+"\n"), 0644); err != nil { - return fmt.Errorf("failed to write modfile at %q: %w", modFilePath, err) - } - } else if err != nil { - return fmt.Errorf("failed to stat downloaded module %q at %q: %w", pkgPath, dst, err) - } - - return nil -} - type Package struct { Dir string `json:",omitempty"` ImportPath string `json:",omitempty"` diff --git a/gnovm/pkg/transpiler/transpiler.go b/gnovm/pkg/transpiler/transpiler.go index bd4bb1b1bc9..b83ef867641 100644 --- a/gnovm/pkg/transpiler/transpiler.go +++ b/gnovm/pkg/transpiler/transpiler.go @@ -227,7 +227,7 @@ func (ctx *transpileCtx) transformCallExpr(_ *astutil.Cursor, ce *ast.CallExpr) // its replacement is a type with the method AssertOriginCall, this system // will incorrectly add a `nil` as the first argument. // A full fix requires understanding scope; the Go standard library recommends - // using go/types, which for proper functioning requires an importer + // using go/types, which for proper functioning requires an packages // which can work with Gno. This is deferred for a future PR. id, ok := fe.X.(*ast.Ident) if !ok { diff --git a/gnovm/tests/imports.go b/gnovm/tests/imports.go index 6f61b3e066c..c00bbfbf80d 100644 --- a/gnovm/tests/imports.go +++ b/gnovm/tests/imports.go @@ -38,7 +38,7 @@ import ( "unicode/utf8" gno "github.com/gnolang/gno/gnovm/pkg/gnolang" - "github.com/gnolang/gno/gnovm/pkg/importer" + "github.com/gnolang/gno/gnovm/pkg/packages" teststdlibs "github.com/gnolang/gno/gnovm/tests/stdlibs" teststd "github.com/gnolang/gno/gnovm/tests/stdlibs/std" "github.com/gnolang/gno/tm2/pkg/db/memdb" @@ -62,7 +62,7 @@ const ( ) // NOTE: this isn't safe, should only be used for testing. -func TestStore(rootDir, filesPath string, pkgs map[string]*importer.Package, stdin io.Reader, stdout, stderr io.Writer, mode importMode) (resStore gno.Store) { +func TestStore(rootDir, filesPath string, pkgs map[string]*packages.Package, stdin io.Reader, stdout, stderr io.Writer, mode importMode) (resStore gno.Store) { getPackage := func(pkgPath string, store gno.Store) (pn *gno.PackageNode, pv *gno.PackageValue) { if pkgPath == "" { panic(fmt.Sprintf("invalid zero package path in testStore().pkgGetter")) diff --git a/gnovm/tests/machine_test.go b/gnovm/tests/machine_test.go index a67d67f1ff2..0eabe096bdf 100644 --- a/gnovm/tests/machine_test.go +++ b/gnovm/tests/machine_test.go @@ -40,7 +40,7 @@ func TestMachineTestMemPackage(t *testing.T) { Name: tt.name, F: func(t2 *testing.T) { //nolint:thelper rootDir := filepath.Join("..", "..") - store := TestStore(rootDir, "test", os.Stdin, os.Stdout, os.Stderr, ImportModeStdlibsOnly) + store := TestStore(rootDir, "test", nil, os.Stdin, os.Stdout, os.Stderr, ImportModeStdlibsOnly) store.SetLogStoreOps(true) m := gno.NewMachineWithOptions(gno.MachineOptions{ PkgPath: "test", diff --git a/gnovm/tests/package_test.go b/gnovm/tests/package_test.go index 8e497941c7f..ff6dd01c7c3 100644 --- a/gnovm/tests/package_test.go +++ b/gnovm/tests/package_test.go @@ -67,7 +67,7 @@ func runPackageTest(t *testing.T, dir string, path string) { stdout := os.Stdout stderr := new(bytes.Buffer) rootDir := filepath.Join("..", "..") - store := TestStore(rootDir, path, stdin, stdout, stderr, ImportModeStdlibsOnly) + store := TestStore(rootDir, path, nil, stdin, stdout, stderr, ImportModeStdlibsOnly) store.SetLogStoreOps(true) m := gno.NewMachineWithOptions(gno.MachineOptions{ PkgPath: "test", diff --git a/misc/genstd/mapping.go b/misc/genstd/mapping.go index 8b70e2f512d..f6130c17889 100644 --- a/misc/genstd/mapping.go +++ b/misc/genstd/mapping.go @@ -251,7 +251,7 @@ func resolveImport(imports []*ast.ImportSpec, ident string) string { // TODO: for simplicity, if i.Name is nil we assume the name to be == // to the last part of the import path. - // ideally, use importer to resolve package directory on user's FS and + // ideally, use packages to resolve package directory on user's FS and // resolve by parsing and reading package clause var name string if i.Name != nil { From 08fbd8441081dd97b149360fe053717315c545d5 Mon Sep 17 00:00:00 2001 From: Norman Meier Date: Fri, 11 Oct 2024 15:54:28 +0200 Subject: [PATCH 017/143] chore: lint and fmt Signed-off-by: Norman Meier --- gnovm/cmd/gno/test.go | 29 ------------ gnovm/cmd/gno/transpile.go | 18 ------- gnovm/cmd/gno/util.go | 85 ---------------------------------- gnovm/pkg/packages/download.go | 6 +-- gnovm/pkg/packages/match.go | 2 +- 5 files changed, 4 insertions(+), 136 deletions(-) diff --git a/gnovm/cmd/gno/test.go b/gnovm/cmd/gno/test.go index 6c283248e4a..893843654ac 100644 --- a/gnovm/cmd/gno/test.go +++ b/gnovm/cmd/gno/test.go @@ -391,35 +391,6 @@ func gnoTestPkg( return errs } -// attempts to determine the full gno pkg path by analyzing the directory. -func pkgPathFromRootDir(pkgPath, rootDir string) string { - abPkgPath, err := filepath.Abs(pkgPath) - if err != nil { - log.Printf("could not determine abs path: %v", err) - return "" - } - abRootDir, err := filepath.Abs(rootDir) - if err != nil { - log.Printf("could not determine abs path: %v", err) - return "" - } - abRootDir += string(filepath.Separator) - if !strings.HasPrefix(abPkgPath, abRootDir) { - return "" - } - impPath := strings.ReplaceAll(abPkgPath[len(abRootDir):], string(filepath.Separator), "/") - for _, prefix := range [...]string{ - "examples/", - "gnovm/stdlibs/", - "gnovm/tests/stdlibs/", - } { - if strings.HasPrefix(impPath, prefix) { - return impPath[len(prefix):] - } - } - return "" -} - func runTestFiles( m *gno.Machine, files *gno.FileSet, diff --git a/gnovm/cmd/gno/transpile.go b/gnovm/cmd/gno/transpile.go index 62dfcb50bb4..fd207a50ce5 100644 --- a/gnovm/cmd/gno/transpile.go +++ b/gnovm/cmd/gno/transpile.go @@ -5,7 +5,6 @@ import ( "errors" "flag" "fmt" - "go/ast" "go/scanner" "go/token" "os" @@ -313,23 +312,6 @@ func goBuildFileOrPkg(io commands.IO, fileOrPkg string, cfg *transpileCfg) error return buildTranspiledPackage(fileOrPkg, goBinary) } -// getPathsFromImportSpec returns the directory paths where the code for each -// importSpec is stored (assuming they start with [transpiler.ImportPrefix]). -func getPathsFromImportSpec(rootDir string, importSpec []*ast.ImportSpec) (dirs []string, err error) { - for _, i := range importSpec { - path, err := strconv.Unquote(i.Path.Value) - if err != nil { - return nil, err - } - if strings.HasPrefix(path, transpiler.ImportPrefix) { - res := strings.TrimPrefix(path, transpiler.ImportPrefix) - - dirs = append(dirs, rootDir+filepath.FromSlash(res)) - } - } - return -} - // buildTranspiledPackage tries to run `go build` against the transpiled .go files. // // This method is the most efficient to detect errors but requires that diff --git a/gnovm/cmd/gno/util.go b/gnovm/cmd/gno/util.go index 738ab003497..5e7ad8f3c26 100644 --- a/gnovm/cmd/gno/util.go +++ b/gnovm/cmd/gno/util.go @@ -17,36 +17,6 @@ func isFileExist(path string) bool { return err == nil } -func gnoFilesFromArgsRecursively(args []string) ([]string, error) { - var paths []string - - for _, argPath := range args { - info, err := os.Stat(argPath) - if err != nil { - return nil, fmt.Errorf("invalid file or package path: %w", err) - } - - if !info.IsDir() { - path := filepath.Join(argPath, fs.FileInfoToDirEntry(info).Name()) - if packages.IsGnoFile(path) { - paths = append(paths, ensurePathPrefix(argPath)) - } - - continue - } - - // Gather package paths from the directory - err = walkDirForGnoFiles(argPath, func(path string) { - paths = append(paths, ensurePathPrefix(path)) - }) - if err != nil { - return nil, fmt.Errorf("unable to walk dir: %w", err) - } - } - - return paths, nil -} - func gnoFilesFromArgs(args []string) ([]string, error) { var paths []string @@ -90,61 +60,6 @@ func ensurePathPrefix(path string) string { return "." + string(filepath.Separator) + path } -func walkDirForGnoFiles(root string, addPath func(path string)) error { - visited := make(map[string]struct{}) - - walkFn := func(currPath string, f fs.DirEntry, err error) error { - if err != nil { - return fmt.Errorf("%s: walk dir: %w", root, err) - } - - path := filepath.Join(currPath, f.Name()) - if f.IsDir() || !packages.IsGnoFile(path) { - return nil - } - - parentDir := filepath.Dir(currPath) - if _, found := visited[parentDir]; found { - return nil - } - - visited[parentDir] = struct{}{} - - addPath(parentDir) - - return nil - } - - return filepath.WalkDir(root, walkFn) -} - -func gnoPackagesFromArgsRecursively(args []string) ([]string, error) { - var paths []string - - for _, argPath := range args { - info, err := os.Stat(argPath) - if err != nil { - return nil, fmt.Errorf("invalid file or package path: %w", err) - } - - if !info.IsDir() { - paths = append(paths, ensurePathPrefix(argPath)) - - continue - } - - // Gather package paths from the directory - err = walkDirForGnoFiles(argPath, func(path string) { - paths = append(paths, ensurePathPrefix(path)) - }) - if err != nil { - return nil, fmt.Errorf("unable to walk dir: %w", err) - } - } - - return paths, nil -} - // targetsFromPatterns returns a list of target paths that match the patterns. // Each pattern can represent a file or a directory, and if the pattern // includes "/...", the "..." is treated as a wildcard, matching any string. diff --git a/gnovm/pkg/packages/download.go b/gnovm/pkg/packages/download.go index 040cc768e89..32a9573cb1f 100644 --- a/gnovm/pkg/packages/download.go +++ b/gnovm/pkg/packages/download.go @@ -37,7 +37,7 @@ func DownloadModule(pkgPath string, dst string) error { if err != nil { return fmt.Errorf("failed to query files list for pkg %q: %w", pkgPath, err) } - if err := os.MkdirAll(dst, 0744); err != nil { + if err := os.MkdirAll(dst, 0o744); err != nil { return fmt.Errorf("failed to create cache dir for %q at %q: %w", pkgPath, dst, err) } files := strings.Split(string(data), "\n") @@ -48,13 +48,13 @@ func DownloadModule(pkgPath string, dst string) error { return fmt.Errorf("failed to query package file %q: %w", filePath, err) } dst := filepath.Join(dst, file) - if err := os.WriteFile(dst, data, 0644); err != nil { + if err := os.WriteFile(dst, data, 0o644); err != nil { return fmt.Errorf("failed to write file at %q: %w", dst, err) } } // write gno.mod - if err := os.WriteFile(modFilePath, []byte("module "+pkgPath+"\n"), 0644); err != nil { + if err := os.WriteFile(modFilePath, []byte("module "+pkgPath+"\n"), 0o644); err != nil { return fmt.Errorf("failed to write modfile at %q: %w", modFilePath, err) } } else if err != nil { diff --git a/gnovm/pkg/packages/match.go b/gnovm/pkg/packages/match.go index 24d10d3b685..01abf9a8bcf 100644 --- a/gnovm/pkg/packages/match.go +++ b/gnovm/pkg/packages/match.go @@ -235,7 +235,7 @@ func Match(paths []string, opts ...MatchOption) ([]string, error) { if err != nil { return nil, err } - err = walkDir(c.fs, pathTrim, &statDirEntry{fi}, func(fsPath string, entry fs.DirEntry, err error) error { + err = walkDir(c.fs, pathTrim, &statDirEntry{fi}, func(fsPath string, entry fs.DirEntry, _ error) error { // BFS guarantees that we get a dir, its files, then its subdirs. if entry.IsDir() { if !re.MatchString(fsPath) { From f93a2217cb7dc4c2a03735dcd8eabc7fffbeac4f Mon Sep 17 00:00:00 2001 From: n0izn0iz Date: Fri, 11 Oct 2024 17:46:34 +0200 Subject: [PATCH 018/143] Update gnovm/pkg/packages/download.go Co-authored-by: Manfred Touron <94029+moul@users.noreply.github.com> --- gnovm/pkg/packages/download.go | 1 + 1 file changed, 1 insertion(+) diff --git a/gnovm/pkg/packages/download.go b/gnovm/pkg/packages/download.go index 32a9573cb1f..03342bb3398 100644 --- a/gnovm/pkg/packages/download.go +++ b/gnovm/pkg/packages/download.go @@ -22,6 +22,7 @@ func DownloadModule(pkgPath string, dst string) error { if len(parts) < 1 { return fmt.Errorf("bad pkg path %q", pkgPath) } + // XXX: retrieve host/port from r/sys/zones. rpcURL := (&url.URL{ Scheme: "https", Host: "rpc." + parts[0] + ":443", From 53c577017d16737715f157e80308da3ff6a9d93d Mon Sep 17 00:00:00 2001 From: Norman Meier Date: Fri, 11 Oct 2024 19:10:06 +0200 Subject: [PATCH 019/143] chore: revert merge fails Signed-off-by: Norman Meier --- gnovm/cmd/gno/test.go | 18 ------------------ gnovm/cmd/gno/util.go | 29 +++++++++++++++++++++++------ 2 files changed, 23 insertions(+), 24 deletions(-) diff --git a/gnovm/cmd/gno/test.go b/gnovm/cmd/gno/test.go index 893843654ac..3ad639fefbc 100644 --- a/gnovm/cmd/gno/test.go +++ b/gnovm/cmd/gno/test.go @@ -592,21 +592,3 @@ func shouldRun(filter filterMatch, path string) bool { ok, _ := filter.matches(elem, matchString) return ok } - -// Adapted from https://yourbasic.org/golang/formatting-byte-size-to-human-readable-format/ -func prettySize(nb int64) string { - const unit = 1000 - if nb < unit { - return fmt.Sprintf("%d", nb) - } - div, exp := int64(unit), 0 - for n := nb / unit; n >= unit; n /= unit { - div *= unit - exp++ - } - return fmt.Sprintf("%.1f%c", float64(nb)/float64(div), "kMGTPE"[exp]) -} - -func fmtDuration(d time.Duration) string { - return fmt.Sprintf("%.2fs", d.Seconds()) -} diff --git a/gnovm/cmd/gno/util.go b/gnovm/cmd/gno/util.go index 5e7ad8f3c26..43adf057d69 100644 --- a/gnovm/cmd/gno/util.go +++ b/gnovm/cmd/gno/util.go @@ -8,6 +8,7 @@ import ( "path/filepath" "regexp" "strings" + "time" "github.com/gnolang/gno/gnovm/pkg/packages" ) @@ -27,8 +28,7 @@ func gnoFilesFromArgs(args []string) ([]string, error) { } if !info.IsDir() { - path := filepath.Join(argPath, fs.FileInfoToDirEntry(info).Name()) - if packages.IsGnoFile(path) { + if packages.IsGnoFile(info.Name()) { paths = append(paths, ensurePathPrefix(argPath)) } continue @@ -39,8 +39,8 @@ func gnoFilesFromArgs(args []string) ([]string, error) { return nil, err } for _, f := range files { - path := filepath.Join(argPath, f.Name()) - if packages.IsGnoFile(path) { + if packages.IsGnoFile(f.Name()) { + path := filepath.Join(argPath, f.Name()) paths = append(paths, ensurePathPrefix(path)) } } @@ -101,8 +101,7 @@ func targetsFromPatterns(patterns []string) ([]string, error) { return fmt.Errorf("%s: walk dir: %w", dirToSearch, err) } // Skip directories and non ".gno" files. - path := filepath.Join(dirToSearch, curpath) - if f.IsDir() || !packages.IsGnoFile(path) { + if f.IsDir() || !packages.IsGnoFile(f.Name()) { return nil } @@ -144,6 +143,10 @@ func matchPattern(pattern string) func(name string) bool { } } +func fmtDuration(d time.Duration) string { + return fmt.Sprintf("%.2fs", d.Seconds()) +} + // ResolvePath determines the path where to place output files. // output is the output directory provided by the user. // dstPath is the desired output path by the gno program. @@ -249,3 +252,17 @@ func copyFile(src, dst string) error { return nil } + +// Adapted from https://yourbasic.org/golang/formatting-byte-size-to-human-readable-format/ +func prettySize(nb int64) string { + const unit = 1000 + if nb < unit { + return fmt.Sprintf("%d", nb) + } + div, exp := int64(unit), 0 + for n := nb / unit; n >= unit; n /= unit { + div *= unit + exp++ + } + return fmt.Sprintf("%.1f%c", float64(nb)/float64(div), "kMGTPE"[exp]) +} From 457b0c9f4104ec67bb4beebd1ef7e503523b603c Mon Sep 17 00:00:00 2001 From: Norman Meier Date: Fri, 11 Oct 2024 19:16:39 +0200 Subject: [PATCH 020/143] chore: add package and module struct fields comments Signed-off-by: Norman Meier --- gnovm/pkg/packages/resolve.go | 23 ----------------------- gnovm/pkg/packages/types.go | 26 ++++++++++++++++++++++++++ 2 files changed, 26 insertions(+), 23 deletions(-) create mode 100644 gnovm/pkg/packages/types.go diff --git a/gnovm/pkg/packages/resolve.go b/gnovm/pkg/packages/resolve.go index 87cf0cdcb06..999bd9f27cb 100644 --- a/gnovm/pkg/packages/resolve.go +++ b/gnovm/pkg/packages/resolve.go @@ -361,23 +361,6 @@ func fillPackage(meta *PackageSummary) (*Package, error) { }, nil } -type Package struct { - Dir string `json:",omitempty"` - ImportPath string `json:",omitempty"` - Name string `json:",omitempty"` - Root string `json:",omitempty"` - Module Module `json:",omitempty"` - Match []string `json:",omitempty"` - GnoFiles []string `json:",omitempty"` - Imports []string `json:",omitempty"` - Deps []string `json:",omitempty"` - TestGnoFiles []string `json:",omitempty"` - TestImports []string `json:",omitempty"` - FiletestGnoFiles []string `json:",omitempty"` - FiletestImports []string `json:",omitempty"` - Errors []error `json:",omitempty"` -} - func (p *Package) MemPkg() (*std.MemPackage, error) { allFiles := append(p.GnoFiles, p.TestGnoFiles...) allFiles = append(allFiles, p.FiletestGnoFiles...) @@ -399,12 +382,6 @@ func (p *Package) MemPkg() (*std.MemPackage, error) { }, nil } -type Module struct { - Path string `json:",omitempty"` - Dir string `json:",omitempty"` - GnoMod string `json:",omitempty"` -} - func resolveNameAndImports(gnoFiles []string) (string, []string, error) { names := map[string]int{} imports := map[string]struct{}{} diff --git a/gnovm/pkg/packages/types.go b/gnovm/pkg/packages/types.go new file mode 100644 index 00000000000..c77914c2ce8 --- /dev/null +++ b/gnovm/pkg/packages/types.go @@ -0,0 +1,26 @@ +package packages + +// ported from https://cs.opensource.google/go/go/+/refs/tags/go1.23.2:src/cmd/go/internal/load/pkg.go +type Package struct { + Dir string `json:",omitempty"` // directory containing package sources + ImportPath string `json:",omitempty"` // import path of package in dir + Name string `json:",omitempty"` // package name + Root string `json:",omitempty"` // Go root, Go path dir, or module root dir containing this package + Module Module `json:",omitempty"` // info about package's module, if any + Match []string `json:",omitempty"` // command-line patterns matching this package + GnoFiles []string `json:",omitempty"` // .gno source files (TestGnoFiles, FiletestGnoFiles) + Imports []string `json:",omitempty"` // import paths used by this package + Deps []string `json:",omitempty"` // all (recursively) imported dependencies + TestGnoFiles []string `json:",omitempty"` // _test.gno files in package + TestImports []string `json:",omitempty"` // imports from TestGnoFiles + FiletestGnoFiles []string `json:",omitempty"` // _filetest.gno files in package + FiletestImports []string `json:",omitempty"` // imports from FiletestGnoFiles + Errors []error `json:",omitempty"` // error loading this package (not dependencies) +} + +// ported from https://cs.opensource.google/go/go/+/refs/tags/go1.23.2:src/cmd/go/internal/modinfo/info.go +type Module struct { + Path string `json:",omitempty"` // module path + Dir string `json:",omitempty"` // directory holding local copy of files, if any + GnoMod string `json:",omitempty"` // path to gno.mod file describing module, if any +} From 59a752a45d3abd0f5e8d6197b0c0a0372455b1ea Mon Sep 17 00:00:00 2001 From: Norman Meier Date: Fri, 11 Oct 2024 19:17:31 +0200 Subject: [PATCH 021/143] chore: go -> gno Signed-off-by: Norman Meier --- gnovm/pkg/packages/types.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gnovm/pkg/packages/types.go b/gnovm/pkg/packages/types.go index c77914c2ce8..8a74a36d1a2 100644 --- a/gnovm/pkg/packages/types.go +++ b/gnovm/pkg/packages/types.go @@ -5,7 +5,7 @@ type Package struct { Dir string `json:",omitempty"` // directory containing package sources ImportPath string `json:",omitempty"` // import path of package in dir Name string `json:",omitempty"` // package name - Root string `json:",omitempty"` // Go root, Go path dir, or module root dir containing this package + Root string `json:",omitempty"` // Gno root, Gno path dir, or module root dir containing this package Module Module `json:",omitempty"` // info about package's module, if any Match []string `json:",omitempty"` // command-line patterns matching this package GnoFiles []string `json:",omitempty"` // .gno source files (TestGnoFiles, FiletestGnoFiles) From e648305445aa2a10c5ed16357ef2da00560879df Mon Sep 17 00:00:00 2001 From: Norman Meier Date: Fri, 11 Oct 2024 19:22:01 +0200 Subject: [PATCH 022/143] fix: use io instead of os Signed-off-by: Norman Meier --- gnovm/cmd/gno/lint.go | 2 +- gnovm/cmd/gno/list.go | 4 ++-- gnovm/cmd/gno/test.go | 2 +- gnovm/cmd/gno/transpile.go | 2 +- gnovm/pkg/packages/download.go | 5 +++-- gnovm/pkg/packages/resolve.go | 13 +++++++------ 6 files changed, 15 insertions(+), 13 deletions(-) diff --git a/gnovm/cmd/gno/lint.go b/gnovm/cmd/gno/lint.go index e9b9735e1cc..dd0e00cbdcd 100644 --- a/gnovm/cmd/gno/lint.go +++ b/gnovm/cmd/gno/lint.go @@ -62,7 +62,7 @@ func execLint(cfg *lintCfg, args []string, io commands.IO) error { rootDir = gnoenv.RootDir() } - pkgs, err := packages.Load(args...) + pkgs, err := packages.Load(io, args...) if err != nil { return fmt.Errorf("list packages from args: %w", err) } diff --git a/gnovm/cmd/gno/list.go b/gnovm/cmd/gno/list.go index 4bc082d4864..2e9af6ca0e9 100644 --- a/gnovm/cmd/gno/list.go +++ b/gnovm/cmd/gno/list.go @@ -46,7 +46,7 @@ Packages not explicitly listed on the command line will have the DepOnly field set to true`) } -func execList(cfg *listCfg, args []string, _ commands.IO) error { +func execList(cfg *listCfg, args []string, io commands.IO) error { if len(args) < 1 { return flag.ErrHelp } @@ -65,7 +65,7 @@ func execList(cfg *listCfg, args []string, _ commands.IO) error { return nil } - pkgs, err := packages.Load(args...) + pkgs, err := packages.Load(io, args...) if err != nil { fmt.Println(err) os.Exit(1) diff --git a/gnovm/cmd/gno/test.go b/gnovm/cmd/gno/test.go index 3ad639fefbc..50f1a0e4ac3 100644 --- a/gnovm/cmd/gno/test.go +++ b/gnovm/cmd/gno/test.go @@ -159,7 +159,7 @@ func execTest(cfg *testCfg, args []string, io commands.IO) error { } // Find targets for test. - pkgs, err := packages.Load(args...) + pkgs, err := packages.Load(io, args...) if err != nil { return fmt.Errorf("list targets from patterns: %w", err) } diff --git a/gnovm/cmd/gno/transpile.go b/gnovm/cmd/gno/transpile.go index fd207a50ce5..5449a782e61 100644 --- a/gnovm/cmd/gno/transpile.go +++ b/gnovm/cmd/gno/transpile.go @@ -134,7 +134,7 @@ func execTranspile(cfg *transpileCfg, args []string, io commands.IO) error { } // transpile .gno packages and files. - pkgs, err := packages.Load(args...) + pkgs, err := packages.Load(io, args...) if err != nil { return fmt.Errorf("load pkgs: %w", err) } diff --git a/gnovm/pkg/packages/download.go b/gnovm/pkg/packages/download.go index 03342bb3398..6d8b98a1ed7 100644 --- a/gnovm/pkg/packages/download.go +++ b/gnovm/pkg/packages/download.go @@ -9,13 +9,14 @@ import ( "strings" "github.com/gnolang/gno/tm2/pkg/bft/rpc/client" + "github.com/gnolang/gno/tm2/pkg/commands" "github.com/gnolang/gno/tm2/pkg/errors" ) -func DownloadModule(pkgPath string, dst string) error { +func DownloadModule(io commands.IO, pkgPath string, dst string) error { modFilePath := filepath.Join(dst, ModfileName) if _, err := os.Stat(modFilePath); os.IsNotExist(err) { - fmt.Fprintln(os.Stderr, "gno: downloading", pkgPath) + io.ErrPrintfln("gno: downloading %s", pkgPath) // create client from pkgpath parts := strings.Split(pkgPath, "/") diff --git a/gnovm/pkg/packages/resolve.go b/gnovm/pkg/packages/resolve.go index 999bd9f27cb..845dc112240 100644 --- a/gnovm/pkg/packages/resolve.go +++ b/gnovm/pkg/packages/resolve.go @@ -11,6 +11,7 @@ import ( "strings" "github.com/gnolang/gno/gnovm/pkg/gnoenv" + "github.com/gnolang/gno/tm2/pkg/commands" "github.com/gnolang/gno/tm2/pkg/std" "golang.org/x/mod/modfile" ) @@ -151,7 +152,7 @@ type PackageSummary struct { } // FIXME: support files -func Load(paths ...string) ([]*Package, error) { +func Load(io commands.IO, paths ...string) ([]*Package, error) { pkgs, err := DiscoverPackages(paths...) if err != nil { return nil, fmt.Errorf("failed to list packages: %w", err) @@ -168,7 +169,7 @@ func Load(paths ...string) ([]*Package, error) { } visited[pkgSum.PkgPath] = struct{}{} - pkg, err := resolvePackage(pkgSum) + pkg, err := resolvePackage(io, pkgSum) if err != nil { pkg = &Package{ ImportPath: pkgSum.PkgPath, @@ -231,12 +232,12 @@ func listDepsRecursive(rootTarget string, target string, pkgs map[string]*Packag return errors.Join(errs...) } -func resolvePackage(meta *PackageSummary) (*Package, error) { +func resolvePackage(io commands.IO, meta *PackageSummary) (*Package, error) { if meta.Root == "" { if !strings.ContainsRune(meta.PkgPath, '.') { return resolveStdlib(meta) } else { - return resolveRemote(meta) + return resolveRemote(io, meta) } } @@ -259,12 +260,12 @@ func resolveStdlib(ometa *PackageSummary) (*Package, error) { } // Does not fill deps -func resolveRemote(ometa *PackageSummary) (*Package, error) { +func resolveRemote(io commands.IO, ometa *PackageSummary) (*Package, error) { meta := *ometa modCache := filepath.Join(gnoenv.HomeDir(), "pkg", "mod") meta.Root = filepath.Join(modCache, meta.PkgPath) - if err := DownloadModule(meta.PkgPath, meta.Root); err != nil { + if err := DownloadModule(io, meta.PkgPath, meta.Root); err != nil { return nil, fmt.Errorf("failed to download module %q: %w", meta.PkgPath, err) } modDir, err := findModDir(meta.Root) From 0123512cd20db2806be130955573dae78342ef0e Mon Sep 17 00:00:00 2001 From: Norman Meier Date: Fri, 11 Oct 2024 19:25:36 +0200 Subject: [PATCH 023/143] chore: explicit args Signed-off-by: Norman Meier --- gnovm/cmd/gno/run.go | 3 ++- gnovm/pkg/gnolang/debugger_test.go | 4 +++- gnovm/pkg/repl/repl.go | 4 +++- gnovm/tests/file.go | 4 +++- gnovm/tests/machine_test.go | 4 +++- gnovm/tests/package_test.go | 4 +++- 6 files changed, 17 insertions(+), 6 deletions(-) diff --git a/gnovm/cmd/gno/run.go b/gnovm/cmd/gno/run.go index 5d57d244e17..17bfaac229a 100644 --- a/gnovm/cmd/gno/run.go +++ b/gnovm/cmd/gno/run.go @@ -90,8 +90,9 @@ func execRun(cfg *runCfg, args []string, io commands.IO) error { stderr := io.Err() // init store and machine + pkgsMap := map[string]*packages.Package{} testStore := tests.TestStore(cfg.rootDir, - "", nil, stdin, stdout, stderr, + "", pkgsMap, stdin, stdout, stderr, tests.ImportModeStdlibsPreferred) if cfg.verbose { testStore.SetLogStoreOps(true) diff --git a/gnovm/pkg/gnolang/debugger_test.go b/gnovm/pkg/gnolang/debugger_test.go index bd9ef347447..5e5b5a89db8 100644 --- a/gnovm/pkg/gnolang/debugger_test.go +++ b/gnovm/pkg/gnolang/debugger_test.go @@ -12,6 +12,7 @@ import ( "github.com/gnolang/gno/gnovm/pkg/gnoenv" "github.com/gnolang/gno/gnovm/pkg/gnolang" + "github.com/gnolang/gno/gnovm/pkg/packages" "github.com/gnolang/gno/gnovm/tests" ) @@ -44,7 +45,8 @@ func evalTest(debugAddr, in, file string) (out, err, stacktrace string) { err = strings.TrimSpace(strings.ReplaceAll(err, "../../tests/files/", "files/")) }() - testStore := tests.TestStore(gnoenv.RootDir(), "../../tests/files", nil, stdin, stdout, stderr, mode) + pkgsMap := map[string]*packages.Package{} + testStore := tests.TestStore(gnoenv.RootDir(), "../../tests/files", pkgsMap, stdin, stdout, stderr, mode) f := gnolang.MustReadFile(file) diff --git a/gnovm/pkg/repl/repl.go b/gnovm/pkg/repl/repl.go index c55b9d47fdf..b09555372c9 100644 --- a/gnovm/pkg/repl/repl.go +++ b/gnovm/pkg/repl/repl.go @@ -14,6 +14,7 @@ import ( "text/template" gno "github.com/gnolang/gno/gnovm/pkg/gnolang" + "github.com/gnolang/gno/gnovm/pkg/packages" "github.com/gnolang/gno/gnovm/tests" ) @@ -124,7 +125,8 @@ func NewRepl(opts ...ReplOption) *Repl { r.stderr = &b r.storeFunc = func() gno.Store { - return tests.TestStore("teststore", "", nil, r.stdin, r.stdout, r.stderr, tests.ImportModeStdlibsOnly) + pkgsMap := map[string]*packages.Package{} + return tests.TestStore("teststore", "", pkgsMap, r.stdin, r.stdout, r.stderr, tests.ImportModeStdlibsOnly) } for _, o := range opts { diff --git a/gnovm/tests/file.go b/gnovm/tests/file.go index 1e53f80e5d0..c966a0f6fad 100644 --- a/gnovm/tests/file.go +++ b/gnovm/tests/file.go @@ -15,6 +15,7 @@ import ( "github.com/gnolang/gno/gno.land/pkg/gnoland/ugnot" gno "github.com/gnolang/gno/gnovm/pkg/gnolang" + "github.com/gnolang/gno/gnovm/pkg/packages" "github.com/gnolang/gno/gnovm/stdlibs" teststd "github.com/gnolang/gno/gnovm/tests/stdlibs/std" "github.com/gnolang/gno/tm2/pkg/crypto" @@ -122,7 +123,8 @@ func RunFileTest(rootDir string, path string, opts ...RunFileTestOption) error { if f.nativeLibs { mode = ImportModeNativePreferred } - store := TestStore(rootDir, "./files", nil, stdin, stdout, stderr, mode) + pkgsMap := map[string]*packages.Package{} + store := TestStore(rootDir, "./files", pkgsMap, stdin, stdout, stderr, mode) store.SetLogStoreOps(true) m := testMachineCustom(store, pkgPath, stdout, maxAlloc, send) checkMachineIsEmpty := true diff --git a/gnovm/tests/machine_test.go b/gnovm/tests/machine_test.go index 0eabe096bdf..90d4b43c327 100644 --- a/gnovm/tests/machine_test.go +++ b/gnovm/tests/machine_test.go @@ -8,6 +8,7 @@ import ( "github.com/stretchr/testify/assert" gno "github.com/gnolang/gno/gnovm/pkg/gnolang" + "github.com/gnolang/gno/gnovm/pkg/packages" ) func TestMachineTestMemPackage(t *testing.T) { @@ -40,7 +41,8 @@ func TestMachineTestMemPackage(t *testing.T) { Name: tt.name, F: func(t2 *testing.T) { //nolint:thelper rootDir := filepath.Join("..", "..") - store := TestStore(rootDir, "test", nil, os.Stdin, os.Stdout, os.Stderr, ImportModeStdlibsOnly) + pkgsMap := map[string]*packages.Package{} + store := TestStore(rootDir, "test", pkgsMap, os.Stdin, os.Stdout, os.Stderr, ImportModeStdlibsOnly) store.SetLogStoreOps(true) m := gno.NewMachineWithOptions(gno.MachineOptions{ PkgPath: "test", diff --git a/gnovm/tests/package_test.go b/gnovm/tests/package_test.go index ff6dd01c7c3..1b562c3eb76 100644 --- a/gnovm/tests/package_test.go +++ b/gnovm/tests/package_test.go @@ -13,6 +13,7 @@ import ( "github.com/stretchr/testify/require" gno "github.com/gnolang/gno/gnovm/pkg/gnolang" + "github.com/gnolang/gno/gnovm/pkg/packages" ) func TestStdlibs(t *testing.T) { @@ -67,7 +68,8 @@ func runPackageTest(t *testing.T, dir string, path string) { stdout := os.Stdout stderr := new(bytes.Buffer) rootDir := filepath.Join("..", "..") - store := TestStore(rootDir, path, nil, stdin, stdout, stderr, ImportModeStdlibsOnly) + pkgsMap := map[string]*packages.Package{} + store := TestStore(rootDir, path, pkgsMap, stdin, stdout, stderr, ImportModeStdlibsOnly) store.SetLogStoreOps(true) m := gno.NewMachineWithOptions(gno.MachineOptions{ PkgPath: "test", From d266b8e4d0a476837c901964b35a376d3689e9a0 Mon Sep 17 00:00:00 2001 From: Norman Meier Date: Fri, 11 Oct 2024 21:20:49 +0200 Subject: [PATCH 024/143] chore: improve comment Signed-off-by: Norman Meier --- gnovm/pkg/packages/types.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gnovm/pkg/packages/types.go b/gnovm/pkg/packages/types.go index 8a74a36d1a2..6ed38c50ea3 100644 --- a/gnovm/pkg/packages/types.go +++ b/gnovm/pkg/packages/types.go @@ -8,7 +8,7 @@ type Package struct { Root string `json:",omitempty"` // Gno root, Gno path dir, or module root dir containing this package Module Module `json:",omitempty"` // info about package's module, if any Match []string `json:",omitempty"` // command-line patterns matching this package - GnoFiles []string `json:",omitempty"` // .gno source files (TestGnoFiles, FiletestGnoFiles) + GnoFiles []string `json:",omitempty"` // .gno source files (excluding TestGnoFiles, FiletestGnoFiles) Imports []string `json:",omitempty"` // import paths used by this package Deps []string `json:",omitempty"` // all (recursively) imported dependencies TestGnoFiles []string `json:",omitempty"` // _test.gno files in package From ac435c2594958caf31babb3309198934c93902bf Mon Sep 17 00:00:00 2001 From: Norman Meier Date: Mon, 21 Oct 2024 18:19:23 +0200 Subject: [PATCH 025/143] fix: break import cycle Signed-off-by: Norman Meier --- gnovm/cmd/gno/run.go | 3 +- gnovm/cmd/gno/test.go | 3 +- gnovm/cmd/gno/transpile.go | 4 +- gnovm/cmd/gno/util.go | 8 +-- .../definitions.go => gnofiles/gnofiles.go} | 2 +- gnovm/pkg/{packages => gnofiles}/match.go | 2 +- .../pkg/{packages => gnofiles}/match_test.go | 2 +- gnovm/pkg/gnomod/gnomod.go | 4 +- gnovm/pkg/packages/download.go | 3 +- gnovm/pkg/packages/resolve.go | 55 +++++++++---------- 10 files changed, 42 insertions(+), 44 deletions(-) rename gnovm/pkg/{packages/definitions.go => gnofiles/gnofiles.go} (98%) rename gnovm/pkg/{packages => gnofiles}/match.go (99%) rename gnovm/pkg/{packages => gnofiles}/match_test.go (99%) diff --git a/gnovm/cmd/gno/run.go b/gnovm/cmd/gno/run.go index 17bfaac229a..5c3d8869566 100644 --- a/gnovm/cmd/gno/run.go +++ b/gnovm/cmd/gno/run.go @@ -8,6 +8,7 @@ import ( "io" "github.com/gnolang/gno/gnovm/pkg/gnoenv" + "github.com/gnolang/gno/gnovm/pkg/gnofiles" gno "github.com/gnolang/gno/gnovm/pkg/gnolang" "github.com/gnolang/gno/gnovm/pkg/packages" "github.com/gnolang/gno/gnovm/tests" @@ -141,7 +142,7 @@ func execRun(cfg *runCfg, args []string, io commands.IO) error { } func parseFiles(fnames []string, stderr io.WriteCloser) ([]*gno.FileNode, error) { - gnoFnames, err := packages.Match(fnames, packages.MatchFiles("!*_test.gno", "!*_filetest.gno")) + gnoFnames, err := gnofiles.Match(fnames, gnofiles.MatchFiles("!*_test.gno", "!*_filetest.gno")) if err != nil { return nil, err } diff --git a/gnovm/cmd/gno/test.go b/gnovm/cmd/gno/test.go index 50f1a0e4ac3..ecca19bb4d3 100644 --- a/gnovm/cmd/gno/test.go +++ b/gnovm/cmd/gno/test.go @@ -17,6 +17,7 @@ import ( "go.uber.org/multierr" "github.com/gnolang/gno/gnovm/pkg/gnoenv" + "github.com/gnolang/gno/gnovm/pkg/gnofiles" gno "github.com/gnolang/gno/gnovm/pkg/gnolang" "github.com/gnolang/gno/gnovm/pkg/packages" "github.com/gnolang/gno/gnovm/tests" @@ -556,7 +557,7 @@ func parseMemPackageTests(memPkg *std.MemPackage) (tset, itset *gno.FileSet) { tset = &gno.FileSet{} itset = &gno.FileSet{} for _, mfile := range memPkg.Files { - if !packages.IsGnoFile(mfile.Name, "!*_filetest.gno") { + if !gnofiles.IsGnoFile(mfile.Name, "!*_filetest.gno") { continue } n, err := gno.ParseFile(mfile.Name, mfile.Body) diff --git a/gnovm/cmd/gno/transpile.go b/gnovm/cmd/gno/transpile.go index 5449a782e61..396a88ad6b0 100644 --- a/gnovm/cmd/gno/transpile.go +++ b/gnovm/cmd/gno/transpile.go @@ -133,17 +133,17 @@ func execTranspile(cfg *transpileCfg, args []string, io commands.IO) error { cfg.rootDir = gnoenv.RootDir() } - // transpile .gno packages and files. + // load packages pkgs, err := packages.Load(io, args...) if err != nil { return fmt.Errorf("load pkgs: %w", err) } - pkgsMap := map[string]*packages.Package{} for _, pkg := range pkgs { pkgsMap[pkg.ImportPath] = pkg } + // transpile .gno packages and files. opts := newTranspileOptions(cfg, io) var errlist scanner.ErrorList for _, pkg := range pkgs { diff --git a/gnovm/cmd/gno/util.go b/gnovm/cmd/gno/util.go index 43adf057d69..60c0017e70e 100644 --- a/gnovm/cmd/gno/util.go +++ b/gnovm/cmd/gno/util.go @@ -10,7 +10,7 @@ import ( "strings" "time" - "github.com/gnolang/gno/gnovm/pkg/packages" + "github.com/gnolang/gno/gnovm/pkg/gnofiles" ) func isFileExist(path string) bool { @@ -28,7 +28,7 @@ func gnoFilesFromArgs(args []string) ([]string, error) { } if !info.IsDir() { - if packages.IsGnoFile(info.Name()) { + if gnofiles.IsGnoFile(info.Name()) { paths = append(paths, ensurePathPrefix(argPath)) } continue @@ -39,7 +39,7 @@ func gnoFilesFromArgs(args []string) ([]string, error) { return nil, err } for _, f := range files { - if packages.IsGnoFile(f.Name()) { + if gnofiles.IsGnoFile(f.Name()) { path := filepath.Join(argPath, f.Name()) paths = append(paths, ensurePathPrefix(path)) } @@ -101,7 +101,7 @@ func targetsFromPatterns(patterns []string) ([]string, error) { return fmt.Errorf("%s: walk dir: %w", dirToSearch, err) } // Skip directories and non ".gno" files. - if f.IsDir() || !packages.IsGnoFile(f.Name()) { + if f.IsDir() || !gnofiles.IsGnoFile(f.Name()) { return nil } diff --git a/gnovm/pkg/packages/definitions.go b/gnovm/pkg/gnofiles/gnofiles.go similarity index 98% rename from gnovm/pkg/packages/definitions.go rename to gnovm/pkg/gnofiles/gnofiles.go index ad4954578a8..d6a5c45731f 100644 --- a/gnovm/pkg/packages/definitions.go +++ b/gnovm/pkg/gnofiles/gnofiles.go @@ -1,4 +1,4 @@ -package packages +package gnofiles import ( "os" diff --git a/gnovm/pkg/packages/match.go b/gnovm/pkg/gnofiles/match.go similarity index 99% rename from gnovm/pkg/packages/match.go rename to gnovm/pkg/gnofiles/match.go index 01abf9a8bcf..b655a7de589 100644 --- a/gnovm/pkg/packages/match.go +++ b/gnovm/pkg/gnofiles/match.go @@ -1,4 +1,4 @@ -package packages +package gnofiles import ( "errors" diff --git a/gnovm/pkg/packages/match_test.go b/gnovm/pkg/gnofiles/match_test.go similarity index 99% rename from gnovm/pkg/packages/match_test.go rename to gnovm/pkg/gnofiles/match_test.go index 4119ee11abb..a1adccd1d9e 100644 --- a/gnovm/pkg/packages/match_test.go +++ b/gnovm/pkg/gnofiles/match_test.go @@ -1,4 +1,4 @@ -package packages +package gnofiles import ( "fmt" diff --git a/gnovm/pkg/gnomod/gnomod.go b/gnovm/pkg/gnomod/gnomod.go index d86ee606d95..181939d6e93 100644 --- a/gnovm/pkg/gnomod/gnomod.go +++ b/gnovm/pkg/gnomod/gnomod.go @@ -10,8 +10,8 @@ import ( "strings" "github.com/gnolang/gno/gnovm/pkg/gnoenv" + "github.com/gnolang/gno/gnovm/pkg/gnofiles" "github.com/gnolang/gno/gnovm/pkg/gnolang" - "github.com/gnolang/gno/gnovm/pkg/packages" "github.com/gnolang/gno/gnovm/pkg/transpiler" "github.com/gnolang/gno/tm2/pkg/std" "golang.org/x/mod/modfile" @@ -171,7 +171,7 @@ func CreateGnoModFile(rootDir, modPath string) error { var pkgName gnolang.Name for _, file := range files { - if file.IsDir() || !packages.IsGnoFile(file.Name(), "!*_filetest.gno") { + if file.IsDir() || !gnofiles.IsGnoFile(file.Name(), "!*_filetest.gno") { continue } diff --git a/gnovm/pkg/packages/download.go b/gnovm/pkg/packages/download.go index 6d8b98a1ed7..9934002ca84 100644 --- a/gnovm/pkg/packages/download.go +++ b/gnovm/pkg/packages/download.go @@ -8,13 +8,14 @@ import ( "path/filepath" "strings" + "github.com/gnolang/gno/gnovm/pkg/gnofiles" "github.com/gnolang/gno/tm2/pkg/bft/rpc/client" "github.com/gnolang/gno/tm2/pkg/commands" "github.com/gnolang/gno/tm2/pkg/errors" ) func DownloadModule(io commands.IO, pkgPath string, dst string) error { - modFilePath := filepath.Join(dst, ModfileName) + modFilePath := filepath.Join(dst, gnofiles.ModfileName) if _, err := os.Stat(modFilePath); os.IsNotExist(err) { io.ErrPrintfln("gno: downloading %s", pkgPath) diff --git a/gnovm/pkg/packages/resolve.go b/gnovm/pkg/packages/resolve.go index 845dc112240..3e05a31cdc3 100644 --- a/gnovm/pkg/packages/resolve.go +++ b/gnovm/pkg/packages/resolve.go @@ -11,6 +11,8 @@ import ( "strings" "github.com/gnolang/gno/gnovm/pkg/gnoenv" + "github.com/gnolang/gno/gnovm/pkg/gnofiles" + "github.com/gnolang/gno/gnovm/pkg/gnomod" "github.com/gnolang/gno/tm2/pkg/commands" "github.com/gnolang/gno/tm2/pkg/std" "golang.org/x/mod/modfile" @@ -77,16 +79,13 @@ func DiscoverPackages(paths ...string) ([]*PackageSummary, error) { } else if err != nil { return nil, fmt.Errorf("failed to find parent module: %w", err) } - modFilePath := filepath.Join(modDir, ModfileName) - modFileBytes, err := os.ReadFile(modFilePath) + modFilePath := filepath.Join(modDir, gnofiles.ModfileName) + modFile, err := gnomod.ParseAt(modDir) if err != nil { - return nil, fmt.Errorf("failed to read modfile: %w", err) - } - modFile, err := modfile.ParseLax(modFilePath, modFileBytes, nil) - if err != nil { - return nil, fmt.Errorf("failed to parse modfile: %w", err) + errs = append(errs, fmt.Errorf("failed to parse modfile for %q: %w", modDir, err)) + continue } - if modFile == nil || modFile.Module == nil { + if modFile == nil || modFile.Module == nil || modFile.Draft { continue } globalPkgPath := modFile.Module.Mod.Path @@ -131,7 +130,7 @@ func DiscoverPackages(paths ...string) ([]*PackageSummary, error) { dirPath := filepath.Join(root, fileName) + recursiveSuffix toVisit = append(toVisit, visitTarget{path: dirPath, match: tgt.match}) } - if !hasGnoFiles && IsGnoFile(fileName) { + if !hasGnoFiles && gnofiles.IsGnoFile(fileName) { hasGnoFiles = true } } @@ -204,6 +203,16 @@ func Load(io commands.IO, paths ...string) ([]*Package, error) { return list, errors.Join(errs...) } +func modIsDraft(modFile *modfile.File) bool { + comments := modFile.Syntax.Comment() + for _, comm := range comments.Before { + if strings.Contains(comm.Token, "Draft") { + return true + } + } + return false +} + func listDeps(target string, pkgs map[string]*Package) ([]string, error) { deps := []string{} err := listDepsRecursive(target, target, pkgs, &deps, make(map[string]struct{})) @@ -268,24 +277,10 @@ func resolveRemote(io commands.IO, ometa *PackageSummary) (*Package, error) { if err := DownloadModule(io, meta.PkgPath, meta.Root); err != nil { return nil, fmt.Errorf("failed to download module %q: %w", meta.PkgPath, err) } - modDir, err := findModDir(meta.Root) - if os.IsNotExist(err) { - return nil, errors.New("failed to clone mod") - } else if err != nil { - return nil, fmt.Errorf("failed to find parent module: %w", err) - } - modFilePath := filepath.Join(modDir, ModfileName) - modFileBytes, err := os.ReadFile(modFilePath) - if err != nil { - return nil, fmt.Errorf("failed to read modfile: %w", err) - } - modFile, err := modfile.ParseLax(modFilePath, modFileBytes, nil) - if err != nil { - return nil, fmt.Errorf("failed to parse modfile: %w", err) - } + modFilePath := filepath.Join(meta.Root, gnofiles.ModfileName) meta.Module = &Module{ - Path: modFile.Module.Mod.Path, - Dir: modDir, + Path: meta.PkgPath, + Dir: meta.Root, GnoMod: modFilePath, } @@ -318,13 +313,13 @@ func fillPackage(meta *PackageSummary) (*Package, error) { } fileName := entry.Name() - if IsGnoTestFile(fileName) { + if gnofiles.IsGnoTestFile(fileName) { fsTestFiles = append(fsTestFiles, filepath.Join(pkgDir, fileName)) testFiles = append(testFiles, fileName) - } else if IsGnoFiletestFile(fileName) { + } else if gnofiles.IsGnoFiletestFile(fileName) { fsFiletestFiles = append(fsFiletestFiles, filepath.Join(pkgDir, fileName)) filetestFiles = append(filetestFiles, fileName) - } else if IsGnoFile(fileName) { + } else if gnofiles.IsGnoFile(fileName) { fsFiles = append(fsFiles, filepath.Join(pkgDir, fileName)) files = append(files, fileName) } @@ -443,7 +438,7 @@ func convertRecursivePathToDir(p string) string { func findModDir(dir string) (string, error) { dir = filepath.Clean(dir) - potentialMod := filepath.Join(dir, ModfileName) + potentialMod := filepath.Join(dir, gnofiles.ModfileName) if _, err := os.Stat(potentialMod); os.IsNotExist(err) { parent, file := filepath.Split(dir) From 9fbe83c823bcc153f3fbcb3bc0e2a75f127dc67e Mon Sep 17 00:00:00 2001 From: Norman Meier Date: Tue, 22 Oct 2024 15:23:00 +0200 Subject: [PATCH 026/143] chore: linter Signed-off-by: Norman Meier --- gnovm/pkg/gnofiles/match.go | 1 + gnovm/pkg/packages/resolve.go | 16 +--------------- 2 files changed, 2 insertions(+), 15 deletions(-) diff --git a/gnovm/pkg/gnofiles/match.go b/gnovm/pkg/gnofiles/match.go index b655a7de589..f95b0274011 100644 --- a/gnovm/pkg/gnofiles/match.go +++ b/gnovm/pkg/gnofiles/match.go @@ -55,6 +55,7 @@ func MatchPatterns(s string, patterns ...string) (bool, error) { var err error if len(pattern) > 1 && pattern[0] == '/' && pattern[len(pattern)-1] == '/' { pattern = pattern[1 : len(pattern)-1] + //nolint:forbidigo res, err = regexp.Match(pattern, bs) } else { res, err = path.Match(pattern, path.Base(s)) diff --git a/gnovm/pkg/packages/resolve.go b/gnovm/pkg/packages/resolve.go index 3e05a31cdc3..a247c5ec265 100644 --- a/gnovm/pkg/packages/resolve.go +++ b/gnovm/pkg/packages/resolve.go @@ -15,7 +15,6 @@ import ( "github.com/gnolang/gno/gnovm/pkg/gnomod" "github.com/gnolang/gno/tm2/pkg/commands" "github.com/gnolang/gno/tm2/pkg/std" - "golang.org/x/mod/modfile" ) const recursiveSuffix = string(os.PathSeparator) + "..." @@ -203,16 +202,6 @@ func Load(io commands.IO, paths ...string) ([]*Package, error) { return list, errors.Join(errs...) } -func modIsDraft(modFile *modfile.File) bool { - comments := modFile.Syntax.Comment() - for _, comm := range comments.Before { - if strings.Contains(comm.Token, "Draft") { - return true - } - } - return false -} - func listDeps(target string, pkgs map[string]*Package) ([]string, error) { deps := []string{} err := listDepsRecursive(target, target, pkgs, &deps, make(map[string]struct{})) @@ -259,10 +248,7 @@ func resolvePackage(io commands.IO, meta *PackageSummary) (*Package, error) { func resolveStdlib(ometa *PackageSummary) (*Package, error) { meta := *ometa - gnoRoot, err := gnoenv.GuessRootDir() - if err != nil { - return nil, fmt.Errorf("failed to guess gno root dir: %w", err) - } + gnoRoot := defaultConfig().RootDir parts := strings.Split(meta.PkgPath, "/") meta.Root = filepath.Join(append([]string{gnoRoot, "gnovm", "stdlibs"}, parts...)...) return fillPackage(&meta) From d78fb9066c99496d37132313bab723dac718d07f Mon Sep 17 00:00:00 2001 From: Norman Meier Date: Tue, 22 Oct 2024 15:25:08 +0200 Subject: [PATCH 027/143] chore: reuse helper Signed-off-by: Norman Meier --- gnovm/pkg/gnofiles/gnofiles.go | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/gnovm/pkg/gnofiles/gnofiles.go b/gnovm/pkg/gnofiles/gnofiles.go index d6a5c45731f..65a5ce2ba9c 100644 --- a/gnovm/pkg/gnofiles/gnofiles.go +++ b/gnovm/pkg/gnofiles/gnofiles.go @@ -2,7 +2,6 @@ package gnofiles import ( "os" - "strings" ) // This file contains "definitions"; it attempts to centralize some common @@ -41,15 +40,9 @@ func IsGnoFile(name string, patterns ...string) bool { } func IsGnoTestFile(p string) bool { - if !IsGnoFile(p) { - return false - } - return strings.HasSuffix(p, "_test.gno") + return IsGnoFile(p, "*_test.gno") } func IsGnoFiletestFile(p string) bool { - if !IsGnoFile(p) { - return false - } - return strings.HasSuffix(p, "_filetest.gno") + return IsGnoFile(p, "*_filetest.gno") } From 6f2330e20e58dd547acac5753329081c7546a1b5 Mon Sep 17 00:00:00 2001 From: Norman Meier Date: Mon, 28 Oct 2024 17:07:42 +0100 Subject: [PATCH 028/143] tmp: use gno.work and add back mod files Signed-off-by: Norman Meier --- examples/Makefile | 16 ++-- examples/gno.land/gno.mod | 1 - examples/gno.land/p/archives/bank/gno.mod | 1 + examples/gno.land/p/demo/acl/gno.mod | 8 ++ examples/gno.land/p/demo/avl/gno.mod | 1 + examples/gno.land/p/demo/avlhelpers/gno.mod | 3 + examples/gno.land/p/demo/bf/gno.mod | 1 + examples/gno.land/p/demo/blog/gno.mod | 7 ++ examples/gno.land/p/demo/cford32/gno.mod | 1 + examples/gno.land/p/demo/context/gno.mod | 1 + examples/gno.land/p/demo/diff/gno.mod | 1 + examples/gno.land/p/demo/dom/gno.mod | 3 + examples/gno.land/p/demo/entropy/gno.mod | 1 + examples/gno.land/p/demo/flow/gno.mod | 1 + examples/gno.land/p/demo/fqname/gno.mod | 3 + examples/gno.land/p/demo/gnode/gno.mod | 1 + .../gno.land/p/demo/gnorkle/agent/gno.mod | 6 ++ examples/gno.land/p/demo/gnorkle/feed/gno.mod | 1 + .../p/demo/gnorkle/feeds/static/gno.mod | 13 ++++ .../gno.land/p/demo/gnorkle/gnorkle/gno.mod | 9 +++ .../gno.land/p/demo/gnorkle/ingester/gno.mod | 1 + .../p/demo/gnorkle/ingesters/single/gno.mod | 8 ++ .../gno.land/p/demo/gnorkle/message/gno.mod | 3 + .../gno.land/p/demo/gnorkle/storage/gno.mod | 1 + .../p/demo/gnorkle/storage/simple/gno.mod | 9 +++ examples/gno.land/p/demo/grc/exts/gno.mod | 1 + examples/gno.land/p/demo/grc/grc1155/gno.mod | 7 ++ examples/gno.land/p/demo/grc/grc20/gno.mod | 9 +++ examples/gno.land/p/demo/grc/grc721/gno.mod | 8 ++ examples/gno.land/p/demo/grc/grc777/gno.mod | 3 + examples/gno.land/p/demo/groups/gno.mod | 6 ++ examples/gno.land/p/demo/int256/gno.mod | 3 + .../gno.land/p/demo/json/eisel_lemire/gno.mod | 1 + examples/gno.land/p/demo/json/gno.mod | 7 ++ examples/gno.land/p/demo/json/ryu/gno.mod | 1 + .../gno.land/p/demo/math_eval/int32/gno.mod | 3 + examples/gno.land/p/demo/memeland/gno.mod | 10 +++ examples/gno.land/p/demo/merkle/gno.mod | 1 + examples/gno.land/p/demo/microblog/gno.mod | 6 ++ examples/gno.land/p/demo/mux/gno.mod | 1 + examples/gno.land/p/demo/nestedpkg/gno.mod | 1 + .../p/demo/ownable/exts/authorizable/gno.mod | 9 +++ examples/gno.land/p/demo/ownable/gno.mod | 6 ++ examples/gno.land/p/demo/pausable/gno.mod | 6 ++ examples/gno.land/p/demo/rat/gno.mod | 1 + examples/gno.land/p/demo/releases/gno.mod | 1 + examples/gno.land/p/demo/seqid/gno.mod | 3 + examples/gno.land/p/demo/stack/gno.mod | 1 + examples/gno.land/p/demo/subscription/gno.mod | 1 + .../p/demo/subscription/lifetime/gno.mod | 8 ++ .../p/demo/subscription/recurring/gno.mod | 8 ++ examples/gno.land/p/demo/svg/gno.mod | 3 + examples/gno.land/p/demo/tamagotchi/gno.mod | 3 + examples/gno.land/p/demo/tests/gno.mod | 7 ++ .../p/demo/tests/p_crossrealm/gno.mod | 1 + .../gno.land/p/demo/tests/subtests/gno.mod | 4 + examples/gno.land/p/demo/testutils/gno.mod | 1 + examples/gno.land/p/demo/todolist/gno.mod | 6 ++ examples/gno.land/p/demo/uassert/gno.mod | 3 + examples/gno.land/p/demo/ufmt/gno.mod | 1 + examples/gno.land/p/demo/ui/gno.mod | 1 + examples/gno.land/p/demo/uint256/gno.mod | 1 + examples/gno.land/p/demo/urequire/gno.mod | 3 + examples/gno.land/p/demo/users/gno.mod | 1 + examples/gno.land/p/demo/watchdog/gno.mod | 3 + examples/gno.land/p/gov/proposal/gno.mod | 7 ++ .../gno.land/p/moul/printfdebugging/gno.mod | 3 + examples/gno.land/p/nt/poa/gno.mod | 10 +++ examples/gno.land/p/sys/validators/gno.mod | 1 + examples/gno.land/r/demo/art/gnoface/gno.mod | 7 ++ .../gno.land/r/demo/art/millipede/gno.mod | 6 ++ examples/gno.land/r/demo/banktest/gno.mod | 1 + examples/gno.land/r/demo/bar20/gno.mod | 8 ++ examples/gno.land/r/demo/boards/gno.mod | 6 ++ examples/gno.land/r/demo/counter/gno.mod | 1 + .../gno.land/r/demo/deep/very/deep/gno.mod | 1 + examples/gno.land/r/demo/disperse/gno.mod | 3 + examples/gno.land/r/demo/echo/gno.mod | 3 + examples/gno.land/r/demo/event/gno.mod | 1 + examples/gno.land/r/demo/foo1155/gno.mod | 8 ++ examples/gno.land/r/demo/foo20/gno.mod | 11 +++ examples/gno.land/r/demo/foo721/gno.mod | 8 ++ .../gno.land/r/demo/games/dice_roller/gno.mod | 11 +++ .../gno.land/r/demo/games/shifumi/gno.mod | 7 ++ examples/gno.land/r/demo/grc20factory/gno.mod | 9 +++ examples/gno.land/r/demo/groups/gno.mod | 6 ++ examples/gno.land/r/demo/keystore/gno.mod | 8 ++ .../gno.land/r/demo/markdown_test/gno.mod | 1 + examples/gno.land/r/demo/math_eval/gno.mod | 6 ++ examples/gno.land/r/demo/memeland/gno.mod | 3 + examples/gno.land/r/demo/microblog/gno.mod | 9 +++ examples/gno.land/r/demo/nft/gno.mod | 6 ++ examples/gno.land/r/demo/profile/gno.mod | 9 +++ .../gno.land/r/demo/releases_example/gno.mod | 3 + examples/gno.land/r/demo/tamagotchi/gno.mod | 6 ++ .../gno.land/r/demo/tests/crossrealm/gno.mod | 6 ++ examples/gno.land/r/demo/tests/gno.mod | 6 ++ .../gno.land/r/demo/tests/subtests/gno.mod | 4 + examples/gno.land/r/demo/tests_foo/gno.mod | 3 + examples/gno.land/r/demo/todolist/gno.mod | 9 +++ examples/gno.land/r/demo/types/gno.mod | 3 + examples/gno.land/r/demo/ui/gno.mod | 6 ++ examples/gno.land/r/demo/userbook/gno.mod | 8 ++ examples/gno.land/r/demo/users/gno.mod | 8 ++ examples/gno.land/r/demo/wugnot/gno.mod | 8 ++ examples/gno.land/r/gnoland/blog/gno.mod | 8 ++ examples/gno.land/r/gnoland/events/gno.mod | 9 +++ examples/gno.land/r/gnoland/faucet/gno.mod | 7 ++ examples/gno.land/r/gnoland/ghverify/gno.mod | 9 +++ examples/gno.land/r/gnoland/home/gno.mod | 9 +++ examples/gno.land/r/gnoland/monit/gno.mod | 8 ++ examples/gno.land/r/gnoland/pages/gno.mod | 6 ++ examples/gno.land/r/gnoland/valopers/gno.mod | 11 +++ examples/gno.land/r/gov/dao/gno.mod | 8 ++ examples/gno.land/r/leon/config/gno.mod | 1 + examples/gno.land/r/leon/home/gno.mod | 8 ++ examples/gno.land/r/manfred/config/gno.mod | 1 + examples/gno.land/r/manfred/home/gno.mod | 3 + examples/gno.land/r/manfred/present/gno.mod | 6 ++ examples/gno.land/r/morgan/guestbook/gno.mod | 7 ++ examples/gno.land/r/morgan/home/gno.mod | 1 + examples/gno.land/r/sys/rewards/gno.mod | 1 + examples/gno.land/r/sys/users/gno.mod | 6 ++ examples/gno.land/r/sys/validators/gno.mod | 12 +++ .../r/x/manfred_upgrade_patterns/gno.mod | 3 + examples/gno.work | 0 gnovm/pkg/gnofiles/gnofiles.go | 30 ++++++++ gnovm/pkg/packages/patterns.go | 41 +++++++++++ gnovm/pkg/packages/resolve.go | 73 ++++++++++++++----- 129 files changed, 721 insertions(+), 28 deletions(-) delete mode 100644 examples/gno.land/gno.mod create mode 100644 examples/gno.land/p/archives/bank/gno.mod create mode 100644 examples/gno.land/p/demo/acl/gno.mod create mode 100644 examples/gno.land/p/demo/avl/gno.mod create mode 100644 examples/gno.land/p/demo/avlhelpers/gno.mod create mode 100644 examples/gno.land/p/demo/bf/gno.mod create mode 100644 examples/gno.land/p/demo/blog/gno.mod create mode 100644 examples/gno.land/p/demo/cford32/gno.mod create mode 100644 examples/gno.land/p/demo/context/gno.mod create mode 100644 examples/gno.land/p/demo/diff/gno.mod create mode 100644 examples/gno.land/p/demo/dom/gno.mod create mode 100644 examples/gno.land/p/demo/entropy/gno.mod create mode 100644 examples/gno.land/p/demo/flow/gno.mod create mode 100644 examples/gno.land/p/demo/fqname/gno.mod create mode 100644 examples/gno.land/p/demo/gnode/gno.mod create mode 100644 examples/gno.land/p/demo/gnorkle/agent/gno.mod create mode 100644 examples/gno.land/p/demo/gnorkle/feed/gno.mod create mode 100644 examples/gno.land/p/demo/gnorkle/feeds/static/gno.mod create mode 100644 examples/gno.land/p/demo/gnorkle/gnorkle/gno.mod create mode 100644 examples/gno.land/p/demo/gnorkle/ingester/gno.mod create mode 100644 examples/gno.land/p/demo/gnorkle/ingesters/single/gno.mod create mode 100644 examples/gno.land/p/demo/gnorkle/message/gno.mod create mode 100644 examples/gno.land/p/demo/gnorkle/storage/gno.mod create mode 100644 examples/gno.land/p/demo/gnorkle/storage/simple/gno.mod create mode 100644 examples/gno.land/p/demo/grc/exts/gno.mod create mode 100644 examples/gno.land/p/demo/grc/grc1155/gno.mod create mode 100644 examples/gno.land/p/demo/grc/grc20/gno.mod create mode 100644 examples/gno.land/p/demo/grc/grc721/gno.mod create mode 100644 examples/gno.land/p/demo/grc/grc777/gno.mod create mode 100644 examples/gno.land/p/demo/groups/gno.mod create mode 100644 examples/gno.land/p/demo/int256/gno.mod create mode 100644 examples/gno.land/p/demo/json/eisel_lemire/gno.mod create mode 100644 examples/gno.land/p/demo/json/gno.mod create mode 100644 examples/gno.land/p/demo/json/ryu/gno.mod create mode 100644 examples/gno.land/p/demo/math_eval/int32/gno.mod create mode 100644 examples/gno.land/p/demo/memeland/gno.mod create mode 100644 examples/gno.land/p/demo/merkle/gno.mod create mode 100644 examples/gno.land/p/demo/microblog/gno.mod create mode 100644 examples/gno.land/p/demo/mux/gno.mod create mode 100644 examples/gno.land/p/demo/nestedpkg/gno.mod create mode 100644 examples/gno.land/p/demo/ownable/exts/authorizable/gno.mod create mode 100644 examples/gno.land/p/demo/ownable/gno.mod create mode 100644 examples/gno.land/p/demo/pausable/gno.mod create mode 100644 examples/gno.land/p/demo/rat/gno.mod create mode 100644 examples/gno.land/p/demo/releases/gno.mod create mode 100644 examples/gno.land/p/demo/seqid/gno.mod create mode 100644 examples/gno.land/p/demo/stack/gno.mod create mode 100644 examples/gno.land/p/demo/subscription/gno.mod create mode 100644 examples/gno.land/p/demo/subscription/lifetime/gno.mod create mode 100644 examples/gno.land/p/demo/subscription/recurring/gno.mod create mode 100644 examples/gno.land/p/demo/svg/gno.mod create mode 100644 examples/gno.land/p/demo/tamagotchi/gno.mod create mode 100644 examples/gno.land/p/demo/tests/gno.mod create mode 100644 examples/gno.land/p/demo/tests/p_crossrealm/gno.mod create mode 100644 examples/gno.land/p/demo/tests/subtests/gno.mod create mode 100644 examples/gno.land/p/demo/testutils/gno.mod create mode 100644 examples/gno.land/p/demo/todolist/gno.mod create mode 100644 examples/gno.land/p/demo/uassert/gno.mod create mode 100644 examples/gno.land/p/demo/ufmt/gno.mod create mode 100644 examples/gno.land/p/demo/ui/gno.mod create mode 100644 examples/gno.land/p/demo/uint256/gno.mod create mode 100644 examples/gno.land/p/demo/urequire/gno.mod create mode 100644 examples/gno.land/p/demo/users/gno.mod create mode 100644 examples/gno.land/p/demo/watchdog/gno.mod create mode 100644 examples/gno.land/p/gov/proposal/gno.mod create mode 100644 examples/gno.land/p/moul/printfdebugging/gno.mod create mode 100644 examples/gno.land/p/nt/poa/gno.mod create mode 100644 examples/gno.land/p/sys/validators/gno.mod create mode 100644 examples/gno.land/r/demo/art/gnoface/gno.mod create mode 100644 examples/gno.land/r/demo/art/millipede/gno.mod create mode 100644 examples/gno.land/r/demo/banktest/gno.mod create mode 100644 examples/gno.land/r/demo/bar20/gno.mod create mode 100644 examples/gno.land/r/demo/boards/gno.mod create mode 100644 examples/gno.land/r/demo/counter/gno.mod create mode 100644 examples/gno.land/r/demo/deep/very/deep/gno.mod create mode 100644 examples/gno.land/r/demo/disperse/gno.mod create mode 100644 examples/gno.land/r/demo/echo/gno.mod create mode 100644 examples/gno.land/r/demo/event/gno.mod create mode 100644 examples/gno.land/r/demo/foo1155/gno.mod create mode 100644 examples/gno.land/r/demo/foo20/gno.mod create mode 100644 examples/gno.land/r/demo/foo721/gno.mod create mode 100644 examples/gno.land/r/demo/games/dice_roller/gno.mod create mode 100644 examples/gno.land/r/demo/games/shifumi/gno.mod create mode 100644 examples/gno.land/r/demo/grc20factory/gno.mod create mode 100644 examples/gno.land/r/demo/groups/gno.mod create mode 100644 examples/gno.land/r/demo/keystore/gno.mod create mode 100644 examples/gno.land/r/demo/markdown_test/gno.mod create mode 100644 examples/gno.land/r/demo/math_eval/gno.mod create mode 100644 examples/gno.land/r/demo/memeland/gno.mod create mode 100644 examples/gno.land/r/demo/microblog/gno.mod create mode 100644 examples/gno.land/r/demo/nft/gno.mod create mode 100644 examples/gno.land/r/demo/profile/gno.mod create mode 100644 examples/gno.land/r/demo/releases_example/gno.mod create mode 100644 examples/gno.land/r/demo/tamagotchi/gno.mod create mode 100644 examples/gno.land/r/demo/tests/crossrealm/gno.mod create mode 100644 examples/gno.land/r/demo/tests/gno.mod create mode 100644 examples/gno.land/r/demo/tests/subtests/gno.mod create mode 100644 examples/gno.land/r/demo/tests_foo/gno.mod create mode 100644 examples/gno.land/r/demo/todolist/gno.mod create mode 100644 examples/gno.land/r/demo/types/gno.mod create mode 100644 examples/gno.land/r/demo/ui/gno.mod create mode 100644 examples/gno.land/r/demo/userbook/gno.mod create mode 100644 examples/gno.land/r/demo/users/gno.mod create mode 100644 examples/gno.land/r/demo/wugnot/gno.mod create mode 100644 examples/gno.land/r/gnoland/blog/gno.mod create mode 100644 examples/gno.land/r/gnoland/events/gno.mod create mode 100644 examples/gno.land/r/gnoland/faucet/gno.mod create mode 100644 examples/gno.land/r/gnoland/ghverify/gno.mod create mode 100644 examples/gno.land/r/gnoland/home/gno.mod create mode 100644 examples/gno.land/r/gnoland/monit/gno.mod create mode 100644 examples/gno.land/r/gnoland/pages/gno.mod create mode 100644 examples/gno.land/r/gnoland/valopers/gno.mod create mode 100644 examples/gno.land/r/gov/dao/gno.mod create mode 100644 examples/gno.land/r/leon/config/gno.mod create mode 100644 examples/gno.land/r/leon/home/gno.mod create mode 100644 examples/gno.land/r/manfred/config/gno.mod create mode 100644 examples/gno.land/r/manfred/home/gno.mod create mode 100644 examples/gno.land/r/manfred/present/gno.mod create mode 100644 examples/gno.land/r/morgan/guestbook/gno.mod create mode 100644 examples/gno.land/r/morgan/home/gno.mod create mode 100644 examples/gno.land/r/sys/rewards/gno.mod create mode 100644 examples/gno.land/r/sys/users/gno.mod create mode 100644 examples/gno.land/r/sys/validators/gno.mod create mode 100644 examples/gno.land/r/x/manfred_upgrade_patterns/gno.mod create mode 100644 examples/gno.work create mode 100644 gnovm/pkg/packages/patterns.go diff --git a/examples/Makefile b/examples/Makefile index f4a2764663b..578b4faf15b 100644 --- a/examples/Makefile +++ b/examples/Makefile @@ -23,21 +23,21 @@ GOIMPORTS_FLAGS ?= $(GOFMT_FLAGS) GOTEST_FLAGS ?= -v -p 1 -timeout=30m # Official packages (non-overridable): more reliable and tested modules, distinct from the experimentation area. -OFFICIAL_PACKAGES = ./gno.land/p/... -OFFICIAL_PACKAGES += ./gno.land/r/demo/... -OFFICIAL_PACKAGES += ./gno.land/r/gnoland/... -OFFICIAL_PACKAGES += ./gno.land/r/sys/... -OFFICIAL_PACKAGES += ./gno.land/r/gov/... +OFFICIAL_PACKAGES = ./gno.land/p +OFFICIAL_PACKAGES += ./gno.land/r/demo +OFFICIAL_PACKAGES += ./gno.land/r/gnoland +OFFICIAL_PACKAGES += ./gno.land/r/sys +OFFICIAL_PACKAGES += ./gno.land/r/gov ######################################## # Dev tools .PHONY: transpile transpile: - go run ../gnovm/cmd/gno transpile -v ... + go run ../gnovm/cmd/gno transpile -v . .PHONY: build build: - go run ../gnovm/cmd/gno transpile -v --gobuild ... + go run ../gnovm/cmd/gno transpile -v --gobuild . .PHONY: test test: @@ -49,7 +49,7 @@ lint: .PHONY: test.sync test.sync: - go run ../gnovm/cmd/gno test -v --update-golden-tests ... + go run ../gnovm/cmd/gno test -v --update-golden-tests ./... .PHONY: clean clean: diff --git a/examples/gno.land/gno.mod b/examples/gno.land/gno.mod deleted file mode 100644 index fb7d383a4ed..00000000000 --- a/examples/gno.land/gno.mod +++ /dev/null @@ -1 +0,0 @@ -module gno.land diff --git a/examples/gno.land/p/archives/bank/gno.mod b/examples/gno.land/p/archives/bank/gno.mod new file mode 100644 index 00000000000..810731aed04 --- /dev/null +++ b/examples/gno.land/p/archives/bank/gno.mod @@ -0,0 +1 @@ +module gno.land/p/demo/bank diff --git a/examples/gno.land/p/demo/acl/gno.mod b/examples/gno.land/p/demo/acl/gno.mod new file mode 100644 index 00000000000..15d9f078048 --- /dev/null +++ b/examples/gno.land/p/demo/acl/gno.mod @@ -0,0 +1,8 @@ +module gno.land/p/demo/acl + +require ( + gno.land/p/demo/avl v0.0.0-latest + gno.land/p/demo/testutils v0.0.0-latest + gno.land/p/demo/uassert v0.0.0-latest + gno.land/p/demo/ufmt v0.0.0-latest +) diff --git a/examples/gno.land/p/demo/avl/gno.mod b/examples/gno.land/p/demo/avl/gno.mod new file mode 100644 index 00000000000..a6a2a1362e3 --- /dev/null +++ b/examples/gno.land/p/demo/avl/gno.mod @@ -0,0 +1 @@ +module gno.land/p/demo/avl diff --git a/examples/gno.land/p/demo/avlhelpers/gno.mod b/examples/gno.land/p/demo/avlhelpers/gno.mod new file mode 100644 index 00000000000..559f60975cf --- /dev/null +++ b/examples/gno.land/p/demo/avlhelpers/gno.mod @@ -0,0 +1,3 @@ +module gno.land/p/demo/avlhelpers + +require gno.land/p/demo/avl v0.0.0-latest diff --git a/examples/gno.land/p/demo/bf/gno.mod b/examples/gno.land/p/demo/bf/gno.mod new file mode 100644 index 00000000000..b887582196c --- /dev/null +++ b/examples/gno.land/p/demo/bf/gno.mod @@ -0,0 +1 @@ +module gno.land/p/demo/bf diff --git a/examples/gno.land/p/demo/blog/gno.mod b/examples/gno.land/p/demo/blog/gno.mod new file mode 100644 index 00000000000..65f58e7a0f6 --- /dev/null +++ b/examples/gno.land/p/demo/blog/gno.mod @@ -0,0 +1,7 @@ +module gno.land/p/demo/blog + +require ( + gno.land/p/demo/avl v0.0.0-latest + gno.land/p/demo/mux v0.0.0-latest + gno.land/p/demo/ufmt v0.0.0-latest +) diff --git a/examples/gno.land/p/demo/cford32/gno.mod b/examples/gno.land/p/demo/cford32/gno.mod new file mode 100644 index 00000000000..20b99c65e4c --- /dev/null +++ b/examples/gno.land/p/demo/cford32/gno.mod @@ -0,0 +1 @@ +module gno.land/p/demo/cford32 diff --git a/examples/gno.land/p/demo/context/gno.mod b/examples/gno.land/p/demo/context/gno.mod new file mode 100644 index 00000000000..a04ae1f91f8 --- /dev/null +++ b/examples/gno.land/p/demo/context/gno.mod @@ -0,0 +1 @@ +module gno.land/p/demo/context diff --git a/examples/gno.land/p/demo/diff/gno.mod b/examples/gno.land/p/demo/diff/gno.mod new file mode 100644 index 00000000000..3041b5f62f1 --- /dev/null +++ b/examples/gno.land/p/demo/diff/gno.mod @@ -0,0 +1 @@ +module gno.land/p/demo/diff diff --git a/examples/gno.land/p/demo/dom/gno.mod b/examples/gno.land/p/demo/dom/gno.mod new file mode 100644 index 00000000000..83ca827cf66 --- /dev/null +++ b/examples/gno.land/p/demo/dom/gno.mod @@ -0,0 +1,3 @@ +module gno.land/p/demo/dom + +require gno.land/p/demo/avl v0.0.0-latest diff --git a/examples/gno.land/p/demo/entropy/gno.mod b/examples/gno.land/p/demo/entropy/gno.mod new file mode 100644 index 00000000000..9a6db8f5b61 --- /dev/null +++ b/examples/gno.land/p/demo/entropy/gno.mod @@ -0,0 +1 @@ +module gno.land/p/demo/entropy diff --git a/examples/gno.land/p/demo/flow/gno.mod b/examples/gno.land/p/demo/flow/gno.mod new file mode 100644 index 00000000000..5adddbfe021 --- /dev/null +++ b/examples/gno.land/p/demo/flow/gno.mod @@ -0,0 +1 @@ +module gno.land/p/demo/flow diff --git a/examples/gno.land/p/demo/fqname/gno.mod b/examples/gno.land/p/demo/fqname/gno.mod new file mode 100644 index 00000000000..1282e262303 --- /dev/null +++ b/examples/gno.land/p/demo/fqname/gno.mod @@ -0,0 +1,3 @@ +module gno.land/p/demo/fqname + +require gno.land/p/demo/uassert v0.0.0-latest diff --git a/examples/gno.land/p/demo/gnode/gno.mod b/examples/gno.land/p/demo/gnode/gno.mod new file mode 100644 index 00000000000..a93c2051830 --- /dev/null +++ b/examples/gno.land/p/demo/gnode/gno.mod @@ -0,0 +1 @@ +module gno.land/p/demo/gnode diff --git a/examples/gno.land/p/demo/gnorkle/agent/gno.mod b/examples/gno.land/p/demo/gnorkle/agent/gno.mod new file mode 100644 index 00000000000..093ca9cf38e --- /dev/null +++ b/examples/gno.land/p/demo/gnorkle/agent/gno.mod @@ -0,0 +1,6 @@ +module gno.land/p/demo/gnorkle/agent + +require ( + gno.land/p/demo/avl v0.0.0-latest + gno.land/p/demo/uassert v0.0.0-latest +) diff --git a/examples/gno.land/p/demo/gnorkle/feed/gno.mod b/examples/gno.land/p/demo/gnorkle/feed/gno.mod new file mode 100644 index 00000000000..65e1ffa5897 --- /dev/null +++ b/examples/gno.land/p/demo/gnorkle/feed/gno.mod @@ -0,0 +1 @@ +module gno.land/p/demo/gnorkle/feed diff --git a/examples/gno.land/p/demo/gnorkle/feeds/static/gno.mod b/examples/gno.land/p/demo/gnorkle/feeds/static/gno.mod new file mode 100644 index 00000000000..c651c62cb1b --- /dev/null +++ b/examples/gno.land/p/demo/gnorkle/feeds/static/gno.mod @@ -0,0 +1,13 @@ +module gno.land/p/demo/gnorkle/feeds/static + +require ( + gno.land/p/demo/gnorkle/feed v0.0.0-latest + gno.land/p/demo/gnorkle/gnorkle v0.0.0-latest + gno.land/p/demo/gnorkle/ingester v0.0.0-latest + gno.land/p/demo/gnorkle/ingesters/single v0.0.0-latest + gno.land/p/demo/gnorkle/message v0.0.0-latest + gno.land/p/demo/gnorkle/storage/simple v0.0.0-latest + gno.land/p/demo/uassert v0.0.0-latest + gno.land/p/demo/ufmt v0.0.0-latest + gno.land/p/demo/urequire v0.0.0-latest +) diff --git a/examples/gno.land/p/demo/gnorkle/gnorkle/gno.mod b/examples/gno.land/p/demo/gnorkle/gnorkle/gno.mod new file mode 100644 index 00000000000..88fb202863f --- /dev/null +++ b/examples/gno.land/p/demo/gnorkle/gnorkle/gno.mod @@ -0,0 +1,9 @@ +module gno.land/p/demo/gnorkle/gnorkle + +require ( + gno.land/p/demo/avl v0.0.0-latest + gno.land/p/demo/gnorkle/agent v0.0.0-latest + gno.land/p/demo/gnorkle/feed v0.0.0-latest + gno.land/p/demo/gnorkle/ingester v0.0.0-latest + gno.land/p/demo/gnorkle/message v0.0.0-latest +) diff --git a/examples/gno.land/p/demo/gnorkle/ingester/gno.mod b/examples/gno.land/p/demo/gnorkle/ingester/gno.mod new file mode 100644 index 00000000000..66fa3abf6ad --- /dev/null +++ b/examples/gno.land/p/demo/gnorkle/ingester/gno.mod @@ -0,0 +1 @@ +module gno.land/p/demo/gnorkle/ingester diff --git a/examples/gno.land/p/demo/gnorkle/ingesters/single/gno.mod b/examples/gno.land/p/demo/gnorkle/ingesters/single/gno.mod new file mode 100644 index 00000000000..71120966a0c --- /dev/null +++ b/examples/gno.land/p/demo/gnorkle/ingesters/single/gno.mod @@ -0,0 +1,8 @@ +module gno.land/p/demo/gnorkle/ingesters/single + +require ( + gno.land/p/demo/gnorkle/gnorkle v0.0.0-latest + gno.land/p/demo/gnorkle/ingester v0.0.0-latest + gno.land/p/demo/gnorkle/storage/simple v0.0.0-latest + gno.land/p/demo/uassert v0.0.0-latest +) diff --git a/examples/gno.land/p/demo/gnorkle/message/gno.mod b/examples/gno.land/p/demo/gnorkle/message/gno.mod new file mode 100644 index 00000000000..4baad40ef86 --- /dev/null +++ b/examples/gno.land/p/demo/gnorkle/message/gno.mod @@ -0,0 +1,3 @@ +module gno.land/p/demo/gnorkle/message + +require gno.land/p/demo/uassert v0.0.0-latest diff --git a/examples/gno.land/p/demo/gnorkle/storage/gno.mod b/examples/gno.land/p/demo/gnorkle/storage/gno.mod new file mode 100644 index 00000000000..d98962cb771 --- /dev/null +++ b/examples/gno.land/p/demo/gnorkle/storage/gno.mod @@ -0,0 +1 @@ +module gno.land/p/demo/gnorkle/storage diff --git a/examples/gno.land/p/demo/gnorkle/storage/simple/gno.mod b/examples/gno.land/p/demo/gnorkle/storage/simple/gno.mod new file mode 100644 index 00000000000..cd673a8771c --- /dev/null +++ b/examples/gno.land/p/demo/gnorkle/storage/simple/gno.mod @@ -0,0 +1,9 @@ +module gno.land/p/demo/gnorkle/storage/simple + +require ( + gno.land/p/demo/gnorkle/feed v0.0.0-latest + gno.land/p/demo/gnorkle/storage v0.0.0-latest + gno.land/p/demo/uassert v0.0.0-latest + gno.land/p/demo/ufmt v0.0.0-latest + gno.land/p/demo/urequire v0.0.0-latest +) diff --git a/examples/gno.land/p/demo/grc/exts/gno.mod b/examples/gno.land/p/demo/grc/exts/gno.mod new file mode 100644 index 00000000000..001c4bf1df0 --- /dev/null +++ b/examples/gno.land/p/demo/grc/exts/gno.mod @@ -0,0 +1 @@ +module gno.land/p/demo/grc/exts diff --git a/examples/gno.land/p/demo/grc/grc1155/gno.mod b/examples/gno.land/p/demo/grc/grc1155/gno.mod new file mode 100644 index 00000000000..d6db0700146 --- /dev/null +++ b/examples/gno.land/p/demo/grc/grc1155/gno.mod @@ -0,0 +1,7 @@ +module gno.land/p/demo/grc/grc1155 + +require ( + gno.land/p/demo/avl v0.0.0-latest + gno.land/p/demo/uassert v0.0.0-latest + gno.land/p/demo/ufmt v0.0.0-latest +) diff --git a/examples/gno.land/p/demo/grc/grc20/gno.mod b/examples/gno.land/p/demo/grc/grc20/gno.mod new file mode 100644 index 00000000000..e872d80ec12 --- /dev/null +++ b/examples/gno.land/p/demo/grc/grc20/gno.mod @@ -0,0 +1,9 @@ +module gno.land/p/demo/grc/grc20 + +require ( + gno.land/p/demo/avl v0.0.0-latest + gno.land/p/demo/grc/exts v0.0.0-latest + gno.land/p/demo/testutils v0.0.0-latest + gno.land/p/demo/ufmt v0.0.0-latest + gno.land/p/demo/urequire v0.0.0-latest +) diff --git a/examples/gno.land/p/demo/grc/grc721/gno.mod b/examples/gno.land/p/demo/grc/grc721/gno.mod new file mode 100644 index 00000000000..9e1d6f56ffc --- /dev/null +++ b/examples/gno.land/p/demo/grc/grc721/gno.mod @@ -0,0 +1,8 @@ +module gno.land/p/demo/grc/grc721 + +require ( + gno.land/p/demo/avl v0.0.0-latest + gno.land/p/demo/testutils v0.0.0-latest + gno.land/p/demo/uassert v0.0.0-latest + gno.land/p/demo/ufmt v0.0.0-latest +) diff --git a/examples/gno.land/p/demo/grc/grc777/gno.mod b/examples/gno.land/p/demo/grc/grc777/gno.mod new file mode 100644 index 00000000000..9fbf2f2b7cd --- /dev/null +++ b/examples/gno.land/p/demo/grc/grc777/gno.mod @@ -0,0 +1,3 @@ +module gno.land/p/demo/grc/grc777 + +require gno.land/p/demo/grc/exts v0.0.0-latest diff --git a/examples/gno.land/p/demo/groups/gno.mod b/examples/gno.land/p/demo/groups/gno.mod new file mode 100644 index 00000000000..f0749e3f411 --- /dev/null +++ b/examples/gno.land/p/demo/groups/gno.mod @@ -0,0 +1,6 @@ +module gno.land/p/demo/groups + +require ( + gno.land/p/demo/rat v0.0.0-latest + gno.land/r/demo/boards v0.0.0-latest +) diff --git a/examples/gno.land/p/demo/int256/gno.mod b/examples/gno.land/p/demo/int256/gno.mod new file mode 100644 index 00000000000..ef906c83c93 --- /dev/null +++ b/examples/gno.land/p/demo/int256/gno.mod @@ -0,0 +1,3 @@ +module gno.land/p/demo/int256 + +require gno.land/p/demo/uint256 v0.0.0-latest diff --git a/examples/gno.land/p/demo/json/eisel_lemire/gno.mod b/examples/gno.land/p/demo/json/eisel_lemire/gno.mod new file mode 100644 index 00000000000..d6670de82e2 --- /dev/null +++ b/examples/gno.land/p/demo/json/eisel_lemire/gno.mod @@ -0,0 +1 @@ +module gno.land/p/demo/json/eisel_lemire diff --git a/examples/gno.land/p/demo/json/gno.mod b/examples/gno.land/p/demo/json/gno.mod new file mode 100644 index 00000000000..8a380644acc --- /dev/null +++ b/examples/gno.land/p/demo/json/gno.mod @@ -0,0 +1,7 @@ +module gno.land/p/demo/json + +require ( + gno.land/p/demo/json/eisel_lemire v0.0.0-latest + gno.land/p/demo/json/ryu v0.0.0-latest + gno.land/p/demo/ufmt v0.0.0-latest +) diff --git a/examples/gno.land/p/demo/json/ryu/gno.mod b/examples/gno.land/p/demo/json/ryu/gno.mod new file mode 100644 index 00000000000..86a1988b052 --- /dev/null +++ b/examples/gno.land/p/demo/json/ryu/gno.mod @@ -0,0 +1 @@ +module gno.land/p/demo/json/ryu diff --git a/examples/gno.land/p/demo/math_eval/int32/gno.mod b/examples/gno.land/p/demo/math_eval/int32/gno.mod new file mode 100644 index 00000000000..de57497a699 --- /dev/null +++ b/examples/gno.land/p/demo/math_eval/int32/gno.mod @@ -0,0 +1,3 @@ +module gno.land/p/demo/math_eval/int32 + +require gno.land/p/demo/ufmt v0.0.0-latest diff --git a/examples/gno.land/p/demo/memeland/gno.mod b/examples/gno.land/p/demo/memeland/gno.mod new file mode 100644 index 00000000000..66f22d1ccee --- /dev/null +++ b/examples/gno.land/p/demo/memeland/gno.mod @@ -0,0 +1,10 @@ +module gno.land/p/demo/memeland + +require ( + gno.land/p/demo/avl v0.0.0-latest + gno.land/p/demo/ownable v0.0.0-latest + gno.land/p/demo/seqid v0.0.0-latest + gno.land/p/demo/testutils v0.0.0-latest + gno.land/p/demo/uassert v0.0.0-latest + gno.land/p/demo/ufmt v0.0.0-latest +) diff --git a/examples/gno.land/p/demo/merkle/gno.mod b/examples/gno.land/p/demo/merkle/gno.mod new file mode 100644 index 00000000000..ae70400aa6d --- /dev/null +++ b/examples/gno.land/p/demo/merkle/gno.mod @@ -0,0 +1 @@ +module gno.land/p/demo/merkle diff --git a/examples/gno.land/p/demo/microblog/gno.mod b/examples/gno.land/p/demo/microblog/gno.mod new file mode 100644 index 00000000000..9bbcfa19e31 --- /dev/null +++ b/examples/gno.land/p/demo/microblog/gno.mod @@ -0,0 +1,6 @@ +module gno.land/p/demo/microblog + +require ( + gno.land/p/demo/avl v0.0.0-latest + gno.land/p/demo/ufmt v0.0.0-latest +) diff --git a/examples/gno.land/p/demo/mux/gno.mod b/examples/gno.land/p/demo/mux/gno.mod new file mode 100644 index 00000000000..972a531e14c --- /dev/null +++ b/examples/gno.land/p/demo/mux/gno.mod @@ -0,0 +1 @@ +module gno.land/p/demo/mux diff --git a/examples/gno.land/p/demo/nestedpkg/gno.mod b/examples/gno.land/p/demo/nestedpkg/gno.mod new file mode 100644 index 00000000000..24e16fdeb74 --- /dev/null +++ b/examples/gno.land/p/demo/nestedpkg/gno.mod @@ -0,0 +1 @@ +module gno.land/p/demo/nestedpkg diff --git a/examples/gno.land/p/demo/ownable/exts/authorizable/gno.mod b/examples/gno.land/p/demo/ownable/exts/authorizable/gno.mod new file mode 100644 index 00000000000..f36823f3f71 --- /dev/null +++ b/examples/gno.land/p/demo/ownable/exts/authorizable/gno.mod @@ -0,0 +1,9 @@ +module gno.land/p/demo/ownable/exts/authorizable + +require ( + gno.land/p/demo/avl v0.0.0-latest + gno.land/p/demo/ownable v0.0.0-latest + gno.land/p/demo/testutils v0.0.0-latest + gno.land/p/demo/uassert v0.0.0-latest + gno.land/p/demo/ufmt v0.0.0-latest +) diff --git a/examples/gno.land/p/demo/ownable/gno.mod b/examples/gno.land/p/demo/ownable/gno.mod new file mode 100644 index 00000000000..00f7812f6f5 --- /dev/null +++ b/examples/gno.land/p/demo/ownable/gno.mod @@ -0,0 +1,6 @@ +module gno.land/p/demo/ownable + +require ( + gno.land/p/demo/testutils v0.0.0-latest + gno.land/p/demo/uassert v0.0.0-latest +) diff --git a/examples/gno.land/p/demo/pausable/gno.mod b/examples/gno.land/p/demo/pausable/gno.mod new file mode 100644 index 00000000000..156875f7d85 --- /dev/null +++ b/examples/gno.land/p/demo/pausable/gno.mod @@ -0,0 +1,6 @@ +module gno.land/p/demo/pausable + +require ( + gno.land/p/demo/ownable v0.0.0-latest + gno.land/p/demo/urequire v0.0.0-latest +) diff --git a/examples/gno.land/p/demo/rat/gno.mod b/examples/gno.land/p/demo/rat/gno.mod new file mode 100644 index 00000000000..c20136fe9bb --- /dev/null +++ b/examples/gno.land/p/demo/rat/gno.mod @@ -0,0 +1 @@ +module gno.land/p/demo/rat diff --git a/examples/gno.land/p/demo/releases/gno.mod b/examples/gno.land/p/demo/releases/gno.mod new file mode 100644 index 00000000000..93214b9bc08 --- /dev/null +++ b/examples/gno.land/p/demo/releases/gno.mod @@ -0,0 +1 @@ +module gno.land/p/demo/releases diff --git a/examples/gno.land/p/demo/seqid/gno.mod b/examples/gno.land/p/demo/seqid/gno.mod new file mode 100644 index 00000000000..d1390012c3c --- /dev/null +++ b/examples/gno.land/p/demo/seqid/gno.mod @@ -0,0 +1,3 @@ +module gno.land/p/demo/seqid + +require gno.land/p/demo/cford32 v0.0.0-latest diff --git a/examples/gno.land/p/demo/stack/gno.mod b/examples/gno.land/p/demo/stack/gno.mod new file mode 100644 index 00000000000..e39ec7b8f28 --- /dev/null +++ b/examples/gno.land/p/demo/stack/gno.mod @@ -0,0 +1 @@ +module gno.land/p/demo/stack diff --git a/examples/gno.land/p/demo/subscription/gno.mod b/examples/gno.land/p/demo/subscription/gno.mod new file mode 100644 index 00000000000..ea60a4c628a --- /dev/null +++ b/examples/gno.land/p/demo/subscription/gno.mod @@ -0,0 +1 @@ +module gno.land/p/demo/subscription diff --git a/examples/gno.land/p/demo/subscription/lifetime/gno.mod b/examples/gno.land/p/demo/subscription/lifetime/gno.mod new file mode 100644 index 00000000000..0084aa714c5 --- /dev/null +++ b/examples/gno.land/p/demo/subscription/lifetime/gno.mod @@ -0,0 +1,8 @@ +module gno.land/p/demo/subscription/lifetime + +require ( + gno.land/p/demo/avl v0.0.0-latest + gno.land/p/demo/ownable v0.0.0-latest + gno.land/p/demo/testutils v0.0.0-latest + gno.land/p/demo/uassert v0.0.0-latest +) diff --git a/examples/gno.land/p/demo/subscription/recurring/gno.mod b/examples/gno.land/p/demo/subscription/recurring/gno.mod new file mode 100644 index 00000000000..d3cf8a044f8 --- /dev/null +++ b/examples/gno.land/p/demo/subscription/recurring/gno.mod @@ -0,0 +1,8 @@ +module gno.land/p/demo/subscription/recurring + +require ( + gno.land/p/demo/avl v0.0.0-latest + gno.land/p/demo/ownable v0.0.0-latest + gno.land/p/demo/testutils v0.0.0-latest + gno.land/p/demo/uassert v0.0.0-latest +) diff --git a/examples/gno.land/p/demo/svg/gno.mod b/examples/gno.land/p/demo/svg/gno.mod new file mode 100644 index 00000000000..0af7ba0636d --- /dev/null +++ b/examples/gno.land/p/demo/svg/gno.mod @@ -0,0 +1,3 @@ +module gno.land/p/demo/svg + +require gno.land/p/demo/ufmt v0.0.0-latest diff --git a/examples/gno.land/p/demo/tamagotchi/gno.mod b/examples/gno.land/p/demo/tamagotchi/gno.mod new file mode 100644 index 00000000000..58441284a6b --- /dev/null +++ b/examples/gno.land/p/demo/tamagotchi/gno.mod @@ -0,0 +1,3 @@ +module gno.land/p/demo/tamagotchi + +require gno.land/p/demo/ufmt v0.0.0-latest diff --git a/examples/gno.land/p/demo/tests/gno.mod b/examples/gno.land/p/demo/tests/gno.mod new file mode 100644 index 00000000000..d3d796f76f8 --- /dev/null +++ b/examples/gno.land/p/demo/tests/gno.mod @@ -0,0 +1,7 @@ +module gno.land/p/demo/tests + +require ( + gno.land/p/demo/tests/subtests v0.0.0-latest + gno.land/p/demo/uassert v0.0.0-latest + gno.land/r/demo/tests v0.0.0-latest +) diff --git a/examples/gno.land/p/demo/tests/p_crossrealm/gno.mod b/examples/gno.land/p/demo/tests/p_crossrealm/gno.mod new file mode 100644 index 00000000000..8585cfd9c8d --- /dev/null +++ b/examples/gno.land/p/demo/tests/p_crossrealm/gno.mod @@ -0,0 +1 @@ +module gno.land/p/demo/tests/p_crossrealm diff --git a/examples/gno.land/p/demo/tests/subtests/gno.mod b/examples/gno.land/p/demo/tests/subtests/gno.mod new file mode 100644 index 00000000000..c8333722809 --- /dev/null +++ b/examples/gno.land/p/demo/tests/subtests/gno.mod @@ -0,0 +1,4 @@ +module gno.land/p/demo/tests/subtests + +// TODO: this file should not exist. +// This is a temporary workaround. Until https://github.com/gnolang/gno/issues/852 diff --git a/examples/gno.land/p/demo/testutils/gno.mod b/examples/gno.land/p/demo/testutils/gno.mod new file mode 100644 index 00000000000..0c97bf4b367 --- /dev/null +++ b/examples/gno.land/p/demo/testutils/gno.mod @@ -0,0 +1 @@ +module gno.land/p/demo/testutils diff --git a/examples/gno.land/p/demo/todolist/gno.mod b/examples/gno.land/p/demo/todolist/gno.mod new file mode 100644 index 00000000000..bbccf357e3b --- /dev/null +++ b/examples/gno.land/p/demo/todolist/gno.mod @@ -0,0 +1,6 @@ +module gno.land/p/demo/todolist + +require ( + gno.land/p/demo/avl v0.0.0-latest + gno.land/p/demo/uassert v0.0.0-latest +) diff --git a/examples/gno.land/p/demo/uassert/gno.mod b/examples/gno.land/p/demo/uassert/gno.mod new file mode 100644 index 00000000000..f22276564bf --- /dev/null +++ b/examples/gno.land/p/demo/uassert/gno.mod @@ -0,0 +1,3 @@ +module gno.land/p/demo/uassert + +require gno.land/p/demo/diff v0.0.0-latest diff --git a/examples/gno.land/p/demo/ufmt/gno.mod b/examples/gno.land/p/demo/ufmt/gno.mod new file mode 100644 index 00000000000..61b52b09fed --- /dev/null +++ b/examples/gno.land/p/demo/ufmt/gno.mod @@ -0,0 +1 @@ +module gno.land/p/demo/ufmt diff --git a/examples/gno.land/p/demo/ui/gno.mod b/examples/gno.land/p/demo/ui/gno.mod new file mode 100644 index 00000000000..41f5cb78d83 --- /dev/null +++ b/examples/gno.land/p/demo/ui/gno.mod @@ -0,0 +1 @@ +module gno.land/p/demo/ui diff --git a/examples/gno.land/p/demo/uint256/gno.mod b/examples/gno.land/p/demo/uint256/gno.mod new file mode 100644 index 00000000000..71e5050c831 --- /dev/null +++ b/examples/gno.land/p/demo/uint256/gno.mod @@ -0,0 +1 @@ +module gno.land/p/demo/uint256 diff --git a/examples/gno.land/p/demo/urequire/gno.mod b/examples/gno.land/p/demo/urequire/gno.mod new file mode 100644 index 00000000000..9689a2222ac --- /dev/null +++ b/examples/gno.land/p/demo/urequire/gno.mod @@ -0,0 +1,3 @@ +module gno.land/p/demo/urequire + +require gno.land/p/demo/uassert v0.0.0-latest diff --git a/examples/gno.land/p/demo/users/gno.mod b/examples/gno.land/p/demo/users/gno.mod new file mode 100644 index 00000000000..ad652803fb8 --- /dev/null +++ b/examples/gno.land/p/demo/users/gno.mod @@ -0,0 +1 @@ +module gno.land/p/demo/users diff --git a/examples/gno.land/p/demo/watchdog/gno.mod b/examples/gno.land/p/demo/watchdog/gno.mod new file mode 100644 index 00000000000..29005441401 --- /dev/null +++ b/examples/gno.land/p/demo/watchdog/gno.mod @@ -0,0 +1,3 @@ +module gno.land/p/demo/watchdog + +require gno.land/p/demo/uassert v0.0.0-latest diff --git a/examples/gno.land/p/gov/proposal/gno.mod b/examples/gno.land/p/gov/proposal/gno.mod new file mode 100644 index 00000000000..3f6ef34a759 --- /dev/null +++ b/examples/gno.land/p/gov/proposal/gno.mod @@ -0,0 +1,7 @@ +module gno.land/p/gov/proposal + +require ( + gno.land/p/demo/context v0.0.0-latest + gno.land/p/demo/uassert v0.0.0-latest + gno.land/p/demo/urequire v0.0.0-latest +) diff --git a/examples/gno.land/p/moul/printfdebugging/gno.mod b/examples/gno.land/p/moul/printfdebugging/gno.mod new file mode 100644 index 00000000000..2cf6aa09e61 --- /dev/null +++ b/examples/gno.land/p/moul/printfdebugging/gno.mod @@ -0,0 +1,3 @@ +module gno.land/p/demo/printfdebugging + +require gno.land/p/demo/ufmt v0.0.0-latest diff --git a/examples/gno.land/p/nt/poa/gno.mod b/examples/gno.land/p/nt/poa/gno.mod new file mode 100644 index 00000000000..5c1b75eb05a --- /dev/null +++ b/examples/gno.land/p/nt/poa/gno.mod @@ -0,0 +1,10 @@ +module gno.land/p/nt/poa + +require ( + gno.land/p/demo/avl v0.0.0-latest + gno.land/p/demo/testutils v0.0.0-latest + gno.land/p/demo/uassert v0.0.0-latest + gno.land/p/demo/ufmt v0.0.0-latest + gno.land/p/demo/urequire v0.0.0-latest + gno.land/p/sys/validators v0.0.0-latest +) diff --git a/examples/gno.land/p/sys/validators/gno.mod b/examples/gno.land/p/sys/validators/gno.mod new file mode 100644 index 00000000000..9c7a38aada0 --- /dev/null +++ b/examples/gno.land/p/sys/validators/gno.mod @@ -0,0 +1 @@ +module gno.land/p/sys/validators diff --git a/examples/gno.land/r/demo/art/gnoface/gno.mod b/examples/gno.land/r/demo/art/gnoface/gno.mod new file mode 100644 index 00000000000..072c98f3bd6 --- /dev/null +++ b/examples/gno.land/r/demo/art/gnoface/gno.mod @@ -0,0 +1,7 @@ +module gno.land/r/demo/art/gnoface + +require ( + gno.land/p/demo/entropy v0.0.0-latest + gno.land/p/demo/uassert v0.0.0-latest + gno.land/p/demo/ufmt v0.0.0-latest +) diff --git a/examples/gno.land/r/demo/art/millipede/gno.mod b/examples/gno.land/r/demo/art/millipede/gno.mod new file mode 100644 index 00000000000..7cd604206fa --- /dev/null +++ b/examples/gno.land/r/demo/art/millipede/gno.mod @@ -0,0 +1,6 @@ +module gno.land/r/demo/art/millipede + +require ( + gno.land/p/demo/uassert v0.0.0-latest + gno.land/p/demo/ufmt v0.0.0-latest +) diff --git a/examples/gno.land/r/demo/banktest/gno.mod b/examples/gno.land/r/demo/banktest/gno.mod new file mode 100644 index 00000000000..7660f338c13 --- /dev/null +++ b/examples/gno.land/r/demo/banktest/gno.mod @@ -0,0 +1 @@ +module gno.land/r/demo/banktest diff --git a/examples/gno.land/r/demo/bar20/gno.mod b/examples/gno.land/r/demo/bar20/gno.mod new file mode 100644 index 00000000000..2ec82d7be0b --- /dev/null +++ b/examples/gno.land/r/demo/bar20/gno.mod @@ -0,0 +1,8 @@ +module gno.land/r/demo/bar20 + +require ( + gno.land/p/demo/grc/grc20 v0.0.0-latest + gno.land/p/demo/testutils v0.0.0-latest + gno.land/p/demo/ufmt v0.0.0-latest + gno.land/p/demo/urequire v0.0.0-latest +) diff --git a/examples/gno.land/r/demo/boards/gno.mod b/examples/gno.land/r/demo/boards/gno.mod new file mode 100644 index 00000000000..434ad019883 --- /dev/null +++ b/examples/gno.land/r/demo/boards/gno.mod @@ -0,0 +1,6 @@ +module gno.land/r/demo/boards + +require ( + gno.land/p/demo/avl v0.0.0-latest + gno.land/r/demo/users v0.0.0-latest +) diff --git a/examples/gno.land/r/demo/counter/gno.mod b/examples/gno.land/r/demo/counter/gno.mod new file mode 100644 index 00000000000..332d4e6da6a --- /dev/null +++ b/examples/gno.land/r/demo/counter/gno.mod @@ -0,0 +1 @@ +module gno.land/r/demo/counter diff --git a/examples/gno.land/r/demo/deep/very/deep/gno.mod b/examples/gno.land/r/demo/deep/very/deep/gno.mod new file mode 100644 index 00000000000..ab5a9ef8536 --- /dev/null +++ b/examples/gno.land/r/demo/deep/very/deep/gno.mod @@ -0,0 +1 @@ +module gno.land/r/demo/deep/very/deep diff --git a/examples/gno.land/r/demo/disperse/gno.mod b/examples/gno.land/r/demo/disperse/gno.mod new file mode 100644 index 00000000000..0ba9c88810a --- /dev/null +++ b/examples/gno.land/r/demo/disperse/gno.mod @@ -0,0 +1,3 @@ +module gno.land/r/demo/disperse + +require gno.land/r/demo/grc20factory v0.0.0-latest diff --git a/examples/gno.land/r/demo/echo/gno.mod b/examples/gno.land/r/demo/echo/gno.mod new file mode 100644 index 00000000000..4ca5ccab6e0 --- /dev/null +++ b/examples/gno.land/r/demo/echo/gno.mod @@ -0,0 +1,3 @@ +module gno.land/r/demo/echo + +require gno.land/p/demo/urequire v0.0.0-latest diff --git a/examples/gno.land/r/demo/event/gno.mod b/examples/gno.land/r/demo/event/gno.mod new file mode 100644 index 00000000000..64987d43d79 --- /dev/null +++ b/examples/gno.land/r/demo/event/gno.mod @@ -0,0 +1 @@ +module gno.land/r/demo/event diff --git a/examples/gno.land/r/demo/foo1155/gno.mod b/examples/gno.land/r/demo/foo1155/gno.mod new file mode 100644 index 00000000000..0a405c5b4a2 --- /dev/null +++ b/examples/gno.land/r/demo/foo1155/gno.mod @@ -0,0 +1,8 @@ +module gno.land/r/demo/foo1155 + +require ( + gno.land/p/demo/grc/grc1155 v0.0.0-latest + gno.land/p/demo/ufmt v0.0.0-latest + gno.land/p/demo/users v0.0.0-latest + gno.land/r/demo/users v0.0.0-latest +) diff --git a/examples/gno.land/r/demo/foo20/gno.mod b/examples/gno.land/r/demo/foo20/gno.mod new file mode 100644 index 00000000000..4035f9b1200 --- /dev/null +++ b/examples/gno.land/r/demo/foo20/gno.mod @@ -0,0 +1,11 @@ +module gno.land/r/demo/foo20 + +require ( + gno.land/p/demo/grc/grc20 v0.0.0-latest + gno.land/p/demo/ownable v0.0.0-latest + gno.land/p/demo/testutils v0.0.0-latest + gno.land/p/demo/uassert v0.0.0-latest + gno.land/p/demo/ufmt v0.0.0-latest + gno.land/p/demo/users v0.0.0-latest + gno.land/r/demo/users v0.0.0-latest +) diff --git a/examples/gno.land/r/demo/foo721/gno.mod b/examples/gno.land/r/demo/foo721/gno.mod new file mode 100644 index 00000000000..e013677379d --- /dev/null +++ b/examples/gno.land/r/demo/foo721/gno.mod @@ -0,0 +1,8 @@ +module gno.land/r/demo/foo721 + +require ( + gno.land/p/demo/grc/grc721 v0.0.0-latest + gno.land/p/demo/ufmt v0.0.0-latest + gno.land/p/demo/users v0.0.0-latest + gno.land/r/demo/users v0.0.0-latest +) diff --git a/examples/gno.land/r/demo/games/dice_roller/gno.mod b/examples/gno.land/r/demo/games/dice_roller/gno.mod new file mode 100644 index 00000000000..75c6473fa3e --- /dev/null +++ b/examples/gno.land/r/demo/games/dice_roller/gno.mod @@ -0,0 +1,11 @@ +module gno.land/r/demo/games/dice_roller + +require ( + gno.land/p/demo/avl v0.0.0-latest + gno.land/p/demo/entropy v0.0.0-latest + gno.land/p/demo/seqid v0.0.0-latest + gno.land/p/demo/testutils v0.0.0-latest + gno.land/p/demo/ufmt v0.0.0-latest + gno.land/p/demo/urequire v0.0.0-latest + gno.land/r/demo/users v0.0.0-latest +) diff --git a/examples/gno.land/r/demo/games/shifumi/gno.mod b/examples/gno.land/r/demo/games/shifumi/gno.mod new file mode 100644 index 00000000000..7a4fc173d3d --- /dev/null +++ b/examples/gno.land/r/demo/games/shifumi/gno.mod @@ -0,0 +1,7 @@ +module gno.land/r/demo/games/shifumi + +require ( + gno.land/p/demo/avl v0.0.0-latest + gno.land/p/demo/seqid v0.0.0-latest + gno.land/r/demo/users v0.0.0-latest +) diff --git a/examples/gno.land/r/demo/grc20factory/gno.mod b/examples/gno.land/r/demo/grc20factory/gno.mod new file mode 100644 index 00000000000..8d0fbd0c46b --- /dev/null +++ b/examples/gno.land/r/demo/grc20factory/gno.mod @@ -0,0 +1,9 @@ +module gno.land/r/demo/grc20factory + +require ( + gno.land/p/demo/avl v0.0.0-latest + gno.land/p/demo/grc/grc20 v0.0.0-latest + gno.land/p/demo/ownable v0.0.0-latest + gno.land/p/demo/uassert v0.0.0-latest + gno.land/p/demo/ufmt v0.0.0-latest +) diff --git a/examples/gno.land/r/demo/groups/gno.mod b/examples/gno.land/r/demo/groups/gno.mod new file mode 100644 index 00000000000..fc6756e13e2 --- /dev/null +++ b/examples/gno.land/r/demo/groups/gno.mod @@ -0,0 +1,6 @@ +module gno.land/r/demo/groups + +require ( + gno.land/p/demo/avl v0.0.0-latest + gno.land/r/demo/users v0.0.0-latest +) diff --git a/examples/gno.land/r/demo/keystore/gno.mod b/examples/gno.land/r/demo/keystore/gno.mod new file mode 100644 index 00000000000..49b0f3494a4 --- /dev/null +++ b/examples/gno.land/r/demo/keystore/gno.mod @@ -0,0 +1,8 @@ +module gno.land/r/demo/keystore + +require ( + gno.land/p/demo/avl v0.0.0-latest + gno.land/p/demo/testutils v0.0.0-latest + gno.land/p/demo/uassert v0.0.0-latest + gno.land/p/demo/ufmt v0.0.0-latest +) diff --git a/examples/gno.land/r/demo/markdown_test/gno.mod b/examples/gno.land/r/demo/markdown_test/gno.mod new file mode 100644 index 00000000000..19c0d1dcfb3 --- /dev/null +++ b/examples/gno.land/r/demo/markdown_test/gno.mod @@ -0,0 +1 @@ +module gno.land/r/demo/markdown_test diff --git a/examples/gno.land/r/demo/math_eval/gno.mod b/examples/gno.land/r/demo/math_eval/gno.mod new file mode 100644 index 00000000000..0e3fcfe6e9b --- /dev/null +++ b/examples/gno.land/r/demo/math_eval/gno.mod @@ -0,0 +1,6 @@ +module gno.land/r/demo/math_eval + +require ( + gno.land/p/demo/math_eval/int32 v0.0.0-latest + gno.land/p/demo/ufmt v0.0.0-latest +) diff --git a/examples/gno.land/r/demo/memeland/gno.mod b/examples/gno.land/r/demo/memeland/gno.mod new file mode 100644 index 00000000000..5c73379519b --- /dev/null +++ b/examples/gno.land/r/demo/memeland/gno.mod @@ -0,0 +1,3 @@ +module gno.land/r/demo/memeland + +require gno.land/p/demo/memeland v0.0.0-latest diff --git a/examples/gno.land/r/demo/microblog/gno.mod b/examples/gno.land/r/demo/microblog/gno.mod new file mode 100644 index 00000000000..26349e481d4 --- /dev/null +++ b/examples/gno.land/r/demo/microblog/gno.mod @@ -0,0 +1,9 @@ +module gno.land/r/demo/microblog + +require ( + gno.land/p/demo/microblog v0.0.0-latest + gno.land/p/demo/testutils v0.0.0-latest + gno.land/p/demo/ufmt v0.0.0-latest + gno.land/p/demo/urequire v0.0.0-latest + gno.land/r/demo/users v0.0.0-latest +) diff --git a/examples/gno.land/r/demo/nft/gno.mod b/examples/gno.land/r/demo/nft/gno.mod new file mode 100644 index 00000000000..89e0055be51 --- /dev/null +++ b/examples/gno.land/r/demo/nft/gno.mod @@ -0,0 +1,6 @@ +module gno.land/r/demo/nft + +require ( + gno.land/p/demo/avl v0.0.0-latest + gno.land/p/demo/grc/grc721 v0.0.0-latest +) diff --git a/examples/gno.land/r/demo/profile/gno.mod b/examples/gno.land/r/demo/profile/gno.mod new file mode 100644 index 00000000000..e7feac5d680 --- /dev/null +++ b/examples/gno.land/r/demo/profile/gno.mod @@ -0,0 +1,9 @@ +module gno.land/r/demo/profile + +require ( + gno.land/p/demo/avl v0.0.0-latest + gno.land/p/demo/mux v0.0.0-latest + gno.land/p/demo/testutils v0.0.0-latest + gno.land/p/demo/uassert v0.0.0-latest + gno.land/p/demo/ufmt v0.0.0-latest +) diff --git a/examples/gno.land/r/demo/releases_example/gno.mod b/examples/gno.land/r/demo/releases_example/gno.mod new file mode 100644 index 00000000000..22f640fe797 --- /dev/null +++ b/examples/gno.land/r/demo/releases_example/gno.mod @@ -0,0 +1,3 @@ +module gno.land/r/demo/releases_example + +require gno.land/p/demo/releases v0.0.0-latest diff --git a/examples/gno.land/r/demo/tamagotchi/gno.mod b/examples/gno.land/r/demo/tamagotchi/gno.mod new file mode 100644 index 00000000000..b7a2deea2c2 --- /dev/null +++ b/examples/gno.land/r/demo/tamagotchi/gno.mod @@ -0,0 +1,6 @@ +module gno.land/r/demo/tamagotchi + +require ( + gno.land/p/demo/tamagotchi v0.0.0-latest + gno.land/p/demo/ufmt v0.0.0-latest +) diff --git a/examples/gno.land/r/demo/tests/crossrealm/gno.mod b/examples/gno.land/r/demo/tests/crossrealm/gno.mod new file mode 100644 index 00000000000..71a89ec2ec5 --- /dev/null +++ b/examples/gno.land/r/demo/tests/crossrealm/gno.mod @@ -0,0 +1,6 @@ +module gno.land/r/demo/tests/crossrealm + +require ( + gno.land/p/demo/tests/p_crossrealm v0.0.0-latest + gno.land/p/demo/ufmt v0.0.0-latest +) diff --git a/examples/gno.land/r/demo/tests/gno.mod b/examples/gno.land/r/demo/tests/gno.mod new file mode 100644 index 00000000000..c51571e7d04 --- /dev/null +++ b/examples/gno.land/r/demo/tests/gno.mod @@ -0,0 +1,6 @@ +module gno.land/r/demo/tests + +require ( + gno.land/p/demo/nestedpkg v0.0.0-latest + gno.land/r/demo/tests/subtests v0.0.0-latest +) diff --git a/examples/gno.land/r/demo/tests/subtests/gno.mod b/examples/gno.land/r/demo/tests/subtests/gno.mod new file mode 100644 index 00000000000..9f466ff77b9 --- /dev/null +++ b/examples/gno.land/r/demo/tests/subtests/gno.mod @@ -0,0 +1,4 @@ +module gno.land/r/demo/tests/subtests + +// TODO: this file should not exist. +// This is a temporary workaround. Until https://github.com/gnolang/gno/issues/852 diff --git a/examples/gno.land/r/demo/tests_foo/gno.mod b/examples/gno.land/r/demo/tests_foo/gno.mod new file mode 100644 index 00000000000..226271ae4b0 --- /dev/null +++ b/examples/gno.land/r/demo/tests_foo/gno.mod @@ -0,0 +1,3 @@ +module gno.land/r/demo/tests_foo + +require gno.land/r/demo/tests v0.0.0-latest diff --git a/examples/gno.land/r/demo/todolist/gno.mod b/examples/gno.land/r/demo/todolist/gno.mod new file mode 100644 index 00000000000..36909859a6f --- /dev/null +++ b/examples/gno.land/r/demo/todolist/gno.mod @@ -0,0 +1,9 @@ +module gno.land/r/demo/todolist + +require ( + gno.land/p/demo/avl v0.0.0-latest + gno.land/p/demo/seqid v0.0.0-latest + gno.land/p/demo/todolist v0.0.0-latest + gno.land/p/demo/uassert v0.0.0-latest + gno.land/p/demo/ufmt v0.0.0-latest +) diff --git a/examples/gno.land/r/demo/types/gno.mod b/examples/gno.land/r/demo/types/gno.mod new file mode 100644 index 00000000000..0e86e5d5676 --- /dev/null +++ b/examples/gno.land/r/demo/types/gno.mod @@ -0,0 +1,3 @@ +module gno.land/r/demo/types + +require gno.land/p/demo/avl v0.0.0-latest diff --git a/examples/gno.land/r/demo/ui/gno.mod b/examples/gno.land/r/demo/ui/gno.mod new file mode 100644 index 00000000000..0ef5d9dd40e --- /dev/null +++ b/examples/gno.land/r/demo/ui/gno.mod @@ -0,0 +1,6 @@ +module gno.land/r/demo/ui + +require ( + gno.land/p/demo/uassert v0.0.0-latest + gno.land/p/demo/ui v0.0.0-latest +) diff --git a/examples/gno.land/r/demo/userbook/gno.mod b/examples/gno.land/r/demo/userbook/gno.mod new file mode 100644 index 00000000000..213586d12ee --- /dev/null +++ b/examples/gno.land/r/demo/userbook/gno.mod @@ -0,0 +1,8 @@ +module gno.land/r/demo/userbook + +require ( + gno.land/p/demo/avl v0.0.0-latest + gno.land/p/demo/mux v0.0.0-latest + gno.land/p/demo/testutils v0.0.0-latest + gno.land/p/demo/ufmt v0.0.0-latest +) diff --git a/examples/gno.land/r/demo/users/gno.mod b/examples/gno.land/r/demo/users/gno.mod new file mode 100644 index 00000000000..cdef52b6952 --- /dev/null +++ b/examples/gno.land/r/demo/users/gno.mod @@ -0,0 +1,8 @@ +module gno.land/r/demo/users + +require ( + gno.land/p/demo/avl v0.0.0-latest + gno.land/p/demo/avlhelpers v0.0.0-latest + gno.land/p/demo/uassert v0.0.0-latest + gno.land/p/demo/users v0.0.0-latest +) diff --git a/examples/gno.land/r/demo/wugnot/gno.mod b/examples/gno.land/r/demo/wugnot/gno.mod new file mode 100644 index 00000000000..f076e90e068 --- /dev/null +++ b/examples/gno.land/r/demo/wugnot/gno.mod @@ -0,0 +1,8 @@ +module gno.land/r/demo/wugnot + +require ( + gno.land/p/demo/grc/grc20 v0.0.0-latest + gno.land/p/demo/ufmt v0.0.0-latest + gno.land/p/demo/users v0.0.0-latest + gno.land/r/demo/users v0.0.0-latest +) diff --git a/examples/gno.land/r/gnoland/blog/gno.mod b/examples/gno.land/r/gnoland/blog/gno.mod new file mode 100644 index 00000000000..17c17e0cfa6 --- /dev/null +++ b/examples/gno.land/r/gnoland/blog/gno.mod @@ -0,0 +1,8 @@ +module gno.land/r/gnoland/blog + +require ( + gno.land/p/demo/avl v0.0.0-latest + gno.land/p/demo/blog v0.0.0-latest + gno.land/p/demo/context v0.0.0-latest + gno.land/p/gov/proposal v0.0.0-latest +) diff --git a/examples/gno.land/r/gnoland/events/gno.mod b/examples/gno.land/r/gnoland/events/gno.mod new file mode 100644 index 00000000000..bd3e4652b04 --- /dev/null +++ b/examples/gno.land/r/gnoland/events/gno.mod @@ -0,0 +1,9 @@ +module gno.land/r/gnoland/events + +require ( + gno.land/p/demo/ownable/exts/authorizable v0.0.0-latest + gno.land/p/demo/seqid v0.0.0-latest + gno.land/p/demo/uassert v0.0.0-latest + gno.land/p/demo/ufmt v0.0.0-latest + gno.land/p/demo/urequire v0.0.0-latest +) diff --git a/examples/gno.land/r/gnoland/faucet/gno.mod b/examples/gno.land/r/gnoland/faucet/gno.mod new file mode 100644 index 00000000000..693b0e795cf --- /dev/null +++ b/examples/gno.land/r/gnoland/faucet/gno.mod @@ -0,0 +1,7 @@ +module gno.land/r/gnoland/faucet + +require ( + gno.land/p/demo/avl v0.0.0-latest + gno.land/p/demo/testutils v0.0.0-latest + gno.land/p/demo/ufmt v0.0.0-latest +) diff --git a/examples/gno.land/r/gnoland/ghverify/gno.mod b/examples/gno.land/r/gnoland/ghverify/gno.mod new file mode 100644 index 00000000000..386bd9293d2 --- /dev/null +++ b/examples/gno.land/r/gnoland/ghverify/gno.mod @@ -0,0 +1,9 @@ +module gno.land/r/gnoland/ghverify + +require ( + gno.land/p/demo/avl v0.0.0-latest + gno.land/p/demo/gnorkle/feeds/static v0.0.0-latest + gno.land/p/demo/gnorkle/gnorkle v0.0.0-latest + gno.land/p/demo/gnorkle/message v0.0.0-latest + gno.land/p/demo/testutils v0.0.0-latest +) diff --git a/examples/gno.land/r/gnoland/home/gno.mod b/examples/gno.land/r/gnoland/home/gno.mod new file mode 100644 index 00000000000..c208ad421c9 --- /dev/null +++ b/examples/gno.land/r/gnoland/home/gno.mod @@ -0,0 +1,9 @@ +module gno.land/r/gnoland/home + +require ( + gno.land/p/demo/ownable v0.0.0-latest + gno.land/p/demo/ufmt v0.0.0-latest + gno.land/p/demo/ui v0.0.0-latest + gno.land/r/gnoland/blog v0.0.0-latest + gno.land/r/gnoland/events v0.0.0-latest +) diff --git a/examples/gno.land/r/gnoland/monit/gno.mod b/examples/gno.land/r/gnoland/monit/gno.mod new file mode 100644 index 00000000000..e67fdaa7d71 --- /dev/null +++ b/examples/gno.land/r/gnoland/monit/gno.mod @@ -0,0 +1,8 @@ +module gno.land/r/gnoland/monit + +require ( + gno.land/p/demo/ownable v0.0.0-latest + gno.land/p/demo/uassert v0.0.0-latest + gno.land/p/demo/ufmt v0.0.0-latest + gno.land/p/demo/watchdog v0.0.0-latest +) diff --git a/examples/gno.land/r/gnoland/pages/gno.mod b/examples/gno.land/r/gnoland/pages/gno.mod new file mode 100644 index 00000000000..31e9ad2c85b --- /dev/null +++ b/examples/gno.land/r/gnoland/pages/gno.mod @@ -0,0 +1,6 @@ +module gno.land/r/gnoland/pages + +require ( + gno.land/p/demo/avl v0.0.0-latest + gno.land/p/demo/blog v0.0.0-latest +) diff --git a/examples/gno.land/r/gnoland/valopers/gno.mod b/examples/gno.land/r/gnoland/valopers/gno.mod new file mode 100644 index 00000000000..2d24fb27952 --- /dev/null +++ b/examples/gno.land/r/gnoland/valopers/gno.mod @@ -0,0 +1,11 @@ +module gno.land/r/gnoland/valopers + +require ( + gno.land/p/demo/avl v0.0.0-latest + gno.land/p/demo/testutils v0.0.0-latest + gno.land/p/demo/uassert v0.0.0-latest + gno.land/p/demo/ufmt v0.0.0-latest + gno.land/p/sys/validators v0.0.0-latest + gno.land/r/gov/dao v0.0.0-latest + gno.land/r/sys/validators v0.0.0-latest +) diff --git a/examples/gno.land/r/gov/dao/gno.mod b/examples/gno.land/r/gov/dao/gno.mod new file mode 100644 index 00000000000..f3c0bae990e --- /dev/null +++ b/examples/gno.land/r/gov/dao/gno.mod @@ -0,0 +1,8 @@ +module gno.land/r/gov/dao + +require ( + gno.land/p/demo/testutils v0.0.0-latest + gno.land/p/demo/ufmt v0.0.0-latest + gno.land/p/demo/urequire v0.0.0-latest + gno.land/p/gov/proposal v0.0.0-latest +) diff --git a/examples/gno.land/r/leon/config/gno.mod b/examples/gno.land/r/leon/config/gno.mod new file mode 100644 index 00000000000..e8cd5cd85b7 --- /dev/null +++ b/examples/gno.land/r/leon/config/gno.mod @@ -0,0 +1 @@ +module gno.land/r/leon/config diff --git a/examples/gno.land/r/leon/home/gno.mod b/examples/gno.land/r/leon/home/gno.mod new file mode 100644 index 00000000000..48cf64a9d0a --- /dev/null +++ b/examples/gno.land/r/leon/home/gno.mod @@ -0,0 +1,8 @@ +module gno.land/r/leon/home + +require ( + gno.land/p/demo/ufmt v0.0.0-latest + gno.land/r/demo/art/gnoface v0.0.0-latest + gno.land/r/demo/art/millipede v0.0.0-latest + gno.land/r/leon/config v0.0.0-latest +) diff --git a/examples/gno.land/r/manfred/config/gno.mod b/examples/gno.land/r/manfred/config/gno.mod new file mode 100644 index 00000000000..516bf38528e --- /dev/null +++ b/examples/gno.land/r/manfred/config/gno.mod @@ -0,0 +1 @@ +module gno.land/r/manfred/config diff --git a/examples/gno.land/r/manfred/home/gno.mod b/examples/gno.land/r/manfred/home/gno.mod new file mode 100644 index 00000000000..6e7aac70cc7 --- /dev/null +++ b/examples/gno.land/r/manfred/home/gno.mod @@ -0,0 +1,3 @@ +module gno.land/r/manfred/home + +require gno.land/r/manfred/config v0.0.0-latest diff --git a/examples/gno.land/r/manfred/present/gno.mod b/examples/gno.land/r/manfred/present/gno.mod new file mode 100644 index 00000000000..5d50447e0e0 --- /dev/null +++ b/examples/gno.land/r/manfred/present/gno.mod @@ -0,0 +1,6 @@ +module gno.land/r/manfred/present + +require ( + gno.land/p/demo/avl v0.0.0-latest + gno.land/p/demo/blog v0.0.0-latest +) diff --git a/examples/gno.land/r/morgan/guestbook/gno.mod b/examples/gno.land/r/morgan/guestbook/gno.mod new file mode 100644 index 00000000000..2591643d33d --- /dev/null +++ b/examples/gno.land/r/morgan/guestbook/gno.mod @@ -0,0 +1,7 @@ +module gno.land/r/morgan/guestbook + +require ( + gno.land/p/demo/avl v0.0.0-latest + gno.land/p/demo/ownable v0.0.0-latest + gno.land/p/demo/seqid v0.0.0-latest +) diff --git a/examples/gno.land/r/morgan/home/gno.mod b/examples/gno.land/r/morgan/home/gno.mod new file mode 100644 index 00000000000..573a7e139e7 --- /dev/null +++ b/examples/gno.land/r/morgan/home/gno.mod @@ -0,0 +1 @@ +module gno.land/r/morgan/home diff --git a/examples/gno.land/r/sys/rewards/gno.mod b/examples/gno.land/r/sys/rewards/gno.mod new file mode 100644 index 00000000000..f615998a01f --- /dev/null +++ b/examples/gno.land/r/sys/rewards/gno.mod @@ -0,0 +1 @@ +module gno.land/r/sys/rewards diff --git a/examples/gno.land/r/sys/users/gno.mod b/examples/gno.land/r/sys/users/gno.mod new file mode 100644 index 00000000000..774a364a272 --- /dev/null +++ b/examples/gno.land/r/sys/users/gno.mod @@ -0,0 +1,6 @@ +module gno.land/r/sys/users + +require ( + gno.land/p/demo/ownable v0.0.0-latest + gno.land/r/demo/users v0.0.0-latest +) diff --git a/examples/gno.land/r/sys/validators/gno.mod b/examples/gno.land/r/sys/validators/gno.mod new file mode 100644 index 00000000000..d9d129dd543 --- /dev/null +++ b/examples/gno.land/r/sys/validators/gno.mod @@ -0,0 +1,12 @@ +module gno.land/r/sys/validators + +require ( + gno.land/p/demo/avl v0.0.0-latest + gno.land/p/demo/seqid v0.0.0-latest + gno.land/p/demo/testutils v0.0.0-latest + gno.land/p/demo/uassert v0.0.0-latest + gno.land/p/demo/ufmt v0.0.0-latest + gno.land/p/gov/proposal v0.0.0-latest + gno.land/p/nt/poa v0.0.0-latest + gno.land/p/sys/validators v0.0.0-latest +) diff --git a/examples/gno.land/r/x/manfred_upgrade_patterns/gno.mod b/examples/gno.land/r/x/manfred_upgrade_patterns/gno.mod new file mode 100644 index 00000000000..d6e97dc39b9 --- /dev/null +++ b/examples/gno.land/r/x/manfred_upgrade_patterns/gno.mod @@ -0,0 +1,3 @@ +// Draft + +module gno.land/r/x/manfred_upgrade_patterns diff --git a/examples/gno.work b/examples/gno.work new file mode 100644 index 00000000000..e69de29bb2d diff --git a/gnovm/pkg/gnofiles/gnofiles.go b/gnovm/pkg/gnofiles/gnofiles.go index 65a5ce2ba9c..0e3fd2d277b 100644 --- a/gnovm/pkg/gnofiles/gnofiles.go +++ b/gnovm/pkg/gnofiles/gnofiles.go @@ -2,6 +2,7 @@ package gnofiles import ( "os" + "path/filepath" ) // This file contains "definitions"; it attempts to centralize some common @@ -18,6 +19,9 @@ const ( // ModfileName is the name of the module file. ModfileName = "gno.mod" + // WorkfileName is the name of the workspace file. + WorkfileName = "gno.work" + // RecursiveSuffix is the os-dependent suffix marking a recursive target RecursiveSuffix = string(os.PathSeparator) + "..." ) @@ -46,3 +50,29 @@ func IsGnoTestFile(p string) bool { func IsGnoFiletestFile(p string) bool { return IsGnoFile(p, "*_filetest.gno") } + +func FindModuleRoot(dir string) (string, error) { + return findRoot(dir, ModfileName) +} + +func FindWorkspaceRoot(dir string) (string, error) { + return findRoot(dir, WorkfileName) +} + +func findRoot(dir string, filename string) (string, error) { + dir = filepath.Clean(dir) + + potentialMod := filepath.Join(dir, filename) + + if _, err := os.Stat(potentialMod); os.IsNotExist(err) { + parent, file := filepath.Split(dir) + if file == "" || (parent == "" && file == ".") { + return "", os.ErrNotExist + } + return findRoot(parent, filename) + } else if err != nil { + return "", err + } + + return filepath.Clean(dir), nil +} diff --git a/gnovm/pkg/packages/patterns.go b/gnovm/pkg/packages/patterns.go new file mode 100644 index 00000000000..b832b59dda9 --- /dev/null +++ b/gnovm/pkg/packages/patterns.go @@ -0,0 +1,41 @@ +package packages + +import ( + "path/filepath" + "strings" +) + +// PathIsLocalImport reports whether the import path is +// a local import path, like ".", "..", "./foo", or "../foo". +func PathIsLocalImport(path string) bool { + return path == "." || path == ".." || + strings.HasPrefix(path, "./") || strings.HasPrefix(path, "../") +} + +// PatternIsLiteral reports whether the pattern is free of wildcards. +// +// A literal pattern must match at most one package. +func PatternIsLiteral(pattern string) bool { + return !strings.Contains(pattern, "...") +} + +// PatternIsLocal reports whether the pattern must be resolved from a specific root or +// directory, such as a filesystem path or a single module. +func PatternIsLocal(pattern string) bool { + return PathIsLocalImport(pattern) || filepath.IsAbs(pattern) +} + +// PatternIsRemote reports whether the pattern is a remote, like "gno.land/p/demo/avl" or "gno.land/r/..." +func PatternIsRemote(pattern string) bool { + if PatternIsLocal(pattern) { + return false + } + + parts := strings.Split(pattern, "/") + if len(parts) < 2 { + return false + } + + domain := parts[0] + return strings.ContainsRune(domain, '.') +} diff --git a/gnovm/pkg/packages/resolve.go b/gnovm/pkg/packages/resolve.go index a247c5ec265..3ebaac8e46f 100644 --- a/gnovm/pkg/packages/resolve.go +++ b/gnovm/pkg/packages/resolve.go @@ -10,24 +10,39 @@ import ( "path/filepath" "strings" - "github.com/gnolang/gno/gnovm/pkg/gnoenv" "github.com/gnolang/gno/gnovm/pkg/gnofiles" "github.com/gnolang/gno/gnovm/pkg/gnomod" "github.com/gnolang/gno/tm2/pkg/commands" "github.com/gnolang/gno/tm2/pkg/std" ) -const recursiveSuffix = string(os.PathSeparator) + "..." - type visitTarget struct { path string match string } -func DiscoverPackages(paths ...string) ([]*PackageSummary, error) { +func DiscoverPackages(patterns ...string) ([]*PackageSummary, error) { + wd, err := os.Getwd() + if err != nil { + return nil, err + } + isWorkspace := true + workRoot, err := gnofiles.FindWorkspaceRoot(wd) + if os.IsNotExist(err) { + isWorkspace = false + workRoot, err = gnofiles.FindModuleRoot(wd) + if err != nil { + return nil, errors.New("can't find parent module or workspace") + } + } else if err != nil { + return nil, err + } + + _ = isWorkspace + toVisit := []visitTarget{} - for _, p := range paths { - toVisit = append(toVisit, visitTarget{path: p, match: p}) + for _, pattern := range patterns { + toVisit = append(toVisit, visitTarget{path: pattern, match: pattern}) } visited := map[visitTarget]struct{}{} cache := map[string]*PackageSummary{} @@ -44,21 +59,41 @@ func DiscoverPackages(paths ...string) ([]*PackageSummary, error) { visited[tgt] = struct{}{} if tgt.path == "" { + // this should not happen, panic? continue } - if tgt.path[0] == '.' { + if PathIsLocalImport(tgt.path) { absPath, err := filepath.Abs(tgt.path) if err != nil { errs = append(errs, fmt.Errorf("failed to get absolute path for %q: %w", tgt, err)) + continue } + + // check that the path is in current work tree + rel, err := filepath.Rel(workRoot, absPath) + if err != nil { + errs = append(errs, fmt.Errorf("failed to get relative path for %q from %q: %w", tgt, workRoot, err)) + continue + } + if rel == ".." || strings.HasPrefix(rel, "../") { + errs = append(errs, fmt.Errorf("path %q is outside work root %q", tgt.path, workRoot)) + continue + } + + // mark absolute path for visit toVisit = append(toVisit, visitTarget{path: absPath, match: tgt.match}) continue } + // at this point we should only have absolute or remote paths + if !(filepath.IsAbs(tgt.path) || PatternIsRemote(tgt.path)) { + panic(fmt.Errorf("%q should be an absolute or remote pattern", tgt.path)) + } + root := convertRecursivePathToDir(tgt.path) - if !isRecursivePath(tgt.path) { + if PatternIsLiteral(tgt.path) { if tgt.path[0] != '/' { pkgPath := tgt.path if pkg, ok := cache[pkgPath]; ok { @@ -116,6 +151,11 @@ func DiscoverPackages(paths ...string) ([]*PackageSummary, error) { continue } + // at this point we should only have recursive patterns + if PatternIsLiteral(tgt.path) { + panic(fmt.Errorf("%q should be a recursive pattern", tgt.path)) + } + dirEntry, err := os.ReadDir(root) if err != nil { errs = append(errs, err) @@ -126,7 +166,7 @@ func DiscoverPackages(paths ...string) ([]*PackageSummary, error) { for _, entry := range dirEntry { fileName := entry.Name() if entry.IsDir() { - dirPath := filepath.Join(root, fileName) + recursiveSuffix + dirPath := filepath.Join(root, fileName) + gnofiles.RecursiveSuffix toVisit = append(toVisit, visitTarget{path: dirPath, match: tgt.match}) } if !hasGnoFiles && gnofiles.IsGnoFile(fileName) { @@ -153,7 +193,7 @@ type PackageSummary struct { func Load(io commands.IO, paths ...string) ([]*Package, error) { pkgs, err := DiscoverPackages(paths...) if err != nil { - return nil, fmt.Errorf("failed to list packages: %w", err) + return nil, err } visited := map[string]struct{}{} @@ -258,7 +298,9 @@ func resolveStdlib(ometa *PackageSummary) (*Package, error) { func resolveRemote(io commands.IO, ometa *PackageSummary) (*Package, error) { meta := *ometa - modCache := filepath.Join(gnoenv.HomeDir(), "pkg", "mod") + homeDir := defaultConfig().GnoHome + + modCache := filepath.Join(homeDir, "pkg", "mod") meta.Root = filepath.Join(modCache, meta.PkgPath) if err := DownloadModule(io, meta.PkgPath, meta.Root); err != nil { return nil, fmt.Errorf("failed to download module %q: %w", meta.PkgPath, err) @@ -407,18 +449,11 @@ func resolveNameAndImports(gnoFiles []string) (string, []string, error) { return bestName, importsSlice, nil } -func isRecursivePath(p string) bool { - return strings.HasSuffix(p, recursiveSuffix) || p == "..." -} - func convertRecursivePathToDir(p string) string { if p == "..." { return "." } - if !strings.HasSuffix(p, recursiveSuffix) { - return p - } - return p[:len(p)-4] + return strings.TrimSuffix(p, gnofiles.RecursiveSuffix) } func findModDir(dir string) (string, error) { From b012a3328d2f44ad8462f5ce5c65a6d8494c122b Mon Sep 17 00:00:00 2001 From: Norman Date: Tue, 7 Jan 2025 19:15:23 +0100 Subject: [PATCH 029/143] fix: build Signed-off-by: Norman --- contribs/gnodev/pkg/dev/node.go | 6 +++--- contribs/gnodev/pkg/dev/packages.go | 10 +++++----- contribs/gnodev/pkg/watcher/watch.go | 4 ++-- examples/no_cycles_test.go | 7 +++---- gno.land/pkg/gnoland/genesis.go | 6 +++--- gno.land/pkg/integration/pkgloader.go | 16 ++++++++-------- gnovm/cmd/gno/mod.go | 2 +- gnovm/pkg/packages/download.go | 5 ++--- gnovm/pkg/{gnomod => packages}/pkg.go | 10 +++++----- gnovm/pkg/{gnomod => packages}/pkg_test.go | 2 +- 10 files changed, 33 insertions(+), 35 deletions(-) rename gnovm/pkg/{gnomod => packages}/pkg.go (93%) rename gnovm/pkg/{gnomod => packages}/pkg_test.go (99%) diff --git a/contribs/gnodev/pkg/dev/node.go b/contribs/gnodev/pkg/dev/node.go index fa9e2d11e29..5b572326278 100644 --- a/contribs/gnodev/pkg/dev/node.go +++ b/contribs/gnodev/pkg/dev/node.go @@ -16,7 +16,7 @@ import ( "github.com/gnolang/gno/gno.land/pkg/gnoland" "github.com/gnolang/gno/gno.land/pkg/gnoland/ugnot" "github.com/gnolang/gno/gno.land/pkg/integration" - "github.com/gnolang/gno/gnovm/pkg/gnomod" + "github.com/gnolang/gno/gnovm/pkg/packages" "github.com/gnolang/gno/tm2/pkg/amino" tmcfg "github.com/gnolang/gno/tm2/pkg/bft/config" "github.com/gnolang/gno/tm2/pkg/bft/node" @@ -140,7 +140,7 @@ func (n *Node) Close() error { return n.Node.Stop() } -func (n *Node) ListPkgs() []gnomod.Pkg { +func (n *Node) ListPkgs() []packages.Pkg { n.muNode.RLock() defer n.muNode.RUnlock() @@ -240,7 +240,7 @@ func (n *Node) updatePackages(paths ...string) error { } // List all packages from target path - pkgslist, err := gnomod.ListPkgs(abspath) + pkgslist, err := packages.ListPkgs(abspath) if err != nil { return fmt.Errorf("failed to list gno packages for %q: %w", path, err) } diff --git a/contribs/gnodev/pkg/dev/packages.go b/contribs/gnodev/pkg/dev/packages.go index 62c1907b8c9..8140aab1389 100644 --- a/contribs/gnodev/pkg/dev/packages.go +++ b/contribs/gnodev/pkg/dev/packages.go @@ -11,7 +11,7 @@ import ( "github.com/gnolang/gno/gno.land/pkg/gnoland" vmm "github.com/gnolang/gno/gno.land/pkg/sdk/vm" gno "github.com/gnolang/gno/gnovm/pkg/gnolang" - "github.com/gnolang/gno/gnovm/pkg/gnomod" + "github.com/gnolang/gno/gnovm/pkg/packages" "github.com/gnolang/gno/tm2/pkg/crypto" "github.com/gnolang/gno/tm2/pkg/std" ) @@ -63,7 +63,7 @@ func ResolvePackagePathQuery(bk *address.Book, path string) (PackagePath, error) } type Package struct { - gnomod.Pkg + packages.Pkg Creator crypto.Address Deposit std.Coins } @@ -88,7 +88,7 @@ func NewPackagesMap(ppaths []PackagePath) (PackagesMap, error) { } // list all packages from target path - pkgslist, err := gnomod.ListPkgs(abspath) + pkgslist, err := packages.ListPkgs(abspath) if err != nil { return nil, fmt.Errorf("listing gno packages: %w", err) } @@ -112,8 +112,8 @@ func NewPackagesMap(ppaths []PackagePath) (PackagesMap, error) { return pkgs, nil } -func (pm PackagesMap) toList() gnomod.PkgList { - list := make([]gnomod.Pkg, 0, len(pm)) +func (pm PackagesMap) toList() packages.PkgList { + list := make([]packages.Pkg, 0, len(pm)) for _, pkg := range pm { list = append(list, pkg.Pkg) } diff --git a/contribs/gnodev/pkg/watcher/watch.go b/contribs/gnodev/pkg/watcher/watch.go index 63158a06c4b..3f49936d48b 100644 --- a/contribs/gnodev/pkg/watcher/watch.go +++ b/contribs/gnodev/pkg/watcher/watch.go @@ -11,9 +11,9 @@ import ( emitter "github.com/gnolang/gno/contribs/gnodev/pkg/emitter" events "github.com/gnolang/gno/contribs/gnodev/pkg/events" + "github.com/gnolang/gno/gnovm/pkg/packages" "github.com/fsnotify/fsnotify" - "github.com/gnolang/gno/gnovm/pkg/gnomod" ) type PackageWatcher struct { @@ -118,7 +118,7 @@ func (p *PackageWatcher) Stop() { // Packages are sorted by their length in descending order to facilitate easier // and more efficient matching with corresponding paths. The longest paths are // compared first. -func (p *PackageWatcher) AddPackages(pkgs ...gnomod.Pkg) error { +func (p *PackageWatcher) AddPackages(pkgs ...packages.Pkg) error { for _, pkg := range pkgs { dir := pkg.Dir diff --git a/examples/no_cycles_test.go b/examples/no_cycles_test.go index 4ad3f120aa3..43220e9eeab 100644 --- a/examples/no_cycles_test.go +++ b/examples/no_cycles_test.go @@ -12,7 +12,6 @@ import ( "github.com/gnolang/gno/gnovm" "github.com/gnolang/gno/gnovm/pkg/gnoenv" - "github.com/gnolang/gno/gnovm/pkg/gnomod" "github.com/gnolang/gno/gnovm/pkg/packages" "github.com/stretchr/testify/require" ) @@ -23,14 +22,14 @@ var injectedTestingLibs = []string{"encoding/json", "fmt", "os", "internal/os_te func TestNoCycles(t *testing.T) { // find stdlibs gnoRoot := gnoenv.RootDir() - pkgs, err := listPkgs(gnomod.Pkg{ + pkgs, err := listPkgs(packages.Pkg{ Dir: filepath.Join(gnoRoot, "gnovm", "stdlibs"), Name: "", }) require.NoError(t, err) // find examples - examples, err := gnomod.ListPkgs(filepath.Join(gnoRoot, "examples")) + examples, err := packages.ListPkgs(filepath.Join(gnoRoot, "examples")) require.NoError(t, err) for _, example := range examples { if example.Draft { @@ -140,7 +139,7 @@ type testPkg struct { } // listPkgs lists all packages in rootMod -func listPkgs(rootMod gnomod.Pkg) ([]testPkg, error) { +func listPkgs(rootMod packages.Pkg) ([]testPkg, error) { res := []testPkg{} rootDir := rootMod.Dir visited := map[string]struct{}{} diff --git a/gno.land/pkg/gnoland/genesis.go b/gno.land/pkg/gnoland/genesis.go index d7844d77b57..3cd3a6358cd 100644 --- a/gno.land/pkg/gnoland/genesis.go +++ b/gno.land/pkg/gnoland/genesis.go @@ -7,7 +7,7 @@ import ( vmm "github.com/gnolang/gno/gno.land/pkg/sdk/vm" gno "github.com/gnolang/gno/gnovm/pkg/gnolang" - "github.com/gnolang/gno/gnovm/pkg/gnomod" + "github.com/gnolang/gno/gnovm/pkg/packages" "github.com/gnolang/gno/tm2/pkg/amino" bft "github.com/gnolang/gno/tm2/pkg/bft/types" "github.com/gnolang/gno/tm2/pkg/crypto" @@ -138,7 +138,7 @@ func LoadGenesisTxsFile(path string, chainID string, genesisRemote string) ([]Tx // It creates and returns a list of transactions based on these packages. func LoadPackagesFromDir(dir string, creator bft.Address, fee std.Fee) ([]TxWithMetadata, error) { // list all packages from target path - pkgs, err := gnomod.ListPkgs(dir) + pkgs, err := packages.ListPkgs(dir) if err != nil { return nil, fmt.Errorf("listing gno packages: %w", err) } @@ -167,7 +167,7 @@ func LoadPackagesFromDir(dir string, creator bft.Address, fee std.Fee) ([]TxWith } // LoadPackage loads a single package into a `std.Tx` -func LoadPackage(pkg gnomod.Pkg, creator bft.Address, fee std.Fee, deposit std.Coins) (std.Tx, error) { +func LoadPackage(pkg packages.Pkg, creator bft.Address, fee std.Fee, deposit std.Coins) (std.Tx, error) { var tx std.Tx // Open files in directory as MemPackage. diff --git a/gno.land/pkg/integration/pkgloader.go b/gno.land/pkg/integration/pkgloader.go index 168242df97e..d2303d2a41c 100644 --- a/gno.land/pkg/integration/pkgloader.go +++ b/gno.land/pkg/integration/pkgloader.go @@ -15,7 +15,7 @@ import ( ) type PkgsLoader struct { - pkgs []gnomod.Pkg + pkgs []packages.Pkg visited map[string]struct{} // list of occurrences to patchs with the given value @@ -25,13 +25,13 @@ type PkgsLoader struct { func NewPkgsLoader() *PkgsLoader { return &PkgsLoader{ - pkgs: make([]gnomod.Pkg, 0), + pkgs: make([]packages.Pkg, 0), visited: make(map[string]struct{}), patchs: make(map[string]string), } } -func (pl *PkgsLoader) List() gnomod.PkgList { +func (pl *PkgsLoader) List() packages.PkgList { return pl.pkgs } @@ -82,7 +82,7 @@ func (pl *PkgsLoader) LoadPackages(creator bft.Address, fee std.Fee, deposit std func (pl *PkgsLoader) LoadAllPackagesFromDir(path string) error { // list all packages from target path - pkgslist, err := gnomod.ListPkgs(path) + pkgslist, err := packages.ListPkgs(path) if err != nil { return fmt.Errorf("listing gno packages: %w", err) } @@ -98,7 +98,7 @@ func (pl *PkgsLoader) LoadAllPackagesFromDir(path string) error { func (pl *PkgsLoader) LoadPackage(modroot string, path, name string) error { // Initialize a queue with the root package - queue := []gnomod.Pkg{{Dir: path, Name: name}} + queue := []packages.Pkg{{Dir: path, Name: name}} for len(queue) > 0 { // Dequeue the first package @@ -151,19 +151,19 @@ func (pl *PkgsLoader) LoadPackage(modroot string, path, name string) error { // Add requirements to the queue for _, pkgPath := range currentPkg.Imports { fullPath := filepath.Join(modroot, pkgPath) - queue = append(queue, gnomod.Pkg{Dir: fullPath}) + queue = append(queue, packages.Pkg{Dir: fullPath}) } } return nil } -func (pl *PkgsLoader) add(pkg gnomod.Pkg) { +func (pl *PkgsLoader) add(pkg packages.Pkg) { pl.visited[pkg.Name] = struct{}{} pl.pkgs = append(pl.pkgs, pkg) } -func (pl *PkgsLoader) exist(pkg gnomod.Pkg) (ok bool) { +func (pl *PkgsLoader) exist(pkg packages.Pkg) (ok bool) { _, ok = pl.visited[pkg.Name] return } diff --git a/gnovm/cmd/gno/mod.go b/gnovm/cmd/gno/mod.go index 5479d934ce6..8ba59d53201 100644 --- a/gnovm/cmd/gno/mod.go +++ b/gnovm/cmd/gno/mod.go @@ -256,7 +256,7 @@ func execModTidy(cfg *modTidyCfg, args []string, io commands.IO) error { } if cfg.recursive { - pkgs, err := gnomod.ListPkgs(wd) + pkgs, err := packages.ListPkgs(wd) if err != nil { return err } diff --git a/gnovm/pkg/packages/download.go b/gnovm/pkg/packages/download.go index 9934002ca84..ddde28f17f8 100644 --- a/gnovm/pkg/packages/download.go +++ b/gnovm/pkg/packages/download.go @@ -11,7 +11,6 @@ import ( "github.com/gnolang/gno/gnovm/pkg/gnofiles" "github.com/gnolang/gno/tm2/pkg/bft/rpc/client" "github.com/gnolang/gno/tm2/pkg/commands" - "github.com/gnolang/gno/tm2/pkg/errors" ) func DownloadModule(io commands.IO, pkgPath string, dst string) error { @@ -76,10 +75,10 @@ func qfile(tmClient client.Client, pkgPath string) ([]byte, error) { qres, err := tmClient.ABCIQuery(path, data) if err != nil { - return nil, errors.Wrap(err, "query qfile") + return nil, fmt.Errorf("query qfile: %w", err) } if qres.Response.Error != nil { - return nil, errors.Wrap(qres.Response.Error, "QFile failed: log:%s", qres.Response.Log) + return nil, fmt.Errorf("qfile failed: %w\nlog:\n%s", qres.Response.Error, qres.Response.Log) } return qres.Response.Data, nil diff --git a/gnovm/pkg/gnomod/pkg.go b/gnovm/pkg/packages/pkg.go similarity index 93% rename from gnovm/pkg/gnomod/pkg.go rename to gnovm/pkg/packages/pkg.go index f05dc2ecfce..91cf4dba6f7 100644 --- a/gnovm/pkg/gnomod/pkg.go +++ b/gnovm/pkg/packages/pkg.go @@ -1,4 +1,4 @@ -package gnomod +package packages import ( "fmt" @@ -8,7 +8,7 @@ import ( "github.com/gnolang/gno/gnovm" "github.com/gnolang/gno/gnovm/pkg/gnolang" - "github.com/gnolang/gno/gnovm/pkg/packages" + "github.com/gnolang/gno/gnovm/pkg/gnomod" ) type Pkg struct { @@ -94,7 +94,7 @@ func ListPkgs(root string) (PkgList, error) { return err } - gnoMod, err := Parse(gnoModPath, data) + gnoMod, err := gnomod.Parse(gnoModPath, data) if err != nil { return fmt.Errorf("parse: %w", err) } @@ -109,12 +109,12 @@ func ListPkgs(root string) (PkgList, error) { pkg = &gnovm.MemPackage{} } - importsMap, err := packages.Imports(pkg, nil) + importsMap, err := Imports(pkg, nil) if err != nil { // ignore imports on error importsMap = nil } - importsRaw := importsMap.Merge(packages.FileKindPackageSource, packages.FileKindTest, packages.FileKindXTest) + importsRaw := importsMap.Merge(FileKindPackageSource, FileKindTest, FileKindXTest) imports := make([]string, 0, len(importsRaw)) for _, imp := range importsRaw { diff --git a/gnovm/pkg/gnomod/pkg_test.go b/gnovm/pkg/packages/pkg_test.go similarity index 99% rename from gnovm/pkg/gnomod/pkg_test.go rename to gnovm/pkg/packages/pkg_test.go index 7c3035a4b7b..1e6d06c8017 100644 --- a/gnovm/pkg/gnomod/pkg_test.go +++ b/gnovm/pkg/packages/pkg_test.go @@ -1,4 +1,4 @@ -package gnomod +package packages import ( "os" From c428052d94050c8abbf23be7bbc1b68651beee33 Mon Sep 17 00:00:00 2001 From: Norman Date: Wed, 8 Jan 2025 02:20:13 +0100 Subject: [PATCH 030/143] feat: working gno list Signed-off-by: Norman --- examples/gno.work | 0 gno.land/pkg/integration/pkgloader.go | 4 +- gnovm/cmd/gno/lint.go | 9 +- gnovm/cmd/gno/list.go | 12 +- gnovm/cmd/gno/main_test.go | 2 +- gnovm/cmd/gno/mod.go | 9 +- gnovm/cmd/gno/test.go | 7 +- gnovm/cmd/gno/transpile.go | 5 +- gnovm/pkg/doc/dirs.go | 6 +- gnovm/pkg/packages/analyze_packages.go | 122 +++++ gnovm/pkg/packages/download.go | 85 ---- .../gno => pkg/packages}/download_deps.go | 30 +- .../packages}/download_deps_test.go | 10 +- gnovm/pkg/packages/expand_patterns.go | 174 +++++++ gnovm/pkg/packages/filekind.go | 12 +- gnovm/pkg/packages/imports.go | 34 +- gnovm/pkg/packages/imports_test.go | 2 +- gnovm/pkg/packages/load.go | 149 ++++++ gnovm/pkg/packages/patterns.go | 44 +- gnovm/pkg/packages/pkg.go | 6 +- .../examplespkgfetcher/examplespkgfetcher.go | 2 +- .../packages}/pkgdownload/pkgdownload.go | 0 .../packages}/pkgdownload/pkgfetcher.go | 0 .../rpcpkgfetcher/rpcpkgfetcher.go | 2 +- .../rpcpkgfetcher/rpcpkgfetcher_test.go | 0 gnovm/pkg/packages/resolve.go | 475 ------------------ gnovm/pkg/packages/types.go | 50 +- gnovm/pkg/test/imports.go | 6 +- 28 files changed, 567 insertions(+), 690 deletions(-) delete mode 100644 examples/gno.work create mode 100644 gnovm/pkg/packages/analyze_packages.go delete mode 100644 gnovm/pkg/packages/download.go rename gnovm/{cmd/gno => pkg/packages}/download_deps.go (63%) rename gnovm/{cmd/gno => pkg/packages}/download_deps_test.go (94%) create mode 100644 gnovm/pkg/packages/expand_patterns.go create mode 100644 gnovm/pkg/packages/load.go rename gnovm/{cmd/gno/internal => pkg/packages}/pkgdownload/examplespkgfetcher/examplespkgfetcher.go (95%) rename gnovm/{cmd/gno/internal => pkg/packages}/pkgdownload/pkgdownload.go (100%) rename gnovm/{cmd/gno/internal => pkg/packages}/pkgdownload/pkgfetcher.go (100%) rename gnovm/{cmd/gno/internal => pkg/packages}/pkgdownload/rpcpkgfetcher/rpcpkgfetcher.go (97%) rename gnovm/{cmd/gno/internal => pkg/packages}/pkgdownload/rpcpkgfetcher/rpcpkgfetcher_test.go (100%) delete mode 100644 gnovm/pkg/packages/resolve.go diff --git a/examples/gno.work b/examples/gno.work deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/gno.land/pkg/integration/pkgloader.go b/gno.land/pkg/integration/pkgloader.go index d2303d2a41c..1772543b0a1 100644 --- a/gno.land/pkg/integration/pkgloader.go +++ b/gno.land/pkg/integration/pkgloader.go @@ -132,10 +132,10 @@ func (pl *PkgsLoader) LoadPackage(modroot string, path, name string) error { } imports := importsMap.Merge(packages.FileKindPackageSource, packages.FileKindTest, packages.FileKindFiletest) for _, imp := range imports { - if imp.PkgPath == currentPkg.Name || gnolang.IsStdlib(imp.PkgPath) { + if imp == currentPkg.Name || gnolang.IsStdlib(imp) { continue } - currentPkg.Imports = append(currentPkg.Imports, imp.PkgPath) + currentPkg.Imports = append(currentPkg.Imports, imp) } } diff --git a/gnovm/cmd/gno/lint.go b/gnovm/cmd/gno/lint.go index de2874dee49..07b76840ecf 100644 --- a/gnovm/cmd/gno/lint.go +++ b/gnovm/cmd/gno/lint.go @@ -91,7 +91,8 @@ func execLint(cfg *lintCfg, args []string, io commands.IO) error { rootDir = gnoenv.RootDir() } - pkgs, err := packages.Load(io, args...) + conf := &packages.LoadConfig{IO: io, Fetcher: testPackageFetcher} + pkgs, err := packages.Load(conf, args...) if err != nil { return fmt.Errorf("list packages from args: %w", err) } @@ -127,7 +128,7 @@ func execLint(cfg *lintCfg, args []string, io commands.IO) error { } // Check if 'gno.mod' exists - if pkg.Module.Path == "" { + if pkg.ModPath == "" { hasError = true issue := lintIssue{ Code: lintGnoMod, @@ -161,8 +162,8 @@ func execLint(cfg *lintCfg, args []string, io commands.IO) error { gs := ts.BeginTransaction(cw, cw, nil) var gmFile *gnomod.File - if pkg.Module.GnoMod != "" { - gmFile, err = gnomod.ParseGnoMod(pkg.Module.GnoMod) + if pkg.ModPath != "" { + gmFile, err = gnomod.ParseGnoMod(filepath.Join(pkg.ModPath, "gno.mod")) if err != nil { io.ErrPrintln(err) hasError = true diff --git a/gnovm/cmd/gno/list.go b/gnovm/cmd/gno/list.go index 2e9af6ca0e9..e36231f4cdf 100644 --- a/gnovm/cmd/gno/list.go +++ b/gnovm/cmd/gno/list.go @@ -51,21 +51,27 @@ func execList(cfg *listCfg, args []string, io commands.IO) error { return flag.ErrHelp } + conf := &packages.LoadConfig{IO: io, Fetcher: testPackageFetcher} + + if cfg.deps { + conf.Deps = true + } + if !cfg.json { - pkgs, err := packages.DiscoverPackages(args...) + pkgs, err := packages.Load(conf, args...) if err != nil { fmt.Println(err) os.Exit(1) } pkgPaths := make([]string, len(pkgs)) for i, pkg := range pkgs { - pkgPaths[i] = pkg.PkgPath + pkgPaths[i] = pkg.ImportPath } fmt.Println(strings.Join(pkgPaths, "\n")) return nil } - pkgs, err := packages.Load(io, args...) + pkgs, err := packages.Load(conf, args...) if err != nil { fmt.Println(err) os.Exit(1) diff --git a/gnovm/cmd/gno/main_test.go b/gnovm/cmd/gno/main_test.go index 2ea3e31f977..c4ea1c7394a 100644 --- a/gnovm/cmd/gno/main_test.go +++ b/gnovm/cmd/gno/main_test.go @@ -9,7 +9,7 @@ import ( "strings" "testing" - "github.com/gnolang/gno/gnovm/cmd/gno/internal/pkgdownload/examplespkgfetcher" + "github.com/gnolang/gno/gnovm/pkg/packages/pkgdownload/examplespkgfetcher" "github.com/gnolang/gno/tm2/pkg/commands" "github.com/stretchr/testify/require" ) diff --git a/gnovm/cmd/gno/mod.go b/gnovm/cmd/gno/mod.go index 8ba59d53201..c02a7ae77e0 100644 --- a/gnovm/cmd/gno/mod.go +++ b/gnovm/cmd/gno/mod.go @@ -8,10 +8,10 @@ import ( "path/filepath" "strings" - "github.com/gnolang/gno/gnovm/cmd/gno/internal/pkgdownload" - "github.com/gnolang/gno/gnovm/cmd/gno/internal/pkgdownload/rpcpkgfetcher" "github.com/gnolang/gno/gnovm/pkg/gnomod" "github.com/gnolang/gno/gnovm/pkg/packages" + "github.com/gnolang/gno/gnovm/pkg/packages/pkgdownload" + "github.com/gnolang/gno/gnovm/pkg/packages/pkgdownload/rpcpkgfetcher" "github.com/gnolang/gno/tm2/pkg/commands" "github.com/gnolang/gno/tm2/pkg/errors" "go.uber.org/multierr" @@ -184,7 +184,8 @@ func execModDownload(cfg *modDownloadCfg, args []string, io commands.IO) error { return fmt.Errorf("validate: %w", err) } - if err := downloadDeps(io, path, gnoMod, fetcher); err != nil { + conf := &packages.LoadConfig{IO: io, Fetcher: fetcher} + if err := packages.DownloadDeps(conf, path, gnoMod); err != nil { return err } @@ -368,7 +369,7 @@ func getImportToFilesMap(pkgPath string) (map[string][]string, error) { } for _, imp := range imports { - m[imp.PkgPath] = append(m[imp.PkgPath], filename) + m[imp] = append(m[imp], filename) } } return m, nil diff --git a/gnovm/cmd/gno/test.go b/gnovm/cmd/gno/test.go index ddbed540b80..2af570ce7c2 100644 --- a/gnovm/cmd/gno/test.go +++ b/gnovm/cmd/gno/test.go @@ -157,7 +157,8 @@ func execTest(cfg *testCfg, args []string, io commands.IO) error { } // Find targets for test. - pkgs, err := packages.Load(io, args...) + conf := &packages.LoadConfig{IO: io, Fetcher: testPackageFetcher} + pkgs, err := packages.Load(conf, args...) if err != nil { return fmt.Errorf("list targets from patterns: %w", err) } @@ -194,7 +195,7 @@ func execTest(cfg *testCfg, args []string, io commands.IO) error { } // Determine gnoPkgPath by reading gno.mod var gnoPkgPath string - modfile, err := gnomod.ParseGnoMod(pkg.Module.GnoMod) + modfile, err := gnomod.ParseGnoMod(filepath.Join(pkg.ModPath, "gno.mod")) if err == nil { gnoPkgPath = modfile.Module.Mod.Path } else { @@ -206,7 +207,7 @@ func execTest(cfg *testCfg, args []string, io commands.IO) error { } } - if len(pkg.TestGnoFiles) == 0 && len(pkg.FiletestGnoFiles) == 0 { + if len(pkg.Files[packages.FileKindTest]) == 0 && len(pkg.Files[packages.FileKindXTest]) == 0 && len(pkg.Files[packages.FileKindFiletest]) == 0 { io.ErrPrintfln("? %s \t[no test files]", pkg.ImportPath) continue } diff --git a/gnovm/cmd/gno/transpile.go b/gnovm/cmd/gno/transpile.go index 396a88ad6b0..66870e0a048 100644 --- a/gnovm/cmd/gno/transpile.go +++ b/gnovm/cmd/gno/transpile.go @@ -134,7 +134,8 @@ func execTranspile(cfg *transpileCfg, args []string, io commands.IO) error { } // load packages - pkgs, err := packages.Load(io, args...) + conf := &packages.LoadConfig{IO: io, Fetcher: testPackageFetcher} + pkgs, err := packages.Load(conf, args...) if err != nil { return fmt.Errorf("load pkgs: %w", err) } @@ -232,7 +233,7 @@ func transpilePkg(pkg *packages.Package, pkgs map[string]*packages.Package, opts if opts.cfg.verbose { opts.io.ErrPrintln(filepath.Clean(dirPath)) } - for _, file := range pkg.GnoFiles { + for _, file := range pkg.Files[packages.FileKindPackageSource] { fpath := filepath.Join(pkg.Dir, file) if err := transpileFile(fpath, pkgs, opts); err != nil { return fmt.Errorf("%s: %w", file, err) diff --git a/gnovm/pkg/doc/dirs.go b/gnovm/pkg/doc/dirs.go index 4c481324e9a..fdbf8bd7b41 100644 --- a/gnovm/pkg/doc/dirs.go +++ b/gnovm/pkg/doc/dirs.go @@ -110,11 +110,7 @@ func packageImportsRecursive(root string, pkgPath string) []string { // ignore packages with invalid imports importsMap = nil } - resRaw := importsMap.Merge(packages.FileKindPackageSource, packages.FileKindTest, packages.FileKindXTest) - res := make([]string, len(resRaw)) - for idx, imp := range resRaw { - res[idx] = imp.PkgPath - } + res := importsMap.Merge(packages.FileKindPackageSource, packages.FileKindTest, packages.FileKindXTest) entries, err := os.ReadDir(root) if err != nil { diff --git a/gnovm/pkg/packages/analyze_packages.go b/gnovm/pkg/packages/analyze_packages.go new file mode 100644 index 00000000000..d803af98fc0 --- /dev/null +++ b/gnovm/pkg/packages/analyze_packages.go @@ -0,0 +1,122 @@ +package packages + +import ( + "errors" + "go/token" + "os" + "path" + "path/filepath" + "strings" + + "github.com/gnolang/gno/gnovm" + "github.com/gnolang/gno/gnovm/pkg/gnolang" + "github.com/gnolang/gno/gnovm/pkg/gnomod" +) + +func readPackages(matches []*pkgMatch) []*Package { + pkgs := make([]*Package, 0, len(matches)) + for _, pkgMatch := range matches { + pkg := readPkg(pkgMatch.Dir) + pkg.Match = pkgMatch.Match + pkgs = append(pkgs, pkg) + } + return pkgs +} + +func readPkg(pkgDir string) *Package { + pkg := &Package{ + Dir: pkgDir, + Files: make(FilesMap), + } + + entries, err := os.ReadDir(pkgDir) + if err != nil { + pkg.Error = errors.Join(pkg.Error, err) + return pkg + } + + fset := token.NewFileSet() + + mempkg := gnovm.MemPackage{} + + for _, entry := range entries { + if entry.IsDir() { + continue + } + + base := entry.Name() + fpath := filepath.Join(pkgDir, base) + + if !strings.HasSuffix(base, ".gno") { + continue + } + + bodyBytes, err := os.ReadFile(fpath) + if err != nil { + pkg.Error = errors.Join(pkg.Error, err) + continue + } + body := string(bodyBytes) + + fileKind, err := GetFileKind(base, body, fset) + if err != nil { + pkg.Error = errors.Join(pkg.Error, err) + continue + } + + mempkg.Files = append(mempkg.Files, &gnovm.MemFile{Name: base, Body: body}) + pkg.Files[fileKind] = append(pkg.Files[fileKind], base) + } + + pkg.Imports, err = Imports(&mempkg, fset) + if err != nil { + pkg.Error = errors.Join(pkg.Error, err) + } + + // we use the ReadMemPkg utils because we want name resolution like the vm + nameFiles := pkg.Files.Merge(FileKindPackageSource, FileKindTest, FileKindXTest) + absFiles := make([]string, 0, len(nameFiles)) + for _, f := range nameFiles { + absFiles = append(absFiles, filepath.Join(pkg.Dir, f)) + } + minMempkg, err := gnolang.ReadMemPackageFromList(absFiles, "") + if err != nil { + pkg.Error = errors.Join(pkg.Error, err) + } else { + pkg.Name = minMempkg.Name + } + + // TODO: check if stdlib + + pkg.Root, err = gnomod.FindRootDir(pkgDir) + if err == gnomod.ErrGnoModNotFound { + return pkg + } + if err != nil { + pkg.Error = errors.Join(pkg.Error, err) + return pkg + } + + mod, err := gnomod.ParseGnoMod(filepath.Join(pkg.Root, "gno.mod")) + if err != nil { + pkg.Error = errors.Join(pkg.Error, err) + return pkg + } + + pkg.Draft = mod.Draft + + if mod.Module == nil { + return pkg + } + + pkg.ModPath = mod.Module.Mod.Path + subPath, err := filepath.Rel(pkg.Root, pkgDir) + if err != nil { + pkg.Error = errors.Join(pkg.Error, err) + return pkg + } + + pkg.ImportPath = path.Join(pkg.ModPath, filepath.ToSlash(subPath)) + + return pkg +} diff --git a/gnovm/pkg/packages/download.go b/gnovm/pkg/packages/download.go deleted file mode 100644 index ddde28f17f8..00000000000 --- a/gnovm/pkg/packages/download.go +++ /dev/null @@ -1,85 +0,0 @@ -package packages - -import ( - "fmt" - "net/url" - "os" - "path" - "path/filepath" - "strings" - - "github.com/gnolang/gno/gnovm/pkg/gnofiles" - "github.com/gnolang/gno/tm2/pkg/bft/rpc/client" - "github.com/gnolang/gno/tm2/pkg/commands" -) - -func DownloadModule(io commands.IO, pkgPath string, dst string) error { - modFilePath := filepath.Join(dst, gnofiles.ModfileName) - if _, err := os.Stat(modFilePath); os.IsNotExist(err) { - io.ErrPrintfln("gno: downloading %s", pkgPath) - - // create client from pkgpath - parts := strings.Split(pkgPath, "/") - if len(parts) < 1 { - return fmt.Errorf("bad pkg path %q", pkgPath) - } - // XXX: retrieve host/port from r/sys/zones. - rpcURL := (&url.URL{ - Scheme: "https", - Host: "rpc." + parts[0] + ":443", - }).String() - tmClient, err := client.NewHTTPClient(rpcURL) - if err != nil { - return fmt.Errorf("failed to instantiate tm2 client with remote %q: %w", rpcURL, err) - } - defer tmClient.Close() - - // fetch files - data, err := qfile(tmClient, pkgPath) - if err != nil { - return fmt.Errorf("failed to query files list for pkg %q: %w", pkgPath, err) - } - if err := os.MkdirAll(dst, 0o744); err != nil { - return fmt.Errorf("failed to create cache dir for %q at %q: %w", pkgPath, dst, err) - } - files := strings.Split(string(data), "\n") - for _, file := range files { - filePath := path.Join(pkgPath, file) - data, err := qfile(tmClient, filePath) - if err != nil { - return fmt.Errorf("failed to query package file %q: %w", filePath, err) - } - dst := filepath.Join(dst, file) - if err := os.WriteFile(dst, data, 0o644); err != nil { - return fmt.Errorf("failed to write file at %q: %w", dst, err) - } - } - - // write gno.mod - if err := os.WriteFile(modFilePath, []byte("module "+pkgPath+"\n"), 0o644); err != nil { - return fmt.Errorf("failed to write modfile at %q: %w", modFilePath, err) - } - } else if err != nil { - return fmt.Errorf("failed to stat downloaded module %q at %q: %w", pkgPath, dst, err) - } - - // modfile exists in modcache, do nothing - - return nil -} - -// not using gno client due to cyclic dep -func qfile(tmClient client.Client, pkgPath string) ([]byte, error) { - path := "vm/qfile" - data := []byte(pkgPath) - - qres, err := tmClient.ABCIQuery(path, data) - if err != nil { - return nil, fmt.Errorf("query qfile: %w", err) - } - if qres.Response.Error != nil { - return nil, fmt.Errorf("qfile failed: %w\nlog:\n%s", qres.Response.Error, qres.Response.Log) - } - - return qres.Response.Data, nil -} diff --git a/gnovm/cmd/gno/download_deps.go b/gnovm/pkg/packages/download_deps.go similarity index 63% rename from gnovm/cmd/gno/download_deps.go rename to gnovm/pkg/packages/download_deps.go index 4e638eb4970..45a704766ac 100644 --- a/gnovm/cmd/gno/download_deps.go +++ b/gnovm/pkg/packages/download_deps.go @@ -1,4 +1,4 @@ -package main +package packages import ( "errors" @@ -7,17 +7,15 @@ import ( "path/filepath" "strings" - "github.com/gnolang/gno/gnovm/cmd/gno/internal/pkgdownload" "github.com/gnolang/gno/gnovm/pkg/gnolang" "github.com/gnolang/gno/gnovm/pkg/gnomod" - "github.com/gnolang/gno/gnovm/pkg/packages" - "github.com/gnolang/gno/tm2/pkg/commands" + "github.com/gnolang/gno/gnovm/pkg/packages/pkgdownload" "golang.org/x/mod/module" ) -// downloadDeps recursively fetches the imports of a local package while following a given gno.mod replace directives -func downloadDeps(io commands.IO, pkgDir string, gnoMod *gnomod.File, fetcher pkgdownload.PackageFetcher) error { - if fetcher == nil { +// DownloadDeps recursively fetches the imports of a local package while following a given gno.mod replace directives +func DownloadDeps(conf *LoadConfig, pkgDir string, gnoMod *gnomod.File) error { + if conf.Fetcher == nil { return errors.New("fetcher is nil") } @@ -25,14 +23,14 @@ func downloadDeps(io commands.IO, pkgDir string, gnoMod *gnomod.File, fetcher pk if err != nil { return fmt.Errorf("read package at %q: %w", pkgDir, err) } - importsMap, err := packages.Imports(pkg, nil) + importsMap, err := Imports(pkg, nil) if err != nil { return fmt.Errorf("read imports at %q: %w", pkgDir, err) } - imports := importsMap.Merge(packages.FileKindPackageSource, packages.FileKindTest, packages.FileKindXTest) + imports := importsMap.Merge(FileKindPackageSource, FileKindTest, FileKindXTest) for _, pkgPath := range imports { - resolved := gnoMod.Resolve(module.Version{Path: pkgPath.PkgPath}) + resolved := gnoMod.Resolve(module.Version{Path: pkgPath}) resolvedPkgPath := resolved.Path if !isRemotePkgPath(resolvedPkgPath) { @@ -41,11 +39,11 @@ func downloadDeps(io commands.IO, pkgDir string, gnoMod *gnomod.File, fetcher pk depDir := gnomod.PackageDir("", module.Version{Path: resolvedPkgPath}) - if err := downloadPackage(io, resolvedPkgPath, depDir, fetcher); err != nil { + if err := downloadPackage(conf, resolvedPkgPath, depDir); err != nil { return fmt.Errorf("download import %q of %q: %w", resolvedPkgPath, pkgDir, err) } - if err := downloadDeps(io, depDir, gnoMod, fetcher); err != nil { + if err := DownloadDeps(conf, depDir, gnoMod); err != nil { return err } } @@ -53,8 +51,8 @@ func downloadDeps(io commands.IO, pkgDir string, gnoMod *gnomod.File, fetcher pk return nil } -// downloadPackage downloads a remote gno package by pkg path and store it at dst -func downloadPackage(io commands.IO, pkgPath string, dst string, fetcher pkgdownload.PackageFetcher) error { +// Download downloads a remote gno package by pkg path and store it at dst +func downloadPackage(conf *LoadConfig, pkgPath string, dst string) error { modFilePath := filepath.Join(dst, "gno.mod") if _, err := os.Stat(modFilePath); err == nil { @@ -64,9 +62,9 @@ func downloadPackage(io commands.IO, pkgPath string, dst string, fetcher pkgdown return fmt.Errorf("stat downloaded module %q at %q: %w", pkgPath, dst, err) } - io.ErrPrintfln("gno: downloading %s", pkgPath) + conf.IO.ErrPrintfln("gno: downloading %s", pkgPath) - if err := pkgdownload.Download(pkgPath, dst, fetcher); err != nil { + if err := pkgdownload.Download(pkgPath, dst, conf.Fetcher); err != nil { return err } diff --git a/gnovm/cmd/gno/download_deps_test.go b/gnovm/pkg/packages/download_deps_test.go similarity index 94% rename from gnovm/cmd/gno/download_deps_test.go rename to gnovm/pkg/packages/download_deps_test.go index 0828e9b2245..b2d75787ee5 100644 --- a/gnovm/cmd/gno/download_deps_test.go +++ b/gnovm/pkg/packages/download_deps_test.go @@ -1,4 +1,4 @@ -package main +package packages import ( "bytes" @@ -7,8 +7,8 @@ import ( "path/filepath" "testing" - "github.com/gnolang/gno/gnovm/cmd/gno/internal/pkgdownload/examplespkgfetcher" "github.com/gnolang/gno/gnovm/pkg/gnomod" + "github.com/gnolang/gno/gnovm/pkg/packages/pkgdownload/examplespkgfetcher" "github.com/gnolang/gno/tm2/pkg/commands" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -115,8 +115,10 @@ func TestDownloadDeps(t *testing.T) { fetcher := examplespkgfetcher.New() + conf := &LoadConfig{IO: io, Fetcher: fetcher} + // gno: downloading dependencies - err = downloadDeps(io, dirPath, &tc.modFile, fetcher) + err = DownloadDeps(conf, dirPath, &tc.modFile) if tc.errorShouldContain != "" { require.ErrorContains(t, err, tc.errorShouldContain) } else { @@ -142,7 +144,7 @@ func TestDownloadDeps(t *testing.T) { mockErr.Reset() // Try fetching again. Should be cached - downloadDeps(io, dirPath, &tc.modFile, fetcher) + DownloadDeps(conf, dirPath, &tc.modFile) for _, c := range tc.ioErrContains { assert.NotContains(t, mockErr.String(), c) } diff --git a/gnovm/pkg/packages/expand_patterns.go b/gnovm/pkg/packages/expand_patterns.go new file mode 100644 index 00000000000..5a4c6a72e3c --- /dev/null +++ b/gnovm/pkg/packages/expand_patterns.go @@ -0,0 +1,174 @@ +package packages + +import ( + "errors" + "fmt" + "io/fs" + "os" + "path" + "path/filepath" + "slices" + "strings" + + "github.com/gnolang/gno/gnovm/pkg/gnomod" + "golang.org/x/mod/module" +) + +/* +REARCH: +- patterns: + - single file -> not supported + - local directory -> add as package + - remote -> download and add dst + - recursive local -> walk directories at the pattern and select the ones that contain gno.mod or .gno files and add them as packages + - recursive remote -> not supported +*/ + +type pkgMatch struct { + Dir string + Match []string +} + +func expandPatterns(conf *LoadConfig, patterns ...string) ([]*pkgMatch, error) { + pkgMatches := []*pkgMatch{} + + addMatch := func(dir string, match string) { + idx := slices.IndexFunc(pkgMatches, func(sum *pkgMatch) bool { return sum.Dir == dir }) + if idx == -1 { + pkgMatches = append(pkgMatches, &pkgMatch{Dir: dir, Match: []string{match}}) + return + } + if slices.Contains(pkgMatches[idx].Match, match) { + return + } + pkgMatches[idx].Match = append(pkgMatches[idx].Match, match) + } + + for _, match := range patterns { + patKind, err := getPatternKind(match) + if err != nil { + return nil, fmt.Errorf("%s: %w", match, err) + } + + switch patKind { + case patternKindSingleFile: + return nil, fmt.Errorf("%s: single file patterns are not supported", match) + + case patternKindRecursiveRemote: + return nil, fmt.Errorf("%s: recursive remote patterns are not supported", match) + } + + pat, err := cleanPattern(match, patKind) + if err != nil { + return nil, fmt.Errorf("%s: %w", match, err) + } + + switch patKind { + case patternKindDirectory: + addMatch(pat, match) + + case patternKindRemote: + dir := gnomod.PackageDir("", module.Version{Path: pat}) + if err := downloadPackage(conf, pat, dir); err != nil { + return nil, err + } + addMatch(dir, match) + + case patternKindRecursiveLocal: + dirs, err := expandRecursive(pat) + if err != nil { + return nil, fmt.Errorf("%s: %w", pat, err) + } + for _, dir := range dirs { + addMatch(dir, match) + } + } + } + + return pkgMatches, nil +} + +func expandRecursive(pat string) ([]string, error) { + root, _ := filepath.Split(pat) + + info, err := os.Stat(root) + if err != nil { + return nil, err + } + + if !info.IsDir() { + return nil, errors.New("glob root is not a directory") + } + + // we swallow errors after this point as we want the most packages we can get + dirs := []string{} + _ = fs.WalkDir(os.DirFS(root), ".", func(path string, d fs.DirEntry, err error) error { + if err != nil { + return nil + } + if d.IsDir() { + return nil + } + dir, base := filepath.Split(path) + dir = filepath.Join(root, dir) + if slices.Contains(dirs, dir) { + return nil + } + if base == "gno.mod" || strings.HasSuffix(base, ".gno") { + dirs = append(dirs, dir) + } + return nil + }) + + return dirs, nil +} + +type patternKind int + +const ( + patternKindUnknown = iota + patternKindSingleFile + patternKindDirectory + patternKindRecursiveLocal + patternKindRemote + patternKindRecursiveRemote +) + +func getPatternKind(pat string) (patternKind, error) { + isLitteral := patternIsLiteral(pat) + + if patternIsRemote(pat) { + if isLitteral { + return patternKindRemote, nil + } + if filepath.Base(pat) != "..." { + return patternKindUnknown, fmt.Errorf("%s: partial globs are not supported", pat) + } + return patternKindRecursiveRemote, nil + } + + if !isLitteral { + return patternKindRecursiveLocal, nil + } + + info, err := os.Stat(pat) + if err != nil { + return patternKindUnknown, err + } + if info.IsDir() { + return patternKindDirectory, nil + } + + return patternKindSingleFile, nil +} + +func cleanPattern(pat string, kind patternKind) (string, error) { + switch kind { + case patternKindSingleFile, patternKindDirectory, patternKindRecursiveLocal: + return filepath.Abs(pat) + case patternKindRemote, patternKindRecursiveRemote: + return path.Clean(pat), nil + default: + return "", fmt.Errorf("unknown pattern kind %d", kind) + } +} diff --git a/gnovm/pkg/packages/filekind.go b/gnovm/pkg/packages/filekind.go index ed2ca84b7d0..382d1d12af7 100644 --- a/gnovm/pkg/packages/filekind.go +++ b/gnovm/pkg/packages/filekind.go @@ -16,14 +16,14 @@ import ( // - [FileKindXTest] -> A *_test.gno file with a package name ending in _test that will be used for blackbox testing // // - [FileKindFiletest] -> A *_filetest.gno file that will be used for snapshot testing -type FileKind uint +type FileKind string const ( - FileKindUnknown FileKind = iota - FileKindPackageSource - FileKindTest - FileKindXTest - FileKindFiletest + FileKindUnknown FileKind = "" + FileKindPackageSource = "PackageSource" + FileKindTest = "Test" + FileKindXTest = "XTest" + FileKindFiletest = "Filetest" ) // GetFileKind analyzes a file's name and body to get it's [FileKind], fset is optional diff --git a/gnovm/pkg/packages/imports.go b/gnovm/pkg/packages/imports.go index d42ee2e9935..4820dc03b7a 100644 --- a/gnovm/pkg/packages/imports.go +++ b/gnovm/pkg/packages/imports.go @@ -2,7 +2,6 @@ package packages import ( "fmt" - "go/ast" "go/parser" "go/token" "sort" @@ -32,14 +31,14 @@ func Imports(pkg *gnovm.MemPackage, fset *token.FileSet) (ImportsMap, error) { return nil, err } for _, im := range imports { - if _, ok := seen[fileKind][im.PkgPath]; ok { + if _, ok := seen[fileKind][im]; ok { continue } res[fileKind] = append(res[fileKind], im) if _, ok := seen[fileKind]; !ok { seen[fileKind] = make(map[string]struct{}, 16) } - seen[fileKind][im.PkgPath] = struct{}{} + seen[fileKind][im] = struct{}{} } } @@ -50,15 +49,9 @@ func Imports(pkg *gnovm.MemPackage, fset *token.FileSet) (ImportsMap, error) { return res, nil } -// FileImport represents an import -type FileImport struct { - PkgPath string - Spec *ast.ImportSpec -} - // FileImports returns the list of gno imports in the given file src. // The given filename is only used when recording position information. -func FileImports(filename string, src string, fset *token.FileSet) ([]FileImport, error) { +func FileImports(filename string, src string, fset *token.FileSet) ([]string, error) { if fset == nil { fset = token.NewFileSet() } @@ -66,7 +59,7 @@ func FileImports(filename string, src string, fset *token.FileSet) ([]FileImport if err != nil { return nil, err } - res := make([]FileImport, len(f.Imports)) + res := make([]string, len(f.Imports)) for i, im := range f.Imports { importPath, err := strconv.Unquote(im.Path.Value) if err != nil { @@ -75,27 +68,24 @@ func FileImports(filename string, src string, fset *token.FileSet) ([]FileImport panic(fmt.Errorf("%v: unexpected invalid import path: %v", fset.Position(im.Pos()).String(), im.Path.Value)) } - res[i] = FileImport{ - PkgPath: importPath, - Spec: im, - } + res[i] = importPath } return res, nil } -type ImportsMap map[FileKind][]FileImport +type ImportsMap map[FileKind][]string // Merge merges imports, it removes duplicates and sorts the result -func (imap ImportsMap) Merge(kinds ...FileKind) []FileImport { - res := make([]FileImport, 0, 16) +func (imap ImportsMap) Merge(kinds ...FileKind) []string { + res := make([]string, 0, 16) seen := make(map[string]struct{}, 16) for _, kind := range kinds { for _, im := range imap[kind] { - if _, ok := seen[im.PkgPath]; ok { + if _, ok := seen[im]; ok { continue } - seen[im.PkgPath] = struct{}{} + seen[im] = struct{}{} res = append(res, im) } @@ -105,9 +95,9 @@ func (imap ImportsMap) Merge(kinds ...FileKind) []FileImport { return res } -func sortImports(imports []FileImport) { +func sortImports(imports []string) { sort.Slice(imports, func(i, j int) bool { - return imports[i].PkgPath < imports[j].PkgPath + return imports[i] < imports[j] }) } diff --git a/gnovm/pkg/packages/imports_test.go b/gnovm/pkg/packages/imports_test.go index 35b98d86ac7..0267f46e17a 100644 --- a/gnovm/pkg/packages/imports_test.go +++ b/gnovm/pkg/packages/imports_test.go @@ -154,7 +154,7 @@ func TestImports(t *testing.T) { for key, vals := range importsMap { stringVals := make([]string, len(vals)) for i, val := range vals { - stringVals[i] = val.PkgPath + stringVals[i] = val } got[key] = stringVals } diff --git a/gnovm/pkg/packages/load.go b/gnovm/pkg/packages/load.go new file mode 100644 index 00000000000..21d8d54b1dc --- /dev/null +++ b/gnovm/pkg/packages/load.go @@ -0,0 +1,149 @@ +package packages + +import ( + "errors" + "fmt" + "os" + "path/filepath" + + "github.com/gnolang/gno/gnovm" + "github.com/gnolang/gno/gnovm/pkg/gnolang" + "github.com/gnolang/gno/gnovm/pkg/gnomod" + "github.com/gnolang/gno/gnovm/pkg/packages/pkgdownload" + "github.com/gnolang/gno/gnovm/pkg/packages/pkgdownload/rpcpkgfetcher" + "github.com/gnolang/gno/tm2/pkg/commands" + "golang.org/x/mod/module" +) + +type LoadConfig struct { + IO commands.IO + Fetcher pkgdownload.PackageFetcher + Deps bool +} + +func (conf *LoadConfig) applyDefaults() { + if conf.IO == nil { + conf.IO = commands.NewDefaultIO() + } + if conf.Fetcher == nil { + conf.Fetcher = rpcpkgfetcher.New(nil) + } +} + +func Load(conf *LoadConfig, patterns ...string) ([]*Package, error) { + conf.applyDefaults() + + expanded, err := expandPatterns(conf, patterns...) + if err != nil { + return nil, err + } + + pkgs := readPackages(expanded) + + if !conf.Deps { + return pkgs, nil + } + + byPkgPath := make(map[string]*Package) + for _, pkg := range pkgs { + if pkg.ImportPath == "" { + continue + } + if _, ok := byPkgPath[pkg.ImportPath]; ok { + continue + } + byPkgPath[pkg.ImportPath] = pkg + } + + visited := map[string]struct{}{} + list := []*Package{} + for pile := pkgs; len(pile) > 0; pile = pile[1:] { + pkg := pile[0] + if _, ok := visited[pkg.ImportPath]; ok { + continue + } + visited[pkg.ImportPath] = struct{}{} + + for _, imp := range pkg.Imports.Merge(FileKindPackageSource, FileKindTest, FileKindXTest, FileKindFiletest) { + if gnolang.IsStdlib(imp) { + continue + } + + if _, ok := byPkgPath[imp]; ok { + continue + } + + // must download + + dir := gnomod.PackageDir("", module.Version{Path: imp}) + if err := downloadPackage(conf, imp, dir); err != nil { + pkg.Error = errors.Join(pkg.Error, err) + byPkgPath[imp] = nil // stop trying to download pkg + continue + } + + byPkgPath[imp] = readPkg(dir) + } + + list = append(list, pkg) + } + + for _, pkg := range list { + // TODO: this could be optimized + pkg.Deps, err = listDeps(pkg.ImportPath, byPkgPath) + if err != nil { + pkg.Error = errors.Join(pkg.Error, err) + } + } + + return list, nil +} + +func listDeps(target string, pkgs map[string]*Package) ([]string, error) { + deps := []string{} + err := listDepsRecursive(target, target, pkgs, &deps, make(map[string]struct{})) + return deps, err +} + +func listDepsRecursive(rootTarget string, target string, pkgs map[string]*Package, deps *[]string, visited map[string]struct{}) error { + if _, ok := visited[target]; ok { + return nil + } + pkg := pkgs[target] + if pkg == nil { + return fmt.Errorf("package %s not found", target) + } + visited[target] = struct{}{} + var outErr error + for _, imp := range pkg.Imports.Merge(FileKindPackageSource, FileKindTest, FileKindXTest, FileKindFiletest) { + err := listDepsRecursive(rootTarget, imp, pkgs, deps, visited) + if err != nil { + outErr = errors.Join(outErr, err) + } + } + if target != rootTarget { + (*deps) = append(*deps, target) + } + return outErr +} + +func (p *Package) MemPkg() (*gnovm.MemPackage, error) { + files := []*gnovm.MemFile{} + for _, cat := range p.Files { + for _, f := range cat { + body, err := os.ReadFile(filepath.Join(p.Dir, f)) + if err != nil { + return nil, err + } + files = append(files, &gnovm.MemFile{ + Name: f, + Body: string(body), + }) + } + } + return &gnovm.MemPackage{ + Name: p.Name, + Path: p.ImportPath, + Files: files, + }, nil +} diff --git a/gnovm/pkg/packages/patterns.go b/gnovm/pkg/packages/patterns.go index b832b59dda9..e139f6ace15 100644 --- a/gnovm/pkg/packages/patterns.go +++ b/gnovm/pkg/packages/patterns.go @@ -1,41 +1,27 @@ package packages import ( - "path/filepath" "strings" ) -// PathIsLocalImport reports whether the import path is -// a local import path, like ".", "..", "./foo", or "../foo". -func PathIsLocalImport(path string) bool { - return path == "." || path == ".." || - strings.HasPrefix(path, "./") || strings.HasPrefix(path, "../") -} - -// PatternIsLiteral reports whether the pattern is free of wildcards. -// -// A literal pattern must match at most one package. -func PatternIsLiteral(pattern string) bool { - return !strings.Contains(pattern, "...") -} - -// PatternIsLocal reports whether the pattern must be resolved from a specific root or -// directory, such as a filesystem path or a single module. -func PatternIsLocal(pattern string) bool { - return PathIsLocalImport(pattern) || filepath.IsAbs(pattern) -} - -// PatternIsRemote reports whether the pattern is a remote, like "gno.land/p/demo/avl" or "gno.land/r/..." -func PatternIsRemote(pattern string) bool { - if PatternIsLocal(pattern) { +// patternIsRemote reports wether a pattern is starting with a domain +func patternIsRemote(path string) bool { + if len(path) == 0 { return false } - - parts := strings.Split(pattern, "/") - if len(parts) < 2 { + if path[0] == '.' { + return false + } + slashIdx := strings.IndexRune(path, '/') + if slashIdx == -1 { return false } + return strings.ContainsRune(path[:slashIdx], '.') +} - domain := parts[0] - return strings.ContainsRune(domain, '.') +// patternIsLiteral reports whether the pattern is free of wildcards. +// +// A literal pattern must match at most one package. +func patternIsLiteral(pattern string) bool { + return !strings.Contains(pattern, "...") } diff --git a/gnovm/pkg/packages/pkg.go b/gnovm/pkg/packages/pkg.go index 91cf4dba6f7..b8ba57e0612 100644 --- a/gnovm/pkg/packages/pkg.go +++ b/gnovm/pkg/packages/pkg.go @@ -119,9 +119,9 @@ func ListPkgs(root string) (PkgList, error) { imports := make([]string, 0, len(importsRaw)) for _, imp := range importsRaw { // remove self and standard libraries from imports - if imp.PkgPath != gnoMod.Module.Mod.Path && - !gnolang.IsStdlib(imp.PkgPath) { - imports = append(imports, imp.PkgPath) + if imp != gnoMod.Module.Mod.Path && + !gnolang.IsStdlib(imp) { + imports = append(imports, imp) } } diff --git a/gnovm/cmd/gno/internal/pkgdownload/examplespkgfetcher/examplespkgfetcher.go b/gnovm/pkg/packages/pkgdownload/examplespkgfetcher/examplespkgfetcher.go similarity index 95% rename from gnovm/cmd/gno/internal/pkgdownload/examplespkgfetcher/examplespkgfetcher.go rename to gnovm/pkg/packages/pkgdownload/examplespkgfetcher/examplespkgfetcher.go index 1642c62d21e..56b567e9525 100644 --- a/gnovm/cmd/gno/internal/pkgdownload/examplespkgfetcher/examplespkgfetcher.go +++ b/gnovm/pkg/packages/pkgdownload/examplespkgfetcher/examplespkgfetcher.go @@ -8,8 +8,8 @@ import ( "path/filepath" "github.com/gnolang/gno/gnovm" - "github.com/gnolang/gno/gnovm/cmd/gno/internal/pkgdownload" "github.com/gnolang/gno/gnovm/pkg/gnoenv" + "github.com/gnolang/gno/gnovm/pkg/packages/pkgdownload" ) type ExamplesPackageFetcher struct{} diff --git a/gnovm/cmd/gno/internal/pkgdownload/pkgdownload.go b/gnovm/pkg/packages/pkgdownload/pkgdownload.go similarity index 100% rename from gnovm/cmd/gno/internal/pkgdownload/pkgdownload.go rename to gnovm/pkg/packages/pkgdownload/pkgdownload.go diff --git a/gnovm/cmd/gno/internal/pkgdownload/pkgfetcher.go b/gnovm/pkg/packages/pkgdownload/pkgfetcher.go similarity index 100% rename from gnovm/cmd/gno/internal/pkgdownload/pkgfetcher.go rename to gnovm/pkg/packages/pkgdownload/pkgfetcher.go diff --git a/gnovm/cmd/gno/internal/pkgdownload/rpcpkgfetcher/rpcpkgfetcher.go b/gnovm/pkg/packages/pkgdownload/rpcpkgfetcher/rpcpkgfetcher.go similarity index 97% rename from gnovm/cmd/gno/internal/pkgdownload/rpcpkgfetcher/rpcpkgfetcher.go rename to gnovm/pkg/packages/pkgdownload/rpcpkgfetcher/rpcpkgfetcher.go index a71c1d43719..ac219aeb387 100644 --- a/gnovm/cmd/gno/internal/pkgdownload/rpcpkgfetcher/rpcpkgfetcher.go +++ b/gnovm/pkg/packages/pkgdownload/rpcpkgfetcher/rpcpkgfetcher.go @@ -8,7 +8,7 @@ import ( "strings" "github.com/gnolang/gno/gnovm" - "github.com/gnolang/gno/gnovm/cmd/gno/internal/pkgdownload" + "github.com/gnolang/gno/gnovm/pkg/packages/pkgdownload" "github.com/gnolang/gno/tm2/pkg/bft/rpc/client" ) diff --git a/gnovm/cmd/gno/internal/pkgdownload/rpcpkgfetcher/rpcpkgfetcher_test.go b/gnovm/pkg/packages/pkgdownload/rpcpkgfetcher/rpcpkgfetcher_test.go similarity index 100% rename from gnovm/cmd/gno/internal/pkgdownload/rpcpkgfetcher/rpcpkgfetcher_test.go rename to gnovm/pkg/packages/pkgdownload/rpcpkgfetcher/rpcpkgfetcher_test.go diff --git a/gnovm/pkg/packages/resolve.go b/gnovm/pkg/packages/resolve.go deleted file mode 100644 index 35bdfef4e00..00000000000 --- a/gnovm/pkg/packages/resolve.go +++ /dev/null @@ -1,475 +0,0 @@ -package packages - -import ( - "errors" - "fmt" - "go/parser" - "go/token" - "os" - "path" - "path/filepath" - "strings" - - "github.com/gnolang/gno/gnovm" - "github.com/gnolang/gno/gnovm/pkg/gnofiles" - "github.com/gnolang/gno/gnovm/pkg/gnomod" - "github.com/gnolang/gno/tm2/pkg/commands" -) - -type visitTarget struct { - path string - match string -} - -func DiscoverPackages(patterns ...string) ([]*PackageSummary, error) { - wd, err := os.Getwd() - if err != nil { - return nil, err - } - isWorkspace := true - workRoot, err := gnofiles.FindWorkspaceRoot(wd) - if os.IsNotExist(err) { - isWorkspace = false - workRoot, err = gnofiles.FindModuleRoot(wd) - if err != nil { - return nil, errors.New("can't find parent module or workspace") - } - } else if err != nil { - return nil, err - } - - _ = isWorkspace - - toVisit := []visitTarget{} - for _, pattern := range patterns { - toVisit = append(toVisit, visitTarget{path: pattern, match: pattern}) - } - visited := map[visitTarget]struct{}{} - cache := map[string]*PackageSummary{} - packages := []*PackageSummary{} - errs := []error{} - - for len(toVisit) > 0 { - tgt := toVisit[0] - toVisit = toVisit[1:] - - if _, ok := visited[tgt]; ok { - continue - } - visited[tgt] = struct{}{} - - if tgt.path == "" { - // this should not happen, panic? - continue - } - - if PathIsLocalImport(tgt.path) { - absPath, err := filepath.Abs(tgt.path) - if err != nil { - errs = append(errs, fmt.Errorf("failed to get absolute path for %q: %w", tgt, err)) - continue - } - - // check that the path is in current work tree - rel, err := filepath.Rel(workRoot, absPath) - if err != nil { - errs = append(errs, fmt.Errorf("failed to get relative path for %q from %q: %w", tgt, workRoot, err)) - continue - } - if rel == ".." || strings.HasPrefix(rel, "../") { - errs = append(errs, fmt.Errorf("path %q is outside work root %q", tgt.path, workRoot)) - continue - } - - // mark absolute path for visit - toVisit = append(toVisit, visitTarget{path: absPath, match: tgt.match}) - continue - } - - // at this point we should only have absolute or remote paths - if !(filepath.IsAbs(tgt.path) || PatternIsRemote(tgt.path)) { - panic(fmt.Errorf("%q should be an absolute or remote pattern", tgt.path)) - } - - root := convertRecursivePathToDir(tgt.path) - - if PatternIsLiteral(tgt.path) { - if tgt.path[0] != '/' { - pkgPath := tgt.path - if pkg, ok := cache[pkgPath]; ok { - pkg.Match = append(pkg.Match, tgt.match) - } else { - cache[pkgPath] = &PackageSummary{ - PkgPath: pkgPath, - Match: []string{tgt.match}, - } - packages = append(packages, cache[pkgPath]) - } - continue - } - modDir, err := findModDir(root) - if os.IsNotExist(err) { - continue - } else if err != nil { - return nil, fmt.Errorf("failed to find parent module: %w", err) - } - modFilePath := filepath.Join(modDir, gnofiles.ModfileName) - modFile, err := gnomod.ParseAt(modDir) - if err != nil { - errs = append(errs, fmt.Errorf("failed to parse modfile for %q: %w", modDir, err)) - continue - } - if modFile == nil || modFile.Module == nil || modFile.Draft { - continue - } - globalPkgPath := modFile.Module.Mod.Path - relfpath, err := filepath.Rel(modDir, tgt.path) - if err != nil { - return nil, fmt.Errorf("failed to get pkg path relative to mod root: %w", err) - } - relpath := strings.Join(filepath.SplitList(relfpath), "/") - absroot, err := filepath.Abs(root) - if err != nil { - return nil, fmt.Errorf("failed to get absolute pkg root") - } - pkgPath := path.Join(globalPkgPath, relpath) - if pkg, ok := cache[pkgPath]; ok { - pkg.Match = append(pkg.Match, tgt.match) - } else { - cache[pkgPath] = &PackageSummary{ - PkgPath: path.Join(globalPkgPath, relpath), - Root: absroot, - Module: &Module{ - Path: globalPkgPath, - Dir: modDir, - GnoMod: modFilePath, - }, - Match: []string{tgt.match}, - } - packages = append(packages, cache[pkgPath]) - } - continue - } - - // at this point we should only have recursive patterns - if PatternIsLiteral(tgt.path) { - panic(fmt.Errorf("%q should be a recursive pattern", tgt.path)) - } - - dirEntry, err := os.ReadDir(root) - if err != nil { - errs = append(errs, err) - continue - } - - hasGnoFiles := false - for _, entry := range dirEntry { - fileName := entry.Name() - if entry.IsDir() { - dirPath := filepath.Join(root, fileName) + gnofiles.RecursiveSuffix - toVisit = append(toVisit, visitTarget{path: dirPath, match: tgt.match}) - } - if !hasGnoFiles && gnofiles.IsGnoFile(fileName) { - hasGnoFiles = true - } - } - - if hasGnoFiles { - toVisit = append(toVisit, visitTarget{path: root, match: tgt.match}) - } - } - - return packages, errors.Join(errs...) -} - -type PackageSummary struct { - PkgPath string - Root string - Module *Module - Match []string -} - -// FIXME: support files -func Load(io commands.IO, paths ...string) ([]*Package, error) { - pkgs, err := DiscoverPackages(paths...) - if err != nil { - return nil, err - } - - visited := map[string]struct{}{} - cache := make(map[string]*Package) - list := []*Package{} - errs := []error{} - for pile := pkgs; len(pile) > 0; pile = pile[1:] { - pkgSum := pile[0] - if _, ok := visited[pkgSum.PkgPath]; ok { - continue - } - visited[pkgSum.PkgPath] = struct{}{} - - pkg, err := resolvePackage(io, pkgSum) - if err != nil { - pkg = &Package{ - ImportPath: pkgSum.PkgPath, - Dir: pkgSum.Root, - Match: pkgSum.Match, - } - pkg.Errors = append(pkg.Errors, fmt.Errorf("failed to resolve package %q: %w", pkgSum.PkgPath, err)) - } - - // TODO: what about TestImports - for _, imp := range pkg.Imports { - pile = append(pile, &PackageSummary{ - PkgPath: imp, - }) - } - - cache[pkg.ImportPath] = pkg - list = append(list, pkg) - } - - for _, pkg := range list { - if len(pkg.Errors) > 0 { - continue - } - // TODO: this could be optimized - pkg.Deps, err = listDeps(pkg.ImportPath, cache) - if err != nil { - pkg.Errors = append(pkg.Errors, err) - } - } - - return list, errors.Join(errs...) -} - -func listDeps(target string, pkgs map[string]*Package) ([]string, error) { - deps := []string{} - err := listDepsRecursive(target, target, pkgs, &deps, make(map[string]struct{})) - return deps, err -} - -func listDepsRecursive(rootTarget string, target string, pkgs map[string]*Package, deps *[]string, visited map[string]struct{}) error { - if _, ok := visited[target]; ok { - return nil - } - pkg, ok := pkgs[target] - if !ok { - return fmt.Errorf("%s not found in cache", target) - } - visited[target] = struct{}{} - var errs []error - for _, imp := range pkg.Imports { - err := listDepsRecursive(rootTarget, imp, pkgs, deps, visited) - if err != nil { - errs = append(errs, err) - } - } - if target != rootTarget { - (*deps) = append(*deps, target) - } - return errors.Join(errs...) -} - -func resolvePackage(io commands.IO, meta *PackageSummary) (*Package, error) { - if meta.Root == "" { - if !strings.ContainsRune(meta.PkgPath, '.') { - return resolveStdlib(meta) - } else { - return resolveRemote(io, meta) - } - } - - if meta.Module == nil { - return nil, errors.New("unexpected nil module") - } - - return fillPackage(meta) -} - -func resolveStdlib(ometa *PackageSummary) (*Package, error) { - meta := *ometa - gnoRoot := defaultConfig().RootDir - parts := strings.Split(meta.PkgPath, "/") - meta.Root = filepath.Join(append([]string{gnoRoot, "gnovm", "stdlibs"}, parts...)...) - return fillPackage(&meta) -} - -// Does not fill deps -func resolveRemote(io commands.IO, ometa *PackageSummary) (*Package, error) { - meta := *ometa - - homeDir := defaultConfig().GnoHome - - modCache := filepath.Join(homeDir, "pkg", "mod") - meta.Root = filepath.Join(modCache, meta.PkgPath) - if err := DownloadModule(io, meta.PkgPath, meta.Root); err != nil { - return nil, fmt.Errorf("failed to download module %q: %w", meta.PkgPath, err) - } - modFilePath := filepath.Join(meta.Root, gnofiles.ModfileName) - meta.Module = &Module{ - Path: meta.PkgPath, - Dir: meta.Root, - GnoMod: modFilePath, - } - - pkg, err := fillPackage(&meta) - if err != nil { - return nil, fmt.Errorf("failed to fill Package %q: %w", meta.PkgPath, err) - } - - return pkg, nil -} - -func fillPackage(meta *PackageSummary) (*Package, error) { - fsFiles := []string{} - files := []string{} - fsTestFiles := []string{} - testFiles := []string{} - fsFiletestFiles := []string{} - filetestFiles := []string{} - - pkgDir := meta.Root - pkgPath := meta.PkgPath - - dir, err := os.ReadDir(pkgDir) - if err != nil { - return nil, fmt.Errorf("failed to read module files list: %w", err) - } - for _, entry := range dir { - if entry.IsDir() { - continue - } - - fileName := entry.Name() - if gnofiles.IsGnoTestFile(fileName) { - fsTestFiles = append(fsTestFiles, filepath.Join(pkgDir, fileName)) - testFiles = append(testFiles, fileName) - } else if gnofiles.IsGnoFiletestFile(fileName) { - fsFiletestFiles = append(fsFiletestFiles, filepath.Join(pkgDir, fileName)) - filetestFiles = append(filetestFiles, fileName) - } else if gnofiles.IsGnoFile(fileName) { - fsFiles = append(fsFiles, filepath.Join(pkgDir, fileName)) - files = append(files, fileName) - } - } - name, imports, err := resolveNameAndImports(fsFiles) - if err != nil { - return nil, fmt.Errorf("failed to resolve name and imports for %q: %w", pkgPath, err) - } - _, testImports, err := resolveNameAndImports(fsTestFiles) - if err != nil { - return nil, fmt.Errorf("failed to resolve test name and imports for %q: %w", pkgPath, err) - } - _, filetestImports, err := resolveNameAndImports(fsFiletestFiles) - if err != nil { - return nil, fmt.Errorf("failed to resolve filetest name and imports for %q: %w", pkgPath, err) - } - - module := Module{} - if meta.Module != nil { - module = *meta.Module - } - - return &Package{ - ImportPath: pkgPath, - Dir: pkgDir, - Name: name, - Module: module, - Match: meta.Match, - GnoFiles: files, - Imports: imports, - TestGnoFiles: testFiles, - TestImports: testImports, - FiletestGnoFiles: filetestFiles, - FiletestImports: filetestImports, - }, nil -} - -func (p *Package) MemPkg() (*gnovm.MemPackage, error) { - allFiles := append(p.GnoFiles, p.TestGnoFiles...) - allFiles = append(allFiles, p.FiletestGnoFiles...) - files := make([]*gnovm.MemFile, len(allFiles)) - for i, f := range allFiles { - body, err := os.ReadFile(filepath.Join(p.Dir, f)) - if err != nil { - return nil, err - } - files[i] = &gnovm.MemFile{ - Name: f, - Body: string(body), - } - } - return &gnovm.MemPackage{ - Name: p.Name, - Path: p.ImportPath, - Files: files, - }, nil -} - -func resolveNameAndImports(gnoFiles []string) (string, []string, error) { - names := map[string]int{} - imports := map[string]struct{}{} - bestName := "" - bestNameCount := 0 - for _, srcPath := range gnoFiles { - src, err := os.ReadFile(srcPath) - if err != nil { - return "", nil, fmt.Errorf("failed to read file %q: %w", srcPath, err) - } - fset := token.NewFileSet() - f, err := parser.ParseFile(fset, srcPath, src, - // SkipObjectResolution -- unused here. - // ParseComments -- so that they show up when re-building the AST. - parser.SkipObjectResolution|parser.ImportsOnly) - if err != nil { - return "", nil, fmt.Errorf("parse: %w", err) - } - name := f.Name.String() - names[name] += 1 - count := names[name] - if count > bestNameCount { - bestName = name - bestNameCount = count - } - for _, imp := range f.Imports { - importPath := imp.Path.Value - // trim quotes - if len(importPath) >= 2 { - importPath = importPath[1 : len(importPath)-1] - } - imports[importPath] = struct{}{} - } - } - importsSlice := make([]string, len(imports)) - i := 0 - for imp := range imports { - importsSlice[i] = imp - i++ - } - return bestName, importsSlice, nil -} - -func convertRecursivePathToDir(p string) string { - if p == "..." { - return "." - } - return strings.TrimSuffix(p, gnofiles.RecursiveSuffix) -} - -func findModDir(dir string) (string, error) { - dir = filepath.Clean(dir) - - potentialMod := filepath.Join(dir, gnofiles.ModfileName) - - if _, err := os.Stat(potentialMod); os.IsNotExist(err) { - parent, file := filepath.Split(dir) - if file == "" || (parent == "" && file == ".") { - return "", os.ErrNotExist - } - return findModDir(parent) - } else if err != nil { - return "", err - } - - return filepath.Clean(dir), nil -} diff --git a/gnovm/pkg/packages/types.go b/gnovm/pkg/packages/types.go index 4fcdb117726..204b94eeb98 100644 --- a/gnovm/pkg/packages/types.go +++ b/gnovm/pkg/packages/types.go @@ -1,28 +1,38 @@ package packages +import "sort" + // ported from https://cs.opensource.google/go/go/+/refs/tags/go1.23.2:src/cmd/go/internal/load/pkg.go type Package struct { - Dir string `json:",omitempty"` // directory containing package sources - ImportPath string `json:",omitempty"` // import path of package in dir - Name string `json:",omitempty"` // package name - Root string `json:",omitempty"` // Gno root, Gno path dir, or module root dir containing this package - Module Module `json:",omitempty"` // info about package's module, if any - Match []string `json:",omitempty"` // command-line patterns matching this package - GnoFiles []string `json:",omitempty"` // .gno source files (excluding TestGnoFiles, FiletestGnoFiles) - Imports []string `json:",omitempty"` // import paths used by this package - Deps []string `json:",omitempty"` // all (recursively) imported dependencies - TestGnoFiles []string `json:",omitempty"` // _test.gno files in package - TestImports []string `json:",omitempty"` // imports from TestGnoFiles - FiletestGnoFiles []string `json:",omitempty"` // _filetest.gno files in package - FiletestImports []string `json:",omitempty"` // imports from FiletestGnoFiles - Errors []error `json:",omitempty"` // error loading this package (not dependencies) + Dir string `json:",omitempty"` // directory containing package sources + ImportPath string `json:",omitempty"` // import path of package in dir + Name string `json:",omitempty"` // package name + Root string `json:",omitempty"` // Gno root, Gno path dir, or module root dir containing this package + ModPath string + Match []string `json:",omitempty"` // command-line patterns matching this package + Error error `json:",omitempty"` // error loading this package (not dependencies) + Draft bool + Files FilesMap + Imports ImportsMap `json:",omitempty"` // import paths used by this package + Deps []string `json:",omitempty"` // all (recursively) imported dependencies +} + +type FilesMap map[FileKind][]string + +// Merge merges imports, it removes duplicates and sorts the result +func (imap FilesMap) Merge(kinds ...FileKind) []string { + res := make([]string, 0, 16) + + for _, kind := range kinds { + res = append(res, imap[kind]...) + } - Draft bool + sortPaths(res) + return res } -// ported from https://cs.opensource.google/go/go/+/refs/tags/go1.23.2:src/cmd/go/internal/modinfo/info.go -type Module struct { - Path string `json:",omitempty"` // module path - Dir string `json:",omitempty"` // directory holding local copy of files, if any - GnoMod string `json:",omitempty"` // path to gno.mod file describing module, if any +func sortPaths(imports []string) { + sort.Slice(imports, func(i, j int) bool { + return imports[i] < imports[j] + }) } diff --git a/gnovm/pkg/test/imports.go b/gnovm/pkg/test/imports.go index a8dd709e501..96d04837a1b 100644 --- a/gnovm/pkg/test/imports.go +++ b/gnovm/pkg/test/imports.go @@ -248,14 +248,14 @@ func LoadImports(store gno.Store, memPkg *gnovm.MemPackage) (err error) { } imports := importsMap.Merge(packages.FileKindPackageSource, packages.FileKindTest, packages.FileKindXTest) for _, imp := range imports { - if gno.IsRealmPath(imp.PkgPath) { + if gno.IsRealmPath(imp) { // Don't eagerly load realms. // Realms persist state and can change the state of other realms in initialization. continue } - pkg := store.GetPackage(imp.PkgPath, true) + pkg := store.GetPackage(imp, true) if pkg == nil { - return fmt.Errorf("%v: unknown import path %v", fset.Position(imp.Spec.Pos()).String(), imp.PkgPath) + return fmt.Errorf("unknown import path %v", imp) } } return nil From 68b245366e74a6b11f2552dc3835ce94c2c5adfc Mon Sep 17 00:00:00 2001 From: Norman Date: Wed, 8 Jan 2025 05:40:24 +0100 Subject: [PATCH 031/143] feat: improve Signed-off-by: Norman --- examples/Makefile | 18 +++--- gnovm/cmd/gno/lint.go | 40 ++++++++++--- gnovm/cmd/gno/list.go | 4 -- gnovm/cmd/gno/run.go | 2 +- gnovm/cmd/gno/test.go | 60 +++++++++++++------- gnovm/cmd/gno/transpile.go | 9 +-- gnovm/pkg/packages/analyze_packages.go | 17 ++++-- gnovm/pkg/packages/load.go | 65 +++++++++++++++++----- gnovm/pkg/repl/repl.go | 2 +- gnovm/pkg/test/imports.go | 35 ++++++++++-- gnovm/pkg/test/test.go | 9 ++- gnovm/tests/integ/invalid_gno_file/gno.mod | 2 +- 12 files changed, 189 insertions(+), 74 deletions(-) diff --git a/examples/Makefile b/examples/Makefile index cdc73ee6b3a..7708417d994 100644 --- a/examples/Makefile +++ b/examples/Makefile @@ -23,21 +23,21 @@ GOIMPORTS_FLAGS ?= $(GOFMT_FLAGS) GOTEST_FLAGS ?= -v -p 1 -timeout=30m # Official packages (non-overridable): more reliable and tested modules, distinct from the experimentation area. -OFFICIAL_PACKAGES = ./gno.land/p -OFFICIAL_PACKAGES += ./gno.land/r/demo -OFFICIAL_PACKAGES += ./gno.land/r/gnoland -OFFICIAL_PACKAGES += ./gno.land/r/sys -OFFICIAL_PACKAGES += ./gno.land/r/gov +OFFICIAL_PACKAGES = ./gno.land/p/... +OFFICIAL_PACKAGES += ./gno.land/r/demo/... +OFFICIAL_PACKAGES += ./gno.land/r/gnoland/... +OFFICIAL_PACKAGES += ./gno.land/r/sys/... +OFFICIAL_PACKAGES += ./gno.land/r/gov/... ######################################## # Dev tools .PHONY: transpile transpile: - go run ../gnovm/cmd/gno transpile -v . + go run ../gnovm/cmd/gno transpile -v ./... .PHONY: build build: - go run ../gnovm/cmd/gno transpile -v --gobuild . + go run ../gnovm/cmd/gno transpile -v --gobuild ./... .PHONY: test test: @@ -45,7 +45,7 @@ test: .PHONY: lint lint: - go run ../gnovm/cmd/gno lint -v $(OFFICIAL_PACKAGES) + go run ../gnovm/cmd/gno lint --root-examples -v $(OFFICIAL_PACKAGES) .PHONY: test.sync test.sync: @@ -58,7 +58,7 @@ clean: .PHONY: fmt GNOFMT_FLAGS ?= -w fmt: - go run ../gnovm/cmd/gno fmt $(GNOFMT_FLAGS) ./... + go run ../gnovm/cmd/gno fmt $(GNOFMT_FLAGS) . .PHONY: tidy tidy: diff --git a/gnovm/cmd/gno/lint.go b/gnovm/cmd/gno/lint.go index 07b76840ecf..c8d7ee30bb4 100644 --- a/gnovm/cmd/gno/lint.go +++ b/gnovm/cmd/gno/lint.go @@ -24,8 +24,9 @@ import ( ) type lintCfg struct { - verbose bool - rootDir string + verbose bool + rootDir string + rootExamples bool // min_confidence: minimum confidence of a problem to print it (default 0.8) // auto-fix: apply suggested fixes automatically. } @@ -51,6 +52,7 @@ func (c *lintCfg) RegisterFlags(fs *flag.FlagSet) { fs.BoolVar(&c.verbose, "v", false, "verbose output when lintning") fs.StringVar(&c.rootDir, "root-dir", rootdir, "clone location of github.com/gnolang/gno (gno tries to guess it)") + fs.BoolVar(&c.rootExamples, "root-examples", false, "use the examples present in GNOROOT rather than downloading them") } type lintCode int @@ -91,8 +93,12 @@ func execLint(cfg *lintCfg, args []string, io commands.IO) error { rootDir = gnoenv.RootDir() } - conf := &packages.LoadConfig{IO: io, Fetcher: testPackageFetcher} - pkgs, err := packages.Load(conf, args...) + loadCfg := &packages.LoadConfig{IO: io, Fetcher: testPackageFetcher} + if cfg.rootExamples { + loadCfg.GnorootExamples = true + } + + pkgs, err := packages.Load(loadCfg, args...) if err != nil { return fmt.Errorf("list packages from args: %w", err) } @@ -105,7 +111,7 @@ func execLint(cfg *lintCfg, args []string, io commands.IO) error { hasError := false bs, ts := test.Store( - rootDir, false, + rootDir, pkgsMap, false, nopReader{}, goio.Discard, goio.Discard, ) @@ -140,7 +146,27 @@ func execLint(cfg *lintCfg, args []string, io commands.IO) error { hasError = true } - memPkg, err := gno.ReadMemPackage(pkgPath, pkgPath) + // load deps + loadDepsCfg := *loadCfg + loadDepsCfg.Deps = true + loadDepsCfg.Cache = pkgsMap + + deps, loadDepsErr := packages.Load(&loadDepsCfg, pkg.Dir) + if loadDepsErr != nil { + io.ErrPrintln(issueFromError(pkgPath, err).String()) + hasError = true + continue + } + + for _, dep := range deps { + if _, ok := pkgsMap[dep.ImportPath]; ok { + continue + } + pkgsMap[dep.ImportPath] = dep + continue + } + + memPkg, err := gno.ReadMemPackage(pkg.Dir, pkgPath) if err != nil { io.ErrPrintln(issueFromError(pkgPath, err).String()) hasError = true @@ -163,7 +189,7 @@ func execLint(cfg *lintCfg, args []string, io commands.IO) error { var gmFile *gnomod.File if pkg.ModPath != "" { - gmFile, err = gnomod.ParseGnoMod(filepath.Join(pkg.ModPath, "gno.mod")) + gmFile, err = gnomod.ParseGnoMod(filepath.Join(pkg.Root, "gno.mod")) if err != nil { io.ErrPrintln(err) hasError = true diff --git a/gnovm/cmd/gno/list.go b/gnovm/cmd/gno/list.go index e36231f4cdf..ef737a00b64 100644 --- a/gnovm/cmd/gno/list.go +++ b/gnovm/cmd/gno/list.go @@ -77,10 +77,6 @@ func execList(cfg *listCfg, args []string, io commands.IO) error { os.Exit(1) } for _, pkg := range pkgs { - if len(pkg.Match) == 0 && !cfg.deps { - continue - } - pkgBytes, err := json.MarshalIndent(pkg, "", "\t") if err != nil { panic(err) diff --git a/gnovm/cmd/gno/run.go b/gnovm/cmd/gno/run.go index d0a72d8ebb1..79d719680d2 100644 --- a/gnovm/cmd/gno/run.go +++ b/gnovm/cmd/gno/run.go @@ -91,7 +91,7 @@ func execRun(cfg *runCfg, args []string, io commands.IO) error { // init store and machine _, testStore := test.Store( - cfg.rootDir, false, + cfg.rootDir, nil, false, stdin, stdout, stderr) if cfg.verbose { testStore.SetLogStoreOps(true) diff --git a/gnovm/cmd/gno/test.go b/gnovm/cmd/gno/test.go index 2af570ce7c2..1ad888c839c 100644 --- a/gnovm/cmd/gno/test.go +++ b/gnovm/cmd/gno/test.go @@ -12,11 +12,9 @@ import ( "github.com/gnolang/gno/gnovm/pkg/gnoenv" gno "github.com/gnolang/gno/gnovm/pkg/gnolang" - "github.com/gnolang/gno/gnovm/pkg/gnomod" "github.com/gnolang/gno/gnovm/pkg/packages" "github.com/gnolang/gno/gnovm/pkg/test" "github.com/gnolang/gno/tm2/pkg/commands" - "github.com/gnolang/gno/tm2/pkg/random" ) type testCfg struct { @@ -174,12 +172,20 @@ func execTest(cfg *testCfg, args []string, io commands.IO) error { }() } + pkgsMap := map[string]*packages.Package{} + for _, pkg := range pkgs { + if _, ok := pkgsMap[pkg.ImportPath]; ok { + continue + } + pkgsMap[pkg.ImportPath] = pkg + } + // Set up options to run tests. stdout := goio.Discard if cfg.verbose { stdout = io.Out() } - opts := test.NewTestOptions(cfg.rootDir, io.In(), stdout, io.Err()) + opts := test.NewTestOptions(cfg.rootDir, pkgsMap, io.In(), stdout, io.Err()) opts.RunFlag = cfg.run opts.Sync = cfg.updateGoldenTests opts.Verbose = cfg.verbose @@ -193,29 +199,43 @@ func execTest(cfg *testCfg, args []string, io commands.IO) error { if len(pkg.Match) == 0 { continue } - // Determine gnoPkgPath by reading gno.mod - var gnoPkgPath string - modfile, err := gnomod.ParseGnoMod(filepath.Join(pkg.ModPath, "gno.mod")) - if err == nil { - gnoPkgPath = modfile.Module.Mod.Path - } else { - gnoPkgPath = pkgPathFromRootDir(pkg.Dir, cfg.rootDir) - if gnoPkgPath == "" { - // unable to read pkgPath from gno.mod, generate a random realm path - io.ErrPrintfln("--- WARNING: unable to read package path from gno.mod or gno root directory; try creating a gno.mod file") - gnoPkgPath = "gno.land/r/" + strings.ToLower(random.RandStr(8)) // XXX: gno.land hardcoded for convenience. - } + + parentName := pkg.ImportPath + if parentName == "" { + parentName = pkg.Dir } if len(pkg.Files[packages.FileKindTest]) == 0 && len(pkg.Files[packages.FileKindXTest]) == 0 && len(pkg.Files[packages.FileKindFiletest]) == 0 { - io.ErrPrintfln("? %s \t[no test files]", pkg.ImportPath) + io.ErrPrintfln("? %s \t[no test files]", parentName) + continue + } + + depsConf := *conf + depsConf.Deps = true + depsConf.Cache = pkgsMap + + deps, loadDepsErr := packages.Load(&depsConf, pkg.Dir) + if loadDepsErr != nil { + io.ErrPrintfln("%s: load deps: %v", pkg.Dir, err) + io.ErrPrintfln("FAIL") + io.ErrPrintfln("FAIL %s", parentName) + io.ErrPrintfln("FAIL") + buildErrCount++ + continue + } + + for _, dep := range deps { + if _, ok := pkgsMap[dep.ImportPath]; ok { + continue + } + pkgsMap[dep.ImportPath] = dep continue } - memPkg := gno.MustReadMemPackage(pkg.Dir, gnoPkgPath) + memPkg := gno.MustReadMemPackage(pkg.Dir, parentName) startedAt := time.Now() - hasError := catchRuntimeError(gnoPkgPath, io.Err(), func() { + hasError := catchRuntimeError(parentName, io.Err(), func() { err = test.Test(memPkg, pkg.Dir, opts) }) @@ -227,11 +247,11 @@ func execTest(cfg *testCfg, args []string, io commands.IO) error { io.ErrPrintfln("%s: test pkg: %v", pkg.Dir, err) } io.ErrPrintfln("FAIL") - io.ErrPrintfln("FAIL %s \t%s", pkg.ImportPath, dstr) + io.ErrPrintfln("FAIL %s \t%s", parentName, dstr) io.ErrPrintfln("FAIL") testErrCount++ } else { - io.ErrPrintfln("ok %s \t%s", pkg.ImportPath, dstr) + io.ErrPrintfln("ok %s \t%s", parentName, dstr) } } if testErrCount > 0 || buildErrCount > 0 { diff --git a/gnovm/cmd/gno/transpile.go b/gnovm/cmd/gno/transpile.go index 66870e0a048..b06a4edbea5 100644 --- a/gnovm/cmd/gno/transpile.go +++ b/gnovm/cmd/gno/transpile.go @@ -16,7 +16,6 @@ import ( "strings" "github.com/gnolang/gno/gnovm/pkg/gnoenv" - "github.com/gnolang/gno/gnovm/pkg/gnomod" "github.com/gnolang/gno/gnovm/pkg/packages" "github.com/gnolang/gno/gnovm/pkg/transpiler" "github.com/gnolang/gno/tm2/pkg/commands" @@ -134,7 +133,7 @@ func execTranspile(cfg *transpileCfg, args []string, io commands.IO) error { } // load packages - conf := &packages.LoadConfig{IO: io, Fetcher: testPackageFetcher} + conf := &packages.LoadConfig{IO: io, Fetcher: testPackageFetcher, Deps: true} pkgs, err := packages.Load(conf, args...) if err != nil { return fmt.Errorf("load pkgs: %w", err) @@ -214,11 +213,7 @@ func transpilePkg(pkg *packages.Package, pkgs map[string]*packages.Package, opts } opts.markAsTranspiled(dirPath) - gmod, err := gnomod.ParseAt(dirPath) - if err != nil && !errors.Is(err, gnomod.ErrGnoModNotFound) { - return err - } - if err == nil && gmod.Draft { + if pkg.Draft { if opts.cfg.verbose { opts.io.ErrPrintfln("%s (skipped, gno.mod marks module as draft)", filepath.Clean(dirPath)) } diff --git a/gnovm/pkg/packages/analyze_packages.go b/gnovm/pkg/packages/analyze_packages.go index d803af98fc0..603c7c108b1 100644 --- a/gnovm/pkg/packages/analyze_packages.go +++ b/gnovm/pkg/packages/analyze_packages.go @@ -2,6 +2,7 @@ package packages import ( "errors" + "go/parser" "go/token" "os" "path" @@ -16,17 +17,18 @@ import ( func readPackages(matches []*pkgMatch) []*Package { pkgs := make([]*Package, 0, len(matches)) for _, pkgMatch := range matches { - pkg := readPkg(pkgMatch.Dir) + pkg := readPkg(pkgMatch.Dir, "") pkg.Match = pkgMatch.Match pkgs = append(pkgs, pkg) } return pkgs } -func readPkg(pkgDir string) *Package { +func readPkg(pkgDir string, importPath string) *Package { pkg := &Package{ - Dir: pkgDir, - Files: make(FilesMap), + Dir: pkgDir, + Files: make(FilesMap), + ImportPath: importPath, } entries, err := os.ReadDir(pkgDir) @@ -64,6 +66,13 @@ func readPkg(pkgDir string) *Package { continue } + // ignore files with invalid package clause + _, err = parser.ParseFile(fset, fpath, nil, parser.PackageClauseOnly) + if err != nil { + pkg.Error = errors.Join(pkg.Error, err) + continue + } + mempkg.Files = append(mempkg.Files, &gnovm.MemFile{Name: base, Body: body}) pkg.Files[fileKind] = append(pkg.Files[fileKind], base) } diff --git a/gnovm/pkg/packages/load.go b/gnovm/pkg/packages/load.go index 21d8d54b1dc..ddbe0dab1d7 100644 --- a/gnovm/pkg/packages/load.go +++ b/gnovm/pkg/packages/load.go @@ -7,6 +7,7 @@ import ( "path/filepath" "github.com/gnolang/gno/gnovm" + "github.com/gnolang/gno/gnovm/pkg/gnoenv" "github.com/gnolang/gno/gnovm/pkg/gnolang" "github.com/gnolang/gno/gnovm/pkg/gnomod" "github.com/gnolang/gno/gnovm/pkg/packages/pkgdownload" @@ -16,9 +17,11 @@ import ( ) type LoadConfig struct { - IO commands.IO - Fetcher pkgdownload.PackageFetcher - Deps bool + IO commands.IO + Fetcher pkgdownload.PackageFetcher + Deps bool + Cache map[string]*Package + GnorootExamples bool // allow loading packages from gnoroot examples if not found in the set } func (conf *LoadConfig) applyDefaults() { @@ -28,6 +31,9 @@ func (conf *LoadConfig) applyDefaults() { if conf.Fetcher == nil { conf.Fetcher = rpcpkgfetcher.New(nil) } + if conf.Cache == nil { + conf.Cache = map[string]*Package{} + } } func Load(conf *LoadConfig, patterns ...string) ([]*Package, error) { @@ -45,19 +51,29 @@ func Load(conf *LoadConfig, patterns ...string) ([]*Package, error) { } byPkgPath := make(map[string]*Package) - for _, pkg := range pkgs { + index := func(pkg *Package) { if pkg.ImportPath == "" { - continue + return } if _, ok := byPkgPath[pkg.ImportPath]; ok { - continue + return } byPkgPath[pkg.ImportPath] = pkg } + for _, pkg := range pkgs { + index(pkg) + } + + gnoroot := gnoenv.RootDir() visited := map[string]struct{}{} list := []*Package{} - for pile := pkgs; len(pile) > 0; pile = pile[1:] { + pile := pkgs + pileDown := func(pkg *Package) { + index(pkg) + pile = append(pile, pkg) + } + for ; len(pile) > 0; pile = pile[1:] { pkg := pile[0] if _, ok := visited[pkg.ImportPath]; ok { continue @@ -65,24 +81,47 @@ func Load(conf *LoadConfig, patterns ...string) ([]*Package, error) { visited[pkg.ImportPath] = struct{}{} for _, imp := range pkg.Imports.Merge(FileKindPackageSource, FileKindTest, FileKindXTest, FileKindFiletest) { - if gnolang.IsStdlib(imp) { + if _, ok := byPkgPath[imp]; ok { continue } - if _, ok := byPkgPath[imp]; ok { + if cached, ok := conf.Cache[imp]; ok { + pileDown(cached) + continue + } + + if gnolang.IsStdlib(imp) { + dir := filepath.Join(gnoroot, "gnovm", "stdlibs", filepath.FromSlash(imp)) + finfo, err := os.Stat(dir) + if err == nil && !finfo.IsDir() { + err = fmt.Errorf("stdlib %q not found", imp) + } + if err != nil { + pkg.Error = errors.Join(pkg.Error, err) + byPkgPath[imp] = nil // stop trying to get this lib, we can't + continue + } + + pileDown(readPkg(dir, imp)) continue } - // must download + if conf.GnorootExamples { + examplePkgDir := filepath.Join(gnoroot, "examples", filepath.FromSlash(imp)) + finfo, err := os.Stat(examplePkgDir) + if err == nil && finfo.IsDir() { + pileDown(readPkg(examplePkgDir, imp)) + continue + } + } dir := gnomod.PackageDir("", module.Version{Path: imp}) if err := downloadPackage(conf, imp, dir); err != nil { pkg.Error = errors.Join(pkg.Error, err) - byPkgPath[imp] = nil // stop trying to download pkg + byPkgPath[imp] = nil // stop trying to download pkg, we can't continue } - - byPkgPath[imp] = readPkg(dir) + pileDown(readPkg(dir, imp)) } list = append(list, pkg) diff --git a/gnovm/pkg/repl/repl.go b/gnovm/pkg/repl/repl.go index b0944d21646..977b664e854 100644 --- a/gnovm/pkg/repl/repl.go +++ b/gnovm/pkg/repl/repl.go @@ -125,7 +125,7 @@ func NewRepl(opts ...ReplOption) *Repl { r.stderr = &b r.storeFunc = func() gno.Store { - _, st := test.Store(gnoenv.RootDir(), false, r.stdin, r.stdout, r.stderr) + _, st := test.Store(gnoenv.RootDir(), nil, false, r.stdin, r.stdout, r.stderr) return st } diff --git a/gnovm/pkg/test/imports.go b/gnovm/pkg/test/imports.go index 96d04837a1b..66b90e12fac 100644 --- a/gnovm/pkg/test/imports.go +++ b/gnovm/pkg/test/imports.go @@ -19,7 +19,6 @@ import ( teststdlibs "github.com/gnolang/gno/gnovm/tests/stdlibs" teststd "github.com/gnolang/gno/gnovm/tests/stdlibs/std" "github.com/gnolang/gno/tm2/pkg/db/memdb" - osm "github.com/gnolang/gno/tm2/pkg/os" "github.com/gnolang/gno/tm2/pkg/std" "github.com/gnolang/gno/tm2/pkg/store/dbadapter" storetypes "github.com/gnolang/gno/tm2/pkg/store/types" @@ -28,6 +27,7 @@ import ( // NOTE: this isn't safe, should only be used for testing. func Store( rootDir string, + pkgs map[string]*packages.Package, withExtern bool, stdin io.Reader, stdout, stderr io.Writer, @@ -36,6 +36,8 @@ func Store( resStore gno.Store, ) { getPackage := func(pkgPath string, store gno.Store) (pn *gno.PackageNode, pv *gno.PackageValue) { + // fmt.Println("getting pkg", pkgPath) + if pkgPath == "" { panic(fmt.Sprintf("invalid zero package path in testStore().pkgGetter")) } @@ -134,14 +136,15 @@ func Store( return } - // if examples package... - examplePath := filepath.Join(rootDir, "examples", pkgPath) - if osm.DirExists(examplePath) { - memPkg := gno.MustReadMemPackage(examplePath, pkgPath) + // if known package + if pkg, ok := pkgs[pkgPath]; ok { + memPkg := gno.MustReadMemPackage(pkg.Dir, pkgPath) if memPkg.IsEmpty() { panic(fmt.Sprintf("found an empty package %q", pkgPath)) } + // fmt.Println("loading", pkgPath, "from", pkg.Dir, "err", pkg.Error) + send := std.Coins{} ctx := Context(pkgPath, send) m2 := gno.NewMachineWithOptions(gno.MachineOptions{ @@ -153,6 +156,28 @@ func Store( pn, pv = m2.RunMemPackage(memPkg, true) return } + + /* + // if examples package... + examplePath := filepath.Join(rootDir, "examples", pkgPath) + if osm.DirExists(examplePath) { + memPkg := gno.MustReadMemPackage(examplePath, pkgPath) + if memPkg.IsEmpty() { + panic(fmt.Sprintf("found an empty package %q", pkgPath)) + } + + send := std.Coins{} + ctx := Context(pkgPath, send) + m2 := gno.NewMachineWithOptions(gno.MachineOptions{ + PkgPath: "test", + Output: stdout, + Store: store, + Context: ctx, + }) + pn, pv = m2.RunMemPackage(memPkg, true) + return + } + */ return nil, nil } db := memdb.NewMemDB() diff --git a/gnovm/pkg/test/test.go b/gnovm/pkg/test/test.go index d06540761d7..5d1a306c44f 100644 --- a/gnovm/pkg/test/test.go +++ b/gnovm/pkg/test/test.go @@ -17,6 +17,7 @@ import ( "github.com/gnolang/gno/gnovm" gno "github.com/gnolang/gno/gnovm/pkg/gnolang" + "github.com/gnolang/gno/gnovm/pkg/packages" "github.com/gnolang/gno/gnovm/stdlibs" teststd "github.com/gnolang/gno/gnovm/tests/stdlibs/std" "github.com/gnolang/gno/tm2/pkg/crypto" @@ -133,14 +134,14 @@ func (opts *TestOptions) WriterForStore() io.Writer { } // NewTestOptions sets up TestOptions, filling out all "required" parameters. -func NewTestOptions(rootDir string, stdin io.Reader, stdout, stderr io.Writer) *TestOptions { +func NewTestOptions(rootDir string, pkgs map[string]*packages.Package, stdin io.Reader, stdout, stderr io.Writer) *TestOptions { opts := &TestOptions{ RootDir: rootDir, Output: stdout, Error: stderr, } opts.BaseStore, opts.TestStore = Store( - rootDir, false, + rootDir, pkgs, false, stdin, opts.WriterForStore(), stderr, ) return opts @@ -181,11 +182,15 @@ func Test(memPkg *gnovm.MemPackage, fsDir string, opts *TestOptions) error { var errs error + // fmt.Println("loading imports for", memPkg.Path, fsDir) + // Eagerly load imports. if err := LoadImports(opts.TestStore, memPkg); err != nil { return err } + // fmt.Println("loaded imports for", memPkg.Path, fsDir) + // Stands for "test", "integration test", and "filetest". // "integration test" are the test files with `package xxx_test` (they are // not necessarily integration tests, it's just for our internal reference.) diff --git a/gnovm/tests/integ/invalid_gno_file/gno.mod b/gnovm/tests/integ/invalid_gno_file/gno.mod index 060e28b9dc4..f68f19aaf66 100644 --- a/gnovm/tests/integ/invalid_gno_file/gno.mod +++ b/gnovm/tests/integ/invalid_gno_file/gno.mod @@ -1 +1 @@ -module test +module invalid_gno_file From fd2917f635d0f5cff485f2168a8b2d64f40944b5 Mon Sep 17 00:00:00 2001 From: Norman Date: Wed, 8 Jan 2025 05:41:40 +0100 Subject: [PATCH 032/143] chore: remove dev artifact Signed-off-by: Norman --- examples/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/Makefile b/examples/Makefile index 7708417d994..3f351b8052e 100644 --- a/examples/Makefile +++ b/examples/Makefile @@ -58,7 +58,7 @@ clean: .PHONY: fmt GNOFMT_FLAGS ?= -w fmt: - go run ../gnovm/cmd/gno fmt $(GNOFMT_FLAGS) . + go run ../gnovm/cmd/gno fmt $(GNOFMT_FLAGS) ./... .PHONY: tidy tidy: From 4908939b433df90c59eb6d6e814a5d595c95583b Mon Sep 17 00:00:00 2001 From: Norman Date: Wed, 8 Jan 2025 05:44:08 +0100 Subject: [PATCH 033/143] chore: remove impossible case Signed-off-by: Norman --- gnovm/cmd/gno/transpile.go | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/gnovm/cmd/gno/transpile.go b/gnovm/cmd/gno/transpile.go index b06a4edbea5..990a6a47576 100644 --- a/gnovm/cmd/gno/transpile.go +++ b/gnovm/cmd/gno/transpile.go @@ -152,22 +152,7 @@ func execTranspile(cfg *transpileCfg, args []string, io commands.IO) error { continue } - st, err := os.Stat(pkg.Dir) - if err != nil { - return err - } - if st.IsDir() { - err = transpilePkg(pkg, pkgsMap, opts) - } else { - panic("should ot try to transpile file yet") - - if opts.cfg.verbose { - io.ErrPrintln(filepath.Clean(pkg.Dir)) - } - - err = transpileFile(pkg.Dir, pkgsMap, opts) - } - if err != nil { + if err := transpilePkg(pkg, pkgsMap, opts); err != nil { var fileErrlist scanner.ErrorList if !errors.As(err, &fileErrlist) { // Not an scanner.ErrorList: return immediately. From bb3057e435a7ad205122e29297ec1f27869aea39 Mon Sep 17 00:00:00 2001 From: Norman Date: Wed, 8 Jan 2025 05:45:48 +0100 Subject: [PATCH 034/143] chore: revert unneeded changes Signed-off-by: Norman --- .github/workflows/examples.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/examples.yml b/.github/workflows/examples.yml index 4f9211ba8d1..422b06935f6 100644 --- a/.github/workflows/examples.yml +++ b/.github/workflows/examples.yml @@ -29,7 +29,7 @@ jobs: with: go-version: ${{ matrix.goversion }} - run: go install -v ./gnovm/cmd/gno - - run: go run ./gnovm/cmd/gno transpile -v --gobuild ./examples/gno.land/... + - run: go run ./gnovm/cmd/gno transpile -v --gobuild ./examples/... test: strategy: fail-fast: false @@ -81,7 +81,7 @@ jobs: strategy: fail-fast: false matrix: - go-version: ["1.22.x"] + go-version: [ "1.22.x" ] # unittests: TODO: matrix with contracts runs-on: ubuntu-latest timeout-minutes: 10 From 74ebf0c6bd492cde577f018282bcc9878daefa17 Mon Sep 17 00:00:00 2001 From: Norman Date: Wed, 8 Jan 2025 06:06:22 +0100 Subject: [PATCH 035/143] feat: inject util + consistent naming Signed-off-by: Norman --- gnovm/cmd/gno/lint.go | 45 ++++++++++++------------------------- gnovm/cmd/gno/test.go | 17 ++------------ gnovm/cmd/gno/transpile.go | 4 +--- gnovm/pkg/packages/types.go | 12 ++++++++++ 4 files changed, 29 insertions(+), 49 deletions(-) diff --git a/gnovm/cmd/gno/lint.go b/gnovm/cmd/gno/lint.go index c8d7ee30bb4..1790bc1b505 100644 --- a/gnovm/cmd/gno/lint.go +++ b/gnovm/cmd/gno/lint.go @@ -104,9 +104,7 @@ func execLint(cfg *lintCfg, args []string, io commands.IO) error { } pkgsMap := map[string]*packages.Package{} - for _, pkg := range pkgs { - pkgsMap[pkg.ImportPath] = pkg - } + packages.Inject(pkgsMap, pkgs) hasError := false @@ -116,21 +114,13 @@ func execLint(cfg *lintCfg, args []string, io commands.IO) error { ) for _, pkg := range pkgs { - // ignore deps - if len(pkg.Match) == 0 { - continue + parentName := pkg.ImportPath + if parentName == "" { + parentName = pkg.Dir } - pkgDir := pkg.Dir - pkgPath := pkg.ImportPath - if verbose { - io.ErrPrintln(pkgPath) - } - - info, err := os.Stat(pkgPath) - if err == nil && !info.IsDir() { - pkgPath = filepath.Dir(pkgPath) + io.ErrPrintln(parentName) } // Check if 'gno.mod' exists @@ -139,7 +129,7 @@ func execLint(cfg *lintCfg, args []string, io commands.IO) error { issue := lintIssue{ Code: lintGnoMod, Confidence: 1, - Location: pkgDir, + Location: pkg.Dir, Msg: "missing 'gno.mod' file", } io.ErrPrintln(issue) @@ -150,38 +140,31 @@ func execLint(cfg *lintCfg, args []string, io commands.IO) error { loadDepsCfg := *loadCfg loadDepsCfg.Deps = true loadDepsCfg.Cache = pkgsMap - deps, loadDepsErr := packages.Load(&loadDepsCfg, pkg.Dir) if loadDepsErr != nil { - io.ErrPrintln(issueFromError(pkgPath, err).String()) + io.ErrPrintln(issueFromError(parentName, err).String()) hasError = true continue } + packages.Inject(pkgsMap, deps) - for _, dep := range deps { - if _, ok := pkgsMap[dep.ImportPath]; ok { - continue - } - pkgsMap[dep.ImportPath] = dep - continue - } - - memPkg, err := gno.ReadMemPackage(pkg.Dir, pkgPath) + // read mempkg + memPkg, err := gno.ReadMemPackage(pkg.Dir, pkg.ImportPath) if err != nil { - io.ErrPrintln(issueFromError(pkgPath, err).String()) + io.ErrPrintln(issueFromError(parentName, err).String()) hasError = true continue } // Perform imports using the parent store. if err := test.LoadImports(ts, memPkg); err != nil { - io.ErrPrintln(issueFromError(pkgPath, err).String()) + io.ErrPrintln(issueFromError(parentName, err).String()) hasError = true continue } // Handle runtime errors - hasRuntimeErr := catchRuntimeError(pkgPath, io.Err(), func() { + hasRuntimeErr := catchRuntimeError(parentName, io.Err(), func() { // Wrap in cache wrap so execution of the linter doesn't impact // other packages. cw := bs.CacheWrap() @@ -206,7 +189,7 @@ func execLint(cfg *lintCfg, args []string, io commands.IO) error { hasError = true } } else if verbose { - io.ErrPrintfln("%s: module is draft, skipping type check", pkgPath) + io.ErrPrintfln("%s: module is draft, skipping type check", parentName) } tm := test.Machine(gs, goio.Discard, memPkg.Path) diff --git a/gnovm/cmd/gno/test.go b/gnovm/cmd/gno/test.go index 1ad888c839c..aefd56264c9 100644 --- a/gnovm/cmd/gno/test.go +++ b/gnovm/cmd/gno/test.go @@ -173,12 +173,7 @@ func execTest(cfg *testCfg, args []string, io commands.IO) error { } pkgsMap := map[string]*packages.Package{} - for _, pkg := range pkgs { - if _, ok := pkgsMap[pkg.ImportPath]; ok { - continue - } - pkgsMap[pkg.ImportPath] = pkg - } + packages.Inject(pkgsMap, pkgs) // Set up options to run tests. stdout := goio.Discard @@ -213,7 +208,6 @@ func execTest(cfg *testCfg, args []string, io commands.IO) error { depsConf := *conf depsConf.Deps = true depsConf.Cache = pkgsMap - deps, loadDepsErr := packages.Load(&depsConf, pkg.Dir) if loadDepsErr != nil { io.ErrPrintfln("%s: load deps: %v", pkg.Dir, err) @@ -223,14 +217,7 @@ func execTest(cfg *testCfg, args []string, io commands.IO) error { buildErrCount++ continue } - - for _, dep := range deps { - if _, ok := pkgsMap[dep.ImportPath]; ok { - continue - } - pkgsMap[dep.ImportPath] = dep - continue - } + packages.Inject(pkgsMap, deps) memPkg := gno.MustReadMemPackage(pkg.Dir, parentName) diff --git a/gnovm/cmd/gno/transpile.go b/gnovm/cmd/gno/transpile.go index 990a6a47576..723a18ce607 100644 --- a/gnovm/cmd/gno/transpile.go +++ b/gnovm/cmd/gno/transpile.go @@ -139,9 +139,7 @@ func execTranspile(cfg *transpileCfg, args []string, io commands.IO) error { return fmt.Errorf("load pkgs: %w", err) } pkgsMap := map[string]*packages.Package{} - for _, pkg := range pkgs { - pkgsMap[pkg.ImportPath] = pkg - } + packages.Inject(pkgsMap, pkgs) // transpile .gno packages and files. opts := newTranspileOptions(cfg, io) diff --git a/gnovm/pkg/packages/types.go b/gnovm/pkg/packages/types.go index 204b94eeb98..0728702556f 100644 --- a/gnovm/pkg/packages/types.go +++ b/gnovm/pkg/packages/types.go @@ -36,3 +36,15 @@ func sortPaths(imports []string) { return imports[i] < imports[j] }) } + +func Inject(pkgsMap map[string]*Package, pkgs []*Package) { + for _, pkg := range pkgs { + if pkg.ImportPath == "" { + continue + } + if _, ok := pkgsMap[pkg.ImportPath]; ok { + continue + } + pkgsMap[pkg.ImportPath] = pkg + } +} From 6c69bede12cc16330385fe55a0de5a838bd2ec3e Mon Sep 17 00:00:00 2001 From: Norman Date: Wed, 8 Jan 2025 06:10:01 +0100 Subject: [PATCH 036/143] chore: simplify condition Signed-off-by: Norman --- gnovm/cmd/gno/lint.go | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/gnovm/cmd/gno/lint.go b/gnovm/cmd/gno/lint.go index 1790bc1b505..6c35efff5ea 100644 --- a/gnovm/cmd/gno/lint.go +++ b/gnovm/cmd/gno/lint.go @@ -16,7 +16,6 @@ import ( "github.com/gnolang/gno/gnovm" "github.com/gnolang/gno/gnovm/pkg/gnoenv" gno "github.com/gnolang/gno/gnovm/pkg/gnolang" - "github.com/gnolang/gno/gnovm/pkg/gnomod" "github.com/gnolang/gno/gnovm/pkg/packages" "github.com/gnolang/gno/gnovm/pkg/test" "github.com/gnolang/gno/tm2/pkg/commands" @@ -170,17 +169,8 @@ func execLint(cfg *lintCfg, args []string, io commands.IO) error { cw := bs.CacheWrap() gs := ts.BeginTransaction(cw, cw, nil) - var gmFile *gnomod.File - if pkg.ModPath != "" { - gmFile, err = gnomod.ParseGnoMod(filepath.Join(pkg.Root, "gno.mod")) - if err != nil { - io.ErrPrintln(err) - hasError = true - } - } - // Run type checking - if gmFile == nil || !pkg.Draft { + if !pkg.Draft { foundErr, err := lintTypeCheck(io, memPkg, gs) if err != nil { io.ErrPrintln(err) From 10701c4b99d25ea9dce1f2a44a9834ba36831c39 Mon Sep 17 00:00:00 2001 From: Norman Date: Wed, 8 Jan 2025 06:26:15 +0100 Subject: [PATCH 037/143] chore: explicit log name Signed-off-by: Norman --- gnovm/cmd/gno/lint.go | 18 +++++++++--------- gnovm/cmd/gno/test.go | 22 +++++++++++----------- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/gnovm/cmd/gno/lint.go b/gnovm/cmd/gno/lint.go index 6c35efff5ea..7d0b1e29372 100644 --- a/gnovm/cmd/gno/lint.go +++ b/gnovm/cmd/gno/lint.go @@ -113,13 +113,13 @@ func execLint(cfg *lintCfg, args []string, io commands.IO) error { ) for _, pkg := range pkgs { - parentName := pkg.ImportPath - if parentName == "" { - parentName = pkg.Dir + logName := pkg.ImportPath + if logName == "" { + logName = pkg.Dir } if verbose { - io.ErrPrintln(parentName) + io.ErrPrintln(logName) } // Check if 'gno.mod' exists @@ -141,7 +141,7 @@ func execLint(cfg *lintCfg, args []string, io commands.IO) error { loadDepsCfg.Cache = pkgsMap deps, loadDepsErr := packages.Load(&loadDepsCfg, pkg.Dir) if loadDepsErr != nil { - io.ErrPrintln(issueFromError(parentName, err).String()) + io.ErrPrintln(issueFromError(logName, err).String()) hasError = true continue } @@ -150,20 +150,20 @@ func execLint(cfg *lintCfg, args []string, io commands.IO) error { // read mempkg memPkg, err := gno.ReadMemPackage(pkg.Dir, pkg.ImportPath) if err != nil { - io.ErrPrintln(issueFromError(parentName, err).String()) + io.ErrPrintln(issueFromError(logName, err).String()) hasError = true continue } // Perform imports using the parent store. if err := test.LoadImports(ts, memPkg); err != nil { - io.ErrPrintln(issueFromError(parentName, err).String()) + io.ErrPrintln(issueFromError(logName, err).String()) hasError = true continue } // Handle runtime errors - hasRuntimeErr := catchRuntimeError(parentName, io.Err(), func() { + hasRuntimeErr := catchRuntimeError(logName, io.Err(), func() { // Wrap in cache wrap so execution of the linter doesn't impact // other packages. cw := bs.CacheWrap() @@ -179,7 +179,7 @@ func execLint(cfg *lintCfg, args []string, io commands.IO) error { hasError = true } } else if verbose { - io.ErrPrintfln("%s: module is draft, skipping type check", parentName) + io.ErrPrintfln("%s: module is draft, skipping type check", logName) } tm := test.Machine(gs, goio.Discard, memPkg.Path) diff --git a/gnovm/cmd/gno/test.go b/gnovm/cmd/gno/test.go index aefd56264c9..53e161148af 100644 --- a/gnovm/cmd/gno/test.go +++ b/gnovm/cmd/gno/test.go @@ -195,13 +195,13 @@ func execTest(cfg *testCfg, args []string, io commands.IO) error { continue } - parentName := pkg.ImportPath - if parentName == "" { - parentName = pkg.Dir + logName := pkg.ImportPath + if logName == "" { + logName = pkg.Dir } if len(pkg.Files[packages.FileKindTest]) == 0 && len(pkg.Files[packages.FileKindXTest]) == 0 && len(pkg.Files[packages.FileKindFiletest]) == 0 { - io.ErrPrintfln("? %s \t[no test files]", parentName) + io.ErrPrintfln("? %s \t[no test files]", logName) continue } @@ -210,19 +210,19 @@ func execTest(cfg *testCfg, args []string, io commands.IO) error { depsConf.Cache = pkgsMap deps, loadDepsErr := packages.Load(&depsConf, pkg.Dir) if loadDepsErr != nil { - io.ErrPrintfln("%s: load deps: %v", pkg.Dir, err) + io.ErrPrintfln("%s: load deps: %v", logName, err) io.ErrPrintfln("FAIL") - io.ErrPrintfln("FAIL %s", parentName) + io.ErrPrintfln("FAIL %s", logName) io.ErrPrintfln("FAIL") buildErrCount++ continue } packages.Inject(pkgsMap, deps) - memPkg := gno.MustReadMemPackage(pkg.Dir, parentName) + memPkg := gno.MustReadMemPackage(pkg.Dir, logName) startedAt := time.Now() - hasError := catchRuntimeError(parentName, io.Err(), func() { + hasError := catchRuntimeError(logName, io.Err(), func() { err = test.Test(memPkg, pkg.Dir, opts) }) @@ -231,14 +231,14 @@ func execTest(cfg *testCfg, args []string, io commands.IO) error { if hasError || err != nil { if err != nil { - io.ErrPrintfln("%s: test pkg: %v", pkg.Dir, err) + io.ErrPrintfln("%s: test pkg: %v", logName, err) } io.ErrPrintfln("FAIL") - io.ErrPrintfln("FAIL %s \t%s", parentName, dstr) + io.ErrPrintfln("FAIL %s \t%s", logName, dstr) io.ErrPrintfln("FAIL") testErrCount++ } else { - io.ErrPrintfln("ok %s \t%s", parentName, dstr) + io.ErrPrintfln("ok %s \t%s", logName, dstr) } } if testErrCount > 0 || buildErrCount > 0 { From 0e981147d1b00c65fb10c1c6236de21305002d34 Mon Sep 17 00:00:00 2001 From: Norman Date: Wed, 8 Jan 2025 06:31:57 +0100 Subject: [PATCH 038/143] chore: remove dev artifact Signed-off-by: Norman --- gnovm/cmd/gno/main.go | 1 - 1 file changed, 1 deletion(-) diff --git a/gnovm/cmd/gno/main.go b/gnovm/cmd/gno/main.go index 2e2a0650d7f..c951fa2c9f3 100644 --- a/gnovm/cmd/gno/main.go +++ b/gnovm/cmd/gno/main.go @@ -35,7 +35,6 @@ func newGnocliCmd(io commands.IO) *commands.Command { newEnvCmd(io), newBugCmd(io), newListCmd(io), - // fmt -- gofmt newFmtCmd(io), // graph // vendor -- download deps from the chain in vendor/ From b6d90077d892f1d68d4c410b3e299e745391d3eb Mon Sep 17 00:00:00 2001 From: Norman Date: Wed, 8 Jan 2025 06:41:13 +0100 Subject: [PATCH 039/143] chore: remove merge fail Signed-off-by: Norman --- gnovm/tests/machine_test.go | 67 --------------------------- gnovm/tests/package_test.go | 92 ------------------------------------- 2 files changed, 159 deletions(-) delete mode 100644 gnovm/tests/machine_test.go delete mode 100644 gnovm/tests/package_test.go diff --git a/gnovm/tests/machine_test.go b/gnovm/tests/machine_test.go deleted file mode 100644 index 90d4b43c327..00000000000 --- a/gnovm/tests/machine_test.go +++ /dev/null @@ -1,67 +0,0 @@ -package tests - -import ( - "os" - "path/filepath" - "testing" - - "github.com/stretchr/testify/assert" - - gno "github.com/gnolang/gno/gnovm/pkg/gnolang" - "github.com/gnolang/gno/gnovm/pkg/packages" -) - -func TestMachineTestMemPackage(t *testing.T) { - matchFunc := func(pat, str string) (bool, error) { return true, nil } - - tests := []struct { - name string - path string - shouldSucceed bool - }{ - { - name: "TestSuccess", - path: "testdata/TestMemPackage/success", - shouldSucceed: true, - }, - { - name: "TestFail", - path: "testdata/TestMemPackage/fail", - shouldSucceed: false, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - // NOTE: Because the purpose of this test is to ensure testing.T.Failed() - // returns true if a gno test is failing, and because we don't want this - // to affect the current testing.T, we are creating an other one thanks - // to testing.RunTests() function. - testing.RunTests(matchFunc, []testing.InternalTest{ - { - Name: tt.name, - F: func(t2 *testing.T) { //nolint:thelper - rootDir := filepath.Join("..", "..") - pkgsMap := map[string]*packages.Package{} - store := TestStore(rootDir, "test", pkgsMap, os.Stdin, os.Stdout, os.Stderr, ImportModeStdlibsOnly) - store.SetLogStoreOps(true) - m := gno.NewMachineWithOptions(gno.MachineOptions{ - PkgPath: "test", - Output: os.Stdout, - Store: store, - Context: nil, - }) - memPkg := gno.ReadMemPackage(tt.path, "test") - - m.TestMemPackage(t2, memPkg) - - if tt.shouldSucceed { - assert.False(t, t2.Failed(), "test %q should have succeed", tt.name) - } else { - assert.True(t, t2.Failed(), "test %q should have failed", tt.name) - } - }, - }, - }) - }) - } -} diff --git a/gnovm/tests/package_test.go b/gnovm/tests/package_test.go deleted file mode 100644 index caaf82bf573..00000000000 --- a/gnovm/tests/package_test.go +++ /dev/null @@ -1,92 +0,0 @@ -package tests - -import ( - "bytes" - "fmt" - "io/fs" - "log" - "os" - "path/filepath" - "strings" - "testing" - - "github.com/stretchr/testify/require" - - gno "github.com/gnolang/gno/gnovm/pkg/gnolang" - "github.com/gnolang/gno/gnovm/pkg/packages" -) - -func TestStdlibs(t *testing.T) { - t.Parallel() - - // NOTE: this test only works using _test.gno files; - // filetests are not meant to be used for testing standard libraries. - // The examples directory is tested directly using `gno test`u - - // find all packages with *_test.gno files. - rootDirs := []string{ - filepath.Join("..", "stdlibs"), - } - testDirs := map[string]string{} // aggregate here, pkgPath -> dir - pkgPaths := []string{} - for _, rootDir := range rootDirs { - fileSystem := os.DirFS(rootDir) - fs.WalkDir(fileSystem, ".", func(path string, d fs.DirEntry, err error) error { - if err != nil { - log.Fatal(err) - } - if d.IsDir() { - return nil - } - if strings.HasSuffix(path, "_test.gno") { - dirPath := filepath.Dir(path) - if _, exists := testDirs[dirPath]; exists { - // already exists. - } else { - testDirs[dirPath] = filepath.Join(rootDir, dirPath) - pkgPaths = append(pkgPaths, dirPath) - } - } - return nil - }) - } - // For each package with testfiles (in testDirs), call Machine.TestMemPackage. - for _, pkgPath := range pkgPaths { - testDir := testDirs[pkgPath] - t.Run(pkgPath, func(t *testing.T) { - pkgPath := pkgPath - t.Parallel() - runPackageTest(t, testDir, pkgPath) - }) - } -} - -func runPackageTest(t *testing.T, dir string, path string) { - t.Helper() - - memPkg := gno.ReadMemPackage(dir, path) - require.False(t, memPkg.IsEmpty()) - - stdin := new(bytes.Buffer) - // stdout := new(bytes.Buffer) - stdout := os.Stdout - stderr := new(bytes.Buffer) - rootDir := filepath.Join("..", "..") - pkgsMap := map[string]*packages.Package{} - store := TestStore(rootDir, path, pkgsMap, stdin, stdout, stderr, ImportModeStdlibsOnly) - store.SetLogStoreOps(true) - m := gno.NewMachineWithOptions(gno.MachineOptions{ - PkgPath: "test", - Output: stdout, - Store: store, - Context: nil, - }) - m.TestMemPackage(t, memPkg) - - // Check that machine is empty. - err := m.CheckEmpty() - if err != nil { - t.Log("last state: \n", m.String()) - panic(fmt.Sprintf("machine not empty after main: %v", err)) - } -} From edba3460301106af2f782852e6d95c6e93136480 Mon Sep 17 00:00:00 2001 From: Norman Date: Wed, 8 Jan 2025 06:42:18 +0100 Subject: [PATCH 040/143] chore: remove merge fail Signed-off-by: Norman --- gnovm/tests/file.go | 699 ----------------------------------------- gnovm/tests/imports.go | 511 ------------------------------ 2 files changed, 1210 deletions(-) delete mode 100644 gnovm/tests/file.go delete mode 100644 gnovm/tests/imports.go diff --git a/gnovm/tests/file.go b/gnovm/tests/file.go deleted file mode 100644 index c2e224ea01f..00000000000 --- a/gnovm/tests/file.go +++ /dev/null @@ -1,699 +0,0 @@ -package tests - -import ( - "bytes" - "encoding/json" - "fmt" - "go/ast" - "go/parser" - "go/token" - "io" - "os" - "regexp" - rtdb "runtime/debug" - "strconv" - "strings" - - "github.com/gnolang/gno/gno.land/pkg/gnoland/ugnot" - "github.com/gnolang/gno/gnovm" - gno "github.com/gnolang/gno/gnovm/pkg/gnolang" - "github.com/gnolang/gno/gnovm/pkg/packages" - "github.com/gnolang/gno/gnovm/stdlibs" - teststd "github.com/gnolang/gno/gnovm/tests/stdlibs/std" - "github.com/gnolang/gno/tm2/pkg/crypto" - osm "github.com/gnolang/gno/tm2/pkg/os" - "github.com/gnolang/gno/tm2/pkg/sdk" - "github.com/gnolang/gno/tm2/pkg/std" - "github.com/pmezard/go-difflib/difflib" -) - -type loggerFunc func(args ...interface{}) - -func TestMachine(store gno.Store, stdout io.Writer, pkgPath string) *gno.Machine { - // default values - var ( - send std.Coins - maxAlloc int64 - ) - - return testMachineCustom(store, pkgPath, stdout, maxAlloc, send) -} - -func testMachineCustom(store gno.Store, pkgPath string, stdout io.Writer, maxAlloc int64, send std.Coins) *gno.Machine { - ctx := TestContext(pkgPath, send) - m := gno.NewMachineWithOptions(gno.MachineOptions{ - PkgPath: "", // set later. - Output: stdout, - Store: store, - Context: ctx, - MaxAllocBytes: maxAlloc, - }) - return m -} - -// TestContext returns a TestExecContext. Usable for test purpose only. -func TestContext(pkgPath string, send std.Coins) *teststd.TestExecContext { - // FIXME: create a better package to manage this, with custom constructors - pkgAddr := gno.DerivePkgAddr(pkgPath) // the addr of the pkgPath called. - caller := gno.DerivePkgAddr("user1.gno") - - pkgCoins := std.MustParseCoins(ugnot.ValueString(200_000_000)).Add(send) // >= send. - banker := newTestBanker(pkgAddr.Bech32(), pkgCoins) - ctx := stdlibs.ExecContext{ - ChainID: "dev", - Height: 123, - Timestamp: 1234567890, - Msg: nil, - OrigCaller: caller.Bech32(), - OrigPkgAddr: pkgAddr.Bech32(), - OrigSend: send, - OrigSendSpent: new(std.Coins), - Banker: banker, - EventLogger: sdk.NewEventLogger(), - } - return &teststd.TestExecContext{ - ExecContext: ctx, - RealmFrames: make(map[*gno.Frame]teststd.RealmOverride), - } -} - -// CleanupMachine can be called during two tests while reusing the same Machine instance. -func CleanupMachine(m *gno.Machine) { - prevCtx := m.Context.(*teststd.TestExecContext) - prevSend := prevCtx.OrigSend - - newCtx := TestContext("", prevCtx.OrigSend) - pkgCoins := std.MustParseCoins(ugnot.ValueString(200_000_000)).Add(prevSend) // >= send. - banker := newTestBanker(prevCtx.OrigPkgAddr, pkgCoins) - newCtx.OrigPkgAddr = prevCtx.OrigPkgAddr - newCtx.Banker = banker - m.Context = newCtx -} - -type runFileTestOptions struct { - nativeLibs bool - logger loggerFunc - syncWanted bool -} - -// RunFileTestOptions specify changing options in [RunFileTest], deviating -// from the zero value. -type RunFileTestOption func(*runFileTestOptions) - -// WithNativeLibs enables using go native libraries (ie, [ImportModeNativePreferred]) -// instead of using stdlibs/*. -func WithNativeLibs() RunFileTestOption { - return func(r *runFileTestOptions) { r.nativeLibs = true } -} - -// WithLoggerFunc sets a logging function for [RunFileTest]. -func WithLoggerFunc(f func(args ...interface{})) RunFileTestOption { - return func(r *runFileTestOptions) { r.logger = f } -} - -// WithSyncWanted sets the syncWanted flag to true. -// It rewrites tests files so that the values of Output: and of Realm: -// comments match the actual output or realm state after the test. -func WithSyncWanted(v bool) RunFileTestOption { - return func(r *runFileTestOptions) { r.syncWanted = v } -} - -// RunFileTest executes the filetest at the given path, using rootDir as -// the directory where to find the "stdlibs" directory. -func RunFileTest(rootDir string, path string, opts ...RunFileTestOption) error { - var f runFileTestOptions - for _, opt := range opts { - opt(&f) - } - - directives, pkgPath, resWanted, errWanted, rops, eventsWanted, stacktraceWanted, maxAlloc, send, preWanted := wantedFromComment(path) - if pkgPath == "" { - pkgPath = "main" - } - pkgName := DefaultPkgName(pkgPath) - stdin := new(bytes.Buffer) - stdout := new(bytes.Buffer) - stderr := new(bytes.Buffer) - mode := ImportModeStdlibsPreferred - if f.nativeLibs { - mode = ImportModeNativePreferred - } - pkgsMap := map[string]*packages.Package{} - store := TestStore(rootDir, "./files", pkgsMap, stdin, stdout, stderr, mode) - store.SetLogStoreOps(true) - m := testMachineCustom(store, pkgPath, stdout, maxAlloc, send) - checkMachineIsEmpty := true - - // TODO support stdlib groups, but make testing safe; - // e.g. not be able to make network connections. - // interp.New(interp.Options{GoPath: goPath, Stdout: &stdout, Stderr: &stderr}) - // m.Use(interp.Symbols) - // m.Use(stdlib.Symbols) - // m.Use(unsafe.Symbols) - bz, err := os.ReadFile(path) - if err != nil { - return err - } - { // Validate result, errors, etc. - var pnc interface{} - func() { - defer func() { - if r := recover(); r != nil { - // print output. - fmt.Printf("OUTPUT:\n%s\n", stdout.String()) - pnc = r - err := strings.TrimSpace(fmt.Sprintf("%v", pnc)) - // print stack if unexpected error. - if errWanted == "" || - !strings.Contains(err, errWanted) { - fmt.Printf("ERROR:\n%s\n", err) - // error didn't match: print stack - // NOTE: will fail testcase later. - rtdb.PrintStack() - } - } - }() - if f.logger != nil { - f.logger("========================================") - f.logger("RUN FILES & INIT") - f.logger("========================================") - } - if !gno.IsRealmPath(pkgPath) { - // simple case. - pn := gno.NewPackageNode(pkgName, pkgPath, &gno.FileSet{}) - pv := pn.NewPackage() - store.SetBlockNode(pn) - store.SetCachePackage(pv) - m.SetActivePackage(pv) - n := gno.MustParseFile(path, string(bz)) // "main.gno", string(bz)) - m.RunFiles(n) - if f.logger != nil { - f.logger("========================================") - f.logger("RUN MAIN") - f.logger("========================================") - } - m.RunMain() - if f.logger != nil { - f.logger("========================================") - f.logger("RUN MAIN END") - f.logger("========================================") - } - } else { - // realm case. - store.SetStrictGo2GnoMapping(true) // in gno.land, natives must be registered. - gno.DisableDebug() // until main call. - // save package using realm crawl procedure. - memPkg := &gnovm.MemPackage{ - Name: string(pkgName), - Path: pkgPath, - Files: []*gnovm.MemFile{ - { - Name: "main.gno", // dontcare - Body: string(bz), - }, - }, - } - // run decls and init functions. - m.RunMemPackage(memPkg, true) - // reconstruct machine and clear store cache. - // whether package is realm or not, since non-realm - // may call realm packages too. - if f.logger != nil { - f.logger("========================================") - f.logger("CLEAR STORE CACHE") - f.logger("========================================") - } - store.ClearCache() - /* - m = gno.NewMachineWithOptions(gno.MachineOptions{ - PkgPath: "", - Output: stdout, - Store: store, - Context: ctx, - MaxAllocBytes: maxAlloc, - }) - */ - if f.logger != nil { - store.Print() - f.logger("========================================") - f.logger("PREPROCESS ALL FILES") - f.logger("========================================") - } - m.PreprocessAllFilesAndSaveBlockNodes() - if f.logger != nil { - f.logger("========================================") - f.logger("RUN MAIN") - f.logger("========================================") - store.Print() - } - pv2 := store.GetPackage(pkgPath, false) - m.SetActivePackage(pv2) - gno.EnableDebug() - if rops != "" { - // clear store.opslog from init function(s), - // and PreprocessAllFilesAndSaveBlockNodes(). - store.SetLogStoreOps(true) // resets. - } - m.RunMain() - if f.logger != nil { - f.logger("========================================") - f.logger("RUN MAIN END") - f.logger("========================================") - } - } - }() - - for _, directive := range directives { - switch directive { - case "Error": - // errWanted given - if errWanted != "" { - if pnc == nil { - panic(fmt.Sprintf("fail on %s: got nil error, want: %q", path, errWanted)) - } - - errstr := "" - switch v := pnc.(type) { - case *gno.TypedValue: - errstr = v.Sprint(m) - case *gno.PreprocessError: - errstr = v.Unwrap().Error() - case gno.UnhandledPanicError: - errstr = v.Error() - default: - errstr = strings.TrimSpace(fmt.Sprintf("%v", pnc)) - } - - parts := strings.SplitN(errstr, ":\n--- preprocess stack ---", 2) - if len(parts) == 2 { - fmt.Println(parts[0]) - errstr = parts[0] - } - if errstr != errWanted { - if f.syncWanted { - // write error to file - replaceWantedInPlace(path, "Error", errstr) - } else { - panic(fmt.Sprintf("fail on %s: got %q, want: %q", path, errstr, errWanted)) - } - } - - // NOTE: ignores any gno.GetDebugErrors(). - gno.ClearDebugErrors() - checkMachineIsEmpty = false // nothing more to do. - } else { - // record errors when errWanted is empty and pnc not nil - if pnc != nil { - errstr := "" - if tv, ok := pnc.(*gno.TypedValue); ok { - errstr = tv.Sprint(m) - } else { - errstr = strings.TrimSpace(fmt.Sprintf("%v", pnc)) - } - parts := strings.SplitN(errstr, ":\n--- preprocess stack ---", 2) - if len(parts) == 2 { - fmt.Println(parts[0]) - errstr = parts[0] - } - // check tip line, write to file - ctl := errstr + - "\n*** CHECK THE ERR MESSAGES ABOVE, MAKE SURE IT'S WHAT YOU EXPECTED, " + - "DELETE THIS LINE AND RUN TEST AGAIN ***" - // write error to file - replaceWantedInPlace(path, "Error", ctl) - panic(fmt.Sprintf("fail on %s: err recorded, check the message and run test again", path)) - } - // check gno debug errors when errWanted is empty, pnc is nil - if gno.HasDebugErrors() { - panic(fmt.Sprintf("fail on %s: got unexpected debug error(s): %v", path, gno.GetDebugErrors())) - } - // pnc is nil, errWanted empty, no gno debug errors - checkMachineIsEmpty = false - } - case "Output": - // panic if got unexpected error - if pnc != nil { - if tv, ok := pnc.(*gno.TypedValue); ok { - panic(fmt.Sprintf("fail on %s: got unexpected error: %s", path, tv.Sprint(m))) - } else { // happens on 'unknown import path ...' - panic(fmt.Sprintf("fail on %s: got unexpected error: %v", path, pnc)) - } - } - // check result - res := strings.TrimSpace(stdout.String()) - res = trimTrailingSpaces(res) - if res != resWanted { - if f.syncWanted { - // write output to file. - replaceWantedInPlace(path, "Output", res) - } else { - // panic so tests immediately fail (for now). - if resWanted == "" { - panic(fmt.Sprintf("fail on %s: got unexpected output: %s", path, res)) - } else { - diff, _ := difflib.GetUnifiedDiffString(difflib.UnifiedDiff{ - A: difflib.SplitLines(resWanted), - B: difflib.SplitLines(res), - FromFile: "Expected", - FromDate: "", - ToFile: "Actual", - ToDate: "", - Context: 1, - }) - panic(fmt.Sprintf("fail on %s: diff:\n%s\n", path, diff)) - } - } - } - case "Events": - // panic if got unexpected error - - if pnc != nil { - if tv, ok := pnc.(*gno.TypedValue); ok { - panic(fmt.Sprintf("fail on %s: got unexpected error: %s", path, tv.Sprint(m))) - } else { // happens on 'unknown import path ...' - panic(fmt.Sprintf("fail on %s: got unexpected error: %v", path, pnc)) - } - } - // check result - events := m.Context.(*teststd.TestExecContext).EventLogger.Events() - evtjson, err := json.Marshal(events) - if err != nil { - panic(err) - } - evtstr := trimTrailingSpaces(string(evtjson)) - if evtstr != eventsWanted { - if f.syncWanted { - // write output to file. - replaceWantedInPlace(path, "Events", evtstr) - } else { - // panic so tests immediately fail (for now). - if eventsWanted == "" { - panic(fmt.Sprintf("fail on %s: got unexpected events: %s", path, evtstr)) - } else { - diff, _ := difflib.GetUnifiedDiffString(difflib.UnifiedDiff{ - A: difflib.SplitLines(eventsWanted), - B: difflib.SplitLines(evtstr), - FromFile: "Expected", - FromDate: "", - ToFile: "Actual", - ToDate: "", - Context: 1, - }) - panic(fmt.Sprintf("fail on %s: diff:\n%s\n", path, diff)) - } - } - } - case "Realm": - // panic if got unexpected error - if pnc != nil { - if tv, ok := pnc.(*gno.TypedValue); ok { - panic(fmt.Sprintf("fail on %s: got unexpected error: %s", path, tv.Sprint(m))) - } else { // TODO: does this happen? - panic(fmt.Sprintf("fail on %s: got unexpected error: %v", path, pnc)) - } - } - // check realm ops - if rops != "" { - rops2 := strings.TrimSpace(store.SprintStoreOps()) - if rops != rops2 { - if f.syncWanted { - // write output to file. - replaceWantedInPlace(path, "Realm", rops2) - } else { - diff, _ := difflib.GetUnifiedDiffString(difflib.UnifiedDiff{ - A: difflib.SplitLines(rops), - B: difflib.SplitLines(rops2), - FromFile: "Expected", - FromDate: "", - ToFile: "Actual", - ToDate: "", - Context: 1, - }) - panic(fmt.Sprintf("fail on %s: diff:\n%s\n", path, diff)) - } - } - } - case "Preprocessed": - // check preprocessed AST. - pn := store.GetBlockNode(gno.PackageNodeLocation(pkgPath)) - pre := pn.(*gno.PackageNode).FileSet.Files[0].String() - if pre != preWanted { - if f.syncWanted { - // write error to file - replaceWantedInPlace(path, "Preprocessed", pre) - } else { - // panic so tests immediately fail (for now). - diff, _ := difflib.GetUnifiedDiffString(difflib.UnifiedDiff{ - A: difflib.SplitLines(preWanted), - B: difflib.SplitLines(pre), - FromFile: "Expected", - FromDate: "", - ToFile: "Actual", - ToDate: "", - Context: 1, - }) - panic(fmt.Sprintf("fail on %s: diff:\n%s\n", path, diff)) - } - } - case "Stacktrace": - if stacktraceWanted != "" { - var stacktrace string - - switch pnc.(type) { - case gno.UnhandledPanicError: - stacktrace = m.ExceptionsStacktrace() - default: - stacktrace = m.Stacktrace().String() - } - - if f.syncWanted { - // write stacktrace to file - replaceWantedInPlace(path, "Stacktrace", stacktrace) - } else { - if !strings.Contains(stacktrace, stacktraceWanted) { - diff, _ := difflib.GetUnifiedDiffString(difflib.UnifiedDiff{ - A: difflib.SplitLines(stacktraceWanted), - B: difflib.SplitLines(stacktrace), - FromFile: "Expected", - FromDate: "", - ToFile: "Actual", - ToDate: "", - Context: 1, - }) - panic(fmt.Sprintf("fail on %s: diff:\n%s\n", path, diff)) - } - } - } - checkMachineIsEmpty = false - default: - return nil - } - } - } - - if checkMachineIsEmpty { - // Check that machine is empty. - err = m.CheckEmpty() - if err != nil { - if f.logger != nil { - f.logger("last state: \n", m.String()) - } - panic(fmt.Sprintf("fail on %s: machine not empty after main: %v", path, err)) - } - } - return nil -} - -func wantedFromComment(p string) (directives []string, pkgPath, res, err, rops, events, stacktrace string, maxAlloc int64, send std.Coins, pre string) { - fset := token.NewFileSet() - f, err2 := parser.ParseFile(fset, p, nil, parser.ParseComments) - if err2 != nil { - panic(err2) - } - if len(f.Comments) == 0 { - return - } - for _, comments := range f.Comments { - text := readComments(comments) - if strings.HasPrefix(text, "PKGPATH:") { - line := strings.SplitN(text, "\n", 2)[0] - pkgPath = strings.TrimSpace(strings.TrimPrefix(line, "PKGPATH:")) - } else if strings.HasPrefix(text, "MAXALLOC:") { - line := strings.SplitN(text, "\n", 2)[0] - maxstr := strings.TrimSpace(strings.TrimPrefix(line, "MAXALLOC:")) - maxint, err := strconv.Atoi(maxstr) - if err != nil { - panic(fmt.Sprintf("invalid maxalloc amount: %v", maxstr)) - } - maxAlloc = int64(maxint) - } else if strings.HasPrefix(text, "SEND:") { - line := strings.SplitN(text, "\n", 2)[0] - sendstr := strings.TrimSpace(strings.TrimPrefix(line, "SEND:")) - send = std.MustParseCoins(sendstr) - } else if strings.HasPrefix(text, "Output:\n") { - res = strings.TrimPrefix(text, "Output:\n") - res = strings.TrimSpace(res) - directives = append(directives, "Output") - } else if strings.HasPrefix(text, "Error:\n") { - err = strings.TrimPrefix(text, "Error:\n") - err = strings.TrimSpace(err) - // XXX temporary until we support line:column. - // If error starts with line:column, trim it. - re := regexp.MustCompile(`^[0-9]+:[0-9]+: `) - err = re.ReplaceAllString(err, "") - directives = append(directives, "Error") - } else if strings.HasPrefix(text, "Realm:\n") { - rops = strings.TrimPrefix(text, "Realm:\n") - rops = strings.TrimSpace(rops) - directives = append(directives, "Realm") - } else if strings.HasPrefix(text, "Events:\n") { - events = strings.TrimPrefix(text, "Events:\n") - events = strings.TrimSpace(events) - directives = append(directives, "Events") - } else if strings.HasPrefix(text, "Preprocessed:\n") { - pre = strings.TrimPrefix(text, "Preprocessed:\n") - pre = strings.TrimSpace(pre) - directives = append(directives, "Preprocessed") - } else if strings.HasPrefix(text, "Stacktrace:\n") { - stacktrace = strings.TrimPrefix(text, "Stacktrace:\n") - stacktrace = strings.TrimSpace(stacktrace) - directives = append(directives, "Stacktrace") - } else { - // ignore unexpected. - } - } - return -} - -// readComments returns //-style comments from cg, but without truncating empty -// lines like cg.Text(). -func readComments(cg *ast.CommentGroup) string { - var b strings.Builder - for _, c := range cg.List { - if len(c.Text) < 2 || c.Text[:2] != "//" { - // ignore no //-style comment - break - } - s := strings.TrimPrefix(c.Text[2:], " ") - b.WriteString(s + "\n") - } - return b.String() -} - -// Replace comment in file with given output given directive. -func replaceWantedInPlace(path string, directive string, output string) { - bz := osm.MustReadFile(path) - body := string(bz) - lines := strings.Split(body, "\n") - isReplacing := false - wroteDirective := false - newlines := []string(nil) - for _, line := range lines { - if line == "// "+directive+":" { - if wroteDirective { - isReplacing = true - continue - } else { - wroteDirective = true - isReplacing = true - newlines = append(newlines, "// "+directive+":") - outlines := strings.Split(output, "\n") - for _, outline := range outlines { - newlines = append(newlines, - strings.TrimRight("// "+outline, " ")) - } - continue - } - } else if isReplacing { - if strings.HasPrefix(line, "//") { - continue - } else { - isReplacing = false - } - } - newlines = append(newlines, line) - } - osm.MustWriteFile(path, []byte(strings.Join(newlines, "\n")), 0o644) -} - -func DefaultPkgName(gopkgPath string) gno.Name { - parts := strings.Split(gopkgPath, "/") - last := parts[len(parts)-1] - parts = strings.Split(last, "-") - name := parts[len(parts)-1] - name = strings.ToLower(name) - return gno.Name(name) -} - -// go comments strip trailing spaces. -func trimTrailingSpaces(result string) string { - lines := strings.Split(result, "\n") - for i, line := range lines { - lines[i] = strings.TrimRight(line, " \t") - } - return strings.Join(lines, "\n") -} - -// ---------------------------------------- -// testBanker - -type testBanker struct { - coinTable map[crypto.Bech32Address]std.Coins -} - -func newTestBanker(args ...interface{}) *testBanker { - coinTable := make(map[crypto.Bech32Address]std.Coins) - if len(args)%2 != 0 { - panic("newTestBanker requires even number of arguments; addr followed by coins") - } - for i := 0; i < len(args); i += 2 { - addr := args[i].(crypto.Bech32Address) - amount := args[i+1].(std.Coins) - coinTable[addr] = amount - } - return &testBanker{ - coinTable: coinTable, - } -} - -func (tb *testBanker) GetCoins(addr crypto.Bech32Address) (dst std.Coins) { - return tb.coinTable[addr] -} - -func (tb *testBanker) SendCoins(from, to crypto.Bech32Address, amt std.Coins) { - fcoins, fexists := tb.coinTable[from] - if !fexists { - panic(fmt.Sprintf( - "source address %s does not exist", - from.String())) - } - if !fcoins.IsAllGTE(amt) { - panic(fmt.Sprintf( - "source address %s has %s; cannot send %s", - from.String(), fcoins, amt)) - } - // First, subtract from 'from'. - frest := fcoins.Sub(amt) - tb.coinTable[from] = frest - // Second, add to 'to'. - // NOTE: even works when from==to, due to 2-step isolation. - tcoins, _ := tb.coinTable[to] - tsum := tcoins.Add(amt) - tb.coinTable[to] = tsum -} - -func (tb *testBanker) TotalCoin(denom string) int64 { - panic("not yet implemented") -} - -func (tb *testBanker) IssueCoin(addr crypto.Bech32Address, denom string, amt int64) { - coins, _ := tb.coinTable[addr] - sum := coins.Add(std.Coins{{Denom: denom, Amount: amt}}) - tb.coinTable[addr] = sum -} - -func (tb *testBanker) RemoveCoin(addr crypto.Bech32Address, denom string, amt int64) { - coins, _ := tb.coinTable[addr] - rest := coins.Sub(std.Coins{{Denom: denom, Amount: amt}}) - tb.coinTable[addr] = rest -} diff --git a/gnovm/tests/imports.go b/gnovm/tests/imports.go deleted file mode 100644 index f4cbffa0bf8..00000000000 --- a/gnovm/tests/imports.go +++ /dev/null @@ -1,511 +0,0 @@ -package tests - -import ( - "bufio" - "bytes" - "compress/flate" - "compress/gzip" - "context" - "crypto/md5" //nolint:gosec - crand "crypto/rand" - "crypto/sha1" //nolint:gosec - "encoding/base64" - "encoding/binary" - "encoding/json" - "encoding/xml" - "errors" - "flag" - "fmt" - "hash/fnv" - "image" - "image/color" - "io" - "log" - "math" - "math/big" - "math/rand/v2" - "net" - "net/url" - "os" - "path/filepath" - "reflect" - "strconv" - "strings" - "sync" - "sync/atomic" - "text/template" - "time" - "unicode/utf8" - - gno "github.com/gnolang/gno/gnovm/pkg/gnolang" - "github.com/gnolang/gno/gnovm/pkg/packages" - teststdlibs "github.com/gnolang/gno/gnovm/tests/stdlibs" - teststd "github.com/gnolang/gno/gnovm/tests/stdlibs/std" - "github.com/gnolang/gno/tm2/pkg/db/memdb" - osm "github.com/gnolang/gno/tm2/pkg/os" - "github.com/gnolang/gno/tm2/pkg/std" - "github.com/gnolang/gno/tm2/pkg/store/dbadapter" - "github.com/gnolang/gno/tm2/pkg/store/iavl" - stypes "github.com/gnolang/gno/tm2/pkg/store/types" -) - -type importMode uint64 - -// Import modes to control the import behaviour of TestStore. -const ( - // use stdlibs/* only (except a few exceptions). for stdlibs/* and examples/* testing. - ImportModeStdlibsOnly importMode = iota - // use stdlibs/* if present, otherwise use native. used in files/tests, excluded for *_native.go - ImportModeStdlibsPreferred - // do not use stdlibs/* if native registered. used in files/tests, excluded for *_stdlibs.go - ImportModeNativePreferred -) - -// NOTE: this isn't safe, should only be used for testing. -func TestStore(rootDir, filesPath string, pkgs map[string]*packages.Package, stdin io.Reader, stdout, stderr io.Writer, mode importMode) (resStore gno.Store) { - getPackage := func(pkgPath string, store gno.Store) (pn *gno.PackageNode, pv *gno.PackageValue) { - if pkgPath == "" { - panic(fmt.Sprintf("invalid zero package path in testStore().pkgGetter")) - } - if mode != ImportModeStdlibsOnly && - mode != ImportModeStdlibsPreferred && - mode != ImportModeNativePreferred { - panic(fmt.Sprintf("unrecognized import mode")) - } - - if filesPath != "" { - // if _test package... - const testPath = "github.com/gnolang/gno/_test/" - if strings.HasPrefix(pkgPath, testPath) { - baseDir := filepath.Join(filesPath, "extern", pkgPath[len(testPath):]) - memPkg := gno.ReadMemPackage(baseDir, pkgPath) - send := std.Coins{} - ctx := TestContext(pkgPath, send) - m2 := gno.NewMachineWithOptions(gno.MachineOptions{ - PkgPath: "test", - Output: stdout, - Store: store, - Context: ctx, - }) - // pkg := gno.NewPackageNode(gno.Name(memPkg.Name), memPkg.Path, nil) - // pv := pkg.NewPackage() - // m2.SetActivePackage(pv) - // XXX remove second arg 'false' and remove all gonative stuff. - return m2.RunMemPackage(memPkg, false) - } - } - - // if stdlibs package is preferred , try to load it first. - if mode == ImportModeStdlibsOnly || - mode == ImportModeStdlibsPreferred { - pn, pv = loadStdlib(rootDir, pkgPath, store, stdout) - if pn != nil { - return - } - } - - // if native package is allowed, return it. - if pkgPath == "os" || // special cases even when StdlibsOnly (for tests). - pkgPath == "fmt" || // TODO: try to minimize these exceptions over time. - pkgPath == "log" || - pkgPath == "crypto/rand" || - pkgPath == "crypto/md5" || - pkgPath == "crypto/sha1" || - pkgPath == "encoding/binary" || - pkgPath == "encoding/json" || - pkgPath == "encoding/xml" || - pkgPath == "internal/os_test" || - pkgPath == "math/big" || - mode == ImportModeStdlibsPreferred || - mode == ImportModeNativePreferred { - switch pkgPath { - case "os": - pkg := gno.NewPackageNode("os", pkgPath, nil) - pkg.DefineGoNativeValue("Stdin", stdin) - pkg.DefineGoNativeValue("Stdout", stdout) - pkg.DefineGoNativeValue("Stderr", stderr) - return pkg, pkg.NewPackage() - case "fmt": - pkg := gno.NewPackageNode("fmt", pkgPath, nil) - pkg.DefineGoNativeType(reflect.TypeOf((*fmt.Stringer)(nil)).Elem()) - pkg.DefineGoNativeType(reflect.TypeOf((*fmt.Formatter)(nil)).Elem()) - pkg.DefineGoNativeValue("Println", func(a ...interface{}) (n int, err error) { - // NOTE: uncomment to debug long running tests - // fmt.Println(a...) - res := fmt.Sprintln(a...) - return stdout.Write([]byte(res)) - }) - pkg.DefineGoNativeValue("Printf", func(format string, a ...interface{}) (n int, err error) { - res := fmt.Sprintf(format, a...) - return stdout.Write([]byte(res)) - }) - pkg.DefineGoNativeValue("Print", func(a ...interface{}) (n int, err error) { - res := fmt.Sprint(a...) - return stdout.Write([]byte(res)) - }) - pkg.DefineGoNativeValue("Sprint", fmt.Sprint) - pkg.DefineGoNativeValue("Sprintf", fmt.Sprintf) - pkg.DefineGoNativeValue("Sprintln", fmt.Sprintln) - pkg.DefineGoNativeValue("Sscanf", fmt.Sscanf) - pkg.DefineGoNativeValue("Errorf", fmt.Errorf) - pkg.DefineGoNativeValue("Fprintln", fmt.Fprintln) - pkg.DefineGoNativeValue("Fprintf", fmt.Fprintf) - pkg.DefineGoNativeValue("Fprint", fmt.Fprint) - return pkg, pkg.NewPackage() - case "encoding/base64": - pkg := gno.NewPackageNode("base64", pkgPath, nil) - pkg.DefineGoNativeValue("RawStdEncoding", base64.RawStdEncoding) - pkg.DefineGoNativeValue("StdEncoding", base64.StdEncoding) - pkg.DefineGoNativeValue("NewDecoder", base64.NewDecoder) - return pkg, pkg.NewPackage() - case "encoding/binary": - pkg := gno.NewPackageNode("binary", pkgPath, nil) - pkg.DefineGoNativeValue("LittleEndian", binary.LittleEndian) - pkg.DefineGoNativeValue("BigEndian", binary.BigEndian) - pkg.DefineGoNativeValue("Write", binary.BigEndian) // warn: use reflection - return pkg, pkg.NewPackage() - case "encoding/json": - pkg := gno.NewPackageNode("json", pkgPath, nil) - pkg.DefineGoNativeValue("Unmarshal", json.Unmarshal) - pkg.DefineGoNativeValue("Marshal", json.Marshal) - return pkg, pkg.NewPackage() - case "encoding/xml": - pkg := gno.NewPackageNode("xml", pkgPath, nil) - pkg.DefineGoNativeValue("Unmarshal", xml.Unmarshal) - return pkg, pkg.NewPackage() - case "internal/os_test": - pkg := gno.NewPackageNode("os_test", pkgPath, nil) - pkg.DefineNative("Sleep", - gno.Flds( // params - "d", gno.AnyT(), // NOTE: should be time.Duration - ), - gno.Flds( // results - ), - func(m *gno.Machine) { - // For testing purposes here, nanoseconds are separately kept track. - arg0 := m.LastBlock().GetParams1().TV - d := arg0.GetInt64() - sec := d / int64(time.Second) - nano := d % int64(time.Second) - ctx := m.Context.(*teststd.TestExecContext) - ctx.Timestamp += sec - ctx.TimestampNano += nano - if ctx.TimestampNano >= int64(time.Second) { - ctx.Timestamp += 1 - ctx.TimestampNano -= int64(time.Second) - } - m.Context = ctx - }, - ) - return pkg, pkg.NewPackage() - case "net": - pkg := gno.NewPackageNode("net", pkgPath, nil) - pkg.DefineGoNativeType(reflect.TypeOf(net.TCPAddr{})) - pkg.DefineGoNativeValue("IPv4", net.IPv4) - return pkg, pkg.NewPackage() - case "net/url": - pkg := gno.NewPackageNode("url", pkgPath, nil) - pkg.DefineGoNativeType(reflect.TypeOf(url.Values{})) - return pkg, pkg.NewPackage() - case "bufio": - pkg := gno.NewPackageNode("bufio", pkgPath, nil) - pkg.DefineGoNativeValue("NewScanner", bufio.NewScanner) - pkg.DefineGoNativeType(reflect.TypeOf(bufio.SplitFunc(nil))) - return pkg, pkg.NewPackage() - case "bytes": - pkg := gno.NewPackageNode("bytes", pkgPath, nil) - pkg.DefineGoNativeValue("Equal", bytes.Equal) - pkg.DefineGoNativeValue("Compare", bytes.Compare) - pkg.DefineGoNativeValue("NewReader", bytes.NewReader) - pkg.DefineGoNativeValue("NewBuffer", bytes.NewBuffer) - pkg.DefineGoNativeValue("Repeat", bytes.Repeat) - pkg.DefineGoNativeType(reflect.TypeOf(bytes.Buffer{})) - return pkg, pkg.NewPackage() - case "time": - pkg := gno.NewPackageNode("time", pkgPath, nil) - pkg.DefineGoNativeConstValue("Millisecond", time.Millisecond) - pkg.DefineGoNativeConstValue("Second", time.Second) - pkg.DefineGoNativeConstValue("Minute", time.Minute) - pkg.DefineGoNativeConstValue("Hour", time.Hour) - pkg.DefineGoNativeConstValue("Date", time.Date) - pkg.DefineGoNativeConstValue("Now", func() time.Time { return time.Unix(0, 0).UTC() }) // deterministic - pkg.DefineGoNativeConstValue("January", time.January) - pkg.DefineGoNativeConstValue("February", time.February) - pkg.DefineGoNativeConstValue("March", time.March) - pkg.DefineGoNativeConstValue("April", time.April) - pkg.DefineGoNativeConstValue("May", time.May) - pkg.DefineGoNativeConstValue("June", time.June) - pkg.DefineGoNativeConstValue("July", time.July) - pkg.DefineGoNativeConstValue("August", time.August) - pkg.DefineGoNativeConstValue("September", time.September) - pkg.DefineGoNativeConstValue("November", time.November) - pkg.DefineGoNativeConstValue("December", time.December) - pkg.DefineGoNativeValue("UTC", time.UTC) - pkg.DefineGoNativeValue("Unix", time.Unix) - pkg.DefineGoNativeType(reflect.TypeOf(time.Time{})) - pkg.DefineGoNativeType(reflect.TypeOf(time.Duration(0))) - pkg.DefineGoNativeType(reflect.TypeOf(time.Month(0))) - pkg.DefineGoNativeValue("LoadLocation", time.LoadLocation) - return pkg, pkg.NewPackage() - case "strconv": - pkg := gno.NewPackageNode("strconv", pkgPath, nil) - pkg.DefineGoNativeValue("Itoa", strconv.Itoa) - pkg.DefineGoNativeValue("Atoi", strconv.Atoi) - pkg.DefineGoNativeValue("ParseInt", strconv.ParseInt) - pkg.DefineGoNativeValue("Quote", strconv.Quote) - pkg.DefineGoNativeValue("FormatUint", strconv.FormatUint) - pkg.DefineGoNativeType(reflect.TypeOf(strconv.NumError{})) - return pkg, pkg.NewPackage() - case "strings": - pkg := gno.NewPackageNode("strings", pkgPath, nil) - pkg.DefineGoNativeValue("Split", strings.Split) - pkg.DefineGoNativeValue("SplitN", strings.SplitN) - pkg.DefineGoNativeValue("Contains", strings.Contains) - pkg.DefineGoNativeValue("TrimSpace", strings.TrimSpace) - pkg.DefineGoNativeValue("HasPrefix", strings.HasPrefix) - pkg.DefineGoNativeValue("NewReader", strings.NewReader) - pkg.DefineGoNativeValue("Index", strings.Index) - pkg.DefineGoNativeValue("IndexRune", strings.IndexRune) - pkg.DefineGoNativeValue("Join", strings.Join) - pkg.DefineGoNativeType(reflect.TypeOf(strings.Builder{})) - return pkg, pkg.NewPackage() - case "math": - pkg := gno.NewPackageNode("math", pkgPath, nil) - pkg.DefineGoNativeValue("Abs", math.Abs) - pkg.DefineGoNativeValue("Cos", math.Cos) - pkg.DefineGoNativeConstValue("Pi", math.Pi) - pkg.DefineGoNativeValue("Float64bits", math.Float64bits) - pkg.DefineGoNativeConstValue("MaxFloat32", math.MaxFloat32) - pkg.DefineGoNativeConstValue("MaxFloat64", math.MaxFloat64) - pkg.DefineGoNativeConstValue("MaxUint32", uint32(math.MaxUint32)) - pkg.DefineGoNativeConstValue("MaxUint64", uint64(math.MaxUint64)) - pkg.DefineGoNativeConstValue("MinInt8", math.MinInt8) - pkg.DefineGoNativeConstValue("MinInt16", math.MinInt16) - pkg.DefineGoNativeConstValue("MinInt32", math.MinInt32) - pkg.DefineGoNativeConstValue("MinInt64", int64(math.MinInt64)) - pkg.DefineGoNativeConstValue("MaxInt8", math.MaxInt8) - pkg.DefineGoNativeConstValue("MaxInt16", math.MaxInt16) - pkg.DefineGoNativeConstValue("MaxInt32", math.MaxInt32) - pkg.DefineGoNativeConstValue("MaxInt64", int64(math.MaxInt64)) - return pkg, pkg.NewPackage() - case "math/rand": - // XXX only expose for tests. - pkg := gno.NewPackageNode("rand", pkgPath, nil) - // make native rand same as gno rand. - rnd := rand.New(rand.NewPCG(0, 0)) //nolint:gosec - pkg.DefineGoNativeValue("IntN", rnd.IntN) - pkg.DefineGoNativeValue("Uint32", rnd.Uint32) - return pkg, pkg.NewPackage() - case "crypto/rand": - pkg := gno.NewPackageNode("rand", pkgPath, nil) - pkg.DefineGoNativeValue("Prime", crand.Prime) - // for determinism: - // pkg.DefineGoNativeValue("Reader", crand.Reader) - pkg.DefineGoNativeValue("Reader", &dummyReader{}) - return pkg, pkg.NewPackage() - case "crypto/md5": - pkg := gno.NewPackageNode("md5", pkgPath, nil) - pkg.DefineGoNativeValue("New", md5.New) - return pkg, pkg.NewPackage() - case "crypto/sha1": - pkg := gno.NewPackageNode("sha1", pkgPath, nil) - pkg.DefineGoNativeValue("New", sha1.New) - return pkg, pkg.NewPackage() - case "image": - pkg := gno.NewPackageNode("image", pkgPath, nil) - pkg.DefineGoNativeType(reflect.TypeOf(image.Point{})) - return pkg, pkg.NewPackage() - case "image/color": - pkg := gno.NewPackageNode("color", pkgPath, nil) - pkg.DefineGoNativeType(reflect.TypeOf(color.NRGBA64{})) - return pkg, pkg.NewPackage() - case "compress/flate": - pkg := gno.NewPackageNode("flate", pkgPath, nil) - pkg.DefineGoNativeConstValue("BestSpeed", flate.BestSpeed) - return pkg, pkg.NewPackage() - case "compress/gzip": - pkg := gno.NewPackageNode("gzip", pkgPath, nil) - pkg.DefineGoNativeType(reflect.TypeOf(gzip.Writer{})) - pkg.DefineGoNativeConstValue("BestCompression", gzip.BestCompression) - pkg.DefineGoNativeConstValue("BestSpeed", gzip.BestSpeed) - return pkg, pkg.NewPackage() - case "context": - pkg := gno.NewPackageNode("context", pkgPath, nil) - pkg.DefineGoNativeType(reflect.TypeOf((*context.Context)(nil)).Elem()) - pkg.DefineGoNativeValue("WithValue", context.WithValue) - pkg.DefineGoNativeValue("Background", context.Background) - return pkg, pkg.NewPackage() - case "sync": - pkg := gno.NewPackageNode("sync", pkgPath, nil) - pkg.DefineGoNativeType(reflect.TypeOf(sync.Mutex{})) - pkg.DefineGoNativeType(reflect.TypeOf(sync.RWMutex{})) - pkg.DefineGoNativeType(reflect.TypeOf(sync.Pool{})) - return pkg, pkg.NewPackage() - case "sync/atomic": - pkg := gno.NewPackageNode("atomic", pkgPath, nil) - pkg.DefineGoNativeType(reflect.TypeOf(atomic.Value{})) - return pkg, pkg.NewPackage() - case "math/big": - pkg := gno.NewPackageNode("big", pkgPath, nil) - pkg.DefineGoNativeValue("NewInt", big.NewInt) - return pkg, pkg.NewPackage() - case "flag": - pkg := gno.NewPackageNode("flag", pkgPath, nil) - pkg.DefineGoNativeType(reflect.TypeOf(flag.Flag{})) - return pkg, pkg.NewPackage() - case "io": - pkg := gno.NewPackageNode("io", pkgPath, nil) - pkg.DefineGoNativeValue("EOF", io.EOF) - pkg.DefineGoNativeValue("NopCloser", io.NopCloser) - pkg.DefineGoNativeValue("ReadFull", io.ReadFull) - pkg.DefineGoNativeValue("ReadAll", io.ReadAll) - pkg.DefineGoNativeType(reflect.TypeOf((*io.ReadCloser)(nil)).Elem()) - pkg.DefineGoNativeType(reflect.TypeOf((*io.Closer)(nil)).Elem()) - pkg.DefineGoNativeType(reflect.TypeOf((*io.Reader)(nil)).Elem()) - return pkg, pkg.NewPackage() - case "log": - pkg := gno.NewPackageNode("log", pkgPath, nil) - pkg.DefineGoNativeValue("Fatal", log.Fatal) - return pkg, pkg.NewPackage() - case "text/template": - pkg := gno.NewPackageNode("template", pkgPath, nil) - pkg.DefineGoNativeType(reflect.TypeOf(template.FuncMap{})) - return pkg, pkg.NewPackage() - case "unicode/utf8": - pkg := gno.NewPackageNode("utf8", pkgPath, nil) - pkg.DefineGoNativeValue("DecodeRuneInString", utf8.DecodeRuneInString) - tv := gno.TypedValue{T: gno.UntypedRuneType} // TODO dry - tv.SetInt32(utf8.RuneSelf) // .. - pkg.Define("RuneSelf", tv) // .. - return pkg, pkg.NewPackage() - case "errors": - pkg := gno.NewPackageNode("errors", pkgPath, nil) - pkg.DefineGoNativeValue("New", errors.New) - return pkg, pkg.NewPackage() - case "hash/fnv": - pkg := gno.NewPackageNode("fnv", pkgPath, nil) - pkg.DefineGoNativeValue("New32a", fnv.New32a) - return pkg, pkg.NewPackage() - default: - // continue on... - } - } - - // if native package is preferred, try to load stdlibs/* as backup. - if mode == ImportModeNativePreferred { - pn, pv = loadStdlib(rootDir, pkgPath, store, stdout) - if pn != nil { - return - } - } - - // packages from resolver - if pkg, ok := pkgs[pkgPath]; ok { - memPkg, err := pkg.MemPkg() - if err != nil { - panic(fmt.Errorf("failed to convert imported pkg to mem pkg: %w", err)) - } - send := std.Coins{} - ctx := TestContext(pkgPath, send) - m2 := gno.NewMachineWithOptions(gno.MachineOptions{ - PkgPath: "test", - Output: stdout, - Store: store, - Context: ctx, - }) - pn, pv = m2.RunMemPackage(memPkg, true) - return - } - - // if examples package... - examplePath := filepath.Join(rootDir, "examples", pkgPath) - if osm.DirExists(examplePath) { - memPkg := gno.ReadMemPackage(examplePath, pkgPath) - if memPkg.IsEmpty() { - panic(fmt.Sprintf("found an empty package %q", pkgPath)) - } - - send := std.Coins{} - ctx := TestContext(pkgPath, send) - m2 := gno.NewMachineWithOptions(gno.MachineOptions{ - PkgPath: "test", - Output: stdout, - Store: store, - Context: ctx, - }) - pn, pv = m2.RunMemPackage(memPkg, true) - return - } - return nil, nil - } - db := memdb.NewMemDB() - baseStore := dbadapter.StoreConstructor(db, stypes.StoreOptions{}) - iavlStore := iavl.StoreConstructor(db, stypes.StoreOptions{}) - // make a new store - resStore = gno.NewStore(nil, baseStore, iavlStore) - resStore.SetPackageGetter(getPackage) - resStore.SetNativeStore(teststdlibs.NativeStore) - resStore.SetStrictGo2GnoMapping(false) - return -} - -func loadStdlib(rootDir, pkgPath string, store gno.Store, stdout io.Writer) (*gno.PackageNode, *gno.PackageValue) { - dirs := [...]string{ - // normal stdlib path. - filepath.Join(rootDir, "gnovm", "stdlibs", pkgPath), - // override path. definitions here override the previous if duplicate. - filepath.Join(rootDir, "gnovm", "tests", "stdlibs", pkgPath), - } - files := make([]string, 0, 32) // pre-alloc 32 as a likely high number of files - for _, path := range dirs { - dl, err := os.ReadDir(path) - if err != nil { - if os.IsNotExist(err) { - continue - } - panic(fmt.Errorf("could not access dir %q: %w", path, err)) - } - - for _, f := range dl { - // NOTE: RunMemPackage has other rules; those should be mostly useful - // for on-chain packages (ie. include README and gno.mod). - if !f.IsDir() && strings.HasSuffix(f.Name(), ".gno") { - files = append(files, filepath.Join(path, f.Name())) - } - } - } - if len(files) == 0 { - return nil, nil - } - - memPkg := gno.ReadMemPackageFromList(files, pkgPath) - m2 := gno.NewMachineWithOptions(gno.MachineOptions{ - // NOTE: see also pkgs/sdk/vm/builtins.go - // Needs PkgPath != its name because TestStore.getPackage is the package - // getter for the store, which calls loadStdlib, so it would be recursively called. - PkgPath: "stdlibload", - Output: stdout, - Store: store, - }) - save := pkgPath != "testing" // never save the "testing" package - return m2.RunMemPackageWithOverrides(memPkg, save) -} - -type dummyReader struct{} - -func (*dummyReader) Read(b []byte) (n int, err error) { - for i := 0; i < len(b); i++ { - b[i] = byte((100 + i) % 256) - } - return len(b), nil -} - -// ---------------------------------------- - -type TestReport struct { - Name string - Verbose bool - Failed bool - Skipped bool - Output string -} From 78b60ed3bcd5cb2e6a1470a9b1c50d8c261ac089 Mon Sep 17 00:00:00 2001 From: Norman Date: Wed, 8 Jan 2025 07:53:35 +0100 Subject: [PATCH 041/143] chore: remove old pkg loader Signed-off-by: Norman --- contribs/gnodev/pkg/dev/node.go | 13 +- contribs/gnodev/pkg/dev/packages.go | 14 +-- contribs/gnodev/pkg/watcher/watch.go | 2 +- gno.land/cmd/gnoland/start.go | 8 +- gno.land/pkg/gnoland/genesis.go | 9 +- gno.land/pkg/integration/node_testing.go | 4 +- gno.land/pkg/integration/pkgloader.go | 84 +++---------- gnovm/cmd/gno/mod.go | 3 +- gnovm/pkg/packages/expand_patterns.go | 5 + gnovm/pkg/packages/load.go | 9 +- .../packages/{pkg_test.go => load_test.go} | 36 +++--- gnovm/pkg/packages/pkg.go | 114 +++--------------- 12 files changed, 99 insertions(+), 202 deletions(-) rename gnovm/pkg/packages/{pkg_test.go => load_test.go} (70%) diff --git a/contribs/gnodev/pkg/dev/node.go b/contribs/gnodev/pkg/dev/node.go index 5b572326278..d9471150f76 100644 --- a/contribs/gnodev/pkg/dev/node.go +++ b/contribs/gnodev/pkg/dev/node.go @@ -44,6 +44,7 @@ type NodeConfig struct { MaxGasPerBlock int64 ChainID string ChainDomain string + LoadConfig *packages.LoadConfig } func DefaultNodeConfig(rootdir, domain string) *NodeConfig { @@ -70,6 +71,7 @@ func DefaultNodeConfig(rootdir, domain string) *NodeConfig { TMConfig: tmc, SkipFailingGenesisTxs: true, MaxGasPerBlock: 10_000_000_000, + LoadConfig: &packages.LoadConfig{Deps: true}, } } @@ -98,7 +100,7 @@ type Node struct { var DefaultFee = std.NewFee(50000, std.MustParseCoin(ugnot.ValueString(1000000))) func NewDevNode(ctx context.Context, cfg *NodeConfig) (*Node, error) { - mpkgs, err := NewPackagesMap(cfg.PackagesPathList) + mpkgs, err := NewPackagesMap(cfg.LoadConfig, cfg.PackagesPathList) if err != nil { return nil, fmt.Errorf("unable map pkgs list: %w", err) } @@ -140,7 +142,7 @@ func (n *Node) Close() error { return n.Node.Stop() } -func (n *Node) ListPkgs() []packages.Pkg { +func (n *Node) ListPkgs() []*packages.Package { n.muNode.RLock() defer n.muNode.RUnlock() @@ -240,7 +242,8 @@ func (n *Node) updatePackages(paths ...string) error { } // List all packages from target path - pkgslist, err := packages.ListPkgs(abspath) + loadCfg := &packages.LoadConfig{Deps: true} + pkgslist, err := packages.Load(loadCfg, filepath.Join(abspath, "...")) if err != nil { return fmt.Errorf("failed to list gno packages for %q: %w", path, err) } @@ -248,12 +251,12 @@ func (n *Node) updatePackages(paths ...string) error { // Update or add package in the current known list. for _, pkg := range pkgslist { n.pkgs[pkg.Dir] = Package{ - Pkg: pkg, + Package: pkg, Creator: deployer, Deposit: deposit, } - n.logger.Debug("pkgs update", "name", pkg.Name, "path", pkg.Dir) + n.logger.Debug("pkgs update", "name", pkg.ImportPath, "path", pkg.Dir) } pkgsUpdated += len(pkgslist) diff --git a/contribs/gnodev/pkg/dev/packages.go b/contribs/gnodev/pkg/dev/packages.go index 8140aab1389..d6585ac30b3 100644 --- a/contribs/gnodev/pkg/dev/packages.go +++ b/contribs/gnodev/pkg/dev/packages.go @@ -63,7 +63,7 @@ func ResolvePackagePathQuery(bk *address.Book, path string) (PackagePath, error) } type Package struct { - packages.Pkg + *packages.Package Creator crypto.Address Deposit std.Coins } @@ -75,7 +75,7 @@ var ( ErrEmptyDepositPackage = errors.New("no deposit specified for package") ) -func NewPackagesMap(ppaths []PackagePath) (PackagesMap, error) { +func NewPackagesMap(cfg *packages.LoadConfig, ppaths []PackagePath) (PackagesMap, error) { pkgs := make(map[string]Package) for _, ppath := range ppaths { if ppath.Creator.IsZero() { @@ -88,7 +88,7 @@ func NewPackagesMap(ppaths []PackagePath) (PackagesMap, error) { } // list all packages from target path - pkgslist, err := packages.ListPkgs(abspath) + pkgslist, err := packages.Load(cfg, filepath.Join(abspath, "...")) if err != nil { return nil, fmt.Errorf("listing gno packages: %w", err) } @@ -102,7 +102,7 @@ func NewPackagesMap(ppaths []PackagePath) (PackagesMap, error) { continue // skip } pkgs[pkg.Dir] = Package{ - Pkg: pkg, + Package: pkg, Creator: ppath.Creator, Deposit: ppath.Deposit, } @@ -113,9 +113,9 @@ func NewPackagesMap(ppaths []PackagePath) (PackagesMap, error) { } func (pm PackagesMap) toList() packages.PkgList { - list := make([]packages.Pkg, 0, len(pm)) + list := make([]*packages.Package, 0, len(pm)) for _, pkg := range pm { - list = append(list, pkg.Pkg) + list = append(list, pkg.Package) } return list } @@ -138,7 +138,7 @@ func (pm PackagesMap) Load(fee std.Fee, start time.Time) ([]gnoland.TxWithMetada } // Open files in directory as MemPackage. - memPkg := gno.MustReadMemPackage(modPkg.Dir, modPkg.Name) + memPkg := gno.MustReadMemPackage(modPkg.Dir, modPkg.ImportPath) if err := memPkg.Validate(); err != nil { return nil, fmt.Errorf("invalid package: %w", err) } diff --git a/contribs/gnodev/pkg/watcher/watch.go b/contribs/gnodev/pkg/watcher/watch.go index 3f49936d48b..a518271f358 100644 --- a/contribs/gnodev/pkg/watcher/watch.go +++ b/contribs/gnodev/pkg/watcher/watch.go @@ -118,7 +118,7 @@ func (p *PackageWatcher) Stop() { // Packages are sorted by their length in descending order to facilitate easier // and more efficient matching with corresponding paths. The longest paths are // compared first. -func (p *PackageWatcher) AddPackages(pkgs ...packages.Pkg) error { +func (p *PackageWatcher) AddPackages(pkgs ...*packages.Package) error { for _, pkg := range pkgs { dir := pkg.Dir diff --git a/gno.land/cmd/gnoland/start.go b/gno.land/cmd/gnoland/start.go index a420e652810..d2b888a33de 100644 --- a/gno.land/cmd/gnoland/start.go +++ b/gno.land/cmd/gnoland/start.go @@ -17,6 +17,7 @@ import ( "github.com/gnolang/gno/gno.land/pkg/gnoland/ugnot" "github.com/gnolang/gno/gno.land/pkg/log" "github.com/gnolang/gno/gnovm/pkg/gnoenv" + "github.com/gnolang/gno/gnovm/pkg/packages" abci "github.com/gnolang/gno/tm2/pkg/bft/abci/types" "github.com/gnolang/gno/tm2/pkg/bft/config" "github.com/gnolang/gno/tm2/pkg/bft/node" @@ -340,7 +341,7 @@ func lazyInitGenesis( } // Generate the new genesis.json file - if err := generateGenesisFile(genesisPath, publicKey, c); err != nil { + if err := generateGenesisFile(io, genesisPath, publicKey, c); err != nil { return fmt.Errorf("unable to generate genesis file, %w", err) } @@ -365,7 +366,7 @@ func initializeLogger(io io.WriteCloser, logLevel, logFormat string) (*zap.Logge return log.GetZapLoggerFn(format)(io, level), nil } -func generateGenesisFile(genesisFile string, pk crypto.PubKey, c *startCfg) error { +func generateGenesisFile(io commands.IO, genesisFile string, pk crypto.PubKey, c *startCfg) error { gen := &bft.GenesisDoc{} gen.GenesisTime = time.Now() gen.ChainID = c.chainID @@ -396,7 +397,8 @@ func generateGenesisFile(genesisFile string, pk crypto.PubKey, c *startCfg) erro // Load examples folder examplesDir := filepath.Join(c.gnoRootDir, "examples") - pkgsTxs, err := gnoland.LoadPackagesFromDir(examplesDir, genesisDeployAddress, genesisDeployFee) + loadCfg := &packages.LoadConfig{IO: io} + pkgsTxs, err := gnoland.LoadPackagesFromDir(loadCfg, examplesDir, genesisDeployAddress, genesisDeployFee) if err != nil { return fmt.Errorf("unable to load examples folder: %w", err) } diff --git a/gno.land/pkg/gnoland/genesis.go b/gno.land/pkg/gnoland/genesis.go index 3cd3a6358cd..820f6feb30a 100644 --- a/gno.land/pkg/gnoland/genesis.go +++ b/gno.land/pkg/gnoland/genesis.go @@ -3,6 +3,7 @@ package gnoland import ( "errors" "fmt" + "path/filepath" "strings" vmm "github.com/gnolang/gno/gno.land/pkg/sdk/vm" @@ -136,9 +137,9 @@ func LoadGenesisTxsFile(path string, chainID string, genesisRemote string) ([]Tx // LoadPackagesFromDir loads gno packages from a directory. // It creates and returns a list of transactions based on these packages. -func LoadPackagesFromDir(dir string, creator bft.Address, fee std.Fee) ([]TxWithMetadata, error) { +func LoadPackagesFromDir(cfg *packages.LoadConfig, dir string, creator bft.Address, fee std.Fee) ([]TxWithMetadata, error) { // list all packages from target path - pkgs, err := packages.ListPkgs(dir) + pkgs, err := packages.Load(cfg, filepath.Join(dir, "...")) if err != nil { return nil, fmt.Errorf("listing gno packages: %w", err) } @@ -167,11 +168,11 @@ func LoadPackagesFromDir(dir string, creator bft.Address, fee std.Fee) ([]TxWith } // LoadPackage loads a single package into a `std.Tx` -func LoadPackage(pkg packages.Pkg, creator bft.Address, fee std.Fee, deposit std.Coins) (std.Tx, error) { +func LoadPackage(pkg *packages.Package, creator bft.Address, fee std.Fee, deposit std.Coins) (std.Tx, error) { var tx std.Tx // Open files in directory as MemPackage. - memPkg := gno.MustReadMemPackage(pkg.Dir, pkg.Name) + memPkg := gno.MustReadMemPackage(pkg.Dir, pkg.ImportPath) err := memPkg.Validate() if err != nil { return tx, fmt.Errorf("invalid package: %w", err) diff --git a/gno.land/pkg/integration/node_testing.go b/gno.land/pkg/integration/node_testing.go index fdf94c8c545..8e8c193a851 100644 --- a/gno.land/pkg/integration/node_testing.go +++ b/gno.land/pkg/integration/node_testing.go @@ -8,6 +8,7 @@ import ( "github.com/gnolang/gno/gno.land/pkg/gnoland" "github.com/gnolang/gno/gno.land/pkg/gnoland/ugnot" + "github.com/gnolang/gno/gnovm/pkg/packages" abci "github.com/gnolang/gno/tm2/pkg/bft/abci/types" tmcfg "github.com/gnolang/gno/tm2/pkg/bft/config" "github.com/gnolang/gno/tm2/pkg/bft/node" @@ -134,7 +135,8 @@ func LoadDefaultPackages(t TestingTS, creator bft.Address, gnoroot string) []gno examplesDir := filepath.Join(gnoroot, "examples") defaultFee := std.NewFee(50000, std.MustParseCoin(ugnot.ValueString(1000000))) - txs, err := gnoland.LoadPackagesFromDir(examplesDir, creator, defaultFee) + cfg := &packages.LoadConfig{SelfContained: true} + txs, err := gnoland.LoadPackagesFromDir(cfg, examplesDir, creator, defaultFee) require.NoError(t, err) return txs diff --git a/gno.land/pkg/integration/pkgloader.go b/gno.land/pkg/integration/pkgloader.go index 1772543b0a1..cddfcfeebf0 100644 --- a/gno.land/pkg/integration/pkgloader.go +++ b/gno.land/pkg/integration/pkgloader.go @@ -7,15 +7,13 @@ import ( "github.com/gnolang/gno/gno.land/pkg/gnoland" "github.com/gnolang/gno/gno.land/pkg/sdk/vm" - "github.com/gnolang/gno/gnovm/pkg/gnolang" - "github.com/gnolang/gno/gnovm/pkg/gnomod" "github.com/gnolang/gno/gnovm/pkg/packages" bft "github.com/gnolang/gno/tm2/pkg/bft/types" "github.com/gnolang/gno/tm2/pkg/std" ) type PkgsLoader struct { - pkgs []packages.Pkg + pkgs []*packages.Package visited map[string]struct{} // list of occurrences to patchs with the given value @@ -25,7 +23,7 @@ type PkgsLoader struct { func NewPkgsLoader() *PkgsLoader { return &PkgsLoader{ - pkgs: make([]packages.Pkg, 0), + pkgs: make([]*packages.Package, 0), visited: make(map[string]struct{}), patchs: make(map[string]string), } @@ -49,7 +47,7 @@ func (pl *PkgsLoader) LoadPackages(creator bft.Address, fee std.Fee, deposit std for i, pkg := range pkgslist { tx, err := gnoland.LoadPackage(pkg, creator, fee, deposit) if err != nil { - return nil, fmt.Errorf("unable to load pkg %q: %w", pkg.Name, err) + return nil, fmt.Errorf("unable to load pkg %q: %w", pkg.ImportPath, err) } // If any replace value is specified, apply them @@ -82,7 +80,8 @@ func (pl *PkgsLoader) LoadPackages(creator bft.Address, fee std.Fee, deposit std func (pl *PkgsLoader) LoadAllPackagesFromDir(path string) error { // list all packages from target path - pkgslist, err := packages.ListPkgs(path) + cfg := &packages.LoadConfig{SelfContained: true} + pkgslist, err := packages.Load(cfg, filepath.Join(path, "...")) if err != nil { return fmt.Errorf("listing gno packages: %w", err) } @@ -96,74 +95,31 @@ func (pl *PkgsLoader) LoadAllPackagesFromDir(path string) error { return nil } -func (pl *PkgsLoader) LoadPackage(modroot string, path, name string) error { - // Initialize a queue with the root package - queue := []packages.Pkg{{Dir: path, Name: name}} - - for len(queue) > 0 { - // Dequeue the first package - currentPkg := queue[0] - queue = queue[1:] - - if currentPkg.Dir == "" { - return fmt.Errorf("no path specified for package") - } - - if currentPkg.Name == "" { - // Load `gno.mod` information - gnoModPath := filepath.Join(currentPkg.Dir, "gno.mod") - gm, err := gnomod.ParseGnoMod(gnoModPath) - if err != nil { - return fmt.Errorf("unable to load %q: %w", gnoModPath, err) - } - gm.Sanitize() - - // Override package info with mod infos - currentPkg.Name = gm.Module.Mod.Path - currentPkg.Draft = gm.Draft - - pkg, err := gnolang.ReadMemPackage(currentPkg.Dir, currentPkg.Name) - if err != nil { - return fmt.Errorf("unable to read package at %q: %w", currentPkg.Dir, err) - } - importsMap, err := packages.Imports(pkg, nil) - if err != nil { - return fmt.Errorf("unable to load package imports in %q: %w", currentPkg.Dir, err) - } - imports := importsMap.Merge(packages.FileKindPackageSource, packages.FileKindTest, packages.FileKindFiletest) - for _, imp := range imports { - if imp == currentPkg.Name || gnolang.IsStdlib(imp) { - continue - } - currentPkg.Imports = append(currentPkg.Imports, imp) - } - } - - if currentPkg.Draft { - continue // Skip draft package - } +func (pl *PkgsLoader) LoadPackage(modroot string, path string, name string) error { + // FIXME: name override + cfg := &packages.LoadConfig{Deps: true, SelfContained: true} + pkgDir := filepath.Join(modroot, path) + pkgs, err := packages.Load(cfg, pkgDir) + if err != nil { + return fmt.Errorf("%q: loading: %w", pkgDir, err) + } - if pl.exist(currentPkg) { + for _, pkg := range pkgs { + if pl.exist(pkg) { continue } - pl.add(currentPkg) - - // Add requirements to the queue - for _, pkgPath := range currentPkg.Imports { - fullPath := filepath.Join(modroot, pkgPath) - queue = append(queue, packages.Pkg{Dir: fullPath}) - } + pl.add(pkg) } return nil } -func (pl *PkgsLoader) add(pkg packages.Pkg) { - pl.visited[pkg.Name] = struct{}{} +func (pl *PkgsLoader) add(pkg *packages.Package) { + pl.visited[pkg.ImportPath] = struct{}{} pl.pkgs = append(pl.pkgs, pkg) } -func (pl *PkgsLoader) exist(pkg packages.Pkg) (ok bool) { - _, ok = pl.visited[pkg.Name] +func (pl *PkgsLoader) exist(pkg *packages.Package) (ok bool) { + _, ok = pl.visited[pkg.ImportPath] return } diff --git a/gnovm/cmd/gno/mod.go b/gnovm/cmd/gno/mod.go index c02a7ae77e0..63c5c506e0a 100644 --- a/gnovm/cmd/gno/mod.go +++ b/gnovm/cmd/gno/mod.go @@ -257,7 +257,8 @@ func execModTidy(cfg *modTidyCfg, args []string, io commands.IO) error { } if cfg.recursive { - pkgs, err := packages.ListPkgs(wd) + loadCfg := packages.LoadConfig{IO: io, Fetcher: testPackageFetcher} + pkgs, err := packages.Load(&loadCfg, filepath.Join(wd, "...")) if err != nil { return err } diff --git a/gnovm/pkg/packages/expand_patterns.go b/gnovm/pkg/packages/expand_patterns.go index 5a4c6a72e3c..e78db46801f 100644 --- a/gnovm/pkg/packages/expand_patterns.go +++ b/gnovm/pkg/packages/expand_patterns.go @@ -56,6 +56,11 @@ func expandPatterns(conf *LoadConfig, patterns ...string) ([]*pkgMatch, error) { case patternKindRecursiveRemote: return nil, fmt.Errorf("%s: recursive remote patterns are not supported", match) + + case patternKindRemote: + if conf.SelfContained { + return nil, fmt.Errorf("%s: remote patterns are not supported in self-contained mode", match) + } } pat, err := cleanPattern(match, patKind) diff --git a/gnovm/pkg/packages/load.go b/gnovm/pkg/packages/load.go index ddbe0dab1d7..6bacb11c121 100644 --- a/gnovm/pkg/packages/load.go +++ b/gnovm/pkg/packages/load.go @@ -22,6 +22,7 @@ type LoadConfig struct { Deps bool Cache map[string]*Package GnorootExamples bool // allow loading packages from gnoroot examples if not found in the set + SelfContained bool } func (conf *LoadConfig) applyDefaults() { @@ -36,7 +37,7 @@ func (conf *LoadConfig) applyDefaults() { } } -func Load(conf *LoadConfig, patterns ...string) ([]*Package, error) { +func Load(conf *LoadConfig, patterns ...string) (PkgList, error) { conf.applyDefaults() expanded, err := expandPatterns(conf, patterns...) @@ -115,6 +116,12 @@ func Load(conf *LoadConfig, patterns ...string) ([]*Package, error) { } } + if conf.SelfContained { + pkg.Error = errors.Join(pkg.Error, fmt.Errorf("self-contained: package %q not found", imp)) + byPkgPath[imp] = nil // stop trying to get this lib, we can't + continue + } + dir := gnomod.PackageDir("", module.Version{Path: imp}) if err := downloadPackage(conf, imp, dir); err != nil { pkg.Error = errors.Join(pkg.Error, err) diff --git a/gnovm/pkg/packages/pkg_test.go b/gnovm/pkg/packages/load_test.go similarity index 70% rename from gnovm/pkg/packages/pkg_test.go rename to gnovm/pkg/packages/load_test.go index 1e6d06c8017..c70ae3b1778 100644 --- a/gnovm/pkg/packages/pkg_test.go +++ b/gnovm/pkg/packages/load_test.go @@ -74,11 +74,11 @@ func TestListAndNonDraftPkgs(t *testing.T) { } // List packages - pkgs, err := ListPkgs(dirPath) + pkgs, err := Load(&LoadConfig{}, filepath.Join(dirPath, "...")) 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.ImportPath) } // Sort packages @@ -89,7 +89,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.ImportPath) } }) } @@ -117,35 +117,35 @@ func TestSortPkgs(t *testing.T) { }{ { desc: "empty_input", - in: []Pkg{}, + in: []*Package{}, expected: make([]string, 0), }, { desc: "no_dependencies", - in: []Pkg{ - {Name: "pkg1", Dir: "/path/to/pkg1", Imports: []string{}}, - {Name: "pkg2", Dir: "/path/to/pkg2", Imports: []string{}}, - {Name: "pkg3", Dir: "/path/to/pkg3", Imports: []string{}}, + in: []*Package{ + {ImportPath: "pkg1", Dir: "/path/to/pkg1", Imports: ImportsMap{}}, + {ImportPath: "pkg2", Dir: "/path/to/pkg2", Imports: ImportsMap{}}, + {ImportPath: "pkg3", Dir: "/path/to/pkg3", Imports: ImportsMap{}}, }, expected: []string{"pkg1", "pkg2", "pkg3"}, }, { desc: "circular_dependencies", - in: []Pkg{ - {Name: "pkg1", Dir: "/path/to/pkg1", Imports: []string{"pkg2"}}, - {Name: "pkg2", Dir: "/path/to/pkg2", Imports: []string{"pkg1"}}, + in: []*Package{ + {ImportPath: "pkg1", Dir: "/path/to/pkg1", Imports: ImportsMap{FileKindPackageSource: []string{"pkg2"}}}, + {ImportPath: "pkg2", Dir: "/path/to/pkg2", Imports: ImportsMap{FileKindPackageSource: []string{"pkg1"}}}, }, shouldErr: true, }, { desc: "missing_dependencies", - in: []Pkg{ - {Name: "pkg1", Dir: "/path/to/pkg1", Imports: []string{"pkg2"}}, + in: []*Package{ + {ImportPath: "pkg1", Dir: "/path/to/pkg1", Imports: ImportsMap{FileKindPackageSource: []string{"pkg2"}}}, }, shouldErr: true, }, { desc: "valid_dependencies", - in: []Pkg{ - {Name: "pkg1", Dir: "/path/to/pkg1", Imports: []string{"pkg2"}}, - {Name: "pkg2", Dir: "/path/to/pkg2", Imports: []string{"pkg3"}}, - {Name: "pkg3", Dir: "/path/to/pkg3", Imports: []string{}}, + in: []*Package{ + {ImportPath: "pkg1", Dir: "/path/to/pkg1", Imports: ImportsMap{FileKindPackageSource: []string{"pkg2"}}}, + {ImportPath: "pkg2", Dir: "/path/to/pkg2", Imports: ImportsMap{FileKindPackageSource: []string{"pkg3"}}}, + {ImportPath: "pkg3", Dir: "/path/to/pkg3", Imports: ImportsMap{}}, }, expected: []string{"pkg3", "pkg2", "pkg1"}, }, @@ -157,7 +157,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].ImportPath) } } }) diff --git a/gnovm/pkg/packages/pkg.go b/gnovm/pkg/packages/pkg.go index b8ba57e0612..98049a51db7 100644 --- a/gnovm/pkg/packages/pkg.go +++ b/gnovm/pkg/packages/pkg.go @@ -2,32 +2,18 @@ package packages import ( "fmt" - "io/fs" - "os" - "path/filepath" - - "github.com/gnolang/gno/gnovm" - "github.com/gnolang/gno/gnovm/pkg/gnolang" - "github.com/gnolang/gno/gnovm/pkg/gnomod" ) -type Pkg struct { - Dir string // absolute path to package dir - Name string // package name - Imports []string // direct imports of this pkg - Draft bool // whether the package is a draft -} - type ( - PkgList []Pkg - SortedPkgList []Pkg + PkgList []*Package + SortedPkgList []*Package ) // sortPkgs sorts the given packages by their dependencies. func (pl PkgList) Sort() (SortedPkgList, error) { visited := make(map[string]bool) onStack := make(map[string]bool) - sortedPkgs := make([]Pkg, 0, len(pl)) + sortedPkgs := make([]*Package, 0, len(pl)) // Visit all packages for _, p := range pl { @@ -40,22 +26,22 @@ 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) +func visitPackage(pkg *Package, pkgs []*Package, visited, onStack map[string]bool, sortedPkgs *[]*Package) error { + if onStack[pkg.ImportPath] { + return fmt.Errorf("cycle detected: %s", pkg.ImportPath) } - if visited[pkg.Name] { + if visited[pkg.ImportPath] { return nil } - visited[pkg.Name] = true - onStack[pkg.Name] = true + visited[pkg.ImportPath] = true + onStack[pkg.ImportPath] = true // Visit package's dependencies - for _, imp := range pkg.Imports { + for _, imp := range pkg.Imports.Merge(FileKindPackageSource) { found := false for _, p := range pkgs { - if p.Name != imp { + if p.ImportPath != imp { continue } if err := visitPackage(p, pkgs, visited, onStack, sortedPkgs); err != nil { @@ -65,97 +51,31 @@ func visitPackage(pkg Pkg, pkgs []Pkg, visited, onStack map[string]bool, sortedP break } if !found { - return fmt.Errorf("missing dependency '%s' for package '%s'", imp, pkg.Name) + return fmt.Errorf("missing dependency '%s' for package '%s'", imp, pkg.ImportPath) } } - onStack[pkg.Name] = false + onStack[pkg.ImportPath] = false *sortedPkgs = append(*sortedPkgs, pkg) return nil } -// ListPkgs lists all gno packages in the given root directory. -func ListPkgs(root string) (PkgList, error) { - var pkgs []Pkg - - err := filepath.WalkDir(root, func(path string, d fs.DirEntry, err error) error { - if err != nil { - return err - } - if !d.IsDir() { - return nil - } - gnoModPath := filepath.Join(path, "gno.mod") - data, err := os.ReadFile(gnoModPath) - if os.IsNotExist(err) { - return nil - } - if err != nil { - return err - } - - gnoMod, err := gnomod.Parse(gnoModPath, data) - if err != nil { - return fmt.Errorf("parse: %w", err) - } - gnoMod.Sanitize() - if err := gnoMod.Validate(); err != nil { - return fmt.Errorf("validate: %w", err) - } - - pkg, err := gnolang.ReadMemPackage(path, gnoMod.Module.Mod.Path) - if err != nil { - // ignore package files on error - pkg = &gnovm.MemPackage{} - } - - importsMap, err := Imports(pkg, nil) - if err != nil { - // ignore imports on error - importsMap = nil - } - importsRaw := importsMap.Merge(FileKindPackageSource, FileKindTest, FileKindXTest) - - imports := make([]string, 0, len(importsRaw)) - for _, imp := range importsRaw { - // remove self and standard libraries from imports - if imp != gnoMod.Module.Mod.Path && - !gnolang.IsStdlib(imp) { - imports = append(imports, imp) - } - } - - pkgs = append(pkgs, Pkg{ - Dir: path, - Name: gnoMod.Module.Mod.Path, - Draft: gnoMod.Draft, - Imports: imports, - }) - return nil - }) - if err != nil { - return nil, err - } - - return pkgs, nil -} - // GetNonDraftPkgs returns packages that are not draft // and have no direct or indirect draft dependencies. func (sp SortedPkgList) GetNonDraftPkgs() SortedPkgList { - res := make([]Pkg, 0, len(sp)) + res := make([]*Package, 0, len(sp)) draft := make(map[string]bool) for _, pkg := range sp { if pkg.Draft { - draft[pkg.Name] = true + draft[pkg.ImportPath] = true continue } dependsOnDraft := false - for _, req := range pkg.Imports { + for _, req := range pkg.Imports.Merge(FileKindPackageSource) { if draft[req] { dependsOnDraft = true - draft[pkg.Name] = true + draft[pkg.ImportPath] = true break } } From 4104372d76d963910d4f87689236852640d3f5d1 Mon Sep 17 00:00:00 2001 From: Norman Date: Wed, 8 Jan 2025 07:56:38 +0100 Subject: [PATCH 042/143] chore: node loadconfig Signed-off-by: Norman --- contribs/gnodev/pkg/dev/node.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/contribs/gnodev/pkg/dev/node.go b/contribs/gnodev/pkg/dev/node.go index d9471150f76..bfd53e67140 100644 --- a/contribs/gnodev/pkg/dev/node.go +++ b/contribs/gnodev/pkg/dev/node.go @@ -242,8 +242,7 @@ func (n *Node) updatePackages(paths ...string) error { } // List all packages from target path - loadCfg := &packages.LoadConfig{Deps: true} - pkgslist, err := packages.Load(loadCfg, filepath.Join(abspath, "...")) + pkgslist, err := packages.Load(n.config.LoadConfig, filepath.Join(abspath, "...")) if err != nil { return fmt.Errorf("failed to list gno packages for %q: %w", path, err) } From 0a8533838c8bdde82562d2b6a3b29d42330eb098 Mon Sep 17 00:00:00 2001 From: Norman Date: Wed, 8 Jan 2025 07:59:17 +0100 Subject: [PATCH 043/143] feat: run gnoland genesis pkg loading in self-contained mode Signed-off-by: Norman --- gno.land/cmd/gnoland/start.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gno.land/cmd/gnoland/start.go b/gno.land/cmd/gnoland/start.go index d2b888a33de..1ab77652c0a 100644 --- a/gno.land/cmd/gnoland/start.go +++ b/gno.land/cmd/gnoland/start.go @@ -397,7 +397,7 @@ func generateGenesisFile(io commands.IO, genesisFile string, pk crypto.PubKey, c // Load examples folder examplesDir := filepath.Join(c.gnoRootDir, "examples") - loadCfg := &packages.LoadConfig{IO: io} + loadCfg := &packages.LoadConfig{IO: io, SelfContained: true} pkgsTxs, err := gnoland.LoadPackagesFromDir(loadCfg, examplesDir, genesisDeployAddress, genesisDeployFee) if err != nil { return fmt.Errorf("unable to load examples folder: %w", err) From f5b7afcdae3d71ae53af7713d09e584afb34803a Mon Sep 17 00:00:00 2001 From: Norman Date: Wed, 8 Jan 2025 08:13:46 +0100 Subject: [PATCH 044/143] fix: gnodev tests Signed-off-by: Norman --- contribs/gnodev/pkg/dev/node.go | 5 +++++ contribs/gnodev/pkg/dev/packages.go | 5 +++++ gnovm/pkg/packages/{pkg.go => pkglist.go} | 11 +++++++++++ 3 files changed, 21 insertions(+) rename gnovm/pkg/packages/{pkg.go => pkglist.go} (89%) diff --git a/contribs/gnodev/pkg/dev/node.go b/contribs/gnodev/pkg/dev/node.go index bfd53e67140..24ffce6f414 100644 --- a/contribs/gnodev/pkg/dev/node.go +++ b/contribs/gnodev/pkg/dev/node.go @@ -16,6 +16,7 @@ import ( "github.com/gnolang/gno/gno.land/pkg/gnoland" "github.com/gnolang/gno/gno.land/pkg/gnoland/ugnot" "github.com/gnolang/gno/gno.land/pkg/integration" + "github.com/gnolang/gno/gnovm/pkg/gnolang" "github.com/gnolang/gno/gnovm/pkg/packages" "github.com/gnolang/gno/tm2/pkg/amino" tmcfg "github.com/gnolang/gno/tm2/pkg/bft/config" @@ -249,6 +250,10 @@ func (n *Node) updatePackages(paths ...string) error { // Update or add package in the current known list. for _, pkg := range pkgslist { + if pkg.ImportPath != "" && gnolang.IsStdlib(pkg.ImportPath) { + continue + } + n.pkgs[pkg.Dir] = Package{ Package: pkg, Creator: deployer, diff --git a/contribs/gnodev/pkg/dev/packages.go b/contribs/gnodev/pkg/dev/packages.go index d6585ac30b3..9327d6bde46 100644 --- a/contribs/gnodev/pkg/dev/packages.go +++ b/contribs/gnodev/pkg/dev/packages.go @@ -10,6 +10,7 @@ import ( "github.com/gnolang/gno/contribs/gnodev/pkg/address" "github.com/gnolang/gno/gno.land/pkg/gnoland" vmm "github.com/gnolang/gno/gno.land/pkg/sdk/vm" + "github.com/gnolang/gno/gnovm/pkg/gnolang" gno "github.com/gnolang/gno/gnovm/pkg/gnolang" "github.com/gnolang/gno/gnovm/pkg/packages" "github.com/gnolang/gno/tm2/pkg/crypto" @@ -98,6 +99,10 @@ func NewPackagesMap(cfg *packages.LoadConfig, ppaths []PackagePath) (PackagesMap continue } + if pkg.ImportPath != "" && gnolang.IsStdlib(pkg.ImportPath) { + continue + } + if _, ok := pkgs[pkg.Dir]; ok { continue // skip } diff --git a/gnovm/pkg/packages/pkg.go b/gnovm/pkg/packages/pkglist.go similarity index 89% rename from gnovm/pkg/packages/pkg.go rename to gnovm/pkg/packages/pkglist.go index 98049a51db7..529b1401251 100644 --- a/gnovm/pkg/packages/pkg.go +++ b/gnovm/pkg/packages/pkglist.go @@ -2,6 +2,9 @@ package packages import ( "fmt" + "slices" + + "github.com/gnolang/gno/gnovm/pkg/gnolang" ) type ( @@ -25,6 +28,8 @@ func (pl PkgList) Sort() (SortedPkgList, error) { return sortedPkgs, nil } +var injectedTestingLibs = []string{"encoding/json", "fmt", "os", "crypto/sha256"} + // visitNode visits a package's and its dependencies dependencies and adds them to the sorted list. func visitPackage(pkg *Package, pkgs []*Package, visited, onStack map[string]bool, sortedPkgs *[]*Package) error { if onStack[pkg.ImportPath] { @@ -39,6 +44,12 @@ func visitPackage(pkg *Package, pkgs []*Package, visited, onStack map[string]boo // Visit package's dependencies for _, imp := range pkg.Imports.Merge(FileKindPackageSource) { + if gnolang.IsStdlib(imp) { + continue + } + if slices.Contains(injectedTestingLibs, imp) { + continue + } found := false for _, p := range pkgs { if p.ImportPath != imp { From fba50a71d28c89f6c1cc20d644944488362d9b11 Mon Sep 17 00:00:00 2001 From: Norman Date: Wed, 8 Jan 2025 08:28:42 +0100 Subject: [PATCH 045/143] fix: go fmt and lint Signed-off-by: Norman --- examples/no_cycles_test.go | 132 ++++--------------------- gnovm/cmd/gno/test.go | 32 ------ gnovm/pkg/doc/dirs.go | 2 +- gnovm/pkg/gnolang/debugger_test.go | 3 +- gnovm/pkg/gnolang/files_test.go | 5 +- gnovm/pkg/gnomod/parse_test.go | 14 +++ gnovm/pkg/packages/analyze_packages.go | 2 +- gnovm/pkg/packages/importer.go | 34 ------- 8 files changed, 40 insertions(+), 184 deletions(-) delete mode 100644 gnovm/pkg/packages/importer.go diff --git a/examples/no_cycles_test.go b/examples/no_cycles_test.go index 43220e9eeab..04069194304 100644 --- a/examples/no_cycles_test.go +++ b/examples/no_cycles_test.go @@ -2,15 +2,11 @@ package examples_test import ( "fmt" - "io/fs" - "os" - pathlib "path" "path/filepath" "slices" "strings" "testing" - "github.com/gnolang/gno/gnovm" "github.com/gnolang/gno/gnovm/pkg/gnoenv" "github.com/gnolang/gno/gnovm/pkg/packages" "github.com/stretchr/testify/require" @@ -20,29 +16,17 @@ var injectedTestingLibs = []string{"encoding/json", "fmt", "os", "internal/os_te // TestNoCycles checks that there is no import cycles in stdlibs and non-draft examples func TestNoCycles(t *testing.T) { - // find stdlibs - gnoRoot := gnoenv.RootDir() - pkgs, err := listPkgs(packages.Pkg{ - Dir: filepath.Join(gnoRoot, "gnovm", "stdlibs"), - Name: "", - }) + // find examples and stdlibs + cfg := &packages.LoadConfig{SelfContained: true, Deps: true} + pkgs, err := packages.Load(cfg, filepath.Join(gnoenv.RootDir(), "examples", "...")) require.NoError(t, err) - // find examples - examples, err := packages.ListPkgs(filepath.Join(gnoRoot, "examples")) - require.NoError(t, err) - for _, example := range examples { - if example.Draft { - continue - } - examplePkgs, err := listPkgs(example) - require.NoError(t, err) - pkgs = append(pkgs, examplePkgs...) - } - // detect cycles visited := make(map[string]bool) for _, p := range pkgs { + if p.Draft { + continue + } require.NoError(t, detectCycles(p, pkgs, visited)) } } @@ -73,7 +57,7 @@ func TestNoCycles(t *testing.T) { // - foo_pkg/foo.go imports bar_pkg // // - bar_pkg/bar_test.go imports foo_pkg -func detectCycles(root testPkg, pkgs []testPkg, visited map[string]bool) error { +func detectCycles(root *packages.Package, pkgs []*packages.Package, visited map[string]bool) error { // check cycles in package's sources stack := []string{} if err := visitPackage(root, pkgs, visited, stack); err != nil { @@ -86,8 +70,8 @@ func detectCycles(root testPkg, pkgs []testPkg, visited map[string]bool) error { // check cycles in tests' imports by marking the current package as visited while visiting the tests' imports // we also consider PackageSource imports here because tests can call package code - visited = map[string]bool{root.PkgPath: true} - stack = []string{root.PkgPath} + visited = map[string]bool{root.ImportPath: true} + stack = []string{root.ImportPath} if err := visitImports([]packages.FileKind{packages.FileKindPackageSource, packages.FileKindTest}, root, pkgs, visited, stack); err != nil { return fmt.Errorf("test import: %w", err) } @@ -96,14 +80,14 @@ func detectCycles(root testPkg, pkgs []testPkg, visited map[string]bool) error { } // visitImports resolves and visits imports by kinds -func visitImports(kinds []packages.FileKind, root testPkg, pkgs []testPkg, visited map[string]bool, stack []string) error { +func visitImports(kinds []packages.FileKind, root *packages.Package, pkgs []*packages.Package, visited map[string]bool, stack []string) error { for _, imp := range root.Imports.Merge(kinds...) { - if slices.Contains(injectedTestingLibs, imp.PkgPath) { + if slices.Contains(injectedTestingLibs, imp) { continue } - idx := slices.IndexFunc(pkgs, func(p testPkg) bool { return p.PkgPath == imp.PkgPath }) + idx := slices.IndexFunc(pkgs, func(p *packages.Package) bool { return p.ImportPath == imp }) if idx == -1 { - return fmt.Errorf("import %q not found for %q tests", imp.PkgPath, root.PkgPath) + return fmt.Errorf("import %q not found for %q tests", imp, root.ImportPath) } if err := visitPackage(pkgs[idx], pkgs, visited, stack); err != nil { return fmt.Errorf("test import error: %w", err) @@ -114,16 +98,16 @@ func visitImports(kinds []packages.FileKind, root testPkg, pkgs []testPkg, visit } // visitNode visits a package and its imports recursively. It only considers imports in PackageSource -func visitPackage(pkg testPkg, pkgs []testPkg, visited map[string]bool, stack []string) error { - if slices.Contains(stack, pkg.PkgPath) { - return fmt.Errorf("cycle detected: %s -> %s", strings.Join(stack, " -> "), pkg.PkgPath) +func visitPackage(pkg *packages.Package, pkgs []*packages.Package, visited map[string]bool, stack []string) error { + if slices.Contains(stack, pkg.ImportPath) { + return fmt.Errorf("cycle detected: %s -> %s", strings.Join(stack, " -> "), pkg.ImportPath) } - if visited[pkg.PkgPath] { + if visited[pkg.ImportPath] { return nil } - visited[pkg.PkgPath] = true - stack = append(stack, pkg.PkgPath) + visited[pkg.ImportPath] = true + stack = append(stack, pkg.ImportPath) if err := visitImports([]packages.FileKind{packages.FileKindPackageSource}, pkg, pkgs, visited, stack); err != nil { return err @@ -131,81 +115,3 @@ func visitPackage(pkg testPkg, pkgs []testPkg, visited map[string]bool, stack [] return nil } - -type testPkg struct { - Dir string - PkgPath string - Imports packages.ImportsMap -} - -// listPkgs lists all packages in rootMod -func listPkgs(rootMod packages.Pkg) ([]testPkg, error) { - res := []testPkg{} - rootDir := rootMod.Dir - visited := map[string]struct{}{} - if err := fs.WalkDir(os.DirFS(rootDir), ".", func(path string, d fs.DirEntry, err error) error { - if err != nil { - return err - } - if d.IsDir() { - return nil - } - if !strings.HasSuffix(d.Name(), ".gno") { - return nil - } - subPath := filepath.Dir(path) - dir := filepath.Join(rootDir, subPath) - if _, ok := visited[dir]; ok { - return nil - } - visited[dir] = struct{}{} - - subPkgPath := pathlib.Join(rootMod.Name, subPath) - - pkg := testPkg{ - Dir: dir, - PkgPath: subPkgPath, - } - - memPkg, err := readPkg(pkg.Dir, pkg.PkgPath) - if err != nil { - return fmt.Errorf("read pkg %q: %w", pkg.Dir, err) - } - pkg.Imports, err = packages.Imports(memPkg, nil) - if err != nil { - return fmt.Errorf("list imports of %q: %w", memPkg.Path, err) - } - - res = append(res, pkg) - return nil - }); err != nil { - return nil, fmt.Errorf("walk dirs at %q: %w", rootDir, err) - } - return res, nil -} - -// readPkg reads the sources of a package. It includes all .gno files but ignores the package name -func readPkg(dir string, pkgPath string) (*gnovm.MemPackage, error) { - list, err := os.ReadDir(dir) - if err != nil { - return nil, err - } - memPkg := &gnovm.MemPackage{Path: pkgPath} - for _, entry := range list { - fpath := filepath.Join(dir, entry.Name()) - if !strings.HasSuffix(fpath, ".gno") { - continue - } - fname := filepath.Base(fpath) - bz, err := os.ReadFile(fpath) - if err != nil { - return nil, err - } - memPkg.Files = append(memPkg.Files, - &gnovm.MemFile{ - Name: fname, - Body: string(bz), - }) - } - return memPkg, nil -} diff --git a/gnovm/cmd/gno/test.go b/gnovm/cmd/gno/test.go index 53e161148af..e3d0dc46a3c 100644 --- a/gnovm/cmd/gno/test.go +++ b/gnovm/cmd/gno/test.go @@ -5,9 +5,6 @@ import ( "flag" "fmt" goio "io" - "log" - "path/filepath" - "strings" "time" "github.com/gnolang/gno/gnovm/pkg/gnoenv" @@ -248,32 +245,3 @@ func execTest(cfg *testCfg, args []string, io commands.IO) error { return nil } - -// attempts to determine the full gno pkg path by analyzing the directory. -func pkgPathFromRootDir(pkgPath, rootDir string) string { - abPkgPath, err := filepath.Abs(pkgPath) - if err != nil { - log.Printf("could not determine abs path: %v", err) - return "" - } - abRootDir, err := filepath.Abs(rootDir) - if err != nil { - log.Printf("could not determine abs path: %v", err) - return "" - } - abRootDir += string(filepath.Separator) - if !strings.HasPrefix(abPkgPath, abRootDir) { - return "" - } - impPath := strings.ReplaceAll(abPkgPath[len(abRootDir):], string(filepath.Separator), "/") - for _, prefix := range [...]string{ - "examples/", - "gnovm/stdlibs/", - "gnovm/tests/stdlibs/", - } { - if strings.HasPrefix(impPath, prefix) { - return impPath[len(prefix):] - } - } - return "" -} diff --git a/gnovm/pkg/doc/dirs.go b/gnovm/pkg/doc/dirs.go index fdbf8bd7b41..b0761e50c23 100644 --- a/gnovm/pkg/doc/dirs.go +++ b/gnovm/pkg/doc/dirs.go @@ -128,7 +128,7 @@ func packageImportsRecursive(root string, pkgPath string) []string { for _, imp := range sub { if !slices.Contains(res, imp) { - res = append(res, imp) //nolint:makezero + res = append(res, imp) } } } diff --git a/gnovm/pkg/gnolang/debugger_test.go b/gnovm/pkg/gnolang/debugger_test.go index 926ff0595e6..a0984b1418a 100644 --- a/gnovm/pkg/gnolang/debugger_test.go +++ b/gnovm/pkg/gnolang/debugger_test.go @@ -12,6 +12,7 @@ import ( "github.com/gnolang/gno/gnovm/pkg/gnoenv" "github.com/gnolang/gno/gnovm/pkg/gnolang" + "github.com/gnolang/gno/gnovm/pkg/packages" "github.com/gnolang/gno/gnovm/pkg/test" ) @@ -39,7 +40,7 @@ func evalTest(debugAddr, in, file string) (out, err string) { err = strings.TrimSpace(strings.ReplaceAll(err, "../../tests/files/", "files/")) }() - _, testStore := test.Store(gnoenv.RootDir(), false, stdin, stdout, stderr) + _, testStore := test.Store(gnoenv.RootDir(), map[string]*packages.Package{}, false, stdin, stdout, stderr) f := gnolang.MustReadFile(file) diff --git a/gnovm/pkg/gnolang/files_test.go b/gnovm/pkg/gnolang/files_test.go index 2c82f6d8f29..214df15bd4d 100644 --- a/gnovm/pkg/gnolang/files_test.go +++ b/gnovm/pkg/gnolang/files_test.go @@ -12,6 +12,7 @@ import ( "testing" "github.com/gnolang/gno/gnovm/pkg/gnolang" + "github.com/gnolang/gno/gnovm/pkg/packages" "github.com/gnolang/gno/gnovm/pkg/test" "github.com/stretchr/testify/require" ) @@ -46,7 +47,7 @@ func TestFiles(t *testing.T) { Sync: *withSync, } o.BaseStore, o.TestStore = test.Store( - rootDir, true, + rootDir, make(map[string]*packages.Package), true, nopReader{}, o.WriterForStore(), io.Discard, ) return o @@ -121,7 +122,7 @@ func TestStdlibs(t *testing.T) { capture = new(bytes.Buffer) out = capture } - opts = test.NewTestOptions(rootDir, nopReader{}, out, out) + opts = test.NewTestOptions(rootDir, make(map[string]*packages.Package), nopReader{}, out, out) opts.Verbose = true return } diff --git a/gnovm/pkg/gnomod/parse_test.go b/gnovm/pkg/gnomod/parse_test.go index ec54c6424fc..a2410cec8d8 100644 --- a/gnovm/pkg/gnomod/parse_test.go +++ b/gnovm/pkg/gnomod/parse_test.go @@ -1,6 +1,7 @@ package gnomod import ( + "os" "path/filepath" "testing" @@ -237,3 +238,16 @@ func TestParseGnoMod(t *testing.T) { }) } } + +func createGnoModPkg(t *testing.T, dirPath, pkgName, modData string) { + t.Helper() + + // Create package dir + pkgDirPath := filepath.Join(dirPath, pkgName) + err := os.MkdirAll(pkgDirPath, 0o755) + require.NoError(t, err) + + // Create gno.mod + err = os.WriteFile(filepath.Join(pkgDirPath, "gno.mod"), []byte(modData), 0o644) + require.NoError(t, err) +} diff --git a/gnovm/pkg/packages/analyze_packages.go b/gnovm/pkg/packages/analyze_packages.go index 603c7c108b1..7e00db57ebd 100644 --- a/gnovm/pkg/packages/analyze_packages.go +++ b/gnovm/pkg/packages/analyze_packages.go @@ -98,7 +98,7 @@ func readPkg(pkgDir string, importPath string) *Package { // TODO: check if stdlib pkg.Root, err = gnomod.FindRootDir(pkgDir) - if err == gnomod.ErrGnoModNotFound { + if errors.Is(err, gnomod.ErrGnoModNotFound) { return pkg } if err != nil { diff --git a/gnovm/pkg/packages/importer.go b/gnovm/pkg/packages/importer.go deleted file mode 100644 index c542a1f4bf1..00000000000 --- a/gnovm/pkg/packages/importer.go +++ /dev/null @@ -1,34 +0,0 @@ -// Package packages allows to match an import path to a directory, and select -// a set of files within that directory which are Gno files. -package packages - -import ( - "sync" - - "github.com/gnolang/gno/gnovm/pkg/gnoenv" -) - -// Config defines the configuration for the packages. -type Config struct { - // RootDir should point to the directory of the gno repository. - // This is used to resolve import paths of packages and realms in - // "examples", and to resolve standard libraries (gnovm/stdlibs and - // gnovm/tests/stdlibs). - RootDir string - - // GnoHome is the path to the "home" directory. - // This is used to resolve imports to on-chain packages. - GnoHome string - - // If set to true, this will enable the usage of "testing standard - // libraries"; ie., some stdlibs packages will be additionally resolved - // to a second directory in gnovm/tests/stdlibs/. - Test bool -} - -var defaultConfig = sync.OnceValue(func() Config { - return Config{ - RootDir: gnoenv.RootDir(), - GnoHome: gnoenv.HomeDir(), - } -}) From b760e369b3e5e1cf4fcdcfe50667aad8abcfb1e4 Mon Sep 17 00:00:00 2001 From: Norman Date: Wed, 8 Jan 2025 08:36:59 +0100 Subject: [PATCH 046/143] fix: gnodev and gnogenesis Signed-off-by: Norman --- contribs/gnodev/pkg/dev/packages.go | 3 +-- contribs/gnogenesis/internal/txs/txs_add_packages.go | 4 +++- gnovm/pkg/packages/load.go | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/contribs/gnodev/pkg/dev/packages.go b/contribs/gnodev/pkg/dev/packages.go index 9327d6bde46..c69e12061ff 100644 --- a/contribs/gnodev/pkg/dev/packages.go +++ b/contribs/gnodev/pkg/dev/packages.go @@ -11,7 +11,6 @@ import ( "github.com/gnolang/gno/gno.land/pkg/gnoland" vmm "github.com/gnolang/gno/gno.land/pkg/sdk/vm" "github.com/gnolang/gno/gnovm/pkg/gnolang" - gno "github.com/gnolang/gno/gnovm/pkg/gnolang" "github.com/gnolang/gno/gnovm/pkg/packages" "github.com/gnolang/gno/tm2/pkg/crypto" "github.com/gnolang/gno/tm2/pkg/std" @@ -143,7 +142,7 @@ func (pm PackagesMap) Load(fee std.Fee, start time.Time) ([]gnoland.TxWithMetada } // Open files in directory as MemPackage. - memPkg := gno.MustReadMemPackage(modPkg.Dir, modPkg.ImportPath) + memPkg := gnolang.MustReadMemPackage(modPkg.Dir, modPkg.ImportPath) if err := memPkg.Validate(); err != nil { return nil, fmt.Errorf("invalid package: %w", err) } diff --git a/contribs/gnogenesis/internal/txs/txs_add_packages.go b/contribs/gnogenesis/internal/txs/txs_add_packages.go index cf863c72116..43e9f218b9f 100644 --- a/contribs/gnogenesis/internal/txs/txs_add_packages.go +++ b/contribs/gnogenesis/internal/txs/txs_add_packages.go @@ -6,6 +6,7 @@ import ( "flag" "fmt" + "github.com/gnolang/gno/gnovm/pkg/packages" "github.com/gnolang/gno/tm2/pkg/crypto" "github.com/gnolang/gno/gno.land/pkg/gnoland" @@ -92,7 +93,8 @@ func execTxsAddPackages( parsedTxs := make([]gnoland.TxWithMetadata, 0) for _, path := range args { // Generate transactions from the packages (recursively) - txs, err := gnoland.LoadPackagesFromDir(path, creator, genesisDeployFee) + loadCfg := &packages.LoadConfig{IO: io, SelfContained: true} + txs, err := gnoland.LoadPackagesFromDir(loadCfg, path, creator, genesisDeployFee) if err != nil { return fmt.Errorf("unable to load txs from directory, %w", err) } diff --git a/gnovm/pkg/packages/load.go b/gnovm/pkg/packages/load.go index 6bacb11c121..a07e1426ccc 100644 --- a/gnovm/pkg/packages/load.go +++ b/gnovm/pkg/packages/load.go @@ -21,7 +21,7 @@ type LoadConfig struct { Fetcher pkgdownload.PackageFetcher Deps bool Cache map[string]*Package - GnorootExamples bool // allow loading packages from gnoroot examples if not found in the set + GnorootExamples bool // allow loading packages from gnoroot examples if not found in the args set SelfContained bool } From ceb039b57d0f67d496f02c896971ac08fca02883 Mon Sep 17 00:00:00 2001 From: Norman Date: Wed, 8 Jan 2025 08:50:57 +0100 Subject: [PATCH 047/143] fix: testing loader paths Signed-off-by: Norman --- gno.land/pkg/gnoclient/integration_test.go | 2 +- gno.land/pkg/integration/pkgloader.go | 5 ++--- gno.land/pkg/integration/testscript_gnoland.go | 2 +- gnovm/pkg/packages/load.go | 11 ++++++----- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/gno.land/pkg/gnoclient/integration_test.go b/gno.land/pkg/gnoclient/integration_test.go index d3d4e0d2c52..da361e538bd 100644 --- a/gno.land/pkg/gnoclient/integration_test.go +++ b/gno.land/pkg/gnoclient/integration_test.go @@ -710,7 +710,7 @@ func loadpkgs(t *testing.T, rootdir string, paths ...string) []gnoland.TxWithMet for _, path := range paths { path = filepath.Clean(path) path = filepath.Join(examplesDir, path) - err := loader.LoadPackage(examplesDir, path, "") + err := loader.LoadPackage(path, "") require.NoErrorf(t, err, "`loadpkg` unable to load package(s) from %q: %s", path, err) } diff --git a/gno.land/pkg/integration/pkgloader.go b/gno.land/pkg/integration/pkgloader.go index cddfcfeebf0..7547b0a3fa9 100644 --- a/gno.land/pkg/integration/pkgloader.go +++ b/gno.land/pkg/integration/pkgloader.go @@ -95,10 +95,9 @@ func (pl *PkgsLoader) LoadAllPackagesFromDir(path string) error { return nil } -func (pl *PkgsLoader) LoadPackage(modroot string, path string, name string) error { +func (pl *PkgsLoader) LoadPackage(pkgDir string, name string) error { // FIXME: name override - cfg := &packages.LoadConfig{Deps: true, SelfContained: true} - pkgDir := filepath.Join(modroot, path) + cfg := &packages.LoadConfig{Deps: true, SelfContained: true, GnorootExamples: true} pkgs, err := packages.Load(cfg, pkgDir) if err != nil { return fmt.Errorf("%q: loading: %w", pkgDir, err) diff --git a/gno.land/pkg/integration/testscript_gnoland.go b/gno.land/pkg/integration/testscript_gnoland.go index ae484a07669..a87064cd08e 100644 --- a/gno.land/pkg/integration/testscript_gnoland.go +++ b/gno.land/pkg/integration/testscript_gnoland.go @@ -524,7 +524,7 @@ func loadpkgCmd(gnoRootDir string) func(ts *testscript.TestScript, neg bool, arg path = filepath.Join(examplesDir, path) } - if err := pkgs.LoadPackage(examplesDir, path, name); err != nil { + if err := pkgs.LoadPackage(path, name); err != nil { ts.Fatalf("`loadpkg` unable to load package(s) from %q: %s", args[0], err) } diff --git a/gnovm/pkg/packages/load.go b/gnovm/pkg/packages/load.go index a07e1426ccc..12ed0716b38 100644 --- a/gnovm/pkg/packages/load.go +++ b/gnovm/pkg/packages/load.go @@ -17,12 +17,13 @@ import ( ) type LoadConfig struct { - IO commands.IO - Fetcher pkgdownload.PackageFetcher - Deps bool - Cache map[string]*Package + IO commands.IO + Fetcher pkgdownload.PackageFetcher + Deps bool + Cache map[string]*Package + SelfContained bool + GnorootExamples bool // allow loading packages from gnoroot examples if not found in the args set - SelfContained bool } func (conf *LoadConfig) applyDefaults() { From c7edab0a34a11fff1244e3044d3301ef1070d569 Mon Sep 17 00:00:00 2001 From: Norman Date: Wed, 8 Jan 2025 08:55:40 +0100 Subject: [PATCH 048/143] fix: ignore stdlibs and allow name override Signed-off-by: Norman --- gno.land/pkg/integration/pkgloader.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/gno.land/pkg/integration/pkgloader.go b/gno.land/pkg/integration/pkgloader.go index 7547b0a3fa9..9776ed81e4a 100644 --- a/gno.land/pkg/integration/pkgloader.go +++ b/gno.land/pkg/integration/pkgloader.go @@ -7,6 +7,7 @@ import ( "github.com/gnolang/gno/gno.land/pkg/gnoland" "github.com/gnolang/gno/gno.land/pkg/sdk/vm" + "github.com/gnolang/gno/gnovm/pkg/gnolang" "github.com/gnolang/gno/gnovm/pkg/packages" bft "github.com/gnolang/gno/tm2/pkg/bft/types" "github.com/gnolang/gno/tm2/pkg/std" @@ -96,7 +97,6 @@ func (pl *PkgsLoader) LoadAllPackagesFromDir(path string) error { } func (pl *PkgsLoader) LoadPackage(pkgDir string, name string) error { - // FIXME: name override cfg := &packages.LoadConfig{Deps: true, SelfContained: true, GnorootExamples: true} pkgs, err := packages.Load(cfg, pkgDir) if err != nil { @@ -104,6 +104,12 @@ func (pl *PkgsLoader) LoadPackage(pkgDir string, name string) error { } for _, pkg := range pkgs { + if pkg.Dir == pkgDir { + pkg.ImportPath = name + } + if gnolang.IsStdlib(pkg.ImportPath) { + continue + } if pl.exist(pkg) { continue } From 82b7ef892c6d7b32671afe6cc1c0e1dc272c529d Mon Sep 17 00:00:00 2001 From: Norman Date: Wed, 8 Jan 2025 09:00:46 +0100 Subject: [PATCH 049/143] chore: don't override name if not requested Signed-off-by: Norman --- gno.land/pkg/integration/pkgloader.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gno.land/pkg/integration/pkgloader.go b/gno.land/pkg/integration/pkgloader.go index 9776ed81e4a..6ba66b9ccdf 100644 --- a/gno.land/pkg/integration/pkgloader.go +++ b/gno.land/pkg/integration/pkgloader.go @@ -104,7 +104,7 @@ func (pl *PkgsLoader) LoadPackage(pkgDir string, name string) error { } for _, pkg := range pkgs { - if pkg.Dir == pkgDir { + if pkg.Dir == pkgDir && name != "" { pkg.ImportPath = name } if gnolang.IsStdlib(pkg.ImportPath) { From 9d6b2d8a460f7205c5e90d3e9004425f885a380d Mon Sep 17 00:00:00 2001 From: Norman Date: Wed, 8 Jan 2025 09:07:44 +0100 Subject: [PATCH 050/143] chore: skip stdlibs and package with no import paths in txs Signed-off-by: Norman --- gno.land/pkg/gnoland/genesis.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/gno.land/pkg/gnoland/genesis.go b/gno.land/pkg/gnoland/genesis.go index 820f6feb30a..ac3090d328b 100644 --- a/gno.land/pkg/gnoland/genesis.go +++ b/gno.land/pkg/gnoland/genesis.go @@ -7,6 +7,7 @@ import ( "strings" vmm "github.com/gnolang/gno/gno.land/pkg/sdk/vm" + "github.com/gnolang/gno/gnovm/pkg/gnolang" gno "github.com/gnolang/gno/gnovm/pkg/gnolang" "github.com/gnolang/gno/gnovm/pkg/packages" "github.com/gnolang/gno/tm2/pkg/amino" @@ -154,6 +155,10 @@ func LoadPackagesFromDir(cfg *packages.LoadConfig, dir string, creator bft.Addre nonDraftPkgs := sortedPkgs.GetNonDraftPkgs() txs := make([]TxWithMetadata, 0, len(nonDraftPkgs)) for _, pkg := range nonDraftPkgs { + if pkg.ImportPath == "" || gnolang.IsStdlib(pkg.ImportPath) { + continue + } + tx, err := LoadPackage(pkg, creator, fee, nil) if err != nil { return nil, fmt.Errorf("unable to load package %q: %w", pkg.Dir, err) From 1405da29875050df430a7de81215819c06b012ed Mon Sep 17 00:00:00 2001 From: Norman Date: Wed, 8 Jan 2025 09:10:40 +0100 Subject: [PATCH 051/143] chore: fix lint Signed-off-by: Norman --- gno.land/pkg/gnoland/genesis.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/gno.land/pkg/gnoland/genesis.go b/gno.land/pkg/gnoland/genesis.go index ac3090d328b..6a8960896a0 100644 --- a/gno.land/pkg/gnoland/genesis.go +++ b/gno.land/pkg/gnoland/genesis.go @@ -8,7 +8,6 @@ import ( vmm "github.com/gnolang/gno/gno.land/pkg/sdk/vm" "github.com/gnolang/gno/gnovm/pkg/gnolang" - gno "github.com/gnolang/gno/gnovm/pkg/gnolang" "github.com/gnolang/gno/gnovm/pkg/packages" "github.com/gnolang/gno/tm2/pkg/amino" bft "github.com/gnolang/gno/tm2/pkg/bft/types" @@ -177,7 +176,7 @@ func LoadPackage(pkg *packages.Package, creator bft.Address, fee std.Fee, deposi var tx std.Tx // Open files in directory as MemPackage. - memPkg := gno.MustReadMemPackage(pkg.Dir, pkg.ImportPath) + memPkg := gnolang.MustReadMemPackage(pkg.Dir, pkg.ImportPath) err := memPkg.Validate() if err != nil { return tx, fmt.Errorf("invalid package: %w", err) From 7bb33b2c5d1c10fdbdef30ee445359d10fa0d4f2 Mon Sep 17 00:00:00 2001 From: Norman Date: Wed, 8 Jan 2025 12:07:24 +0100 Subject: [PATCH 052/143] tmp Signed-off-by: Norman --- gnovm/cmd/gno/lint.go | 30 +++++++++++++++++++++++--- gnovm/pkg/gnolang/store.go | 2 ++ gnovm/pkg/packages/analyze_packages.go | 18 ++++++++-------- gnovm/pkg/packages/load.go | 8 +++---- gnovm/pkg/packages/types.go | 2 +- 5 files changed, 43 insertions(+), 17 deletions(-) diff --git a/gnovm/cmd/gno/lint.go b/gnovm/cmd/gno/lint.go index 7d0b1e29372..a76f49e9394 100644 --- a/gnovm/cmd/gno/lint.go +++ b/gnovm/cmd/gno/lint.go @@ -122,17 +122,41 @@ func execLint(cfg *lintCfg, args []string, io commands.IO) error { io.ErrPrintln(logName) } - // Check if 'gno.mod' exists - if pkg.ModPath == "" { + for _, err := range pkg.Errors { hasError = true issue := lintIssue{ Code: lintGnoMod, Confidence: 1, Location: pkg.Dir, - Msg: "missing 'gno.mod' file", + Msg: err.Error(), } io.ErrPrintln(issue) + } + if len(pkg.Errors) != 0 { + continue + } + + // Check if 'gno.mod' exists + if pkg.ModPath == "" { hasError = true + wd, err := os.Getwd() + location := pkg.Dir + if err == nil { + location, err = filepath.Rel(wd, pkg.Dir) + if err != nil { + location = pkg.Dir + } else { + location = fmt.Sprintf(".%c%s", filepath.Separator, filepath.Join(location)) + } + + } + issue := lintIssue{ + Code: lintGnoMod, + Confidence: 1, + Location: location, + Msg: "gno.mod file not found in current or any parent directory", + } + io.ErrPrintln(issue) } // load deps diff --git a/gnovm/pkg/gnolang/store.go b/gnovm/pkg/gnolang/store.go index bc56a7c6313..0a5355570b7 100644 --- a/gnovm/pkg/gnolang/store.go +++ b/gnovm/pkg/gnolang/store.go @@ -3,6 +3,7 @@ package gnolang import ( "fmt" "reflect" + dbg "runtime/debug" "slices" "strconv" "strings" @@ -711,6 +712,7 @@ func (ds *defaultStore) GetBlockNodeSafe(loc Location) BlockNode { func (ds *defaultStore) SetBlockNode(bn BlockNode) { loc := bn.GetLocation() if loc.IsZero() { + dbg.PrintStack() panic("unexpected zero location in blocknode") } // save node to backend. diff --git a/gnovm/pkg/packages/analyze_packages.go b/gnovm/pkg/packages/analyze_packages.go index 7e00db57ebd..08a66409682 100644 --- a/gnovm/pkg/packages/analyze_packages.go +++ b/gnovm/pkg/packages/analyze_packages.go @@ -33,7 +33,7 @@ func readPkg(pkgDir string, importPath string) *Package { entries, err := os.ReadDir(pkgDir) if err != nil { - pkg.Error = errors.Join(pkg.Error, err) + pkg.Errors = append(pkg.Errors, err) return pkg } @@ -55,21 +55,21 @@ func readPkg(pkgDir string, importPath string) *Package { bodyBytes, err := os.ReadFile(fpath) if err != nil { - pkg.Error = errors.Join(pkg.Error, err) + pkg.Errors = append(pkg.Errors, err) continue } body := string(bodyBytes) fileKind, err := GetFileKind(base, body, fset) if err != nil { - pkg.Error = errors.Join(pkg.Error, err) + pkg.Errors = append(pkg.Errors, err) continue } // ignore files with invalid package clause _, err = parser.ParseFile(fset, fpath, nil, parser.PackageClauseOnly) if err != nil { - pkg.Error = errors.Join(pkg.Error, err) + pkg.Errors = append(pkg.Errors, err) continue } @@ -79,7 +79,7 @@ func readPkg(pkgDir string, importPath string) *Package { pkg.Imports, err = Imports(&mempkg, fset) if err != nil { - pkg.Error = errors.Join(pkg.Error, err) + pkg.Errors = append(pkg.Errors, err) } // we use the ReadMemPkg utils because we want name resolution like the vm @@ -90,7 +90,7 @@ func readPkg(pkgDir string, importPath string) *Package { } minMempkg, err := gnolang.ReadMemPackageFromList(absFiles, "") if err != nil { - pkg.Error = errors.Join(pkg.Error, err) + pkg.Errors = append(pkg.Errors, err) } else { pkg.Name = minMempkg.Name } @@ -102,13 +102,13 @@ func readPkg(pkgDir string, importPath string) *Package { return pkg } if err != nil { - pkg.Error = errors.Join(pkg.Error, err) + pkg.Errors = append(pkg.Errors, err) return pkg } mod, err := gnomod.ParseGnoMod(filepath.Join(pkg.Root, "gno.mod")) if err != nil { - pkg.Error = errors.Join(pkg.Error, err) + pkg.Errors = append(pkg.Errors, err) return pkg } @@ -121,7 +121,7 @@ func readPkg(pkgDir string, importPath string) *Package { pkg.ModPath = mod.Module.Mod.Path subPath, err := filepath.Rel(pkg.Root, pkgDir) if err != nil { - pkg.Error = errors.Join(pkg.Error, err) + pkg.Errors = append(pkg.Errors, err) return pkg } diff --git a/gnovm/pkg/packages/load.go b/gnovm/pkg/packages/load.go index 12ed0716b38..c57f94eb04c 100644 --- a/gnovm/pkg/packages/load.go +++ b/gnovm/pkg/packages/load.go @@ -99,7 +99,7 @@ func Load(conf *LoadConfig, patterns ...string) (PkgList, error) { err = fmt.Errorf("stdlib %q not found", imp) } if err != nil { - pkg.Error = errors.Join(pkg.Error, err) + pkg.Errors = append(pkg.Errors, err) byPkgPath[imp] = nil // stop trying to get this lib, we can't continue } @@ -118,14 +118,14 @@ func Load(conf *LoadConfig, patterns ...string) (PkgList, error) { } if conf.SelfContained { - pkg.Error = errors.Join(pkg.Error, fmt.Errorf("self-contained: package %q not found", imp)) + pkg.Errors = append(pkg.Errors, fmt.Errorf("self-contained: package %q not found", imp)) byPkgPath[imp] = nil // stop trying to get this lib, we can't continue } dir := gnomod.PackageDir("", module.Version{Path: imp}) if err := downloadPackage(conf, imp, dir); err != nil { - pkg.Error = errors.Join(pkg.Error, err) + pkg.Errors = append(pkg.Errors, err) byPkgPath[imp] = nil // stop trying to download pkg, we can't continue } @@ -139,7 +139,7 @@ func Load(conf *LoadConfig, patterns ...string) (PkgList, error) { // TODO: this could be optimized pkg.Deps, err = listDeps(pkg.ImportPath, byPkgPath) if err != nil { - pkg.Error = errors.Join(pkg.Error, err) + pkg.Errors = append(pkg.Errors, err) } } diff --git a/gnovm/pkg/packages/types.go b/gnovm/pkg/packages/types.go index 0728702556f..8442b153935 100644 --- a/gnovm/pkg/packages/types.go +++ b/gnovm/pkg/packages/types.go @@ -10,7 +10,7 @@ type Package struct { Root string `json:",omitempty"` // Gno root, Gno path dir, or module root dir containing this package ModPath string Match []string `json:",omitempty"` // command-line patterns matching this package - Error error `json:",omitempty"` // error loading this package (not dependencies) + Errors []error `json:",omitempty"` // error loading this package (not dependencies) Draft bool Files FilesMap Imports ImportsMap `json:",omitempty"` // import paths used by this package From c95d2bf88ff7bd96d6cfb3a011edf35f5b0cf6a6 Mon Sep 17 00:00:00 2001 From: Norman Date: Wed, 8 Jan 2025 12:52:25 +0100 Subject: [PATCH 053/143] fix: linter errors locations Signed-off-by: Norman --- gnovm/cmd/gno/lint.go | 63 +++++++++++++------------------- gnovm/pkg/gnolang/go2gno.go | 22 ++++++----- gnovm/pkg/gnolang/go2gno_test.go | 6 +-- gnovm/pkg/test/imports.go | 21 ----------- 4 files changed, 42 insertions(+), 70 deletions(-) diff --git a/gnovm/cmd/gno/lint.go b/gnovm/cmd/gno/lint.go index a76f49e9394..183ed004aaf 100644 --- a/gnovm/cmd/gno/lint.go +++ b/gnovm/cmd/gno/lint.go @@ -75,8 +75,18 @@ type lintIssue struct { } func (i lintIssue) String() string { + location := i.Location + wd, err := os.Getwd() + if err == nil { + location, err = filepath.Rel(wd, i.Location) + if err != nil { + location = i.Location + } else { + location = fmt.Sprintf(".%c%s", filepath.Separator, filepath.Join(location)) + } + } // TODO: consider crafting a doc URL based on Code. - return fmt.Sprintf("%s: %s (code=%d)", i.Location, i.Msg, i.Code) + return fmt.Sprintf("%s: %s (code=%d)", location, i.Msg, i.Code) } func execLint(cfg *lintCfg, args []string, io commands.IO) error { @@ -122,41 +132,16 @@ func execLint(cfg *lintCfg, args []string, io commands.IO) error { io.ErrPrintln(logName) } - for _, err := range pkg.Errors { - hasError = true - issue := lintIssue{ - Code: lintGnoMod, - Confidence: 1, - Location: pkg.Dir, - Msg: err.Error(), - } - io.ErrPrintln(issue) - } - if len(pkg.Errors) != 0 { - continue - } - // Check if 'gno.mod' exists - if pkg.ModPath == "" { - hasError = true - wd, err := os.Getwd() - location := pkg.Dir - if err == nil { - location, err = filepath.Rel(wd, pkg.Dir) - if err != nil { - location = pkg.Dir - } else { - location = fmt.Sprintf(".%c%s", filepath.Separator, filepath.Join(location)) - } - - } + if pkg.Root == "" { issue := lintIssue{ Code: lintGnoMod, Confidence: 1, - Location: location, + Location: pkg.Dir, Msg: "gno.mod file not found in current or any parent directory", } io.ErrPrintln(issue) + hasError = true } // load deps @@ -165,29 +150,33 @@ func execLint(cfg *lintCfg, args []string, io commands.IO) error { loadDepsCfg.Cache = pkgsMap deps, loadDepsErr := packages.Load(&loadDepsCfg, pkg.Dir) if loadDepsErr != nil { - io.ErrPrintln(issueFromError(logName, err).String()) + io.ErrPrintln(issueFromError(pkg.Dir, err).String()) hasError = true continue } packages.Inject(pkgsMap, deps) // read mempkg - memPkg, err := gno.ReadMemPackage(pkg.Dir, pkg.ImportPath) + memPkgPath := pkg.ImportPath + if memPkgPath == "" { + memPkgPath = pkg.Dir + } + memPkg, err := gno.ReadMemPackage(pkg.Dir, memPkgPath) if err != nil { - io.ErrPrintln(issueFromError(logName, err).String()) + io.ErrPrintln(issueFromError(pkg.Dir, err).String()) hasError = true continue } // Perform imports using the parent store. if err := test.LoadImports(ts, memPkg); err != nil { - io.ErrPrintln(issueFromError(logName, err).String()) + io.ErrPrintln(issueFromError(pkg.Dir, err).String()) hasError = true continue } // Handle runtime errors - hasRuntimeErr := catchRuntimeError(logName, io.Err(), func() { + hasRuntimeErr := catchRuntimeError(pkg.Dir, io.Err(), func() { // Wrap in cache wrap so execution of the linter doesn't impact // other packages. cw := bs.CacheWrap() @@ -195,7 +184,7 @@ func execLint(cfg *lintCfg, args []string, io commands.IO) error { // Run type checking if !pkg.Draft { - foundErr, err := lintTypeCheck(io, memPkg, gs) + foundErr, err := lintTypeCheck(io, pkg.Dir, memPkg, gs) if err != nil { io.ErrPrintln(err) hasError = true @@ -229,8 +218,8 @@ func execLint(cfg *lintCfg, args []string, io commands.IO) error { return nil } -func lintTypeCheck(io commands.IO, memPkg *gnovm.MemPackage, testStore gno.Store) (errorsFound bool, err error) { - tcErr := gno.TypeCheckMemPackageTest(memPkg, testStore) +func lintTypeCheck(io commands.IO, pkgDir string, memPkg *gnovm.MemPackage, testStore gno.Store) (errorsFound bool, err error) { + tcErr := gno.TypeCheckMemPackageTest(pkgDir, memPkg, testStore) if tcErr == nil { return false, nil } diff --git a/gnovm/pkg/gnolang/go2gno.go b/gnovm/pkg/gnolang/go2gno.go index 82d5c69b08b..e7f14393dba 100644 --- a/gnovm/pkg/gnolang/go2gno.go +++ b/gnovm/pkg/gnolang/go2gno.go @@ -500,19 +500,19 @@ type MemPackageGetter interface { // // If format is true, the code will be automatically updated with the // formatted source code. -func TypeCheckMemPackage(mempkg *gnovm.MemPackage, getter MemPackageGetter, format bool) error { - return typeCheckMemPackage(mempkg, getter, false, format) +func TypeCheckMemPackage(pkgDir string, mempkg *gnovm.MemPackage, getter MemPackageGetter, format bool) error { + return typeCheckMemPackage(pkgDir, mempkg, getter, false, format) } // TypeCheckMemPackageTest performs the same type checks as [TypeCheckMemPackage], // but allows re-declarations. // // Note: like TypeCheckMemPackage, this function ignores tests and filetests. -func TypeCheckMemPackageTest(mempkg *gnovm.MemPackage, getter MemPackageGetter) error { - return typeCheckMemPackage(mempkg, getter, true, false) +func TypeCheckMemPackageTest(pkgDir string, mempkg *gnovm.MemPackage, getter MemPackageGetter) error { + return typeCheckMemPackage(pkgDir, mempkg, getter, true, false) } -func typeCheckMemPackage(mempkg *gnovm.MemPackage, getter MemPackageGetter, testing, format bool) error { +func typeCheckMemPackage(pkgDir string, mempkg *gnovm.MemPackage, getter MemPackageGetter, testing, format bool) error { var errs error imp := &gnoImporter{ getter: getter, @@ -526,7 +526,11 @@ func typeCheckMemPackage(mempkg *gnovm.MemPackage, getter MemPackageGetter, test } imp.cfg.Importer = imp - _, err := imp.parseCheckMemPackage(mempkg, format) + if pkgDir == "" { + pkgDir = mempkg.Path + } + + _, err := imp.parseCheckMemPackage(pkgDir, mempkg, format) // prefer to return errs instead of err: // err will generally contain only the first error encountered. if errs != nil { @@ -571,12 +575,12 @@ func (g *gnoImporter) ImportFrom(path, _ string, _ types.ImportMode) (*types.Pac return nil, err } fmt := false - result, err := g.parseCheckMemPackage(mpkg, fmt) + result, err := g.parseCheckMemPackage("", mpkg, fmt) g.cache[path] = gnoImporterResult{pkg: result, err: err} return result, err } -func (g *gnoImporter) parseCheckMemPackage(mpkg *gnovm.MemPackage, fmt bool) (*types.Package, error) { +func (g *gnoImporter) parseCheckMemPackage(pkgDir string, mpkg *gnovm.MemPackage, fmt bool) (*types.Package, error) { // This map is used to allow for function re-definitions, which are allowed // in Gno (testing context) but not in Go. // This map links each function identifier with a closure to remove its @@ -600,7 +604,7 @@ func (g *gnoImporter) parseCheckMemPackage(mpkg *gnovm.MemPackage, fmt bool) (*t } const parseOpts = parser.ParseComments | parser.DeclarationErrors | parser.SkipObjectResolution - f, err := parser.ParseFile(fset, path.Join(mpkg.Path, file.Name), file.Body, parseOpts) + f, err := parser.ParseFile(fset, path.Join(pkgDir, file.Name), file.Body, parseOpts) if err != nil { errs = multierr.Append(errs, err) continue diff --git a/gnovm/pkg/gnolang/go2gno_test.go b/gnovm/pkg/gnolang/go2gno_test.go index 8aba5d7f293..83365cfb0a6 100644 --- a/gnovm/pkg/gnolang/go2gno_test.go +++ b/gnovm/pkg/gnolang/go2gno_test.go @@ -325,7 +325,7 @@ func TestTypeCheckMemPackage(t *testing.T) { t.Parallel() format := false - err := TypeCheckMemPackage(tc.pkg, tc.getter, format) + err := TypeCheckMemPackage("", tc.pkg, tc.getter, format) if tc.check == nil { assert.NoError(t, err) } else { @@ -360,7 +360,7 @@ func TestTypeCheckMemPackage_format(t *testing.T) { mpkgGetter := mockPackageGetter{} format := false - err := TypeCheckMemPackage(pkg, mpkgGetter, format) + err := TypeCheckMemPackage("", pkg, mpkgGetter, format) assert.NoError(t, err) assert.Equal(t, input, pkg.Files[0].Body) // unchanged @@ -372,7 +372,7 @@ func Hello(name string) string { ` format = true - err = TypeCheckMemPackage(pkg, mpkgGetter, format) + err = TypeCheckMemPackage("", pkg, mpkgGetter, format) assert.NoError(t, err) assert.NotEqual(t, input, pkg.Files[0].Body) assert.Equal(t, expected, pkg.Files[0].Body) diff --git a/gnovm/pkg/test/imports.go b/gnovm/pkg/test/imports.go index 66b90e12fac..fc81cab17d6 100644 --- a/gnovm/pkg/test/imports.go +++ b/gnovm/pkg/test/imports.go @@ -157,27 +157,6 @@ func Store( return } - /* - // if examples package... - examplePath := filepath.Join(rootDir, "examples", pkgPath) - if osm.DirExists(examplePath) { - memPkg := gno.MustReadMemPackage(examplePath, pkgPath) - if memPkg.IsEmpty() { - panic(fmt.Sprintf("found an empty package %q", pkgPath)) - } - - send := std.Coins{} - ctx := Context(pkgPath, send) - m2 := gno.NewMachineWithOptions(gno.MachineOptions{ - PkgPath: "test", - Output: stdout, - Store: store, - Context: ctx, - }) - pn, pv = m2.RunMemPackage(memPkg, true) - return - } - */ return nil, nil } db := memdb.NewMemDB() From 6d6ee0d68e2dd6bd37c76663dff9dd0a1b064f44 Mon Sep 17 00:00:00 2001 From: Norman Date: Wed, 8 Jan 2025 13:49:30 +0100 Subject: [PATCH 054/143] feat: single file pattern support + misc bugs Signed-off-by: Norman --- gnovm/cmd/gno/lint_test.go | 4 +- gnovm/pkg/packages/analyze_packages.go | 83 ++++++++++++++++++++++---- gnovm/pkg/packages/expand_patterns.go | 18 ++++++ gnovm/pkg/packages/load.go | 14 +++-- 4 files changed, 100 insertions(+), 19 deletions(-) diff --git a/gnovm/cmd/gno/lint_test.go b/gnovm/cmd/gno/lint_test.go index 4589fc55f92..e1a730130d2 100644 --- a/gnovm/cmd/gno/lint_test.go +++ b/gnovm/cmd/gno/lint_test.go @@ -16,11 +16,11 @@ func TestLintApp(t *testing.T) { errShouldBe: "exit code: 1", }, { args: []string{"lint", "../../tests/integ/undefined_variable_test/undefined_variables_test.gno"}, - stderrShouldContain: "undefined_variables_test.gno:6:28: name toto not declared (code=2)", + stderrShouldContain: "../../tests/integ/undefined_variable_test:6:28: name toto not declared (code=2)", errShouldBe: "exit code: 1", }, { args: []string{"lint", "../../tests/integ/package_not_declared/main.gno"}, - stderrShouldContain: "main.gno:4:2: name fmt not declared (code=2)", + stderrShouldContain: "../../tests/integ/package_not_declared/main.gno:4:2: undefined: fmt (code=4)", errShouldBe: "exit code: 1", }, { args: []string{"lint", "../../tests/integ/several-lint-errors/main.gno"}, diff --git a/gnovm/pkg/packages/analyze_packages.go b/gnovm/pkg/packages/analyze_packages.go index 08a66409682..0f69dc5e9bb 100644 --- a/gnovm/pkg/packages/analyze_packages.go +++ b/gnovm/pkg/packages/analyze_packages.go @@ -2,6 +2,7 @@ package packages import ( "errors" + "fmt" "go/parser" "go/token" "os" @@ -14,45 +15,96 @@ import ( "github.com/gnolang/gno/gnovm/pkg/gnomod" ) -func readPackages(matches []*pkgMatch) []*Package { +func readPackages(matches []*pkgMatch, fset *token.FileSet) ([]*Package, error) { + if fset == nil { + fset = token.NewFileSet() + } pkgs := make([]*Package, 0, len(matches)) for _, pkgMatch := range matches { - pkg := readPkg(pkgMatch.Dir, "") + var pkg *Package + var err error + if pkgMatch.Dir == "command-line-arguments" { + pkg, err = readCLAPkg(pkgMatch.Match, fset) + if err != nil { + return nil, err + } + } else { + pkg = readPkgDir(pkgMatch.Dir, "", fset) + } pkg.Match = pkgMatch.Match pkgs = append(pkgs, pkg) } - return pkgs + return pkgs, nil +} + +func readCLAPkg(patterns []string, fset *token.FileSet) (*Package, error) { + pkg := &Package{ + ImportPath: "command-line-arguments", + Files: make(FilesMap), + Imports: make(ImportsMap), + } + + files := []string{} + for _, match := range patterns { + dir := filepath.Dir(match) + if pkg.Dir == "" { + pkg.Dir = dir + } else if dir != pkg.Dir { + return nil, fmt.Errorf("named files must all be in one directory; have %s and %s", pkg.Dir, dir) + } + abs, err := filepath.Abs(match) + if err != nil { + return nil, fmt.Errorf("%s: %w", match, err) + } + files = append(files, abs) + } + + return readPkgFiles(pkg, files, fset), nil } -func readPkg(pkgDir string, importPath string) *Package { +func readPkgDir(pkgDir string, importPath string, fset *token.FileSet) *Package { pkg := &Package{ Dir: pkgDir, Files: make(FilesMap), + Imports: make(ImportsMap), ImportPath: importPath, } + files := []string{} entries, err := os.ReadDir(pkgDir) if err != nil { pkg.Errors = append(pkg.Errors, err) return pkg } - - fset := token.NewFileSet() - - mempkg := gnovm.MemPackage{} - for _, entry := range entries { if entry.IsDir() { continue } base := entry.Name() - fpath := filepath.Join(pkgDir, base) if !strings.HasSuffix(base, ".gno") { continue } + fpath := filepath.Join(pkgDir, base) + files = append(files, fpath) + } + + return readPkgFiles(pkg, files, fset) + +} + +func readPkgFiles(pkg *Package, files []string, fset *token.FileSet) *Package { + if fset == nil { + fset = token.NewFileSet() + } + + mempkg := gnovm.MemPackage{} + + for _, fpath := range files { + base := filepath.Base(fpath) + bodyBytes, err := os.ReadFile(fpath) if err != nil { pkg.Errors = append(pkg.Errors, err) @@ -77,12 +129,13 @@ func readPkg(pkgDir string, importPath string) *Package { pkg.Files[fileKind] = append(pkg.Files[fileKind], base) } + var err error pkg.Imports, err = Imports(&mempkg, fset) if err != nil { pkg.Errors = append(pkg.Errors, err) } - // we use the ReadMemPkg utils because we want name resolution like the vm + // we use the ReadMemPkg utils to get the package name because we want name resolution like the vm nameFiles := pkg.Files.Merge(FileKindPackageSource, FileKindTest, FileKindXTest) absFiles := make([]string, 0, len(nameFiles)) for _, f := range nameFiles { @@ -97,7 +150,11 @@ func readPkg(pkgDir string, importPath string) *Package { // TODO: check if stdlib - pkg.Root, err = gnomod.FindRootDir(pkgDir) + if pkg.ImportPath == "command-line-arguments" { + return pkg + } + + pkg.Root, err = gnomod.FindRootDir(pkg.Dir) if errors.Is(err, gnomod.ErrGnoModNotFound) { return pkg } @@ -119,7 +176,7 @@ func readPkg(pkgDir string, importPath string) *Package { } pkg.ModPath = mod.Module.Mod.Path - subPath, err := filepath.Rel(pkg.Root, pkgDir) + subPath, err := filepath.Rel(pkg.Root, pkg.Dir) if err != nil { pkg.Errors = append(pkg.Errors, err) return pkg diff --git a/gnovm/pkg/packages/expand_patterns.go b/gnovm/pkg/packages/expand_patterns.go index e78db46801f..b360912ec48 100644 --- a/gnovm/pkg/packages/expand_patterns.go +++ b/gnovm/pkg/packages/expand_patterns.go @@ -44,11 +44,29 @@ func expandPatterns(conf *LoadConfig, patterns ...string) ([]*pkgMatch, error) { pkgMatches[idx].Match = append(pkgMatches[idx].Match, match) } + kinds := make([]patternKind, 0, len(patterns)) for _, match := range patterns { patKind, err := getPatternKind(match) if err != nil { return nil, fmt.Errorf("%s: %w", match, err) } + kinds = append(kinds, patKind) + } + + if slices.Contains(kinds, patternKindSingleFile) { + files := make([]string, 0, len(patterns)) + for i, match := range patterns { + kind := kinds[i] + if kind != patternKindSingleFile || !strings.HasSuffix(match, ".gno") { + return nil, fmt.Errorf("named files must be .gno files: %s", match) + } + files = append(files, match) + } + return []*pkgMatch{{Dir: "command-line-arguments", Match: files}}, nil + } + + for i, match := range patterns { + patKind := kinds[i] switch patKind { case patternKindSingleFile: diff --git a/gnovm/pkg/packages/load.go b/gnovm/pkg/packages/load.go index c57f94eb04c..0c46d2de324 100644 --- a/gnovm/pkg/packages/load.go +++ b/gnovm/pkg/packages/load.go @@ -3,6 +3,7 @@ package packages import ( "errors" "fmt" + "go/token" "os" "path/filepath" @@ -41,12 +42,17 @@ func (conf *LoadConfig) applyDefaults() { func Load(conf *LoadConfig, patterns ...string) (PkgList, error) { conf.applyDefaults() + fset := token.NewFileSet() + expanded, err := expandPatterns(conf, patterns...) if err != nil { return nil, err } - pkgs := readPackages(expanded) + pkgs, err := readPackages(expanded, fset) + if err != nil { + return nil, err + } if !conf.Deps { return pkgs, nil @@ -104,7 +110,7 @@ func Load(conf *LoadConfig, patterns ...string) (PkgList, error) { continue } - pileDown(readPkg(dir, imp)) + pileDown(readPkgDir(dir, imp, fset)) continue } @@ -112,7 +118,7 @@ func Load(conf *LoadConfig, patterns ...string) (PkgList, error) { examplePkgDir := filepath.Join(gnoroot, "examples", filepath.FromSlash(imp)) finfo, err := os.Stat(examplePkgDir) if err == nil && finfo.IsDir() { - pileDown(readPkg(examplePkgDir, imp)) + pileDown(readPkgDir(examplePkgDir, imp, fset)) continue } } @@ -129,7 +135,7 @@ func Load(conf *LoadConfig, patterns ...string) (PkgList, error) { byPkgPath[imp] = nil // stop trying to download pkg, we can't continue } - pileDown(readPkg(dir, imp)) + pileDown(readPkgDir(dir, imp, fset)) } list = append(list, pkg) From c5754b5de53240c4bfe8680b2e7af4c6996874bf Mon Sep 17 00:00:00 2001 From: Norman Date: Wed, 8 Jan 2025 13:53:03 +0100 Subject: [PATCH 055/143] chore: fmt and lint go Signed-off-by: Norman --- gno.land/pkg/sdk/vm/keeper.go | 4 ++-- gnovm/pkg/packages/analyze_packages.go | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/gno.land/pkg/sdk/vm/keeper.go b/gno.land/pkg/sdk/vm/keeper.go index bf16cd44243..5b588a8524d 100644 --- a/gno.land/pkg/sdk/vm/keeper.go +++ b/gno.land/pkg/sdk/vm/keeper.go @@ -349,7 +349,7 @@ func (vm *VMKeeper) AddPackage(ctx sdk.Context, msg MsgAddPackage) (err error) { // Validate Gno syntax and type check. format := true - if err := gno.TypeCheckMemPackage(memPkg, gnostore, format); err != nil { + if err := gno.TypeCheckMemPackage("", memPkg, gnostore, format); err != nil { return ErrTypeCheck(err) } @@ -564,7 +564,7 @@ func (vm *VMKeeper) Run(ctx sdk.Context, msg MsgRun) (res string, err error) { // Validate Gno syntax and type check. format := false - if err = gno.TypeCheckMemPackage(memPkg, gnostore, format); err != nil { + if err = gno.TypeCheckMemPackage("", memPkg, gnostore, format); err != nil { return "", ErrTypeCheck(err) } diff --git a/gnovm/pkg/packages/analyze_packages.go b/gnovm/pkg/packages/analyze_packages.go index 0f69dc5e9bb..7453927eaee 100644 --- a/gnovm/pkg/packages/analyze_packages.go +++ b/gnovm/pkg/packages/analyze_packages.go @@ -92,7 +92,6 @@ func readPkgDir(pkgDir string, importPath string, fset *token.FileSet) *Package } return readPkgFiles(pkg, files, fset) - } func readPkgFiles(pkg *Package, files []string, fset *token.FileSet) *Package { From a73ba043ef4c06388ac67f4b66c567cab2ad375b Mon Sep 17 00:00:00 2001 From: Norman Date: Wed, 8 Jan 2025 16:59:29 +0100 Subject: [PATCH 056/143] fix: don't ignore files without valid package clause Signed-off-by: Norman --- gnovm/cmd/gno/lint.go | 34 +++++---- gnovm/cmd/gno/lint_test.go | 4 +- gnovm/cmd/gno/mod.go | 4 +- gnovm/cmd/gno/test.go | 8 ++- gnovm/cmd/gno/testdata/lint/no_gnomod.txtar | 2 +- gnovm/pkg/packages/analyze_packages.go | 22 +++--- gnovm/pkg/packages/filekind.go | 2 +- gnovm/pkg/packages/imports.go | 77 +++++++++++++++++---- gnovm/pkg/packages/imports_test.go | 14 ++-- gnovm/pkg/test/imports.go | 17 +++-- 10 files changed, 118 insertions(+), 66 deletions(-) diff --git a/gnovm/cmd/gno/lint.go b/gnovm/cmd/gno/lint.go index 183ed004aaf..3fdb8411daa 100644 --- a/gnovm/cmd/gno/lint.go +++ b/gnovm/cmd/gno/lint.go @@ -81,8 +81,6 @@ func (i lintIssue) String() string { location, err = filepath.Rel(wd, i.Location) if err != nil { location = i.Location - } else { - location = fmt.Sprintf(".%c%s", filepath.Separator, filepath.Join(location)) } } // TODO: consider crafting a doc URL based on Code. @@ -133,7 +131,7 @@ func execLint(cfg *lintCfg, args []string, io commands.IO) error { } // Check if 'gno.mod' exists - if pkg.Root == "" { + if pkg.Root == "" && pkg.ImportPath != "command-line-arguments" { issue := lintIssue{ Code: lintGnoMod, Confidence: 1, @@ -158,7 +156,7 @@ func execLint(cfg *lintCfg, args []string, io commands.IO) error { // read mempkg memPkgPath := pkg.ImportPath - if memPkgPath == "" { + if memPkgPath == "" || memPkgPath == "command-line-arguments" { memPkgPath = pkg.Dir } memPkg, err := gno.ReadMemPackage(pkg.Dir, memPkgPath) @@ -278,12 +276,12 @@ func lintTestFiles(memPkg *gnovm.MemPackage) *gno.FileSet { return testfiles } -func guessSourcePath(pkg, source string) string { - if info, err := os.Stat(pkg); !os.IsNotExist(err) && !info.IsDir() { - pkg = filepath.Dir(pkg) +func guessSourcePath(pkgDir, source string) string { + if info, err := os.Stat(pkgDir); !os.IsNotExist(err) && !info.IsDir() { + pkgDir = filepath.Dir(pkgDir) } - sourceJoin := filepath.Join(pkg, source) + sourceJoin := filepath.Join(pkgDir, source) if _, err := os.Stat(sourceJoin); !os.IsNotExist(err) { return filepath.Clean(sourceJoin) } @@ -292,7 +290,7 @@ func guessSourcePath(pkg, source string) string { return filepath.Clean(source) } - return filepath.Clean(pkg) + return filepath.Clean(pkgDir) } // reParseRecover is a regex designed to parse error details from a string. @@ -300,7 +298,7 @@ func guessSourcePath(pkg, source string) string { // XXX: Ideally, error handling should encapsulate location details within a dedicated error type. var reParseRecover = regexp.MustCompile(`^([^:]+)((?::(?:\d+)){1,2}):? *(.*)$`) -func catchRuntimeError(pkgPath string, stderr goio.WriteCloser, action func()) (hasError bool) { +func catchRuntimeError(pkgDir string, stderr goio.WriteCloser, action func()) (hasError bool) { defer func() { // Errors catched here mostly come from: gnovm/pkg/gnolang/preprocess.go r := recover() @@ -311,21 +309,21 @@ func catchRuntimeError(pkgPath string, stderr goio.WriteCloser, action func()) ( switch verr := r.(type) { case *gno.PreprocessError: err := verr.Unwrap() - fmt.Fprintln(stderr, issueFromError(pkgPath, err).String()) + fmt.Fprintln(stderr, issueFromError(pkgDir, err).String()) case error: errors := multierr.Errors(verr) for _, err := range errors { errList, ok := err.(scanner.ErrorList) if ok { for _, errorInList := range errList { - fmt.Fprintln(stderr, issueFromError(pkgPath, errorInList).String()) + fmt.Fprintln(stderr, issueFromError(pkgDir, errorInList).String()) } } else { - fmt.Fprintln(stderr, issueFromError(pkgPath, err).String()) + fmt.Fprintln(stderr, issueFromError(pkgDir, err).String()) } } case string: - fmt.Fprintln(stderr, issueFromError(pkgPath, errors.New(verr)).String()) + fmt.Fprintln(stderr, issueFromError(pkgDir, errors.New(verr)).String()) default: panic(r) } @@ -335,21 +333,21 @@ func catchRuntimeError(pkgPath string, stderr goio.WriteCloser, action func()) ( return } -func issueFromError(pkgPath string, err error) lintIssue { +func issueFromError(pkgDir string, err error) lintIssue { var issue lintIssue issue.Confidence = 1 issue.Code = lintGnoError parsedError := strings.TrimSpace(err.Error()) - parsedError = strings.TrimPrefix(parsedError, pkgPath+"/") + parsedError = strings.TrimPrefix(parsedError, pkgDir+"/") matches := reParseRecover.FindStringSubmatch(parsedError) if len(matches) > 0 { - sourcepath := guessSourcePath(pkgPath, matches[1]) + sourcepath := guessSourcePath(pkgDir, matches[1]) issue.Location = sourcepath + matches[2] issue.Msg = strings.TrimSpace(matches[3]) } else { - issue.Location = fmt.Sprintf("%s:0", filepath.Clean(pkgPath)) + issue.Location = fmt.Sprintf("%s:0", filepath.Clean(pkgDir)) issue.Msg = err.Error() } return issue diff --git a/gnovm/cmd/gno/lint_test.go b/gnovm/cmd/gno/lint_test.go index e1a730130d2..72ca51f240e 100644 --- a/gnovm/cmd/gno/lint_test.go +++ b/gnovm/cmd/gno/lint_test.go @@ -12,11 +12,11 @@ func TestLintApp(t *testing.T) { errShouldBe: "flag: help requested", }, { args: []string{"lint", "../../tests/integ/run_main/"}, - stderrShouldContain: "./../../tests/integ/run_main: gno.mod file not found in current or any parent directory (code=1)", + stderrShouldContain: "../../tests/integ/run_main: gno.mod file not found in current or any parent directory (code=1)", errShouldBe: "exit code: 1", }, { args: []string{"lint", "../../tests/integ/undefined_variable_test/undefined_variables_test.gno"}, - stderrShouldContain: "../../tests/integ/undefined_variable_test:6:28: name toto not declared (code=2)", + stderrShouldContain: "../../tests/integ/undefined_variable_test/undefined_variables_test.gno:6:28: name toto not declared (code=2)", errShouldBe: "exit code: 1", }, { args: []string{"lint", "../../tests/integ/package_not_declared/main.gno"}, diff --git a/gnovm/cmd/gno/mod.go b/gnovm/cmd/gno/mod.go index 63c5c506e0a..18d583c5810 100644 --- a/gnovm/cmd/gno/mod.go +++ b/gnovm/cmd/gno/mod.go @@ -364,13 +364,13 @@ func getImportToFilesMap(pkgPath string) (map[string][]string, error) { if err != nil { return nil, err } - imports, err := packages.FileImports(filename, string(data), nil) + imports, err := packages.FileImportsSpecs(filename, string(data), nil) if err != nil { return nil, err } for _, imp := range imports { - m[imp] = append(m[imp], filename) + m[imp.Path.Value] = append(m[imp.Path.Value], filename) } } return m, nil diff --git a/gnovm/cmd/gno/test.go b/gnovm/cmd/gno/test.go index e3d0dc46a3c..2d1d4a2347a 100644 --- a/gnovm/cmd/gno/test.go +++ b/gnovm/cmd/gno/test.go @@ -216,10 +216,14 @@ func execTest(cfg *testCfg, args []string, io commands.IO) error { } packages.Inject(pkgsMap, deps) - memPkg := gno.MustReadMemPackage(pkg.Dir, logName) + memPkg, err := gno.ReadMemPackage(pkg.Dir, logName) + if err != nil { + io.ErrPrintln(err) + return nil + } startedAt := time.Now() - hasError := catchRuntimeError(logName, io.Err(), func() { + hasError := catchRuntimeError(pkg.Dir, io.Err(), func() { err = test.Test(memPkg, pkg.Dir, opts) }) diff --git a/gnovm/cmd/gno/testdata/lint/no_gnomod.txtar b/gnovm/cmd/gno/testdata/lint/no_gnomod.txtar index 775c921aee2..072e7cca551 100644 --- a/gnovm/cmd/gno/testdata/lint/no_gnomod.txtar +++ b/gnovm/cmd/gno/testdata/lint/no_gnomod.txtar @@ -14,4 +14,4 @@ func main() { -- stdout.golden -- -- stderr.golden -- -.: missing 'gno.mod' file (code=1). +.: gno.mod file not found in current or any parent directory (code=1) diff --git a/gnovm/pkg/packages/analyze_packages.go b/gnovm/pkg/packages/analyze_packages.go index 7453927eaee..9ae9009d047 100644 --- a/gnovm/pkg/packages/analyze_packages.go +++ b/gnovm/pkg/packages/analyze_packages.go @@ -3,7 +3,6 @@ package packages import ( "errors" "fmt" - "go/parser" "go/token" "os" "path" @@ -43,20 +42,22 @@ func readCLAPkg(patterns []string, fset *token.FileSet) (*Package, error) { Files: make(FilesMap), Imports: make(ImportsMap), } + var err error files := []string{} for _, match := range patterns { - dir := filepath.Dir(match) + dir, base := filepath.Split(match) + dir, err = filepath.Abs(dir) + if err != nil { + return nil, err + } if pkg.Dir == "" { pkg.Dir = dir } else if dir != pkg.Dir { return nil, fmt.Errorf("named files must all be in one directory; have %s and %s", pkg.Dir, dir) } - abs, err := filepath.Abs(match) - if err != nil { - return nil, fmt.Errorf("%s: %w", match, err) - } - files = append(files, abs) + + files = append(files, filepath.Join(dir, base)) } return readPkgFiles(pkg, files, fset), nil @@ -117,13 +118,6 @@ func readPkgFiles(pkg *Package, files []string, fset *token.FileSet) *Package { continue } - // ignore files with invalid package clause - _, err = parser.ParseFile(fset, fpath, nil, parser.PackageClauseOnly) - if err != nil { - pkg.Errors = append(pkg.Errors, err) - continue - } - mempkg.Files = append(mempkg.Files, &gnovm.MemFile{Name: base, Body: body}) pkg.Files[fileKind] = append(pkg.Files[fileKind], base) } diff --git a/gnovm/pkg/packages/filekind.go b/gnovm/pkg/packages/filekind.go index 382d1d12af7..cbad0cc6c1e 100644 --- a/gnovm/pkg/packages/filekind.go +++ b/gnovm/pkg/packages/filekind.go @@ -45,7 +45,7 @@ func GetFileKind(filename string, body string, fset *token.FileSet) (FileKind, e } ast, err := parser.ParseFile(fset, filename, body, parser.PackageClauseOnly) if err != nil { - return FileKindUnknown, err + return FileKindTest, nil } packageName := ast.Name.Name diff --git a/gnovm/pkg/packages/imports.go b/gnovm/pkg/packages/imports.go index 4820dc03b7a..6a684bcc57a 100644 --- a/gnovm/pkg/packages/imports.go +++ b/gnovm/pkg/packages/imports.go @@ -2,6 +2,7 @@ package packages import ( "fmt" + "go/ast" "go/parser" "go/token" "sort" @@ -11,10 +12,18 @@ import ( "github.com/gnolang/gno/gnovm" ) +func Imports(pkg *gnovm.MemPackage, fset *token.FileSet) (ImportsMap, error) { + specs, err := ImportsSpecs(pkg, fset) + if err != nil { + return nil, err + } + return ImportsMapFromSpecs(specs, fset), nil +} + // Imports returns the list of gno imports from a [gnovm.MemPackage]. // fset is optional. -func Imports(pkg *gnovm.MemPackage, fset *token.FileSet) (ImportsMap, error) { - res := make(ImportsMap, 16) +func ImportsSpecs(pkg *gnovm.MemPackage, fset *token.FileSet) (ImportsSpecsMap, error) { + res := make(ImportsSpecsMap, 16) seen := make(map[FileKind]map[string]struct{}, 16) for _, file := range pkg.Files { @@ -26,32 +35,30 @@ func Imports(pkg *gnovm.MemPackage, fset *token.FileSet) (ImportsMap, error) { if err != nil { return nil, err } - imports, err := FileImports(file.Name, file.Body, fset) + imports, err := FileImportsSpecs(file.Name, file.Body, fset) if err != nil { return nil, err } for _, im := range imports { - if _, ok := seen[fileKind][im]; ok { + if _, ok := seen[fileKind][im.Path.Value]; ok { continue } res[fileKind] = append(res[fileKind], im) if _, ok := seen[fileKind]; !ok { seen[fileKind] = make(map[string]struct{}, 16) } - seen[fileKind][im] = struct{}{} + seen[fileKind][im.Path.Value] = struct{}{} } } for _, imports := range res { - sortImports(imports) + sortImportsSpecs(imports) } return res, nil } -// FileImports returns the list of gno imports in the given file src. -// The given filename is only used when recording position information. -func FileImports(filename string, src string, fset *token.FileSet) ([]string, error) { +func FileImportsSpecs(filename string, src string, fset *token.FileSet) ([]*ast.ImportSpec, error) { if fset == nil { fset = token.NewFileSet() } @@ -59,22 +66,40 @@ func FileImports(filename string, src string, fset *token.FileSet) ([]string, er if err != nil { return nil, err } - res := make([]string, len(f.Imports)) + res := make([]*ast.ImportSpec, len(f.Imports)) for i, im := range f.Imports { - importPath, err := strconv.Unquote(im.Path.Value) + _, err := strconv.Unquote(im.Path.Value) if err != nil { // should not happen - parser.ParseFile should already ensure we get // a valid string literal here. panic(fmt.Errorf("%v: unexpected invalid import path: %v", fset.Position(im.Pos()).String(), im.Path.Value)) } - res[i] = importPath + res[i] = im } return res, nil } type ImportsMap map[FileKind][]string +func ImportsMapFromSpecs(specs ImportsSpecsMap, fset *token.FileSet) ImportsMap { + res := make(ImportsMap, len(specs)) + for k, v := range specs { + c := make([]string, 0, len(v)) + for _, x := range v { + pkgPath, err := strconv.Unquote(x.Path.Value) + if err != nil { + // should not happen - parser.ParseFile should already ensure we get + // a valid string literal here. + panic(fmt.Errorf("%v: unexpected invalid import path: %v", fset.Position(x.Pos()).String(), x.Path.Value)) + } + c = append(c, pkgPath) + } + res[k] = c + } + return res +} + // Merge merges imports, it removes duplicates and sorts the result func (imap ImportsMap) Merge(kinds ...FileKind) []string { res := make([]string, 0, 16) @@ -95,12 +120,40 @@ func (imap ImportsMap) Merge(kinds ...FileKind) []string { return res } +type ImportsSpecsMap map[FileKind][]*ast.ImportSpec + +// Merge merges imports, it removes duplicates and sorts the result +func (imap ImportsSpecsMap) Merge(kinds ...FileKind) []*ast.ImportSpec { + res := make([]*ast.ImportSpec, 0, 16) + seen := make(map[string]struct{}, 16) + + for _, kind := range kinds { + for _, im := range imap[kind] { + if _, ok := seen[im.Path.Value]; ok { + continue + } + seen[im.Path.Value] = struct{}{} + + res = append(res, im) + } + } + + sortImportsSpecs(res) + return res +} + func sortImports(imports []string) { sort.Slice(imports, func(i, j int) bool { return imports[i] < imports[j] }) } +func sortImportsSpecs(imports []*ast.ImportSpec) { + sort.Slice(imports, func(i, j int) bool { + return imports[i].Path.Value < imports[j].Path.Value + }) +} + func FilePackageName(filename string, src string) (string, error) { fs := token.NewFileSet() f, err := parser.ParseFile(fs, filename, src, parser.PackageClauseOnly) diff --git a/gnovm/pkg/packages/imports_test.go b/gnovm/pkg/packages/imports_test.go index 0267f46e17a..9a396d1ac40 100644 --- a/gnovm/pkg/packages/imports_test.go +++ b/gnovm/pkg/packages/imports_test.go @@ -1,6 +1,7 @@ package packages_test import ( + "go/token" "os" "path/filepath" "testing" @@ -146,18 +147,13 @@ func TestImports(t *testing.T) { pkg, err := gnolang.ReadMemPackage(tmpDir, "test") require.NoError(t, err) - importsMap, err := Imports(pkg, nil) + fset := token.NewFileSet() + + importsSpec, err := ImportsSpecs(pkg, nil) require.NoError(t, err) // ignore specs - got := map[FileKind][]string{} - for key, vals := range importsMap { - stringVals := make([]string, len(vals)) - for i, val := range vals { - stringVals[i] = val - } - got[key] = stringVals - } + got := ImportsMapFromSpecs(importsSpec, fset) require.Equal(t, expected, got) } diff --git a/gnovm/pkg/test/imports.go b/gnovm/pkg/test/imports.go index fc81cab17d6..8b96400ead8 100644 --- a/gnovm/pkg/test/imports.go +++ b/gnovm/pkg/test/imports.go @@ -10,6 +10,7 @@ import ( "os" "path/filepath" "runtime/debug" + "strconv" "strings" "time" @@ -246,20 +247,26 @@ func LoadImports(store gno.Store, memPkg *gnovm.MemPackage) (err error) { }() fset := token.NewFileSet() - importsMap, err := packages.Imports(memPkg, fset) + importsMap, err := packages.ImportsSpecs(memPkg, fset) if err != nil { return err } imports := importsMap.Merge(packages.FileKindPackageSource, packages.FileKindTest, packages.FileKindXTest) - for _, imp := range imports { - if gno.IsRealmPath(imp) { + for _, im := range imports { + pkgPath, err := strconv.Unquote(im.Path.Value) + if err != nil { + // should not happen - parser.ParseFile should already ensure we get + // a valid string literal here. + panic(fmt.Errorf("%v: unexpected invalid import path: %v", fset.Position(im.Pos()).String(), im.Path.Value)) + } + if gno.IsRealmPath(pkgPath) { // Don't eagerly load realms. // Realms persist state and can change the state of other realms in initialization. continue } - pkg := store.GetPackage(imp, true) + pkg := store.GetPackage(pkgPath, true) if pkg == nil { - return fmt.Errorf("unknown import path %v", imp) + return fmt.Errorf("%s: unknown import path %s", fset.Position(im.Pos()).String(), pkgPath) } } return nil From cef59f3ac62debcbbdf7d5383330c3c870fbf156 Mon Sep 17 00:00:00 2001 From: Norman Date: Wed, 8 Jan 2025 19:36:46 +0100 Subject: [PATCH 057/143] fix: mod why Signed-off-by: Norman --- gnovm/cmd/gno/mod.go | 37 +++++++++++++++++++++++++------------ gnovm/cmd/gno/test.go | 6 +----- 2 files changed, 26 insertions(+), 17 deletions(-) diff --git a/gnovm/cmd/gno/mod.go b/gnovm/cmd/gno/mod.go index 18d583c5810..b704870cba7 100644 --- a/gnovm/cmd/gno/mod.go +++ b/gnovm/cmd/gno/mod.go @@ -4,8 +4,10 @@ import ( "context" "flag" "fmt" + "go/token" "os" "path/filepath" + "strconv" "strings" "github.com/gnolang/gno/gnovm/pkg/gnomod" @@ -298,23 +300,27 @@ func execModWhy(args []string, io commands.IO) error { return flag.ErrHelp } - wd, err := os.Getwd() + conf := &packages.LoadConfig{SelfContained: true, Fetcher: testPackageFetcher} + pkgs, err := packages.Load(conf, ".") if err != nil { return err } - fname := filepath.Join(wd, "gno.mod") - gm, err := gnomod.ParseGnoMod(fname) - if err != nil { - return err + if len(pkgs) < 1 { + return errors.New("no package found") } + pkg := pkgs[0] - importToFilesMap, err := getImportToFilesMap(wd) + importToFilesMap, err := getImportToFilesMap(pkg) if err != nil { return err } + if pkg.ModPath == "" { + pkg.ModPath = "." + } + // Format and print `gno mod why` output stanzas - out := formatModWhyStanzas(gm.Module.Mod.Path, args, importToFilesMap) + out := formatModWhyStanzas(pkg.ModPath, args, importToFilesMap) io.Printf(out) return nil @@ -345,11 +351,12 @@ func formatModWhyStanzas(modulePath string, args []string, importToFilesMap map[ // getImportToFilesMap returns a map where each key is an import path and its // value is a list of files importing that package with the specified import path. -func getImportToFilesMap(pkgPath string) (map[string][]string, error) { - entries, err := os.ReadDir(pkgPath) +func getImportToFilesMap(pkg *packages.Package) (map[string][]string, error) { + entries, err := os.ReadDir(pkg.Dir) if err != nil { return nil, err } + fset := token.NewFileSet() m := make(map[string][]string) // import -> []file for _, e := range entries { filename := e.Name() @@ -360,17 +367,23 @@ func getImportToFilesMap(pkgPath string) (map[string][]string, error) { continue } - data, err := os.ReadFile(filepath.Join(pkgPath, filename)) + data, err := os.ReadFile(filepath.Join(pkg.Dir, filename)) if err != nil { return nil, err } - imports, err := packages.FileImportsSpecs(filename, string(data), nil) + imports, err := packages.FileImportsSpecs(filename, string(data), fset) if err != nil { return nil, err } for _, imp := range imports { - m[imp.Path.Value] = append(m[imp.Path.Value], filename) + pkgPath, err := strconv.Unquote(imp.Path.Value) + if err != nil { + // should not happen - parser.ParseFile should already ensure we get + // a valid string literal here. + panic(fmt.Errorf("%v: unexpected invalid import path: %v", fset.Position(imp.Pos()).String(), imp.Path.Value)) + } + m[pkgPath] = append(m[pkgPath], filename) } } return m, nil diff --git a/gnovm/cmd/gno/test.go b/gnovm/cmd/gno/test.go index 2d1d4a2347a..4ad819ed249 100644 --- a/gnovm/cmd/gno/test.go +++ b/gnovm/cmd/gno/test.go @@ -208,11 +208,7 @@ func execTest(cfg *testCfg, args []string, io commands.IO) error { deps, loadDepsErr := packages.Load(&depsConf, pkg.Dir) if loadDepsErr != nil { io.ErrPrintfln("%s: load deps: %v", logName, err) - io.ErrPrintfln("FAIL") - io.ErrPrintfln("FAIL %s", logName) - io.ErrPrintfln("FAIL") - buildErrCount++ - continue + return nil } packages.Inject(pkgsMap, deps) From b73027bc49d91b7ec1fd2a8498bedfc1783e0835 Mon Sep 17 00:00:00 2001 From: Norman Date: Wed, 8 Jan 2025 22:24:49 +0100 Subject: [PATCH 058/143] fix: pass all tests except transpile Signed-off-by: Norman --- gnovm/cmd/gno/mod_test.go | 4 +- gnovm/cmd/gno/test.go | 47 +++++++++++++------ gnovm/cmd/gno/testdata/test/empty_dir.txtar | 6 +-- .../gno/testdata/test/multitest_events.txtar | 5 +- .../gno/testdata/test/realm_boundmethod.txtar | 6 +++ gnovm/pkg/packages/expand_patterns.go | 47 ++++++++++++++----- gnovm/pkg/packages/filekind.go | 4 ++ gnovm/pkg/packages/patterns.go | 4 +- gnovm/pkg/packages/types.go | 8 ++++ 9 files changed, 97 insertions(+), 34 deletions(-) diff --git a/gnovm/cmd/gno/mod_test.go b/gnovm/cmd/gno/mod_test.go index afce25597cd..efd60d2906e 100644 --- a/gnovm/cmd/gno/mod_test.go +++ b/gnovm/cmd/gno/mod_test.go @@ -177,7 +177,9 @@ func TestModApp(t *testing.T) { args: []string{"mod", "why", "std"}, testDir: "../../tests/integ/empty_dir", simulateExternalRepo: true, - errShouldContain: "could not read gno.mod file", + stdoutShouldBe: `# std +(module . does not need package std) +`, }, { args: []string{"mod", "why", "std"}, diff --git a/gnovm/cmd/gno/test.go b/gnovm/cmd/gno/test.go index 4ad819ed249..efccfccc728 100644 --- a/gnovm/cmd/gno/test.go +++ b/gnovm/cmd/gno/test.go @@ -2,9 +2,13 @@ package main import ( "context" + "errors" "flag" "fmt" goio "io" + "os" + "path/filepath" + "slices" "time" "github.com/gnolang/gno/gnovm/pkg/gnoenv" @@ -151,6 +155,9 @@ func execTest(cfg *testCfg, args []string, io commands.IO) error { cfg.rootDir = gnoenv.RootDir() } + // inject cwd, we need this to pass some txtars + args = append(args, fmt.Sprintf(".%c...", filepath.Separator)) + // Find targets for test. conf := &packages.LoadConfig{IO: io, Fetcher: testPackageFetcher} pkgs, err := packages.Load(conf, args...) @@ -158,8 +165,12 @@ func execTest(cfg *testCfg, args []string, io commands.IO) error { return fmt.Errorf("list targets from patterns: %w", err) } if len(pkgs) == 0 { - io.ErrPrintln("no packages to test") - return nil + return errors.New("no packages to test") + } + for _, pkg := range pkgs { + if len(pkg.Match) != 0 && !slices.ContainsFunc(pkg.Match, func(s string) bool { return !packages.PatternIsLiteral(s) }) && pkg.Files.Size() == 0 { + return fmt.Errorf("no Gno files in %s", pkg.Dir) + } } if cfg.timeout > 0 { @@ -187,18 +198,24 @@ func execTest(cfg *testCfg, args []string, io commands.IO) error { buildErrCount := 0 testErrCount := 0 for _, pkg := range pkgs { - // ignore deps + // ignore side-loaded packages, mostly coming from siblings injection (TODO: explain siblings injection, why we chose it over go's forcing to have an arching context (go.mod, go.work)) if len(pkg.Match) == 0 { continue } - logName := pkg.ImportPath - if logName == "" { - logName = pkg.Dir + label := pkg.ImportPath + if label == "" { + label = pkg.Dir + } + if cwd, err := os.Getwd(); err == nil { + relLogName, err := filepath.Rel(cwd, label) + if err == nil { + label = relLogName + } } if len(pkg.Files[packages.FileKindTest]) == 0 && len(pkg.Files[packages.FileKindXTest]) == 0 && len(pkg.Files[packages.FileKindFiletest]) == 0 { - io.ErrPrintfln("? %s \t[no test files]", logName) + io.ErrPrintfln("? %s \t[no test files]", label) continue } @@ -207,15 +224,17 @@ func execTest(cfg *testCfg, args []string, io commands.IO) error { depsConf.Cache = pkgsMap deps, loadDepsErr := packages.Load(&depsConf, pkg.Dir) if loadDepsErr != nil { - io.ErrPrintfln("%s: load deps: %v", logName, err) - return nil + io.ErrPrintfln("%s: load deps: %v", label, err) + buildErrCount++ + continue } packages.Inject(pkgsMap, deps) - memPkg, err := gno.ReadMemPackage(pkg.Dir, logName) + memPkg, err := gno.ReadMemPackage(pkg.Dir, label) if err != nil { io.ErrPrintln(err) - return nil + buildErrCount++ + continue } startedAt := time.Now() @@ -228,14 +247,14 @@ func execTest(cfg *testCfg, args []string, io commands.IO) error { if hasError || err != nil { if err != nil { - io.ErrPrintfln("%s: test pkg: %v", logName, err) + io.ErrPrintfln("%s: test pkg: %v", label, err) } io.ErrPrintfln("FAIL") - io.ErrPrintfln("FAIL %s \t%s", logName, dstr) + io.ErrPrintfln("FAIL %s \t%s", label, dstr) io.ErrPrintfln("FAIL") testErrCount++ } else { - io.ErrPrintfln("ok %s \t%s", logName, dstr) + io.ErrPrintfln("ok %s \t%s", label, dstr) } } if testErrCount > 0 || buildErrCount > 0 { diff --git a/gnovm/cmd/gno/testdata/test/empty_dir.txtar b/gnovm/cmd/gno/testdata/test/empty_dir.txtar index e7cf49c5795..fb2a400f9b2 100644 --- a/gnovm/cmd/gno/testdata/test/empty_dir.txtar +++ b/gnovm/cmd/gno/testdata/test/empty_dir.txtar @@ -3,9 +3,9 @@ ! gno test . ! stdout .+ -stderr 'no valid gno files' +stderr 'no Gno files in ' -gno test ./... +! gno test ./... ! stdout .+ -stderr 'no packages to test' +stderr 'gno: warning: "./..." matched no packages\nno packages to test' diff --git a/gnovm/cmd/gno/testdata/test/multitest_events.txtar b/gnovm/cmd/gno/testdata/test/multitest_events.txtar index 321c790561a..a7367e29494 100644 --- a/gnovm/cmd/gno/testdata/test/multitest_events.txtar +++ b/gnovm/cmd/gno/testdata/test/multitest_events.txtar @@ -5,7 +5,10 @@ gno test -print-events . ! stdout .+ stderr 'EVENTS: \[{\"type\":\"EventA\",\"attrs\":\[\],\"pkg_path\":\"gno.land/r/.*\",\"func\":\"TestA\"}\]' stderr 'EVENTS: \[{\"type\":\"EventB\",\"attrs\":\[{\"key\":\"keyA\",\"value\":\"valA\"}\],\"pkg_path\":\"gno.land/r/.*\",\"func\":\"TestB\"},{\"type\":\"EventC\",\"attrs\":\[{\"key\":\"keyD\",\"value\":\"valD\"}\],\"pkg_path\":\"gno.land/r/.*\",\"func\":\"TestB\"}\]' -stderr 'ok \. \d\.\d\ds' +stderr 'ok gno.land/r/.* \d\.\d\ds' + +-- gno.mod -- +module gno.land/r/tests/multitest_events -- valid.gno -- package valid diff --git a/gnovm/cmd/gno/testdata/test/realm_boundmethod.txtar b/gnovm/cmd/gno/testdata/test/realm_boundmethod.txtar index 9d935df74c2..e53cde13667 100644 --- a/gnovm/cmd/gno/testdata/test/realm_boundmethod.txtar +++ b/gnovm/cmd/gno/testdata/test/realm_boundmethod.txtar @@ -25,6 +25,9 @@ func (c *Counter) Inc() { c.n++ } +-- examples/gno.land/r/demo/realm1/gno.mod -- +module gno.land/r/demo/realm1 + -- examples/gno.land/r/demo/realm1/realm1.gno -- package realm1 @@ -36,6 +39,9 @@ func GetCounter() *counter.Counter { return &c } +-- examples/gno.land/r/demo/realm2/gno.mod -- +module gno.land/r/demo/realm2 + -- examples/gno.land/r/demo/realm2/realm2.gno -- package realm2 diff --git a/gnovm/pkg/packages/expand_patterns.go b/gnovm/pkg/packages/expand_patterns.go index b360912ec48..a4525bdd2a0 100644 --- a/gnovm/pkg/packages/expand_patterns.go +++ b/gnovm/pkg/packages/expand_patterns.go @@ -32,16 +32,23 @@ type pkgMatch struct { func expandPatterns(conf *LoadConfig, patterns ...string) ([]*pkgMatch, error) { pkgMatches := []*pkgMatch{} - addMatch := func(dir string, match string) { + addPkgDir := func(dir string, match *string) { idx := slices.IndexFunc(pkgMatches, func(sum *pkgMatch) bool { return sum.Dir == dir }) if idx == -1 { - pkgMatches = append(pkgMatches, &pkgMatch{Dir: dir, Match: []string{match}}) + matches := []string{} + if match != nil { + matches = append(matches, *match) + } + pkgMatches = append(pkgMatches, &pkgMatch{Dir: dir, Match: matches}) + return + } + if match == nil { return } - if slices.Contains(pkgMatches[idx].Match, match) { + if slices.Contains(pkgMatches[idx].Match, *match) { return } - pkgMatches[idx].Match = append(pkgMatches[idx].Match, match) + pkgMatches[idx].Match = append(pkgMatches[idx].Match, *match) } kinds := make([]patternKind, 0, len(patterns)) @@ -54,24 +61,33 @@ func expandPatterns(conf *LoadConfig, patterns ...string) ([]*pkgMatch, error) { } if slices.Contains(kinds, patternKindSingleFile) { + remaining := []string{} + remainingKinds := []patternKind{} + files := make([]string, 0, len(patterns)) for i, match := range patterns { kind := kinds[i] - if kind != patternKindSingleFile || !strings.HasSuffix(match, ".gno") { + if kind != patternKindSingleFile { + remaining = append(remaining, match) + remainingKinds = append(remainingKinds, kind) + continue + } + if !strings.HasSuffix(match, ".gno") { return nil, fmt.Errorf("named files must be .gno files: %s", match) } files = append(files, match) } - return []*pkgMatch{{Dir: "command-line-arguments", Match: files}}, nil + + pkgMatches = append(pkgMatches, &pkgMatch{Dir: "command-line-arguments", Match: files}) + + patterns = remaining + kinds = remainingKinds } for i, match := range patterns { patKind := kinds[i] switch patKind { - case patternKindSingleFile: - return nil, fmt.Errorf("%s: single file patterns are not supported", match) - case patternKindRecursiveRemote: return nil, fmt.Errorf("%s: recursive remote patterns are not supported", match) @@ -79,6 +95,8 @@ func expandPatterns(conf *LoadConfig, patterns ...string) ([]*pkgMatch, error) { if conf.SelfContained { return nil, fmt.Errorf("%s: remote patterns are not supported in self-contained mode", match) } + case patternKindSingleFile: + return nil, fmt.Errorf("unexpected single pattern at this point") } pat, err := cleanPattern(match, patKind) @@ -88,22 +106,25 @@ func expandPatterns(conf *LoadConfig, patterns ...string) ([]*pkgMatch, error) { switch patKind { case patternKindDirectory: - addMatch(pat, match) + addPkgDir(pat, &match) case patternKindRemote: dir := gnomod.PackageDir("", module.Version{Path: pat}) if err := downloadPackage(conf, pat, dir); err != nil { return nil, err } - addMatch(dir, match) + addPkgDir(dir, &match) case patternKindRecursiveLocal: dirs, err := expandRecursive(pat) if err != nil { return nil, fmt.Errorf("%s: %w", pat, err) } + if len(dirs) == 0 { + conf.IO.ErrPrintfln(`gno: warning: %q matched no packages`, match) + } for _, dir := range dirs { - addMatch(dir, match) + addPkgDir(dir, &match) } } } @@ -158,7 +179,7 @@ const ( ) func getPatternKind(pat string) (patternKind, error) { - isLitteral := patternIsLiteral(pat) + isLitteral := PatternIsLiteral(pat) if patternIsRemote(pat) { if isLitteral { diff --git a/gnovm/pkg/packages/filekind.go b/gnovm/pkg/packages/filekind.go index cbad0cc6c1e..88a8ca72e96 100644 --- a/gnovm/pkg/packages/filekind.go +++ b/gnovm/pkg/packages/filekind.go @@ -26,6 +26,10 @@ const ( FileKindFiletest = "Filetest" ) +func AllFileKinds() []FileKind { + return []FileKind{FileKindPackageSource, FileKindTest, FileKindXTest, FileKindFiletest} +} + // GetFileKind analyzes a file's name and body to get it's [FileKind], fset is optional func GetFileKind(filename string, body string, fset *token.FileSet) (FileKind, error) { if !strings.HasSuffix(filename, ".gno") { diff --git a/gnovm/pkg/packages/patterns.go b/gnovm/pkg/packages/patterns.go index e139f6ace15..220101694c9 100644 --- a/gnovm/pkg/packages/patterns.go +++ b/gnovm/pkg/packages/patterns.go @@ -19,9 +19,9 @@ func patternIsRemote(path string) bool { return strings.ContainsRune(path[:slashIdx], '.') } -// patternIsLiteral reports whether the pattern is free of wildcards. +// PatternIsLiteral reports whether the pattern is free of wildcards. // // A literal pattern must match at most one package. -func patternIsLiteral(pattern string) bool { +func PatternIsLiteral(pattern string) bool { return !strings.Contains(pattern, "...") } diff --git a/gnovm/pkg/packages/types.go b/gnovm/pkg/packages/types.go index 8442b153935..ac556d83a03 100644 --- a/gnovm/pkg/packages/types.go +++ b/gnovm/pkg/packages/types.go @@ -19,6 +19,14 @@ type Package struct { type FilesMap map[FileKind][]string +func (fm FilesMap) Size() int { + total := 0 + for _, kind := range AllFileKinds() { + total += len(fm[kind]) + } + return total +} + // Merge merges imports, it removes duplicates and sorts the result func (imap FilesMap) Merge(kinds ...FileKind) []string { res := make([]string, 0, 16) From 5b5386f8e59431693b896593ea94d574c797e82e Mon Sep 17 00:00:00 2001 From: Norman Date: Thu, 9 Jan 2025 20:41:57 +0100 Subject: [PATCH 059/143] tmp Signed-off-by: Norman --- gnovm/cmd/gno/lint.go | 9 +- gnovm/cmd/gno/test.go | 42 +++---- gnovm/cmd/gno/testdata/test/empty_dir.txtar | 2 +- .../testdata/transpile/valid_empty_dir.txtar | 2 +- .../transpile/valid_output_flag.txtar | 6 +- .../transpile/valid_transpile_file.txtar | 15 --- .../transpile/valid_transpile_tree.txtar | 4 +- gnovm/cmd/gno/transpile.go | 89 ++++++++++++-- gnovm/cmd/gno/util.go | 26 +++- gnovm/pkg/packages/load.go | 112 ++++++++++++------ gnovm/pkg/packages/pkglist.go | 27 +++++ 11 files changed, 227 insertions(+), 107 deletions(-) diff --git a/gnovm/cmd/gno/lint.go b/gnovm/cmd/gno/lint.go index 3fdb8411daa..2c3388eb18f 100644 --- a/gnovm/cmd/gno/lint.go +++ b/gnovm/cmd/gno/lint.go @@ -75,14 +75,7 @@ type lintIssue struct { } func (i lintIssue) String() string { - location := i.Location - wd, err := os.Getwd() - if err == nil { - location, err = filepath.Rel(wd, i.Location) - if err != nil { - location = i.Location - } - } + location := tryRelativize(i.Location) // TODO: consider crafting a doc URL based on Code. return fmt.Sprintf("%s: %s (code=%d)", location, i.Msg, i.Code) } diff --git a/gnovm/cmd/gno/test.go b/gnovm/cmd/gno/test.go index efccfccc728..a5e0eca193b 100644 --- a/gnovm/cmd/gno/test.go +++ b/gnovm/cmd/gno/test.go @@ -2,13 +2,9 @@ package main import ( "context" - "errors" "flag" "fmt" goio "io" - "os" - "path/filepath" - "slices" "time" "github.com/gnolang/gno/gnovm/pkg/gnoenv" @@ -155,24 +151,20 @@ func execTest(cfg *testCfg, args []string, io commands.IO) error { cfg.rootDir = gnoenv.RootDir() } - // inject cwd, we need this to pass some txtars - args = append(args, fmt.Sprintf(".%c...", filepath.Separator)) - // Find targets for test. - conf := &packages.LoadConfig{IO: io, Fetcher: testPackageFetcher} + conf := &packages.LoadConfig{ + IO: io, + Fetcher: testPackageFetcher, + DepsPatterns: []string{"./..."}, + } pkgs, err := packages.Load(conf, args...) if err != nil { - return fmt.Errorf("list targets from patterns: %w", err) - } - if len(pkgs) == 0 { - return errors.New("no packages to test") - } - for _, pkg := range pkgs { - if len(pkg.Match) != 0 && !slices.ContainsFunc(pkg.Match, func(s string) bool { return !packages.PatternIsLiteral(s) }) && pkg.Files.Size() == 0 { - return fmt.Errorf("no Gno files in %s", pkg.Dir) - } + return err } + pkgsMap := map[string]*packages.Package{} + packages.Inject(pkgsMap, pkgs) + if cfg.timeout > 0 { go func() { time.Sleep(cfg.timeout) @@ -180,9 +172,6 @@ func execTest(cfg *testCfg, args []string, io commands.IO) error { }() } - pkgsMap := map[string]*packages.Package{} - packages.Inject(pkgsMap, pkgs) - // Set up options to run tests. stdout := goio.Discard if cfg.verbose { @@ -198,21 +187,20 @@ func execTest(cfg *testCfg, args []string, io commands.IO) error { buildErrCount := 0 testErrCount := 0 for _, pkg := range pkgs { - // ignore side-loaded packages, mostly coming from siblings injection (TODO: explain siblings injection, why we chose it over go's forcing to have an arching context (go.mod, go.work)) + // ignore deps if len(pkg.Match) == 0 { continue } + if !pkg.Draft && pkg.Files.Size() == 0 { + return fmt.Errorf("no Gno files in %s", pkg.Dir) + } + label := pkg.ImportPath if label == "" { label = pkg.Dir } - if cwd, err := os.Getwd(); err == nil { - relLogName, err := filepath.Rel(cwd, label) - if err == nil { - label = relLogName - } - } + label = tryRelativize(label) if len(pkg.Files[packages.FileKindTest]) == 0 && len(pkg.Files[packages.FileKindXTest]) == 0 && len(pkg.Files[packages.FileKindFiletest]) == 0 { io.ErrPrintfln("? %s \t[no test files]", label) diff --git a/gnovm/cmd/gno/testdata/test/empty_dir.txtar b/gnovm/cmd/gno/testdata/test/empty_dir.txtar index fb2a400f9b2..f7ea441e0c3 100644 --- a/gnovm/cmd/gno/testdata/test/empty_dir.txtar +++ b/gnovm/cmd/gno/testdata/test/empty_dir.txtar @@ -8,4 +8,4 @@ stderr 'no Gno files in ' ! gno test ./... ! stdout .+ -stderr 'gno: warning: "./..." matched no packages\nno packages to test' +stderr 'gno: warning: "./..." matched no packages\nno packages' diff --git a/gnovm/cmd/gno/testdata/transpile/valid_empty_dir.txtar b/gnovm/cmd/gno/testdata/transpile/valid_empty_dir.txtar index 72752b651c6..9106de41622 100644 --- a/gnovm/cmd/gno/testdata/transpile/valid_empty_dir.txtar +++ b/gnovm/cmd/gno/testdata/transpile/valid_empty_dir.txtar @@ -3,4 +3,4 @@ ! gno transpile . ! stdout .+ -stderr 'no valid gno files' +stderr 'no Gno files in ' diff --git a/gnovm/cmd/gno/testdata/transpile/valid_output_flag.txtar b/gnovm/cmd/gno/testdata/transpile/valid_output_flag.txtar index b1a63890f46..4d285b83561 100644 --- a/gnovm/cmd/gno/testdata/transpile/valid_output_flag.txtar +++ b/gnovm/cmd/gno/testdata/transpile/valid_output_flag.txtar @@ -13,8 +13,8 @@ gno transpile -v -output directory/hello $WORK ! stdout .+ cmpenv stderr stderr2.golden -exists directory/hello$WORK/main.gno.gen.go -! exists directory/hello/main.gno.gen.go +! exists directory/hello$WORK/main.gno.gen.go +exists directory/hello/main.gno.gen.go rm directory # Try running in subdirectory, using a "relative non-local path." (ie. has "../") @@ -30,7 +30,7 @@ exists hello$WORK/main.gno.gen.go -- stderr1.golden -- . -- stderr2.golden -- -$WORK +. -- stderr3.golden -- .. -- main.gno -- diff --git a/gnovm/cmd/gno/testdata/transpile/valid_transpile_file.txtar b/gnovm/cmd/gno/testdata/transpile/valid_transpile_file.txtar index 86cc6f12f7a..ecf7d323ffe 100644 --- a/gnovm/cmd/gno/testdata/transpile/valid_transpile_file.txtar +++ b/gnovm/cmd/gno/testdata/transpile/valid_transpile_file.txtar @@ -18,11 +18,9 @@ gno transpile -v main.gno hello_test.gno cmp stderr transpile-files-stderr.golden cmp main.gno.gen.go main.gno.gen.go.golden -cmp .hello_test.gno.gen_test.go .hello_test.gno.gen_test.go.golden -- transpile-files-stderr.golden -- main.gno -hello_test.gno -- main.gno -- package main @@ -50,16 +48,3 @@ package main func main() { println("hello") } --- .hello_test.gno.gen_test.go.golden -- -// Code generated by github.com/gnolang/gno. DO NOT EDIT. - -//go:build gno && test - -//line hello_test.gno:1:1 -package main - -import "github.com/gnolang/gno/gnovm/stdlibs/std" - -func hello() { - std.AssertOriginCall(nil) -} diff --git a/gnovm/cmd/gno/testdata/transpile/valid_transpile_tree.txtar b/gnovm/cmd/gno/testdata/transpile/valid_transpile_tree.txtar index a765ab5093b..d8073d9c67d 100644 --- a/gnovm/cmd/gno/testdata/transpile/valid_transpile_tree.txtar +++ b/gnovm/cmd/gno/testdata/transpile/valid_transpile_tree.txtar @@ -1,7 +1,7 @@ # Run gno transpile with dependencies env GNOROOT=$WORK -gno transpile -v ./examples +gno transpile -v ./examples/... ! stdout .+ cmpenv stderr stderr.golden @@ -13,7 +13,7 @@ cmp gnovm/stdlibs/math/math.gno.gen.go gnovm/stdlibs/math/math.gno.gen.go.golden -- stderr.golden -- examples/gno.land/r/answer -$WORK/gnovm/stdlibs/math +gnovm/stdlibs/math examples/gno.land/r/question (skipped, gno.mod marks module as draft) -- examples/gno.land/r/question/gno.mod -- // Draft diff --git a/gnovm/cmd/gno/transpile.go b/gnovm/cmd/gno/transpile.go index 723a18ce607..56648355011 100644 --- a/gnovm/cmd/gno/transpile.go +++ b/gnovm/cmd/gno/transpile.go @@ -15,6 +15,7 @@ import ( "strconv" "strings" + "github.com/davecgh/go-spew/spew" "github.com/gnolang/gno/gnovm/pkg/gnoenv" "github.com/gnolang/gno/gnovm/pkg/packages" "github.com/gnolang/gno/gnovm/pkg/transpiler" @@ -53,8 +54,8 @@ func (p *transpileOptions) getFlags() *transpileCfg { return p.cfg } -func (p *transpileOptions) isTranspiled(pkg string) bool { - _, transpiled := p.transpiled[pkg] +func (p *transpileOptions) isTranspiled(path string) bool { + _, transpiled := p.transpiled[path] return transpiled } @@ -122,6 +123,23 @@ func (c *transpileCfg) RegisterFlags(fs *flag.FlagSet) { ) } +type transpileTarget struct { + subPath string + pkg *packages.Package +} + +// REARCH: +// - only allow files in the cwd or remote pkgs as params +// - find output subpath +// - if file belongs to an identifiable pkg (pkg that has an import path) -> ./some.pkg/path/filename +// - else -> ./path/to/file +// - if no-build -> transpile to output and exit +// - else -> transpile to tmpdir +// - create go.work in tmpdir including all packages +// - create go.mod in tmpdir for pkg-less sources +// - build +// - copy to output without go.work and go.mod + func execTranspile(cfg *transpileCfg, args []string, io commands.IO) error { if len(args) < 1 { return flag.ErrHelp @@ -133,21 +151,50 @@ func execTranspile(cfg *transpileCfg, args []string, io commands.IO) error { } // load packages - conf := &packages.LoadConfig{IO: io, Fetcher: testPackageFetcher, Deps: true} + conf := &packages.LoadConfig{IO: io, Fetcher: testPackageFetcher, Deps: true, DepsPatterns: []string{"./..."}} pkgs, err := packages.Load(conf, args...) if err != nil { - return fmt.Errorf("load pkgs: %w", err) + return err + } + + // find subpaths + targets := []transpileTarget{} + for _, pkg := range pkgs { + if pkg.ImportPath == "" || pkg.ImportPath == "command-line-arguments" { + rel, err := relativize(pkg.Dir) + if err != nil { + return fmt.Errorf("%s: can't relativize: %w", pkg.Dir, err) + } + if !filepath.IsLocal(rel) { + return fmt.Errorf("%s: is not a local path", rel) + } + targets = append(targets, transpileTarget{ + subPath: rel, + pkg: pkg, + }) + continue + } + + targets = append(targets, transpileTarget{ + subPath: filepath.FromSlash(pkg.ImportPath), + }) } + + fmt.Println("targets", targets) + pkgsMap := map[string]*packages.Package{} packages.Inject(pkgsMap, pkgs) + // spew.Dump(pkgs) + // transpile .gno packages and files. opts := newTranspileOptions(cfg, io) var errlist scanner.ErrorList for _, pkg := range pkgs { - // ignore deps - if len(pkg.Match) == 0 { - continue + spew.Dump(pkg) + + if !pkg.Draft && pkg.Files.Size() == 0 { + return fmt.Errorf("no Gno files in %s", tryRelativize(pkg.Dir)) } if err := transpilePkg(pkg, pkgsMap, opts); err != nil { @@ -160,6 +207,9 @@ func execTranspile(cfg *transpileCfg, args []string, io commands.IO) error { } } + dumpDir(".") + dumpDir("./directory/hello") + if errlist.Len() == 0 && cfg.gobuild { for _, pkg := range pkgs { if slices.Contains(opts.skipped, pkg.Dir) { @@ -179,7 +229,7 @@ func execTranspile(cfg *transpileCfg, args []string, io commands.IO) error { if errlist.Len() > 0 { for _, err := range errlist { - io.ErrPrintfln(err.Error()) + io.ErrPrintfln(tryRelativize(err.Error())) } return fmt.Errorf("%d transpile error(s)", errlist.Len()) } @@ -198,7 +248,7 @@ func transpilePkg(pkg *packages.Package, pkgs map[string]*packages.Package, opts if pkg.Draft { if opts.cfg.verbose { - opts.io.ErrPrintfln("%s (skipped, gno.mod marks module as draft)", filepath.Clean(dirPath)) + opts.io.ErrPrintfln("%s (skipped, gno.mod marks module as draft)", tryRelativize(filepath.Clean(dirPath))) } opts.skipped = append(opts.skipped, dirPath) return nil @@ -208,11 +258,16 @@ func transpilePkg(pkg *packages.Package, pkgs map[string]*packages.Package, opts // The transpiler doesn't currently support "test stdlibs", and even if it // did all packages like "fmt" would have to exist as standard libraries to work. // Easier to skip for now. - if opts.cfg.verbose { - opts.io.ErrPrintln(filepath.Clean(dirPath)) + if opts.cfg.verbose && pkg.ImportPath != "command-line-arguments" { + label := tryRelativize(dirPath) + opts.io.ErrPrintln(filepath.Clean(label)) } for _, file := range pkg.Files[packages.FileKindPackageSource] { fpath := filepath.Join(pkg.Dir, file) + if opts.cfg.verbose && pkg.ImportPath == "command-line-arguments" { + label := tryRelativize(fpath) + opts.io.ErrPrintln(filepath.Clean(label)) + } if err := transpileFile(fpath, pkgs, opts); err != nil { return fmt.Errorf("%s: %w", file, err) } @@ -288,14 +343,14 @@ func goBuildFileOrPkg(io commands.IO, fileOrPkg string, cfg *transpileCfg) error io.ErrPrintfln("%s [build]", filepath.Clean(fileOrPkg)) } - return buildTranspiledPackage(fileOrPkg, goBinary) + return buildTranspiledPackage(fileOrPkg, goBinary, cfg) } // buildTranspiledPackage tries to run `go build` against the transpiled .go files. // // This method is the most efficient to detect errors but requires that // all the import are valid and available. -func buildTranspiledPackage(fileOrPkg, goBinary string) error { +func buildTranspiledPackage(fileOrPkg, goBinary string, cfg *transpileCfg) error { // TODO: use cmd/compile instead of exec? // TODO: find the nearest go.mod file, chdir in the same folder, trim prefix? // TODO: temporarily create an in-memory go.mod or disable go modules for gno? @@ -382,3 +437,11 @@ func parseGoBuildErrors(out string) error { return errList.Err() } + +func dumpDir(dir string) { + entries, err := os.ReadDir(dir) + if err != nil { + fmt.Println("dumpDir error:", err) + } + spew.Dump(entries) +} diff --git a/gnovm/cmd/gno/util.go b/gnovm/cmd/gno/util.go index 52962be133d..fbddd1ffa34 100644 --- a/gnovm/cmd/gno/util.go +++ b/gnovm/cmd/gno/util.go @@ -158,8 +158,10 @@ func fmtDuration(d time.Duration) string { // // See related test for examples. func ResolvePath(output, dstPath string) (string, error) { - if filepath.IsAbs(dstPath) || - filepath.IsLocal(dstPath) { + if filepath.IsAbs(dstPath) { + dstPath = tryRelativize(dstPath) + } + if filepath.IsLocal(dstPath) { return filepath.Join(output, dstPath), nil } // Make dstPath absolute and join it with output. @@ -252,3 +254,23 @@ func copyFile(src, dst string) error { return nil } + +func tryRelativize(dirPath string) string { + rel, err := relativize(dirPath) + if err != nil { + return dirPath + } + return rel +} + +func relativize(dirPath string) (string, error) { + wd, err := os.Getwd() + if err != nil { + return "", err + } + rel, err := filepath.Rel(wd, dirPath) + if err != nil { + return "", err + } + return rel, nil +} diff --git a/gnovm/pkg/packages/load.go b/gnovm/pkg/packages/load.go index 0c46d2de324..567bad895a6 100644 --- a/gnovm/pkg/packages/load.go +++ b/gnovm/pkg/packages/load.go @@ -21,15 +21,17 @@ type LoadConfig struct { IO commands.IO Fetcher pkgdownload.PackageFetcher Deps bool - Cache map[string]*Package + Cache PackagesMap SelfContained bool + AllowEmpty bool + DepsPatterns []string GnorootExamples bool // allow loading packages from gnoroot examples if not found in the args set } func (conf *LoadConfig) applyDefaults() { if conf.IO == nil { - conf.IO = commands.NewDefaultIO() + conf.IO = commands.NewTestIO() } if conf.Fetcher == nil { conf.Fetcher = rpcpkgfetcher.New(nil) @@ -54,50 +56,71 @@ func Load(conf *LoadConfig, patterns ...string) (PkgList, error) { return nil, err } + if !conf.AllowEmpty { + if len(pkgs) == 0 { + return nil, errors.New("no packages") + } + } + if !conf.Deps { return pkgs, nil } - byPkgPath := make(map[string]*Package) - index := func(pkg *Package) { - if pkg.ImportPath == "" { - return - } - if _, ok := byPkgPath[pkg.ImportPath]; ok { - return - } - byPkgPath[pkg.ImportPath] = pkg + extra, err := expandPatterns(conf, conf.DepsPatterns...) + if err != nil { + return nil, err } - for _, pkg := range pkgs { - index(pkg) + for _, m := range extra { + m.Match = []string{} } + extraPkgs, err := readPackages(extra, fset) + if err != nil { + return nil, err + } + extraMap := NewPackagesMap(extraPkgs...) + gnoroot := gnoenv.RootDir() + toVisit := pkgs + queuedByPkgPath := NewPackagesMap(pkgs...) + markForVisit := func(pkg *Package) { + queuedByPkgPath.Add(pkg) + toVisit = append(toVisit, pkg) + } + visited := map[string]struct{}{} - list := []*Package{} - pile := pkgs - pileDown := func(pkg *Package) { - index(pkg) - pile = append(pile, pkg) - } - for ; len(pile) > 0; pile = pile[1:] { - pkg := pile[0] - if _, ok := visited[pkg.ImportPath]; ok { + loaded := []*Package{} + + for { + pkg, ok := fifoNext(&toVisit) + if !ok { + break + } + + if added := setAdd(visited, pkg.Dir); !added { continue } - visited[pkg.ImportPath] = struct{}{} for _, imp := range pkg.Imports.Merge(FileKindPackageSource, FileKindTest, FileKindXTest, FileKindFiletest) { - if _, ok := byPkgPath[imp]; ok { + // check if we already queued this dep + if _, ok := queuedByPkgPath[imp]; ok { continue } + // check if we have it in config cache if cached, ok := conf.Cache[imp]; ok { - pileDown(cached) + markForVisit(cached) continue } + // check if we have it in extra deps patterns + if extra, ok := extraMap[imp]; ok { + markForVisit(extra) + continue + } + + // check if this is a stdlib and queue it if gnolang.IsStdlib(imp) { dir := filepath.Join(gnoroot, "gnovm", "stdlibs", filepath.FromSlash(imp)) finfo, err := os.Stat(dir) @@ -106,11 +129,11 @@ func Load(conf *LoadConfig, patterns ...string) (PkgList, error) { } if err != nil { pkg.Errors = append(pkg.Errors, err) - byPkgPath[imp] = nil // stop trying to get this lib, we can't + delete(queuedByPkgPath, imp) // stop trying to get this lib, we can't continue } - pileDown(readPkgDir(dir, imp, fset)) + markForVisit(readPkgDir(dir, imp, fset)) continue } @@ -118,38 +141,38 @@ func Load(conf *LoadConfig, patterns ...string) (PkgList, error) { examplePkgDir := filepath.Join(gnoroot, "examples", filepath.FromSlash(imp)) finfo, err := os.Stat(examplePkgDir) if err == nil && finfo.IsDir() { - pileDown(readPkgDir(examplePkgDir, imp, fset)) + markForVisit(readPkgDir(examplePkgDir, imp, fset)) continue } } if conf.SelfContained { pkg.Errors = append(pkg.Errors, fmt.Errorf("self-contained: package %q not found", imp)) - byPkgPath[imp] = nil // stop trying to get this lib, we can't + delete(queuedByPkgPath, imp) // stop trying to get this lib, we can't continue } dir := gnomod.PackageDir("", module.Version{Path: imp}) if err := downloadPackage(conf, imp, dir); err != nil { pkg.Errors = append(pkg.Errors, err) - byPkgPath[imp] = nil // stop trying to download pkg, we can't + delete(queuedByPkgPath, imp) // stop trying to get this lib, we can't continue } - pileDown(readPkgDir(dir, imp, fset)) + markForVisit(readPkgDir(dir, imp, fset)) } - list = append(list, pkg) + loaded = append(loaded, pkg) } - for _, pkg := range list { + for _, pkg := range loaded { // TODO: this could be optimized - pkg.Deps, err = listDeps(pkg.ImportPath, byPkgPath) + pkg.Deps, err = listDeps(pkg.ImportPath, queuedByPkgPath) if err != nil { pkg.Errors = append(pkg.Errors, err) } } - return list, nil + return loaded, nil } func listDeps(target string, pkgs map[string]*Package) ([]string, error) { @@ -200,3 +223,22 @@ func (p *Package) MemPkg() (*gnovm.MemPackage, error) { Files: files, }, nil } + +func fifoNext[T any](slice *[]T) (T, bool) { + if len(*slice) == 0 { + return *new(T), false + } + + elem := (*slice)[0] + *slice = (*slice)[1:] + return elem, true +} + +func setAdd[T comparable](set map[T]struct{}, val T) bool { + if _, ok := set[val]; ok { + return false + } + + set[val] = struct{}{} + return true +} diff --git a/gnovm/pkg/packages/pkglist.go b/gnovm/pkg/packages/pkglist.go index 529b1401251..7af85f47123 100644 --- a/gnovm/pkg/packages/pkglist.go +++ b/gnovm/pkg/packages/pkglist.go @@ -96,3 +96,30 @@ func (sp SortedPkgList) GetNonDraftPkgs() SortedPkgList { } return res } + +type PackagesMap map[string]*Package + +func NewPackagesMap(pkgs ...*Package) PackagesMap { + pm := make(PackagesMap, len(pkgs)) + pm.AddBulk(pkgs...) + return pm +} + +func (pm PackagesMap) Add(pkg *Package) bool { + if pkg.ImportPath == "" { + return false + } + + if _, ok := pm[pkg.ImportPath]; ok { + return false + } + + pm[pkg.ImportPath] = pkg + return true +} + +func (pm PackagesMap) AddBulk(pkgs ...*Package) { + for _, pkg := range pkgs { + _ = pm.Add(pkg) + } +} From 07375f82c2553fe3f3b306b7985cbf8014281e7f Mon Sep 17 00:00:00 2001 From: Norman Date: Thu, 9 Jan 2025 21:50:07 +0100 Subject: [PATCH 060/143] fix: always use relative paths for files in pkgs Signed-off-by: Norman --- gnovm/pkg/packages/analyze_packages.go | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/gnovm/pkg/packages/analyze_packages.go b/gnovm/pkg/packages/analyze_packages.go index 9ae9009d047..504b4ca3d32 100644 --- a/gnovm/pkg/packages/analyze_packages.go +++ b/gnovm/pkg/packages/analyze_packages.go @@ -57,7 +57,7 @@ func readCLAPkg(patterns []string, fset *token.FileSet) (*Package, error) { return nil, fmt.Errorf("named files must all be in one directory; have %s and %s", pkg.Dir, dir) } - files = append(files, filepath.Join(dir, base)) + files = append(files, base) } return readPkgFiles(pkg, files, fset), nil @@ -88,8 +88,7 @@ func readPkgDir(pkgDir string, importPath string, fset *token.FileSet) *Package continue } - fpath := filepath.Join(pkgDir, base) - files = append(files, fpath) + files = append(files, base) } return readPkgFiles(pkg, files, fset) @@ -102,8 +101,8 @@ func readPkgFiles(pkg *Package, files []string, fset *token.FileSet) *Package { mempkg := gnovm.MemPackage{} - for _, fpath := range files { - base := filepath.Base(fpath) + for _, base := range files { + fpath := filepath.Join(pkg.Dir, base) bodyBytes, err := os.ReadFile(fpath) if err != nil { From 95963dcb58a8f843233ebd4f52632cb87032b75d Mon Sep 17 00:00:00 2001 From: Norman Date: Thu, 9 Jan 2025 21:57:26 +0100 Subject: [PATCH 061/143] chore: revert transpile changes Signed-off-by: Norman --- .../transpile/gobuild_flag_build_error.txtar | 2 +- .../testdata/transpile/invalid_import.txtar | 2 +- .../gno/testdata/transpile/parse_error.txtar | 2 +- .../testdata/transpile/valid_empty_dir.txtar | 4 +- .../transpile/valid_output_flag.txtar | 6 +- .../transpile/valid_transpile_file.txtar | 15 ++ .../transpile/valid_transpile_package.txtar | 2 +- .../transpile/valid_transpile_tree.txtar | 4 +- gnovm/cmd/gno/transpile.go | 252 ++++++++++-------- 9 files changed, 174 insertions(+), 115 deletions(-) diff --git a/gnovm/cmd/gno/testdata/transpile/gobuild_flag_build_error.txtar b/gnovm/cmd/gno/testdata/transpile/gobuild_flag_build_error.txtar index c509ec8fc22..145fe796c09 100644 --- a/gnovm/cmd/gno/testdata/transpile/gobuild_flag_build_error.txtar +++ b/gnovm/cmd/gno/testdata/transpile/gobuild_flag_build_error.txtar @@ -1,7 +1,7 @@ # Run gno transpile with -gobuild flag # The error messages changed sometime in go1.23, so this avoids errors -! gno transpile -gobuild ./... +! gno transpile -gobuild . ! stdout .+ stderr '^main.gno:4:6: .*declared and not used' diff --git a/gnovm/cmd/gno/testdata/transpile/invalid_import.txtar b/gnovm/cmd/gno/testdata/transpile/invalid_import.txtar index 5c1b715ddf7..0c51012feb7 100644 --- a/gnovm/cmd/gno/testdata/transpile/invalid_import.txtar +++ b/gnovm/cmd/gno/testdata/transpile/invalid_import.txtar @@ -1,6 +1,6 @@ # Run gno transpile with gno files with an invalid import path -! gno transpile ./... +! gno transpile . ! stdout .+ stderr '^main.gno:5:2: import "xxx" does not exist$' diff --git a/gnovm/cmd/gno/testdata/transpile/parse_error.txtar b/gnovm/cmd/gno/testdata/transpile/parse_error.txtar index d9c2c6b313f..b94b86992af 100644 --- a/gnovm/cmd/gno/testdata/transpile/parse_error.txtar +++ b/gnovm/cmd/gno/testdata/transpile/parse_error.txtar @@ -1,6 +1,6 @@ # Run gno transpile with gno files with parse errors -! gno transpile ./... +! gno transpile . ! stdout .+ stderr '^main.gno:3:1: expected declaration, found invalid$' diff --git a/gnovm/cmd/gno/testdata/transpile/valid_empty_dir.txtar b/gnovm/cmd/gno/testdata/transpile/valid_empty_dir.txtar index 9106de41622..2bd1841d2b4 100644 --- a/gnovm/cmd/gno/testdata/transpile/valid_empty_dir.txtar +++ b/gnovm/cmd/gno/testdata/transpile/valid_empty_dir.txtar @@ -1,6 +1,6 @@ # Run gno transpile on an empty dir -! gno transpile . +gno transpile . ! stdout .+ -stderr 'no Gno files in ' +! stderr .+ diff --git a/gnovm/cmd/gno/testdata/transpile/valid_output_flag.txtar b/gnovm/cmd/gno/testdata/transpile/valid_output_flag.txtar index 4d285b83561..b1a63890f46 100644 --- a/gnovm/cmd/gno/testdata/transpile/valid_output_flag.txtar +++ b/gnovm/cmd/gno/testdata/transpile/valid_output_flag.txtar @@ -13,8 +13,8 @@ gno transpile -v -output directory/hello $WORK ! stdout .+ cmpenv stderr stderr2.golden -! exists directory/hello$WORK/main.gno.gen.go -exists directory/hello/main.gno.gen.go +exists directory/hello$WORK/main.gno.gen.go +! exists directory/hello/main.gno.gen.go rm directory # Try running in subdirectory, using a "relative non-local path." (ie. has "../") @@ -30,7 +30,7 @@ exists hello$WORK/main.gno.gen.go -- stderr1.golden -- . -- stderr2.golden -- -. +$WORK -- stderr3.golden -- .. -- main.gno -- diff --git a/gnovm/cmd/gno/testdata/transpile/valid_transpile_file.txtar b/gnovm/cmd/gno/testdata/transpile/valid_transpile_file.txtar index ecf7d323ffe..86cc6f12f7a 100644 --- a/gnovm/cmd/gno/testdata/transpile/valid_transpile_file.txtar +++ b/gnovm/cmd/gno/testdata/transpile/valid_transpile_file.txtar @@ -18,9 +18,11 @@ gno transpile -v main.gno hello_test.gno cmp stderr transpile-files-stderr.golden cmp main.gno.gen.go main.gno.gen.go.golden +cmp .hello_test.gno.gen_test.go .hello_test.gno.gen_test.go.golden -- transpile-files-stderr.golden -- main.gno +hello_test.gno -- main.gno -- package main @@ -48,3 +50,16 @@ package main func main() { println("hello") } +-- .hello_test.gno.gen_test.go.golden -- +// Code generated by github.com/gnolang/gno. DO NOT EDIT. + +//go:build gno && test + +//line hello_test.gno:1:1 +package main + +import "github.com/gnolang/gno/gnovm/stdlibs/std" + +func hello() { + std.AssertOriginCall(nil) +} diff --git a/gnovm/cmd/gno/testdata/transpile/valid_transpile_package.txtar b/gnovm/cmd/gno/testdata/transpile/valid_transpile_package.txtar index fa0b191d4da..2a24423598a 100644 --- a/gnovm/cmd/gno/testdata/transpile/valid_transpile_package.txtar +++ b/gnovm/cmd/gno/testdata/transpile/valid_transpile_package.txtar @@ -1,6 +1,6 @@ # Run gno transpile with valid gno files -gno transpile ./... +gno transpile . ! stdout .+ ! stderr .+ diff --git a/gnovm/cmd/gno/testdata/transpile/valid_transpile_tree.txtar b/gnovm/cmd/gno/testdata/transpile/valid_transpile_tree.txtar index d8073d9c67d..a765ab5093b 100644 --- a/gnovm/cmd/gno/testdata/transpile/valid_transpile_tree.txtar +++ b/gnovm/cmd/gno/testdata/transpile/valid_transpile_tree.txtar @@ -1,7 +1,7 @@ # Run gno transpile with dependencies env GNOROOT=$WORK -gno transpile -v ./examples/... +gno transpile -v ./examples ! stdout .+ cmpenv stderr stderr.golden @@ -13,7 +13,7 @@ cmp gnovm/stdlibs/math/math.gno.gen.go gnovm/stdlibs/math/math.gno.gen.go.golden -- stderr.golden -- examples/gno.land/r/answer -gnovm/stdlibs/math +$WORK/gnovm/stdlibs/math examples/gno.land/r/question (skipped, gno.mod marks module as draft) -- examples/gno.land/r/question/gno.mod -- // Draft diff --git a/gnovm/cmd/gno/transpile.go b/gnovm/cmd/gno/transpile.go index 56648355011..1eb60418195 100644 --- a/gnovm/cmd/gno/transpile.go +++ b/gnovm/cmd/gno/transpile.go @@ -5,8 +5,10 @@ import ( "errors" "flag" "fmt" + "go/ast" "go/scanner" "go/token" + "io/fs" "os" "os/exec" "path/filepath" @@ -15,9 +17,8 @@ import ( "strconv" "strings" - "github.com/davecgh/go-spew/spew" "github.com/gnolang/gno/gnovm/pkg/gnoenv" - "github.com/gnolang/gno/gnovm/pkg/packages" + "github.com/gnolang/gno/gnovm/pkg/gnomod" "github.com/gnolang/gno/gnovm/pkg/transpiler" "github.com/gnolang/gno/tm2/pkg/commands" ) @@ -54,8 +55,8 @@ func (p *transpileOptions) getFlags() *transpileCfg { return p.cfg } -func (p *transpileOptions) isTranspiled(path string) bool { - _, transpiled := p.transpiled[path] +func (p *transpileOptions) isTranspiled(pkg string) bool { + _, transpiled := p.transpiled[pkg] return transpiled } @@ -123,23 +124,6 @@ func (c *transpileCfg) RegisterFlags(fs *flag.FlagSet) { ) } -type transpileTarget struct { - subPath string - pkg *packages.Package -} - -// REARCH: -// - only allow files in the cwd or remote pkgs as params -// - find output subpath -// - if file belongs to an identifiable pkg (pkg that has an import path) -> ./some.pkg/path/filename -// - else -> ./path/to/file -// - if no-build -> transpile to output and exit -// - else -> transpile to tmpdir -// - create go.work in tmpdir including all packages -// - create go.mod in tmpdir for pkg-less sources -// - build -// - copy to output without go.work and go.mod - func execTranspile(cfg *transpileCfg, args []string, io commands.IO) error { if len(args) < 1 { return flag.ErrHelp @@ -150,77 +134,54 @@ func execTranspile(cfg *transpileCfg, args []string, io commands.IO) error { cfg.rootDir = gnoenv.RootDir() } - // load packages - conf := &packages.LoadConfig{IO: io, Fetcher: testPackageFetcher, Deps: true, DepsPatterns: []string{"./..."}} - pkgs, err := packages.Load(conf, args...) + // transpile .gno packages and files. + paths, err := gnoFilesFromArgsRecursively(args) if err != nil { - return err - } - - // find subpaths - targets := []transpileTarget{} - for _, pkg := range pkgs { - if pkg.ImportPath == "" || pkg.ImportPath == "command-line-arguments" { - rel, err := relativize(pkg.Dir) - if err != nil { - return fmt.Errorf("%s: can't relativize: %w", pkg.Dir, err) - } - if !filepath.IsLocal(rel) { - return fmt.Errorf("%s: is not a local path", rel) - } - targets = append(targets, transpileTarget{ - subPath: rel, - pkg: pkg, - }) - continue - } - - targets = append(targets, transpileTarget{ - subPath: filepath.FromSlash(pkg.ImportPath), - }) + return fmt.Errorf("list paths: %w", err) } - fmt.Println("targets", targets) - - pkgsMap := map[string]*packages.Package{} - packages.Inject(pkgsMap, pkgs) - - // spew.Dump(pkgs) - - // transpile .gno packages and files. opts := newTranspileOptions(cfg, io) var errlist scanner.ErrorList - for _, pkg := range pkgs { - spew.Dump(pkg) - - if !pkg.Draft && pkg.Files.Size() == 0 { - return fmt.Errorf("no Gno files in %s", tryRelativize(pkg.Dir)) + for _, path := range paths { + st, err := os.Stat(path) + if err != nil { + return err } + if st.IsDir() { + err = transpilePkg(path, opts) + } else { + if opts.cfg.verbose { + io.ErrPrintln(filepath.Clean(path)) + } - if err := transpilePkg(pkg, pkgsMap, opts); err != nil { + err = transpileFile(path, opts) + } + if err != nil { var fileErrlist scanner.ErrorList if !errors.As(err, &fileErrlist) { // Not an scanner.ErrorList: return immediately. - return fmt.Errorf("%s: transpile: %w", pkg.ImportPath, err) + return fmt.Errorf("%s: transpile: %w", path, err) } errlist = append(errlist, fileErrlist...) } } - dumpDir(".") - dumpDir("./directory/hello") - if errlist.Len() == 0 && cfg.gobuild { - for _, pkg := range pkgs { - if slices.Contains(opts.skipped, pkg.Dir) { + for _, pkgPath := range paths { + if slices.Contains(opts.skipped, pkgPath) { continue } - err := goBuildFileOrPkg(io, pkg.Dir, cfg) + if cfg.output != "." { + if pkgPath, err = ResolvePath(cfg.output, pkgPath); err != nil { + return fmt.Errorf("resolve output path: %w", err) + } + } + err := goBuildFileOrPkg(io, pkgPath, cfg) if err != nil { var fileErrlist scanner.ErrorList if !errors.As(err, &fileErrlist) { // Not an scanner.ErrorList: return immediately. - return fmt.Errorf("%s: build: %w", pkg.ImportPath, err) + return fmt.Errorf("%s: build: %w", pkgPath, err) } errlist = append(errlist, fileErrlist...) } @@ -229,7 +190,7 @@ func execTranspile(cfg *transpileCfg, args []string, io commands.IO) error { if errlist.Len() > 0 { for _, err := range errlist { - io.ErrPrintfln(tryRelativize(err.Error())) + io.ErrPrintfln(err.Error()) } return fmt.Errorf("%d transpile error(s)", errlist.Len()) } @@ -239,16 +200,19 @@ func execTranspile(cfg *transpileCfg, args []string, io commands.IO) error { // transpilePkg transpiles all non-test files at the given location. // Additionally, it checks the gno.mod in said location, and skips it if it is // a draft module -func transpilePkg(pkg *packages.Package, pkgs map[string]*packages.Package, opts *transpileOptions) error { - dirPath := pkg.Dir +func transpilePkg(dirPath string, opts *transpileOptions) error { if opts.isTranspiled(dirPath) { return nil } opts.markAsTranspiled(dirPath) - if pkg.Draft { + gmod, err := gnomod.ParseAt(dirPath) + if err != nil && !errors.Is(err, gnomod.ErrGnoModNotFound) { + return err + } + if err == nil && gmod.Draft { if opts.cfg.verbose { - opts.io.ErrPrintfln("%s (skipped, gno.mod marks module as draft)", tryRelativize(filepath.Clean(dirPath))) + opts.io.ErrPrintfln("%s (skipped, gno.mod marks module as draft)", filepath.Clean(dirPath)) } opts.skipped = append(opts.skipped, dirPath) return nil @@ -258,17 +222,16 @@ func transpilePkg(pkg *packages.Package, pkgs map[string]*packages.Package, opts // The transpiler doesn't currently support "test stdlibs", and even if it // did all packages like "fmt" would have to exist as standard libraries to work. // Easier to skip for now. - if opts.cfg.verbose && pkg.ImportPath != "command-line-arguments" { - label := tryRelativize(dirPath) - opts.io.ErrPrintln(filepath.Clean(label)) + files, err := listNonTestFiles(dirPath) + if err != nil { + return err } - for _, file := range pkg.Files[packages.FileKindPackageSource] { - fpath := filepath.Join(pkg.Dir, file) - if opts.cfg.verbose && pkg.ImportPath == "command-line-arguments" { - label := tryRelativize(fpath) - opts.io.ErrPrintln(filepath.Clean(label)) - } - if err := transpileFile(fpath, pkgs, opts); err != nil { + + if opts.cfg.verbose { + opts.io.ErrPrintln(filepath.Clean(dirPath)) + } + for _, file := range files { + if err := transpileFile(file, opts); err != nil { return fmt.Errorf("%s: %w", file, err) } } @@ -276,7 +239,7 @@ func transpilePkg(pkg *packages.Package, pkgs map[string]*packages.Package, opts return nil } -func transpileFile(srcPath string, pkgs map[string]*packages.Package, opts *transpileOptions) error { +func transpileFile(srcPath string, opts *transpileOptions) error { flags := opts.getFlags() // parse .gno. @@ -315,18 +278,12 @@ func transpileFile(srcPath string, pkgs map[string]*packages.Package, opts *tran // transpile imported packages, if `SkipImports` sets to false if !flags.skipImports && !strings.HasSuffix(srcPath, "_filetest.gno") && !strings.HasSuffix(srcPath, "_test.gno") { - for _, imp := range transpileRes.Imports { - pkgPath, err := strconv.Unquote(imp.Path.Value) - if err != nil { - return fmt.Errorf("failed to unquote pkg path %v", imp.Path.Value) - } - pkgPath = strings.TrimPrefix(pkgPath, "github.com/gnolang/gno/gnovm/stdlibs/") - pkgPath = strings.TrimPrefix(pkgPath, "github.com/gnolang/gno/examples/") - dep := pkgs[pkgPath] - if dep == nil { - return fmt.Errorf("failed to find matching package for %q", pkgPath) - } - if err := transpilePkg(dep, pkgs, opts); err != nil { + dirPaths, err := getPathsFromImportSpec(opts.cfg.rootDir, transpileRes.Imports) + if err != nil { + return err + } + for _, path := range dirPaths { + if err := transpilePkg(path, opts); err != nil { return err } } @@ -343,14 +300,31 @@ func goBuildFileOrPkg(io commands.IO, fileOrPkg string, cfg *transpileCfg) error io.ErrPrintfln("%s [build]", filepath.Clean(fileOrPkg)) } - return buildTranspiledPackage(fileOrPkg, goBinary, cfg) + return buildTranspiledPackage(fileOrPkg, goBinary) +} + +// getPathsFromImportSpec returns the directory paths where the code for each +// importSpec is stored (assuming they start with [transpiler.ImportPrefix]). +func getPathsFromImportSpec(rootDir string, importSpec []*ast.ImportSpec) (dirs []string, err error) { + for _, i := range importSpec { + path, err := strconv.Unquote(i.Path.Value) + if err != nil { + return nil, err + } + if strings.HasPrefix(path, transpiler.ImportPrefix) { + res := strings.TrimPrefix(path, transpiler.ImportPrefix) + + dirs = append(dirs, rootDir+filepath.FromSlash(res)) + } + } + return } // buildTranspiledPackage tries to run `go build` against the transpiled .go files. // // This method is the most efficient to detect errors but requires that // all the import are valid and available. -func buildTranspiledPackage(fileOrPkg, goBinary string, cfg *transpileCfg) error { +func buildTranspiledPackage(fileOrPkg, goBinary string) error { // TODO: use cmd/compile instead of exec? // TODO: find the nearest go.mod file, chdir in the same folder, trim prefix? // TODO: temporarily create an in-memory go.mod or disable go modules for gno? @@ -438,10 +412,80 @@ func parseGoBuildErrors(out string) error { return errList.Err() } -func dumpDir(dir string) { - entries, err := os.ReadDir(dir) +func gnoFilesFromArgsRecursively(args []string) ([]string, error) { + var paths []string + + for _, argPath := range args { + info, err := os.Stat(argPath) + if err != nil { + return nil, fmt.Errorf("invalid file or package path: %w", err) + } + + if !info.IsDir() { + if isGnoFile(fs.FileInfoToDirEntry(info)) { + paths = append(paths, ensurePathPrefix(argPath)) + } + + continue + } + + // Gather package paths from the directory + err = walkDirForGnoFiles(argPath, func(path string) { + paths = append(paths, ensurePathPrefix(path)) + }) + if err != nil { + return nil, fmt.Errorf("unable to walk dir: %w", err) + } + } + + return paths, nil +} + +func listNonTestFiles(dir string) ([]string, error) { + fs, err := os.ReadDir(dir) if err != nil { - fmt.Println("dumpDir error:", err) + return nil, err + } + fn := make([]string, 0, len(fs)) + for _, f := range fs { + n := f.Name() + if isGnoFile(f) && + !strings.HasSuffix(n, "_test.gno") && + !strings.HasSuffix(n, "_filetest.gno") { + fn = append(fn, filepath.Join(dir, n)) + } + } + return fn, nil +} + +func walkDirForGnoFiles(root string, addPath func(path string)) error { + visited := make(map[string]struct{}) + + walkFn := func(currPath string, f fs.DirEntry, err error) error { + if err != nil { + return fmt.Errorf("%s: walk dir: %w", root, err) + } + + if f.IsDir() || !isGnoFile(f) { + return nil + } + + parentDir := filepath.Dir(currPath) + if _, found := visited[parentDir]; found { + return nil + } + + visited[parentDir] = struct{}{} + + addPath(parentDir) + + return nil } - spew.Dump(entries) + + return filepath.WalkDir(root, walkFn) +} + +func isGnoFile(f fs.DirEntry) bool { + name := f.Name() + return !strings.HasPrefix(name, ".") && strings.HasSuffix(name, ".gno") && !f.IsDir() } From 1d67048a061eb411f1150e18470ed6c2b2a0d99e Mon Sep 17 00:00:00 2001 From: Norman Date: Fri, 10 Jan 2025 01:02:12 +0100 Subject: [PATCH 062/143] fix: sort Signed-off-by: Norman --- gnovm/cmd/gno/transpile.go | 5 ----- gnovm/cmd/gno/util.go | 19 ++++++++++--------- gnovm/pkg/packages/filekind_test.go | 12 ++++++------ gnovm/pkg/packages/load_test.go | 3 ++- gnovm/pkg/packages/pkglist.go | 9 --------- 5 files changed, 18 insertions(+), 30 deletions(-) diff --git a/gnovm/cmd/gno/transpile.go b/gnovm/cmd/gno/transpile.go index 1eb60418195..d43f31e3846 100644 --- a/gnovm/cmd/gno/transpile.go +++ b/gnovm/cmd/gno/transpile.go @@ -484,8 +484,3 @@ func walkDirForGnoFiles(root string, addPath func(path string)) error { return filepath.WalkDir(root, walkFn) } - -func isGnoFile(f fs.DirEntry) bool { - name := f.Name() - return !strings.HasPrefix(name, ".") && strings.HasSuffix(name, ".gno") && !f.IsDir() -} diff --git a/gnovm/cmd/gno/util.go b/gnovm/cmd/gno/util.go index fbddd1ffa34..87f37f14bb7 100644 --- a/gnovm/cmd/gno/util.go +++ b/gnovm/cmd/gno/util.go @@ -9,10 +9,13 @@ import ( "regexp" "strings" "time" - - "github.com/gnolang/gno/gnovm/pkg/gnofiles" ) +func isGnoFile(f fs.DirEntry) bool { + name := f.Name() + return !strings.HasPrefix(name, ".") && strings.HasSuffix(name, ".gno") && !f.IsDir() +} + func isFileExist(path string) bool { _, err := os.Stat(path) return err == nil @@ -28,7 +31,7 @@ func gnoFilesFromArgs(args []string) ([]string, error) { } if !info.IsDir() { - if gnofiles.IsGnoFile(info.Name()) { + if isGnoFile(fs.FileInfoToDirEntry(info)) { paths = append(paths, ensurePathPrefix(argPath)) } continue @@ -39,7 +42,7 @@ func gnoFilesFromArgs(args []string) ([]string, error) { return nil, err } for _, f := range files { - if gnofiles.IsGnoFile(f.Name()) { + if isGnoFile(f) { path := filepath.Join(argPath, f.Name()) paths = append(paths, ensurePathPrefix(path)) } @@ -101,7 +104,7 @@ func targetsFromPatterns(patterns []string) ([]string, error) { return fmt.Errorf("%s: walk dir: %w", dirToSearch, err) } // Skip directories and non ".gno" files. - if f.IsDir() || !gnofiles.IsGnoFile(f.Name()) { + if f.IsDir() || !isGnoFile(f) { return nil } @@ -158,10 +161,8 @@ func fmtDuration(d time.Duration) string { // // See related test for examples. func ResolvePath(output, dstPath string) (string, error) { - if filepath.IsAbs(dstPath) { - dstPath = tryRelativize(dstPath) - } - if filepath.IsLocal(dstPath) { + if filepath.IsAbs(dstPath) || + filepath.IsLocal(dstPath) { return filepath.Join(output, dstPath), nil } // Make dstPath absolute and join it with output. diff --git a/gnovm/pkg/packages/filekind_test.go b/gnovm/pkg/packages/filekind_test.go index bd06b49fb45..735ecd64273 100644 --- a/gnovm/pkg/packages/filekind_test.go +++ b/gnovm/pkg/packages/filekind_test.go @@ -25,6 +25,12 @@ func TestGetFileKind(t *testing.T) { body: "package foo", fileKind: FileKindTest, }, + { + name: "test_badpkgclause", + filename: "foo_test.gno", + body: "pakage foo", + fileKind: FileKindTest, + }, { name: "xtest", filename: "foo_test.gno", @@ -36,12 +42,6 @@ func TestGetFileKind(t *testing.T) { filename: "foo_filetest.gno", fileKind: FileKindFiletest, }, - { - name: "err_badpkgclause", - filename: "foo_test.gno", - body: "pakage foo", - errorContains: "foo_test.gno:1:1: expected 'package', found pakage", - }, { name: "err_notgnofile", filename: "foo.gno.bck", diff --git a/gnovm/pkg/packages/load_test.go b/gnovm/pkg/packages/load_test.go index c70ae3b1778..72bcdd1d6c9 100644 --- a/gnovm/pkg/packages/load_test.go +++ b/gnovm/pkg/packages/load_test.go @@ -5,6 +5,7 @@ import ( "path/filepath" "testing" + "github.com/gnolang/gno/gnovm/pkg/packages/pkgdownload/examplespkgfetcher" "github.com/gnolang/gno/tm2/pkg/testutils" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -74,7 +75,7 @@ func TestListAndNonDraftPkgs(t *testing.T) { } // List packages - pkgs, err := Load(&LoadConfig{}, filepath.Join(dirPath, "...")) + pkgs, err := Load(&LoadConfig{AllowEmpty: true, Fetcher: examplespkgfetcher.New()}, filepath.Join(dirPath, "...")) require.NoError(t, err) assert.Equal(t, len(tc.outListPkgs), len(pkgs)) for _, p := range pkgs { diff --git a/gnovm/pkg/packages/pkglist.go b/gnovm/pkg/packages/pkglist.go index 7af85f47123..30cada01638 100644 --- a/gnovm/pkg/packages/pkglist.go +++ b/gnovm/pkg/packages/pkglist.go @@ -2,9 +2,6 @@ package packages import ( "fmt" - "slices" - - "github.com/gnolang/gno/gnovm/pkg/gnolang" ) type ( @@ -44,12 +41,6 @@ func visitPackage(pkg *Package, pkgs []*Package, visited, onStack map[string]boo // Visit package's dependencies for _, imp := range pkg.Imports.Merge(FileKindPackageSource) { - if gnolang.IsStdlib(imp) { - continue - } - if slices.Contains(injectedTestingLibs, imp) { - continue - } found := false for _, p := range pkgs { if p.ImportPath != imp { From c0eacfbfd29b815818f690d307aae082ae1b4e89 Mon Sep 17 00:00:00 2001 From: Norman Date: Fri, 10 Jan 2025 01:25:17 +0100 Subject: [PATCH 063/143] chore: fix debugger Signed-off-by: Norman --- gnovm/pkg/gnolang/debugger_test.go | 24 +++++++++++++++--------- gnovm/pkg/gnolang/files_test.go | 6 +++++- gnovm/pkg/packages/imports_test.go | 3 ++- gnovm/pkg/packages/pkglist.go | 2 -- 4 files changed, 22 insertions(+), 13 deletions(-) diff --git a/gnovm/pkg/gnolang/debugger_test.go b/gnovm/pkg/gnolang/debugger_test.go index a0984b1418a..9c1a5c193ad 100644 --- a/gnovm/pkg/gnolang/debugger_test.go +++ b/gnovm/pkg/gnolang/debugger_test.go @@ -6,6 +6,7 @@ import ( "fmt" "io" "net" + "path/filepath" "strings" "testing" "time" @@ -14,6 +15,7 @@ import ( "github.com/gnolang/gno/gnovm/pkg/gnolang" "github.com/gnolang/gno/gnovm/pkg/packages" "github.com/gnolang/gno/gnovm/pkg/test" + "github.com/stretchr/testify/require" ) type dtest struct{ in, out string } @@ -25,7 +27,7 @@ type writeNopCloser struct{ io.Writer } func (writeNopCloser) Close() error { return nil } // TODO (Marc): move evalTest to gnovm/tests package and remove code duplicates -func evalTest(debugAddr, in, file string) (out, err string) { +func evalTest(debugAddr, in, file string, pkgs packages.PackagesMap) (out, err string) { bout := bytes.NewBufferString("") berr := bytes.NewBufferString("") stdin := bytes.NewBufferString(in) @@ -40,7 +42,7 @@ func evalTest(debugAddr, in, file string) (out, err string) { err = strings.TrimSpace(strings.ReplaceAll(err, "../../tests/files/", "files/")) }() - _, testStore := test.Store(gnoenv.RootDir(), map[string]*packages.Package{}, false, stdin, stdout, stderr) + _, testStore := test.Store(gnoenv.RootDir(), pkgs, false, stdin, stdout, stderr) f := gnolang.MustReadFile(file) @@ -69,12 +71,12 @@ func evalTest(debugAddr, in, file string) (out, err string) { return } -func runDebugTest(t *testing.T, targetPath string, tests []dtest) { +func runDebugTest(t *testing.T, targetPath string, tests []dtest, pkgs packages.PackagesMap) { t.Helper() for _, test := range tests { t.Run("", func(t *testing.T) { - out, err := evalTest("", test.in, targetPath) + out, err := evalTest("", test.in, targetPath, pkgs) t.Log("in:", test.in, "out:", out, "err:", err) if !strings.Contains(out, test.out) { t.Errorf("unexpected output\nwant\"%s\"\n got \"%s\"", test.out, out) @@ -88,6 +90,10 @@ func TestDebug(t *testing.T) { cont := brk + "continue\n" cont2 := "break 21\ncontinue\n" + pkgs, err := packages.Load(&packages.LoadConfig{}, filepath.FromSlash("../../../examples/...")) + require.NoError(t, err) + pkgsMap := packages.NewPackagesMap(pkgs...) + runDebugTest(t, debugTarget, []dtest{ {in: "\n", out: "Welcome to the Gnovm debugger. Type 'help' for list of commands."}, {in: "help\n", out: "The following commands are available"}, @@ -144,18 +150,18 @@ func TestDebug(t *testing.T) { {in: "b 27\nc\np b\n", out: `("!zero" string)`}, {in: "b 22\nc\np t.A[3]\n", out: "Command failed: &{(\"slice index out of bounds: 3 (len=3)\" string) }"}, {in: "b 43\nc\nc\nc\np i\ndetach\n", out: "(1 int)"}, - }) + }, pkgsMap) runDebugTest(t, "../../tests/files/a1.gno", []dtest{ {in: "l\n", out: "unknown source file"}, {in: "b 5\n", out: "unknown source file"}, - }) + }, pkgsMap) runDebugTest(t, "../../tests/integ/debugger/sample2.gno", []dtest{ {in: "s\np tests\n", out: "(package(tests gno.land/p/demo/tests) package{})"}, {in: "s\np tests.World\n", out: `("world" string)`}, {in: "s\np tests.xxx\n", out: "Command failed: invalid selector: xxx"}, - }) + }, pkgsMap) } const debugAddress = "localhost:17358" @@ -167,7 +173,7 @@ func TestRemoteDebug(t *testing.T) { retry int ) - go evalTest(debugAddress, "", debugTarget) + go evalTest(debugAddress, "", debugTarget, nil) for retry = 100; retry > 0; retry-- { conn, err = net.Dial("tcp", debugAddress) @@ -190,7 +196,7 @@ func TestRemoteDebug(t *testing.T) { } func TestRemoteError(t *testing.T) { - _, err := evalTest(":xxx", "", debugTarget) + _, err := evalTest(":xxx", "", debugTarget, nil) t.Log("err:", err) if !strings.Contains(err, "tcp/xxx: unknown port") && !strings.Contains(err, "tcp/xxx: nodename nor servname provided, or not known") { diff --git a/gnovm/pkg/gnolang/files_test.go b/gnovm/pkg/gnolang/files_test.go index 214df15bd4d..f6d3a9480b7 100644 --- a/gnovm/pkg/gnolang/files_test.go +++ b/gnovm/pkg/gnolang/files_test.go @@ -39,6 +39,10 @@ func TestFiles(t *testing.T) { rootDir, err := filepath.Abs("../../../") require.NoError(t, err) + pkgs, err := packages.Load(&packages.LoadConfig{}, filepath.Join(rootDir, "examples", "....")) + require.NoError(t, err) + pkgsMap := packages.NewPackagesMap(pkgs...) + newOpts := func() *test.TestOptions { o := &test.TestOptions{ RootDir: rootDir, @@ -47,7 +51,7 @@ func TestFiles(t *testing.T) { Sync: *withSync, } o.BaseStore, o.TestStore = test.Store( - rootDir, make(map[string]*packages.Package), true, + rootDir, pkgsMap, true, nopReader{}, o.WriterForStore(), io.Discard, ) return o diff --git a/gnovm/pkg/packages/imports_test.go b/gnovm/pkg/packages/imports_test.go index 9a396d1ac40..efc1640ec93 100644 --- a/gnovm/pkg/packages/imports_test.go +++ b/gnovm/pkg/packages/imports_test.go @@ -7,6 +7,7 @@ import ( "testing" "github.com/gnolang/gno/gnovm/pkg/gnolang" + "github.com/gnolang/gno/gnovm/pkg/packages" . "github.com/gnolang/gno/gnovm/pkg/packages" "github.com/stretchr/testify/require" ) @@ -114,7 +115,7 @@ func TestImports(t *testing.T) { // - ignore subdirs // - ignore duplicate // - should be sorted - expected := map[FileKind][]string{ + expected := packages.ImportsMap{ FileKindPackageSource: { "gno.land/p/demo/pkg1", "gno.land/p/demo/pkg2", diff --git a/gnovm/pkg/packages/pkglist.go b/gnovm/pkg/packages/pkglist.go index 30cada01638..a0c0106c20b 100644 --- a/gnovm/pkg/packages/pkglist.go +++ b/gnovm/pkg/packages/pkglist.go @@ -25,8 +25,6 @@ func (pl PkgList) Sort() (SortedPkgList, error) { return sortedPkgs, nil } -var injectedTestingLibs = []string{"encoding/json", "fmt", "os", "crypto/sha256"} - // visitNode visits a package's and its dependencies dependencies and adds them to the sorted list. func visitPackage(pkg *Package, pkgs []*Package, visited, onStack map[string]bool, sortedPkgs *[]*Package) error { if onStack[pkg.ImportPath] { From 740e3e30e680ebc47dc3f19c24722da482d17b0f Mon Sep 17 00:00:00 2001 From: Norman Date: Fri, 10 Jan 2025 04:16:13 +0100 Subject: [PATCH 064/143] feat: detect stdlibs Signed-off-by: Norman --- gno.land/pkg/integration/pkgloader.go | 4 +++- gnovm/cmd/gno/lint.go | 3 ++- gnovm/pkg/packages/analyze_packages.go | 25 +++++++++++++++++++++++++ gnovm/pkg/packages/load.go | 20 +++++--------------- 4 files changed, 35 insertions(+), 17 deletions(-) diff --git a/gno.land/pkg/integration/pkgloader.go b/gno.land/pkg/integration/pkgloader.go index 21995bbf76c..f5929201d00 100644 --- a/gno.land/pkg/integration/pkgloader.go +++ b/gno.land/pkg/integration/pkgloader.go @@ -7,6 +7,7 @@ import ( "github.com/gnolang/gno/gno.land/pkg/gnoland" "github.com/gnolang/gno/gno.land/pkg/sdk/vm" + "github.com/gnolang/gno/gnovm/pkg/gnoenv" "github.com/gnolang/gno/gnovm/pkg/gnolang" "github.com/gnolang/gno/gnovm/pkg/packages" "github.com/gnolang/gno/tm2/pkg/crypto" @@ -102,7 +103,8 @@ func (pl *PkgsLoader) LoadAllPackagesFromDir(path string) error { } func (pl *PkgsLoader) LoadPackage(pkgDir string, name string) error { - cfg := &packages.LoadConfig{Deps: true, SelfContained: true, GnorootExamples: true} + examples := filepath.Join(gnoenv.RootDir(), "examples", "...") + cfg := &packages.LoadConfig{Deps: true, SelfContained: true, DepsPatterns: []string{examples}} pkgs, err := packages.Load(cfg, pkgDir) if err != nil { return fmt.Errorf("%q: loading: %w", pkgDir, err) diff --git a/gnovm/cmd/gno/lint.go b/gnovm/cmd/gno/lint.go index 2c3388eb18f..16c7d552c45 100644 --- a/gnovm/cmd/gno/lint.go +++ b/gnovm/cmd/gno/lint.go @@ -95,7 +95,8 @@ func execLint(cfg *lintCfg, args []string, io commands.IO) error { loadCfg := &packages.LoadConfig{IO: io, Fetcher: testPackageFetcher} if cfg.rootExamples { - loadCfg.GnorootExamples = true + examples := filepath.Join(gnoenv.RootDir(), "examples", "...") + loadCfg.DepsPatterns = append(loadCfg.DepsPatterns, examples) } pkgs, err := packages.Load(loadCfg, args...) diff --git a/gnovm/pkg/packages/analyze_packages.go b/gnovm/pkg/packages/analyze_packages.go index 504b4ca3d32..e30e93865e9 100644 --- a/gnovm/pkg/packages/analyze_packages.go +++ b/gnovm/pkg/packages/analyze_packages.go @@ -7,9 +7,11 @@ import ( "os" "path" "path/filepath" + "slices" "strings" "github.com/gnolang/gno/gnovm" + "github.com/gnolang/gno/gnovm/pkg/gnoenv" "github.com/gnolang/gno/gnovm/pkg/gnolang" "github.com/gnolang/gno/gnovm/pkg/gnomod" ) @@ -71,6 +73,16 @@ func readPkgDir(pkgDir string, importPath string, fset *token.FileSet) *Package ImportPath: importPath, } + stdlibsPath := filepath.Join(gnoenv.RootDir(), "gnovm", "stdlibs") + if strings.HasPrefix(filepath.Clean(pkg.Dir), stdlibsPath) { + libPath, err := filepath.Rel(stdlibsPath, pkg.Dir) + if err != nil { + pkg.Errors = append(pkg.Errors, err) + return pkg + } + pkg.ImportPath = libPath + } + files := []string{} entries, err := os.ReadDir(pkgDir) if err != nil { @@ -178,3 +190,16 @@ func readPkgFiles(pkg *Package, files []string, fset *token.FileSet) *Package { return pkg } + +// Contains reports whether v is present in s. +func sliceContainsSequence[S ~[]E, E comparable](s S, seq ...E) bool { + if len(seq) == 0 || len(s) < len(seq) { + return false + } + for i := 0; i < len(s)-len(seq); i++ { + if slices.Equal(s[i:i+len(seq)], seq) { + return true + } + } + return false +} diff --git a/gnovm/pkg/packages/load.go b/gnovm/pkg/packages/load.go index 567bad895a6..5a8f062089a 100644 --- a/gnovm/pkg/packages/load.go +++ b/gnovm/pkg/packages/load.go @@ -25,8 +25,6 @@ type LoadConfig struct { SelfContained bool AllowEmpty bool DepsPatterns []string - - GnorootExamples bool // allow loading packages from gnoroot examples if not found in the args set } func (conf *LoadConfig) applyDefaults() { @@ -123,9 +121,9 @@ func Load(conf *LoadConfig, patterns ...string) (PkgList, error) { // check if this is a stdlib and queue it if gnolang.IsStdlib(imp) { dir := filepath.Join(gnoroot, "gnovm", "stdlibs", filepath.FromSlash(imp)) - finfo, err := os.Stat(dir) - if err == nil && !finfo.IsDir() { - err = fmt.Errorf("stdlib %q not found", imp) + dirInfo, err := os.Stat(dir) + if err == nil && !dirInfo.IsDir() { + err = fmt.Errorf("%q is not a directory", dir) } if err != nil { pkg.Errors = append(pkg.Errors, err) @@ -133,19 +131,11 @@ func Load(conf *LoadConfig, patterns ...string) (PkgList, error) { continue } - markForVisit(readPkgDir(dir, imp, fset)) + pkg := readPkgDir(dir, imp, fset) + markForVisit(pkg) continue } - if conf.GnorootExamples { - examplePkgDir := filepath.Join(gnoroot, "examples", filepath.FromSlash(imp)) - finfo, err := os.Stat(examplePkgDir) - if err == nil && finfo.IsDir() { - markForVisit(readPkgDir(examplePkgDir, imp, fset)) - continue - } - } - if conf.SelfContained { pkg.Errors = append(pkg.Errors, fmt.Errorf("self-contained: package %q not found", imp)) delete(queuedByPkgPath, imp) // stop trying to get this lib, we can't From 9380f1ccc237f456908933a035060f1609eb2425 Mon Sep 17 00:00:00 2001 From: Norman Date: Fri, 10 Jan 2025 05:35:52 +0100 Subject: [PATCH 065/143] fix: gnodev, gnogensis Signed-off-by: Norman --- contribs/gnodev/pkg/dev/node.go | 5 ----- contribs/gnodev/pkg/dev/packages.go | 8 ++++---- .../gnogenesis/internal/txs/txs_add_packages.go | 2 +- gnovm/pkg/packages/analyze_packages.go | 16 +++++++++------- gnovm/pkg/packages/load.go | 13 +------------ 5 files changed, 15 insertions(+), 29 deletions(-) diff --git a/contribs/gnodev/pkg/dev/node.go b/contribs/gnodev/pkg/dev/node.go index 1be0d51a05c..3ebcd657b6d 100644 --- a/contribs/gnodev/pkg/dev/node.go +++ b/contribs/gnodev/pkg/dev/node.go @@ -16,7 +16,6 @@ import ( "github.com/gnolang/gno/gno.land/pkg/gnoland" "github.com/gnolang/gno/gno.land/pkg/gnoland/ugnot" "github.com/gnolang/gno/gno.land/pkg/integration" - "github.com/gnolang/gno/gnovm/pkg/gnolang" "github.com/gnolang/gno/gnovm/pkg/packages" "github.com/gnolang/gno/tm2/pkg/amino" tmcfg "github.com/gnolang/gno/tm2/pkg/bft/config" @@ -250,10 +249,6 @@ func (n *Node) updatePackages(paths ...string) error { // Update or add package in the current known list. for _, pkg := range pkgslist { - if pkg.ImportPath != "" && gnolang.IsStdlib(pkg.ImportPath) { - continue - } - n.pkgs[pkg.Dir] = Package{ Package: pkg, Creator: deployer, diff --git a/contribs/gnodev/pkg/dev/packages.go b/contribs/gnodev/pkg/dev/packages.go index c69e12061ff..41ba499458d 100644 --- a/contribs/gnodev/pkg/dev/packages.go +++ b/contribs/gnodev/pkg/dev/packages.go @@ -98,10 +98,6 @@ func NewPackagesMap(cfg *packages.LoadConfig, ppaths []PackagePath) (PackagesMap continue } - if pkg.ImportPath != "" && gnolang.IsStdlib(pkg.ImportPath) { - continue - } - if _, ok := pkgs[pkg.Dir]; ok { continue // skip } @@ -136,6 +132,10 @@ func (pm PackagesMap) Load(fee std.Fee, start time.Time) ([]gnoland.TxWithMetada metatxs := make([]gnoland.TxWithMetadata, 0, len(nonDraft)) for _, modPkg := range nonDraft { + if modPkg.ImportPath == "" || gnolang.IsStdlib(modPkg.ImportPath) { + continue + } + pkg := pm[modPkg.Dir] if pkg.Creator.IsZero() { return nil, fmt.Errorf("no creator set for %q", pkg.Dir) diff --git a/contribs/gnogenesis/internal/txs/txs_add_packages.go b/contribs/gnogenesis/internal/txs/txs_add_packages.go index a79a847408b..daabc726bf0 100644 --- a/contribs/gnogenesis/internal/txs/txs_add_packages.go +++ b/contribs/gnogenesis/internal/txs/txs_add_packages.go @@ -125,7 +125,7 @@ func execTxsAddPackages( parsedTxs := make([]gnoland.TxWithMetadata, 0) for _, path := range args { // Generate transactions from the packages (recursively) - loadCfg := &packages.LoadConfig{IO: io, SelfContained: true} + loadCfg := &packages.LoadConfig{IO: io, Deps: true} txs, err := gnoland.LoadPackagesFromDir(loadCfg, path, creator, genesisDeployFee) if err != nil { return fmt.Errorf("unable to load txs from directory, %w", err) diff --git a/gnovm/pkg/packages/analyze_packages.go b/gnovm/pkg/packages/analyze_packages.go index e30e93865e9..84f7e298a9a 100644 --- a/gnovm/pkg/packages/analyze_packages.go +++ b/gnovm/pkg/packages/analyze_packages.go @@ -73,14 +73,16 @@ func readPkgDir(pkgDir string, importPath string, fset *token.FileSet) *Package ImportPath: importPath, } - stdlibsPath := filepath.Join(gnoenv.RootDir(), "gnovm", "stdlibs") - if strings.HasPrefix(filepath.Clean(pkg.Dir), stdlibsPath) { - libPath, err := filepath.Rel(stdlibsPath, pkg.Dir) - if err != nil { - pkg.Errors = append(pkg.Errors, err) - return pkg + if pkg.ImportPath == "" { + stdlibsPath := filepath.Join(gnoenv.RootDir(), "gnovm", "stdlibs") + if strings.HasPrefix(filepath.Clean(pkg.Dir), stdlibsPath) { + libPath, err := filepath.Rel(stdlibsPath, pkg.Dir) + if err != nil { + pkg.Errors = append(pkg.Errors, err) + return pkg + } + pkg.ImportPath = libPath } - pkg.ImportPath = libPath } files := []string{} diff --git a/gnovm/pkg/packages/load.go b/gnovm/pkg/packages/load.go index 5a8f062089a..b82e58d7ced 100644 --- a/gnovm/pkg/packages/load.go +++ b/gnovm/pkg/packages/load.go @@ -120,18 +120,7 @@ func Load(conf *LoadConfig, patterns ...string) (PkgList, error) { // check if this is a stdlib and queue it if gnolang.IsStdlib(imp) { - dir := filepath.Join(gnoroot, "gnovm", "stdlibs", filepath.FromSlash(imp)) - dirInfo, err := os.Stat(dir) - if err == nil && !dirInfo.IsDir() { - err = fmt.Errorf("%q is not a directory", dir) - } - if err != nil { - pkg.Errors = append(pkg.Errors, err) - delete(queuedByPkgPath, imp) // stop trying to get this lib, we can't - continue - } - - pkg := readPkgDir(dir, imp, fset) + pkg := readPkgDir(filepath.Join(gnoroot, "gnovm", "stdlibs", filepath.FromSlash(imp)), imp, fset) markForVisit(pkg) continue } From 2f9c35ae029f03192b1535faed21ffd4ca840b39 Mon Sep 17 00:00:00 2001 From: Norman Date: Fri, 10 Jan 2025 05:42:29 +0100 Subject: [PATCH 066/143] chore: lint and fmt Signed-off-by: Norman --- .../testdata/corpra/parsefile/bug_3013.go | 28 +++++++++---------- gnovm/pkg/packages/analyze_packages.go | 14 ---------- gnovm/pkg/packages/imports_test.go | 6 ++-- gnovm/pkg/packages/types.go | 4 +-- tm2/pkg/overflow/overflow_impl.go | 12 +++----- 5 files changed, 22 insertions(+), 42 deletions(-) diff --git a/gnovm/pkg/gnolang/testdata/corpra/parsefile/bug_3013.go b/gnovm/pkg/gnolang/testdata/corpra/parsefile/bug_3013.go index f664f68f1b6..1ddb7f51aa5 100644 --- a/gnovm/pkg/gnolang/testdata/corpra/parsefile/bug_3013.go +++ b/gnovm/pkg/gnolang/testdata/corpra/parsefile/bug_3013.go @@ -3,20 +3,20 @@ package main import "testing" func TestDummy(t *testing.T) { - testTable := []struct { - name string - }{ - { - "one", - }, - { - "two", - }, - } + testTable := []struct { + name string + }{ + { + "one", + }, + { + "two", + }, + } - for _, testCase := range testTable { - testCase := testCase + for _, testCase := range testTable { + testCase := testCase - println(testCase.name) - } + println(testCase.name) + } } diff --git a/gnovm/pkg/packages/analyze_packages.go b/gnovm/pkg/packages/analyze_packages.go index 84f7e298a9a..55fbc27f3b3 100644 --- a/gnovm/pkg/packages/analyze_packages.go +++ b/gnovm/pkg/packages/analyze_packages.go @@ -7,7 +7,6 @@ import ( "os" "path" "path/filepath" - "slices" "strings" "github.com/gnolang/gno/gnovm" @@ -192,16 +191,3 @@ func readPkgFiles(pkg *Package, files []string, fset *token.FileSet) *Package { return pkg } - -// Contains reports whether v is present in s. -func sliceContainsSequence[S ~[]E, E comparable](s S, seq ...E) bool { - if len(seq) == 0 || len(s) < len(seq) { - return false - } - for i := 0; i < len(s)-len(seq); i++ { - if slices.Equal(s[i:i+len(seq)], seq) { - return true - } - } - return false -} diff --git a/gnovm/pkg/packages/imports_test.go b/gnovm/pkg/packages/imports_test.go index efc1640ec93..08892fe413b 100644 --- a/gnovm/pkg/packages/imports_test.go +++ b/gnovm/pkg/packages/imports_test.go @@ -1,4 +1,4 @@ -package packages_test +package packages import ( "go/token" @@ -7,8 +7,6 @@ import ( "testing" "github.com/gnolang/gno/gnovm/pkg/gnolang" - "github.com/gnolang/gno/gnovm/pkg/packages" - . "github.com/gnolang/gno/gnovm/pkg/packages" "github.com/stretchr/testify/require" ) @@ -115,7 +113,7 @@ func TestImports(t *testing.T) { // - ignore subdirs // - ignore duplicate // - should be sorted - expected := packages.ImportsMap{ + expected := ImportsMap{ FileKindPackageSource: { "gno.land/p/demo/pkg1", "gno.land/p/demo/pkg2", diff --git a/gnovm/pkg/packages/types.go b/gnovm/pkg/packages/types.go index ac556d83a03..e2988341301 100644 --- a/gnovm/pkg/packages/types.go +++ b/gnovm/pkg/packages/types.go @@ -28,11 +28,11 @@ func (fm FilesMap) Size() int { } // Merge merges imports, it removes duplicates and sorts the result -func (imap FilesMap) Merge(kinds ...FileKind) []string { +func (fm FilesMap) Merge(kinds ...FileKind) []string { res := make([]string, 0, 16) for _, kind := range kinds { - res = append(res, imap[kind]...) + res = append(res, fm[kind]...) } sortPaths(res) diff --git a/tm2/pkg/overflow/overflow_impl.go b/tm2/pkg/overflow/overflow_impl.go index 0f057f65387..ab9f13c163d 100644 --- a/tm2/pkg/overflow/overflow_impl.go +++ b/tm2/pkg/overflow/overflow_impl.go @@ -84,10 +84,9 @@ func Quotient8(a, b int8) (int8, int8, bool) { } c := a / b status := (c < 0) == ((a < 0) != (b < 0)) || (c == 0) // no sign check for 0 quotient - return c, a%b, status + return c, a % b, status } - // Add16 performs + operation on two int16 operands, returning a result and status. func Add16(a, b int16) (int16, bool) { c := a + b @@ -170,10 +169,9 @@ func Quotient16(a, b int16) (int16, int16, bool) { } c := a / b status := (c < 0) == ((a < 0) != (b < 0)) || (c == 0) // no sign check for 0 quotient - return c, a%b, status + return c, a % b, status } - // Add32 performs + operation on two int32 operands, returning a result and status. func Add32(a, b int32) (int32, bool) { c := a + b @@ -256,10 +254,9 @@ func Quotient32(a, b int32) (int32, int32, bool) { } c := a / b status := (c < 0) == ((a < 0) != (b < 0)) || (c == 0) // no sign check for 0 quotient - return c, a%b, status + return c, a % b, status } - // Add64 performs + operation on two int64 operands, returning a result and status. func Add64(a, b int64) (int64, bool) { c := a + b @@ -342,6 +339,5 @@ func Quotient64(a, b int64) (int64, int64, bool) { } c := a / b status := (c < 0) == ((a < 0) != (b < 0)) || (c == 0) // no sign check for 0 quotient - return c, a%b, status + return c, a % b, status } - From 85c800a84aa4de62e460927c7cd2867f8129d431 Mon Sep 17 00:00:00 2001 From: Norman Date: Fri, 10 Jan 2025 05:45:10 +0100 Subject: [PATCH 067/143] chore: generate Signed-off-by: Norman --- tm2/pkg/overflow/overflow_impl.go | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/tm2/pkg/overflow/overflow_impl.go b/tm2/pkg/overflow/overflow_impl.go index ab9f13c163d..0f057f65387 100644 --- a/tm2/pkg/overflow/overflow_impl.go +++ b/tm2/pkg/overflow/overflow_impl.go @@ -84,9 +84,10 @@ func Quotient8(a, b int8) (int8, int8, bool) { } c := a / b status := (c < 0) == ((a < 0) != (b < 0)) || (c == 0) // no sign check for 0 quotient - return c, a % b, status + return c, a%b, status } + // Add16 performs + operation on two int16 operands, returning a result and status. func Add16(a, b int16) (int16, bool) { c := a + b @@ -169,9 +170,10 @@ func Quotient16(a, b int16) (int16, int16, bool) { } c := a / b status := (c < 0) == ((a < 0) != (b < 0)) || (c == 0) // no sign check for 0 quotient - return c, a % b, status + return c, a%b, status } + // Add32 performs + operation on two int32 operands, returning a result and status. func Add32(a, b int32) (int32, bool) { c := a + b @@ -254,9 +256,10 @@ func Quotient32(a, b int32) (int32, int32, bool) { } c := a / b status := (c < 0) == ((a < 0) != (b < 0)) || (c == 0) // no sign check for 0 quotient - return c, a % b, status + return c, a%b, status } + // Add64 performs + operation on two int64 operands, returning a result and status. func Add64(a, b int64) (int64, bool) { c := a + b @@ -339,5 +342,6 @@ func Quotient64(a, b int64) (int64, int64, bool) { } c := a / b status := (c < 0) == ((a < 0) != (b < 0)) || (c == 0) // no sign check for 0 quotient - return c, a % b, status + return c, a%b, status } + From 5b068747c2304c9de764370d56c0d9de88d65af6 Mon Sep 17 00:00:00 2001 From: Norman Date: Fri, 10 Jan 2025 06:18:11 +0100 Subject: [PATCH 068/143] test: gnodev Signed-off-by: Norman --- contribs/gnodev/pkg/dev/node_state_test.go | 2 +- contribs/gnodev/pkg/dev/node_test.go | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/contribs/gnodev/pkg/dev/node_state_test.go b/contribs/gnodev/pkg/dev/node_state_test.go index efaeb979693..c5993bfb390 100644 --- a/contribs/gnodev/pkg/dev/node_state_test.go +++ b/contribs/gnodev/pkg/dev/node_state_test.go @@ -158,7 +158,7 @@ func Render(_ string) string { return strconv.Itoa(value) } // Call NewDevNode with no package should work node, emitter := newTestingDevNode(t, counterPkg) - assert.Len(t, node.ListPkgs(), 1) + assert.Len(t, node.ListPkgs(), 23) // Test rendering render, err := testingRenderRealm(t, node, testCounterRealm) diff --git a/contribs/gnodev/pkg/dev/node_test.go b/contribs/gnodev/pkg/dev/node_test.go index 4a4acc232b9..e4183231408 100644 --- a/contribs/gnodev/pkg/dev/node_test.go +++ b/contribs/gnodev/pkg/dev/node_test.go @@ -499,7 +499,6 @@ func newTestingDevNodeWithConfig(t *testing.T, cfg *NodeConfig) (*Node, *mock.Se node, err := NewDevNode(ctx, cfg) require.NoError(t, err) - assert.Len(t, node.ListPkgs(), len(cfg.PackagesPathList)) t.Cleanup(func() { node.Close() From 3e42e55188cc71fbf6ccab76a77e25578aa067f2 Mon Sep 17 00:00:00 2001 From: Norman Date: Fri, 17 Jan 2025 17:24:33 +0100 Subject: [PATCH 069/143] tmp Signed-off-by: Norman --- examples/no_cycles_test.go | 4 +- gnovm/blip/foo.go | 5 + gnovm/blip/sub/thing.go | 9 ++ gnovm/blipgno/foo.gno | 5 + gnovm/blipgno/gno.mod | 1 + gnovm/blipgno/sub/thing.gno | 9 ++ gnovm/cmd/gno/lint.go | 19 +++- gnovm/cmd/gno/lint_test.go | 7 +- gnovm/cmd/gno/list.go | 22 ++-- gnovm/cmd/gno/mod.go | 11 +- gnovm/cmd/gno/test.go | 5 +- gnovm/cmd/gno/testdata/lint/bad_import.txtar | 2 +- gnovm/pkg/doc/dirs.go | 74 ++----------- gnovm/pkg/gnolang/nodes.go | 26 +++-- gnovm/pkg/gnomod/gnomod.go | 2 +- gnovm/pkg/gnomod/parse.go | 10 +- gnovm/pkg/packages/analyze_packages.go | 75 +++++++++---- gnovm/pkg/packages/download_deps.go | 6 +- gnovm/pkg/packages/imports.go | 98 ++++++----------- gnovm/pkg/packages/imports_test.go | 11 +- gnovm/pkg/packages/load.go | 109 ++++++++++++------- gnovm/pkg/packages/load_test.go | 38 +++++-- gnovm/pkg/packages/pkgdownload/pkgfetcher.go | 16 ++- gnovm/pkg/packages/pkglist.go | 10 +- gnovm/pkg/packages/types.go | 60 +++++++++- gnovm/pkg/test/filetest.go | 2 +- gnovm/pkg/test/imports.go | 30 +++-- gnovm/pkg/test/test.go | 2 +- 28 files changed, 383 insertions(+), 285 deletions(-) create mode 100644 gnovm/blip/foo.go create mode 100644 gnovm/blip/sub/thing.go create mode 100644 gnovm/blipgno/foo.gno create mode 100644 gnovm/blipgno/gno.mod create mode 100644 gnovm/blipgno/sub/thing.gno diff --git a/examples/no_cycles_test.go b/examples/no_cycles_test.go index 04069194304..5e9c0fec54a 100644 --- a/examples/no_cycles_test.go +++ b/examples/no_cycles_test.go @@ -12,8 +12,6 @@ import ( "github.com/stretchr/testify/require" ) -var injectedTestingLibs = []string{"encoding/json", "fmt", "os", "internal/os_test"} - // TestNoCycles checks that there is no import cycles in stdlibs and non-draft examples func TestNoCycles(t *testing.T) { // find examples and stdlibs @@ -82,7 +80,7 @@ func detectCycles(root *packages.Package, pkgs []*packages.Package, visited map[ // visitImports resolves and visits imports by kinds func visitImports(kinds []packages.FileKind, root *packages.Package, pkgs []*packages.Package, visited map[string]bool, stack []string) error { for _, imp := range root.Imports.Merge(kinds...) { - if slices.Contains(injectedTestingLibs, imp) { + if packages.IsInjectedTestingStdlib(imp) { continue } idx := slices.IndexFunc(pkgs, func(p *packages.Package) bool { return p.ImportPath == imp }) diff --git a/gnovm/blip/foo.go b/gnovm/blip/foo.go new file mode 100644 index 00000000000..0f7cb0697e1 --- /dev/null +++ b/gnovm/blip/foo.go @@ -0,0 +1,5 @@ +package bloup + +import ( + "whatever" +) diff --git a/gnovm/blip/sub/thing.go b/gnovm/blip/sub/thing.go new file mode 100644 index 00000000000..7342d0b05d4 --- /dev/null +++ b/gnovm/blip/sub/thing.go @@ -0,0 +1,9 @@ +package sub + +import ( + "gno.land/lel/lol" +) + +func main() { + lol.Foo() +} diff --git a/gnovm/blipgno/foo.gno b/gnovm/blipgno/foo.gno new file mode 100644 index 00000000000..0f7cb0697e1 --- /dev/null +++ b/gnovm/blipgno/foo.gno @@ -0,0 +1,5 @@ +package bloup + +import ( + "whatever" +) diff --git a/gnovm/blipgno/gno.mod b/gnovm/blipgno/gno.mod new file mode 100644 index 00000000000..9e3ddf6d8aa --- /dev/null +++ b/gnovm/blipgno/gno.mod @@ -0,0 +1 @@ +module gno.land/p/demo/blip diff --git a/gnovm/blipgno/sub/thing.gno b/gnovm/blipgno/sub/thing.gno new file mode 100644 index 00000000000..7342d0b05d4 --- /dev/null +++ b/gnovm/blipgno/sub/thing.gno @@ -0,0 +1,9 @@ +package sub + +import ( + "gno.land/lel/lol" +) + +func main() { + lol.Foo() +} diff --git a/gnovm/cmd/gno/lint.go b/gnovm/cmd/gno/lint.go index 16c7d552c45..e44055a9ba4 100644 --- a/gnovm/cmd/gno/lint.go +++ b/gnovm/cmd/gno/lint.go @@ -93,7 +93,7 @@ func execLint(cfg *lintCfg, args []string, io commands.IO) error { rootDir = gnoenv.RootDir() } - loadCfg := &packages.LoadConfig{IO: io, Fetcher: testPackageFetcher} + loadCfg := &packages.LoadConfig{IO: io, Fetcher: testPackageFetcher, Deps: true} if cfg.rootExamples { examples := filepath.Join(gnoenv.RootDir(), "examples", "...") loadCfg.DepsPatterns = append(loadCfg.DepsPatterns, examples) @@ -115,6 +115,11 @@ func execLint(cfg *lintCfg, args []string, io commands.IO) error { ) for _, pkg := range pkgs { + // ignore dep + if len(pkg.Match) == 0 { + continue + } + logName := pkg.ImportPath if logName == "" { logName = pkg.Dir @@ -124,6 +129,14 @@ func execLint(cfg *lintCfg, args []string, io commands.IO) error { io.ErrPrintln(logName) } + for _, err := range pkg.Errors { + io.ErrPrintln(tryRelativize(err.Error())) + hasError = true + } + if hasError { + continue + } + // Check if 'gno.mod' exists if pkg.Root == "" && pkg.ImportPath != "command-line-arguments" { issue := lintIssue{ @@ -153,7 +166,7 @@ func execLint(cfg *lintCfg, args []string, io commands.IO) error { if memPkgPath == "" || memPkgPath == "command-line-arguments" { memPkgPath = pkg.Dir } - memPkg, err := gno.ReadMemPackage(pkg.Dir, memPkgPath) + memPkg, err := gno.ReadMemPackage(pkg.Dir, memPkgPath, loadCfg.Fset) if err != nil { io.ErrPrintln(issueFromError(pkg.Dir, err).String()) hasError = true @@ -161,7 +174,7 @@ func execLint(cfg *lintCfg, args []string, io commands.IO) error { } // Perform imports using the parent store. - if err := test.LoadImports(ts, memPkg); err != nil { + if err := test.LoadImports(ts, memPkg, loadCfg.Fset); err != nil { io.ErrPrintln(issueFromError(pkg.Dir, err).String()) hasError = true continue diff --git a/gnovm/cmd/gno/lint_test.go b/gnovm/cmd/gno/lint_test.go index 72ca51f240e..88470f977f9 100644 --- a/gnovm/cmd/gno/lint_test.go +++ b/gnovm/cmd/gno/lint_test.go @@ -42,11 +42,12 @@ func TestLintApp(t *testing.T) { args: []string{"lint", "../../tests/integ/minimalist_gnomod/"}, // TODO: raise an error because there is a gno.mod, but no .gno files }, { - args: []string{"lint", "../../tests/integ/invalid_module_name/"}, - // TODO: raise an error because gno.mod is invalid + args: []string{"lint", "../../tests/integ/invalid_module_name/"}, + stderrShouldContain: "../../tests/integ/invalid_module_name/gno.mod:1: usage: module module/path", + errShouldBe: "exit code: 1", }, { args: []string{"lint", "../../tests/integ/invalid_gno_file/"}, - stderrShouldContain: "../../tests/integ/invalid_gno_file/invalid.gno:1:1: expected 'package', found packag (code=2)", + stderrShouldContain: "../../tests/integ/invalid_gno_file/invalid.gno:1:1: expected 'package', found packag", errShouldBe: "exit code: 1", }, { args: []string{"lint", "../../tests/integ/typecheck_missing_return/"}, diff --git a/gnovm/cmd/gno/list.go b/gnovm/cmd/gno/list.go index ef737a00b64..5012b4b2e41 100644 --- a/gnovm/cmd/gno/list.go +++ b/gnovm/cmd/gno/list.go @@ -57,12 +57,19 @@ func execList(cfg *listCfg, args []string, io commands.IO) error { conf.Deps = true } + pkgs, err := packages.Load(conf, args...) + if err != nil { + fmt.Println(err) + os.Exit(1) + } + + // try sort + sorted, err := pkgs.Sort() + if err == nil { + pkgs = packages.PkgList(sorted) + } + if !cfg.json { - pkgs, err := packages.Load(conf, args...) - if err != nil { - fmt.Println(err) - os.Exit(1) - } pkgPaths := make([]string, len(pkgs)) for i, pkg := range pkgs { pkgPaths[i] = pkg.ImportPath @@ -71,11 +78,6 @@ func execList(cfg *listCfg, args []string, io commands.IO) error { return nil } - pkgs, err := packages.Load(conf, args...) - if err != nil { - fmt.Println(err) - os.Exit(1) - } for _, pkg := range pkgs { pkgBytes, err := json.MarshalIndent(pkg, "", "\t") if err != nil { diff --git a/gnovm/cmd/gno/mod.go b/gnovm/cmd/gno/mod.go index b704870cba7..0db07f8af80 100644 --- a/gnovm/cmd/gno/mod.go +++ b/gnovm/cmd/gno/mod.go @@ -7,7 +7,6 @@ import ( "go/token" "os" "path/filepath" - "strconv" "strings" "github.com/gnolang/gno/gnovm/pkg/gnomod" @@ -371,19 +370,13 @@ func getImportToFilesMap(pkg *packages.Package) (map[string][]string, error) { if err != nil { return nil, err } - imports, err := packages.FileImportsSpecs(filename, string(data), fset) + imports, err := packages.FileImports(filename, string(data), fset) if err != nil { return nil, err } for _, imp := range imports { - pkgPath, err := strconv.Unquote(imp.Path.Value) - if err != nil { - // should not happen - parser.ParseFile should already ensure we get - // a valid string literal here. - panic(fmt.Errorf("%v: unexpected invalid import path: %v", fset.Position(imp.Pos()).String(), imp.Path.Value)) - } - m[pkgPath] = append(m[pkgPath], filename) + m[imp.PkgPath] = append(m[imp.PkgPath], filename) } } return m, nil diff --git a/gnovm/cmd/gno/test.go b/gnovm/cmd/gno/test.go index a5e0eca193b..ce54d1c4019 100644 --- a/gnovm/cmd/gno/test.go +++ b/gnovm/cmd/gno/test.go @@ -198,9 +198,8 @@ func execTest(cfg *testCfg, args []string, io commands.IO) error { label := pkg.ImportPath if label == "" { - label = pkg.Dir + label = tryRelativize(pkg.Dir) } - label = tryRelativize(label) if len(pkg.Files[packages.FileKindTest]) == 0 && len(pkg.Files[packages.FileKindXTest]) == 0 && len(pkg.Files[packages.FileKindFiletest]) == 0 { io.ErrPrintfln("? %s \t[no test files]", label) @@ -218,7 +217,7 @@ func execTest(cfg *testCfg, args []string, io commands.IO) error { } packages.Inject(pkgsMap, deps) - memPkg, err := gno.ReadMemPackage(pkg.Dir, label) + memPkg, err := gno.ReadMemPackage(pkg.Dir, label, conf.Fset) if err != nil { io.ErrPrintln(err) buildErrCount++ diff --git a/gnovm/cmd/gno/testdata/lint/bad_import.txtar b/gnovm/cmd/gno/testdata/lint/bad_import.txtar index e2c0431443c..31d81a6658d 100644 --- a/gnovm/cmd/gno/testdata/lint/bad_import.txtar +++ b/gnovm/cmd/gno/testdata/lint/bad_import.txtar @@ -19,4 +19,4 @@ module gno.land/p/test -- stdout.golden -- -- stderr.golden -- -bad_file.gno:3:8: unknown import path python (code=2) +bad_file.gno:3:8: package python is not in std (/Users/norman/Code/gno/gnovm/stdlibs/python) diff --git a/gnovm/pkg/doc/dirs.go b/gnovm/pkg/doc/dirs.go index b0761e50c23..7411a59839c 100644 --- a/gnovm/pkg/doc/dirs.go +++ b/gnovm/pkg/doc/dirs.go @@ -9,15 +9,11 @@ import ( "os" "path" "path/filepath" - "slices" "sort" "strings" - "github.com/gnolang/gno/gnovm" - "github.com/gnolang/gno/gnovm/pkg/gnolang" "github.com/gnolang/gno/gnovm/pkg/gnomod" "github.com/gnolang/gno/gnovm/pkg/packages" - "golang.org/x/mod/module" ) // A bfsDir describes a directory holding code by specifying @@ -65,79 +61,31 @@ func newDirs(dirs []string, modDirs []string) *bfsDirs { dir: mdir, importPath: gm.Module.Mod.Path, }) - roots = append(roots, getGnoModDirs(gm, mdir)...) + roots = append(roots, getGnoModDirs(mdir)...) } go d.walk(roots) return d } -func getGnoModDirs(gm *gnomod.File, root string) []bfsDir { - // cmd/go makes use of the go list command, we don't have that here. - - imports := packageImportsRecursive(root, gm.Module.Mod.Path) +func getGnoModDirs(root string) []bfsDir { + pkgs, err := packages.Load(&packages.LoadConfig{Deps: true}, filepath.Join(root, "...")) + if err != nil { + log.Println("open source directories from import:", err) + return nil + } - dirs := make([]bfsDir, 0, len(imports)) - for _, r := range imports { - mv := gm.Resolve(module.Version{Path: r}) - path := gnomod.PackageDir("", mv) - if _, err := os.Stat(path); err != nil { - // only give directories which actually exist and don't give - // an error when accessing - if !os.IsNotExist(err) { - log.Println("open source directories from import:", err) - } - continue - } + dirs := make([]bfsDir, 0, len(pkgs)) + for _, pkg := range pkgs { dirs = append(dirs, bfsDir{ - importPath: mv.Path, - dir: path, + importPath: pkg.ImportPath, + dir: pkg.Dir, }) } return dirs } -func packageImportsRecursive(root string, pkgPath string) []string { - pkg, err := gnolang.ReadMemPackage(root, pkgPath) - if err != nil { - // ignore invalid packages - pkg = &gnovm.MemPackage{} - } - - importsMap, err := packages.Imports(pkg, nil) - if err != nil { - // ignore packages with invalid imports - importsMap = nil - } - res := importsMap.Merge(packages.FileKindPackageSource, packages.FileKindTest, packages.FileKindXTest) - - entries, err := os.ReadDir(root) - if err != nil { - // ignore unreadable dirs - entries = nil - } - - for _, entry := range entries { - if !entry.IsDir() { - continue - } - - dirName := entry.Name() - sub := packageImportsRecursive(filepath.Join(root, dirName), path.Join(pkgPath, dirName)) - - for _, imp := range sub { - if !slices.Contains(res, imp) { - res = append(res, imp) - } - } - } - - sort.Strings(res) - - return res -} - // Reset puts the scan back at the beginning. func (d *bfsDirs) Reset() { d.offset = 0 diff --git a/gnovm/pkg/gnolang/nodes.go b/gnovm/pkg/gnolang/nodes.go index 0496d37ed72..10dc3967dea 100644 --- a/gnovm/pkg/gnolang/nodes.go +++ b/gnovm/pkg/gnolang/nodes.go @@ -1132,8 +1132,10 @@ type FileSet struct { // PackageNameFromFileBody extracts the package name from the given Gno code body. // The 'name' parameter is used for better error traces, and 'body' contains the Gno code. -func PackageNameFromFileBody(name, body string) (Name, error) { - fset := token.NewFileSet() +func PackageNameFromFileBody(name, body string, fset *token.FileSet) (Name, error) { + if fset == nil { + fset = token.NewFileSet() + } astFile, err := parser.ParseFile(fset, name, body, parser.PackageClauseOnly) if err != nil { return "", err @@ -1143,8 +1145,8 @@ func PackageNameFromFileBody(name, body string) (Name, error) { } // MustPackageNameFromFileBody is a wrapper around [PackageNameFromFileBody] that panics on error. -func MustPackageNameFromFileBody(name, body string) Name { - pkgName, err := PackageNameFromFileBody(name, body) +func MustPackageNameFromFileBody(name, body string, fset *token.FileSet) Name { + pkgName, err := PackageNameFromFileBody(name, body, fset) if err != nil { panic(err) } @@ -1161,7 +1163,7 @@ func MustPackageNameFromFileBody(name, body string) Name { // // NOTE: panics if package name is invalid (characters must be alphanumeric or _, // lowercase, and must start with a letter). -func ReadMemPackage(dir string, pkgPath string) (*gnovm.MemPackage, error) { +func ReadMemPackage(dir string, pkgPath string, fset *token.FileSet) (*gnovm.MemPackage, error) { files, err := os.ReadDir(dir) if err != nil { return nil, err @@ -1192,12 +1194,12 @@ func ReadMemPackage(dir string, pkgPath string) (*gnovm.MemPackage, error) { } list = append(list, filepath.Join(dir, file.Name())) } - return ReadMemPackageFromList(list, pkgPath) + return ReadMemPackageFromList(list, pkgPath, fset) } // MustReadMemPackage is a wrapper around [ReadMemPackage] that panics on error. -func MustReadMemPackage(dir string, pkgPath string) *gnovm.MemPackage { - pkg, err := ReadMemPackage(dir, pkgPath) +func MustReadMemPackage(dir string, pkgPath string, fset *token.FileSet) *gnovm.MemPackage { + pkg, err := ReadMemPackage(dir, pkgPath, fset) if err != nil { panic(err) } @@ -1210,7 +1212,7 @@ func MustReadMemPackage(dir string, pkgPath string) *gnovm.MemPackage { // // NOTE: errors out if package name is invalid (characters must be alphanumeric or _, // lowercase, and must start with a letter). -func ReadMemPackageFromList(list []string, pkgPath string) (*gnovm.MemPackage, error) { +func ReadMemPackageFromList(list []string, pkgPath string, fset *token.FileSet) (*gnovm.MemPackage, error) { memPkg := &gnovm.MemPackage{Path: pkgPath} var pkgName Name for _, fpath := range list { @@ -1221,7 +1223,7 @@ func ReadMemPackageFromList(list []string, pkgPath string) (*gnovm.MemPackage, e } // XXX: should check that all pkg names are the same (else package is invalid) if pkgName == "" && strings.HasSuffix(fname, ".gno") { - pkgName, err = PackageNameFromFileBody(fname, string(bz)) + pkgName, err = PackageNameFromFileBody(fname, string(bz), fset) if err != nil { return nil, err } @@ -1248,8 +1250,8 @@ func ReadMemPackageFromList(list []string, pkgPath string) (*gnovm.MemPackage, e } // MustReadMemPackageFromList is a wrapper around [ReadMemPackageFromList] that panics on error. -func MustReadMemPackageFromList(list []string, pkgPath string) *gnovm.MemPackage { - pkg, err := ReadMemPackageFromList(list, pkgPath) +func MustReadMemPackageFromList(list []string, pkgPath string, fset *token.FileSet) *gnovm.MemPackage { + pkg, err := ReadMemPackageFromList(list, pkgPath, fset) if err != nil { panic(err) } diff --git a/gnovm/pkg/gnomod/gnomod.go b/gnovm/pkg/gnomod/gnomod.go index d41cb47ae0b..b1e2f9b0314 100644 --- a/gnovm/pkg/gnomod/gnomod.go +++ b/gnovm/pkg/gnomod/gnomod.go @@ -58,7 +58,7 @@ func CreateGnoModFile(rootDir, modPath string) error { return fmt.Errorf("read file %q: %w", fpath, err) } - pn := gnolang.MustPackageNameFromFileBody(file.Name(), string(bz)) + pn := gnolang.MustPackageNameFromFileBody(file.Name(), string(bz), nil) if strings.HasSuffix(string(pkgName), "_test") { pkgName = pkgName[:len(pkgName)-len("_test")] } diff --git a/gnovm/pkg/gnomod/parse.go b/gnovm/pkg/gnomod/parse.go index e3a3fbcaeea..a9cd65aedd1 100644 --- a/gnovm/pkg/gnomod/parse.go +++ b/gnovm/pkg/gnomod/parse.go @@ -50,22 +50,22 @@ func ParseAt(dir string) (*File, error) { func ParseGnoMod(fname string) (*File, error) { file, err := os.Stat(fname) if err != nil { - return nil, fmt.Errorf("could not read gno.mod file: %w", err) + return nil, fmt.Errorf("%s: stat: %w", fname, err) } if file.IsDir() { - return nil, fmt.Errorf("invalid gno.mod at %q: is a directory", fname) + return nil, fmt.Errorf("%s: gno.mod is a directory", fname) } b, err := os.ReadFile(fname) if err != nil { - return nil, fmt.Errorf("could not read gno.mod file: %w", err) + return nil, fmt.Errorf("%s: read: %w", fname, err) } gm, err := Parse(fname, b) if err != nil { - return nil, fmt.Errorf("error parsing gno.mod file at %q: %w", fname, err) + return nil, err } if err := gm.Validate(); err != nil { - return nil, fmt.Errorf("error validating gno.mod file at %q: %w", fname, err) + return nil, fmt.Errorf("%s: validate: %w", fname, err) } return gm, nil } diff --git a/gnovm/pkg/packages/analyze_packages.go b/gnovm/pkg/packages/analyze_packages.go index 55fbc27f3b3..d93de6b80be 100644 --- a/gnovm/pkg/packages/analyze_packages.go +++ b/gnovm/pkg/packages/analyze_packages.go @@ -39,9 +39,10 @@ func readPackages(matches []*pkgMatch, fset *token.FileSet) ([]*Package, error) func readCLAPkg(patterns []string, fset *token.FileSet) (*Package, error) { pkg := &Package{ - ImportPath: "command-line-arguments", - Files: make(FilesMap), - Imports: make(ImportsMap), + ImportPath: "command-line-arguments", + Files: FilesMap{}, + Imports: map[FileKind][]string{}, + ImportsSpecs: ImportsMap{}, } var err error @@ -66,10 +67,11 @@ func readCLAPkg(patterns []string, fset *token.FileSet) (*Package, error) { func readPkgDir(pkgDir string, importPath string, fset *token.FileSet) *Package { pkg := &Package{ - Dir: pkgDir, - Files: make(FilesMap), - Imports: make(ImportsMap), - ImportPath: importPath, + Dir: pkgDir, + Files: FilesMap{}, + Imports: map[FileKind][]string{}, + ImportsSpecs: ImportsMap{}, + ImportPath: importPath, } if pkg.ImportPath == "" { @@ -77,7 +79,10 @@ func readPkgDir(pkgDir string, importPath string, fset *token.FileSet) *Package if strings.HasPrefix(filepath.Clean(pkg.Dir), stdlibsPath) { libPath, err := filepath.Rel(stdlibsPath, pkg.Dir) if err != nil { - pkg.Errors = append(pkg.Errors, err) + pkg.Errors = append(pkg.Errors, &Error{ + Pos: pkg.Dir, + Msg: err.Error(), + }) return pkg } pkg.ImportPath = libPath @@ -87,7 +92,10 @@ func readPkgDir(pkgDir string, importPath string, fset *token.FileSet) *Package files := []string{} entries, err := os.ReadDir(pkgDir) if err != nil { - pkg.Errors = append(pkg.Errors, err) + pkg.Errors = append(pkg.Errors, &Error{ + Pos: pkg.Dir, + Msg: err.Error(), + }) return pkg } for _, entry := range entries { @@ -119,14 +127,17 @@ func readPkgFiles(pkg *Package, files []string, fset *token.FileSet) *Package { bodyBytes, err := os.ReadFile(fpath) if err != nil { - pkg.Errors = append(pkg.Errors, err) + pkg.Errors = append(pkg.Errors, &Error{ + Pos: fpath, + Msg: err.Error(), + }) continue } body := string(bodyBytes) fileKind, err := GetFileKind(base, body, fset) if err != nil { - pkg.Errors = append(pkg.Errors, err) + pkg.Errors = append(pkg.Errors, FromErr(err, fset, fpath, false)...) continue } @@ -135,20 +146,22 @@ func readPkgFiles(pkg *Package, files []string, fset *token.FileSet) *Package { } var err error - pkg.Imports, err = Imports(&mempkg, fset) + pkg.ImportsSpecs, err = Imports(&mempkg, fset) if err != nil { - pkg.Errors = append(pkg.Errors, err) + pkg.Errors = append(pkg.Errors, FromErr(err, fset, pkg.Dir, true)...) } + pkg.Imports = pkg.ImportsSpecs.ToStrings() // we use the ReadMemPkg utils to get the package name because we want name resolution like the vm - nameFiles := pkg.Files.Merge(FileKindPackageSource, FileKindTest, FileKindXTest) + nameFiles := pkg.Files.Merge(FileKindPackageSource, FileKindTest, FileKindXTest, FileKindFiletest) absFiles := make([]string, 0, len(nameFiles)) for _, f := range nameFiles { absFiles = append(absFiles, filepath.Join(pkg.Dir, f)) } - minMempkg, err := gnolang.ReadMemPackageFromList(absFiles, "") + + minMempkg, err := gnolang.ReadMemPackageFromList(absFiles, "", fset) if err != nil { - pkg.Errors = append(pkg.Errors, err) + pkg.Errors = append(pkg.Errors, FromErr(err, fset, pkg.Dir, true)...) } else { pkg.Name = minMempkg.Name } @@ -164,13 +177,17 @@ func readPkgFiles(pkg *Package, files []string, fset *token.FileSet) *Package { return pkg } if err != nil { - pkg.Errors = append(pkg.Errors, err) + pkg.Errors = append(pkg.Errors, &Error{ + Pos: pkg.Dir, + Msg: err.Error(), + }) return pkg } - mod, err := gnomod.ParseGnoMod(filepath.Join(pkg.Root, "gno.mod")) + modFpath := filepath.Join(pkg.Root, "gno.mod") + mod, err := gnomod.ParseGnoMod(modFpath) if err != nil { - pkg.Errors = append(pkg.Errors, err) + pkg.Errors = append(pkg.Errors, FromErr(err, fset, modFpath, false)...) return pkg } @@ -183,11 +200,29 @@ func readPkgFiles(pkg *Package, files []string, fset *token.FileSet) *Package { pkg.ModPath = mod.Module.Mod.Path subPath, err := filepath.Rel(pkg.Root, pkg.Dir) if err != nil { - pkg.Errors = append(pkg.Errors, err) + pkg.Errors = append(pkg.Errors, FromErr(err, fset, pkg.Dir, false)...) return pkg } pkg.ImportPath = path.Join(pkg.ModPath, filepath.ToSlash(subPath)) + pkg.Errors = cleanErrors(pkg.Errors) + return pkg } + +func cleanErrors(s []*Error) []*Error { + seen := map[Error]struct{}{} + res := []*Error{} + for _, elem := range s { + if elem == nil { + continue + } + if _, ok := seen[*elem]; ok { + continue + } + res = append(res, elem) + seen[*elem] = struct{}{} + } + return res +} diff --git a/gnovm/pkg/packages/download_deps.go b/gnovm/pkg/packages/download_deps.go index 45a704766ac..4e948490d05 100644 --- a/gnovm/pkg/packages/download_deps.go +++ b/gnovm/pkg/packages/download_deps.go @@ -19,7 +19,7 @@ func DownloadDeps(conf *LoadConfig, pkgDir string, gnoMod *gnomod.File) error { return errors.New("fetcher is nil") } - pkg, err := gnolang.ReadMemPackage(pkgDir, gnoMod.Module.Mod.Path) + pkg, err := gnolang.ReadMemPackage(pkgDir, gnoMod.Module.Mod.Path, nil) if err != nil { return fmt.Errorf("read package at %q: %w", pkgDir, err) } @@ -29,8 +29,8 @@ func DownloadDeps(conf *LoadConfig, pkgDir string, gnoMod *gnomod.File) error { } imports := importsMap.Merge(FileKindPackageSource, FileKindTest, FileKindXTest) - for _, pkgPath := range imports { - resolved := gnoMod.Resolve(module.Version{Path: pkgPath}) + for _, imp := range imports { + resolved := gnoMod.Resolve(module.Version{Path: imp.PkgPath}) resolvedPkgPath := resolved.Path if !isRemotePkgPath(resolvedPkgPath) { diff --git a/gnovm/pkg/packages/imports.go b/gnovm/pkg/packages/imports.go index f75d6a5c978..d3c39ea5302 100644 --- a/gnovm/pkg/packages/imports.go +++ b/gnovm/pkg/packages/imports.go @@ -12,18 +12,10 @@ import ( "github.com/gnolang/gno/gnovm" ) -func Imports(pkg *gnovm.MemPackage, fset *token.FileSet) (ImportsMap, error) { - specs, err := ImportsSpecs(pkg, fset) - if err != nil { - return nil, err - } - return ImportsMapFromSpecs(specs, fset), nil -} - // Imports returns the list of gno imports from a [gnovm.MemPackage]. // fset is optional. -func ImportsSpecs(pkg *gnovm.MemPackage, fset *token.FileSet) (ImportsSpecsMap, error) { - res := make(ImportsSpecsMap, 16) +func Imports(pkg *gnovm.MemPackage, fset *token.FileSet) (ImportsMap, error) { + res := make(ImportsMap, 16) seen := make(map[FileKind]map[string]struct{}, 16) for _, file := range pkg.Files { @@ -35,30 +27,30 @@ func ImportsSpecs(pkg *gnovm.MemPackage, fset *token.FileSet) (ImportsSpecsMap, if err != nil { return nil, err } - imports, err := FileImportsSpecs(file.Name, file.Body, fset) + imports, err := FileImports(file.Name, file.Body, fset) if err != nil { return nil, err } for _, im := range imports { - if _, ok := seen[fileKind][im.Path.Value]; ok { + if _, ok := seen[fileKind][im.PkgPath]; ok { continue } res[fileKind] = append(res[fileKind], im) if _, ok := seen[fileKind]; !ok { seen[fileKind] = make(map[string]struct{}, 16) } - seen[fileKind][im.Path.Value] = struct{}{} + seen[fileKind][im.PkgPath] = struct{}{} } } for _, imports := range res { - sortImportsSpecs(imports) + sortImports(imports) } return res, nil } -func FileImportsSpecs(filename string, src string, fset *token.FileSet) ([]*ast.ImportSpec, error) { +func FileImports(filename string, src string, fset *token.FileSet) ([]*FileImport, error) { if fset == nil { fset = token.NewFileSet() } @@ -66,34 +58,36 @@ func FileImportsSpecs(filename string, src string, fset *token.FileSet) ([]*ast. if err != nil { return nil, err } - res := make([]*ast.ImportSpec, len(f.Imports)) - for i, im := range f.Imports { - _, err := strconv.Unquote(im.Path.Value) + res := make([]*FileImport, len(f.Imports)) + for i, spec := range f.Imports { + pkgPath, err := strconv.Unquote(spec.Path.Value) if err != nil { // should not happen - parser.ParseFile should already ensure we get // a valid string literal here. - panic(fmt.Errorf("%v: unexpected invalid import path: %v", fset.Position(im.Pos()).String(), im.Path.Value)) + panic(fmt.Errorf("%v: unexpected invalid import path: %v", fset.Position(spec.Pos()).String(), spec.Path.Value)) } - res[i] = im + res[i] = &FileImport{ + PkgPath: pkgPath, + Spec: spec, + } } return res, nil } -type ImportsMap map[FileKind][]string +type FileImport struct { + PkgPath string + Spec *ast.ImportSpec +} + +type ImportsMap map[FileKind][]*FileImport -func ImportsMapFromSpecs(specs ImportsSpecsMap, fset *token.FileSet) ImportsMap { - res := make(ImportsMap, len(specs)) - for k, v := range specs { +func (im ImportsMap) ToStrings() map[FileKind][]string { + res := make(map[FileKind][]string, len(im)) + for k, v := range im { c := make([]string, 0, len(v)) for _, x := range v { - pkgPath, err := strconv.Unquote(x.Path.Value) - if err != nil { - // should not happen - parser.ParseFile should already ensure we get - // a valid string literal here. - panic(fmt.Errorf("%v: unexpected invalid import path: %v", fset.Position(x.Pos()).String(), x.Path.Value)) - } - c = append(c, pkgPath) + c = append(c, x.PkgPath) } res[k] = c } @@ -101,55 +95,31 @@ func ImportsMapFromSpecs(specs ImportsSpecsMap, fset *token.FileSet) ImportsMap } // Merge merges imports, it removes duplicates and sorts the result -func (imap ImportsMap) Merge(kinds ...FileKind) []string { - res := make([]string, 0, 16) - seen := make(map[string]struct{}, 16) - - for _, kind := range kinds { - for _, im := range imap[kind] { - if _, ok := seen[im]; ok { - continue - } - seen[im] = struct{}{} - - res = append(res, im) - } +func (imap ImportsMap) Merge(kinds ...FileKind) []*FileImport { + if len(kinds) == 0 { + kinds = AllFileKinds() } - sortImports(res) - return res -} - -type ImportsSpecsMap map[FileKind][]*ast.ImportSpec - -// Merge merges imports, it removes duplicates and sorts the result -func (imap ImportsSpecsMap) Merge(kinds ...FileKind) []*ast.ImportSpec { - res := make([]*ast.ImportSpec, 0, 16) + res := make([]*FileImport, 0, 16) seen := make(map[string]struct{}, 16) for _, kind := range kinds { for _, im := range imap[kind] { - if _, ok := seen[im.Path.Value]; ok { + if _, ok := seen[im.PkgPath]; ok { continue } - seen[im.Path.Value] = struct{}{} + seen[im.PkgPath] = struct{}{} res = append(res, im) } } - sortImportsSpecs(res) + sortImports(res) return res } -func sortImports(imports []string) { - sort.Slice(imports, func(i, j int) bool { - return imports[i] < imports[j] - }) -} - -func sortImportsSpecs(imports []*ast.ImportSpec) { +func sortImports(imports []*FileImport) { sort.Slice(imports, func(i, j int) bool { - return imports[i].Path.Value < imports[j].Path.Value + return imports[i].PkgPath < imports[j].PkgPath }) } diff --git a/gnovm/pkg/packages/imports_test.go b/gnovm/pkg/packages/imports_test.go index 08892fe413b..fdcd0f52c27 100644 --- a/gnovm/pkg/packages/imports_test.go +++ b/gnovm/pkg/packages/imports_test.go @@ -1,7 +1,6 @@ package packages import ( - "go/token" "os" "path/filepath" "testing" @@ -113,7 +112,7 @@ func TestImports(t *testing.T) { // - ignore subdirs // - ignore duplicate // - should be sorted - expected := ImportsMap{ + expected := map[FileKind][]string{ FileKindPackageSource: { "gno.land/p/demo/pkg1", "gno.land/p/demo/pkg2", @@ -143,16 +142,14 @@ func TestImports(t *testing.T) { require.NoError(t, err) } - pkg, err := gnolang.ReadMemPackage(tmpDir, "test") + pkg, err := gnolang.ReadMemPackage(tmpDir, "test", nil) require.NoError(t, err) - fset := token.NewFileSet() - - importsSpec, err := ImportsSpecs(pkg, nil) + imports, err := Imports(pkg, nil) require.NoError(t, err) // ignore specs - got := ImportsMapFromSpecs(importsSpec, fset) + got := imports.ToStrings() require.Equal(t, expected, got) } diff --git a/gnovm/pkg/packages/load.go b/gnovm/pkg/packages/load.go index b82e58d7ced..6cdf4bea756 100644 --- a/gnovm/pkg/packages/load.go +++ b/gnovm/pkg/packages/load.go @@ -6,6 +6,7 @@ import ( "go/token" "os" "path/filepath" + "slices" "github.com/gnolang/gno/gnovm" "github.com/gnolang/gno/gnovm/pkg/gnoenv" @@ -25,6 +26,13 @@ type LoadConfig struct { SelfContained bool AllowEmpty bool DepsPatterns []string + Fset *token.FileSet +} + +var injectedTestingLibs = []string{"encoding/json", "fmt", "internal/os_test", "os"} + +func IsInjectedTestingStdlib(pkgPath string) bool { + return slices.Contains(injectedTestingLibs, pkgPath) } func (conf *LoadConfig) applyDefaults() { @@ -37,19 +45,23 @@ func (conf *LoadConfig) applyDefaults() { if conf.Cache == nil { conf.Cache = map[string]*Package{} } + if conf.Fset == nil { + conf.Fset = token.NewFileSet() + } } func Load(conf *LoadConfig, patterns ...string) (PkgList, error) { + if conf == nil { + conf = &LoadConfig{} + } conf.applyDefaults() - fset := token.NewFileSet() - expanded, err := expandPatterns(conf, patterns...) if err != nil { return nil, err } - pkgs, err := readPackages(expanded, fset) + pkgs, err := readPackages(expanded, conf.Fset) if err != nil { return nil, err } @@ -72,14 +84,12 @@ func Load(conf *LoadConfig, patterns ...string) (PkgList, error) { m.Match = []string{} } - extraPkgs, err := readPackages(extra, fset) + extraPkgs, err := readPackages(extra, conf.Fset) if err != nil { return nil, err } extraMap := NewPackagesMap(extraPkgs...) - gnoroot := gnoenv.RootDir() - toVisit := pkgs queuedByPkgPath := NewPackagesMap(pkgs...) markForVisit := func(pkg *Package) { @@ -100,44 +110,60 @@ func Load(conf *LoadConfig, patterns ...string) (PkgList, error) { continue } - for _, imp := range pkg.Imports.Merge(FileKindPackageSource, FileKindTest, FileKindXTest, FileKindFiletest) { + for _, imp := range pkg.ImportsSpecs.Merge(FileKindPackageSource, FileKindTest, FileKindXTest, FileKindFiletest) { + if IsInjectedTestingStdlib(imp.PkgPath) { + continue + } + // check if we already queued this dep - if _, ok := queuedByPkgPath[imp]; ok { + if _, ok := queuedByPkgPath[imp.PkgPath]; ok { continue } // check if we have it in config cache - if cached, ok := conf.Cache[imp]; ok { + if cached, ok := conf.Cache[imp.PkgPath]; ok { markForVisit(cached) continue } // check if we have it in extra deps patterns - if extra, ok := extraMap[imp]; ok { + if extra, ok := extraMap[imp.PkgPath]; ok { markForVisit(extra) continue } - // check if this is a stdlib and queue it - if gnolang.IsStdlib(imp) { - pkg := readPkgDir(filepath.Join(gnoroot, "gnovm", "stdlibs", filepath.FromSlash(imp)), imp, fset) - markForVisit(pkg) + // check if this is a stdlib and load it from gnoroot if available + if gnolang.IsStdlib(imp.PkgPath) { + dir := filepath.Join(gnoenv.RootDir(), "gnovm", "stdlibs", filepath.FromSlash(imp.PkgPath)) + dirInfo, err := os.Stat(dir) + if err != nil || !dirInfo.IsDir() { + err := &Error{ + Pos: filepath.Join(filepath.FromSlash(pkg.Dir), conf.Fset.Position(imp.Spec.Pos()).String()), + Msg: fmt.Sprintf("package %s is not in std (%s)", imp.PkgPath, dir), + } + pkg.Errors = append(pkg.Errors, err) + } + markForVisit(readPkgDir(dir, imp.PkgPath, conf.Fset)) continue } if conf.SelfContained { - pkg.Errors = append(pkg.Errors, fmt.Errorf("self-contained: package %q not found", imp)) - delete(queuedByPkgPath, imp) // stop trying to get this lib, we can't + pkg.Errors = append(pkg.Errors, &Error{ + Pos: pkg.Dir, + Msg: fmt.Sprintf("package %q not found (self-contained)", imp.PkgPath), + }) continue } - dir := gnomod.PackageDir("", module.Version{Path: imp}) - if err := downloadPackage(conf, imp, dir); err != nil { - pkg.Errors = append(pkg.Errors, err) - delete(queuedByPkgPath, imp) // stop trying to get this lib, we can't + dir := gnomod.PackageDir("", module.Version{Path: imp.PkgPath}) + if err := downloadPackage(conf, imp.PkgPath, dir); err != nil { + pkg.Errors = append(pkg.Errors, &Error{ + Pos: pkg.Dir, + Msg: fmt.Sprintf("download %q: %v", imp.PkgPath, err), + }) continue } - markForVisit(readPkgDir(dir, imp, fset)) + markForVisit(readPkgDir(dir, imp.PkgPath, conf.Fset)) } loaded = append(loaded, pkg) @@ -145,41 +171,44 @@ func Load(conf *LoadConfig, patterns ...string) (PkgList, error) { for _, pkg := range loaded { // TODO: this could be optimized - pkg.Deps, err = listDeps(pkg.ImportPath, queuedByPkgPath) - if err != nil { - pkg.Errors = append(pkg.Errors, err) - } + var errs []*Error + pkg.Deps, errs = listDeps(pkg, queuedByPkgPath) + pkg.Errors = append(pkg.Errors, errs...) } return loaded, nil } -func listDeps(target string, pkgs map[string]*Package) ([]string, error) { +func listDeps(target *Package, pkgs PackagesMap) ([]string, []*Error) { deps := []string{} err := listDepsRecursive(target, target, pkgs, &deps, make(map[string]struct{})) return deps, err } -func listDepsRecursive(rootTarget string, target string, pkgs map[string]*Package, deps *[]string, visited map[string]struct{}) error { - if _, ok := visited[target]; ok { +func listDepsRecursive(rootTarget *Package, target *Package, pkgs PackagesMap, deps *[]string, visited map[string]struct{}) []*Error { + if _, ok := visited[target.ImportPath]; ok { return nil } - pkg := pkgs[target] - if pkg == nil { - return fmt.Errorf("package %s not found", target) - } - visited[target] = struct{}{} - var outErr error - for _, imp := range pkg.Imports.Merge(FileKindPackageSource, FileKindTest, FileKindXTest, FileKindFiletest) { - err := listDepsRecursive(rootTarget, imp, pkgs, deps, visited) - if err != nil { - outErr = errors.Join(outErr, err) + visited[target.ImportPath] = struct{}{} + var outErrs []*Error + for _, imp := range target.ImportsSpecs.Merge(FileKindPackageSource, FileKindTest, FileKindXTest, FileKindFiletest) { + if IsInjectedTestingStdlib(imp.PkgPath) { + continue + } + dep := pkgs[imp.PkgPath] + if dep == nil { + return []*Error{{ + Pos: rootTarget.Dir, + Msg: fmt.Sprintf("package %q not found", imp.PkgPath), + }} } + errs := listDepsRecursive(rootTarget, dep, pkgs, deps, visited) + outErrs = append(outErrs, errs...) } if target != rootTarget { - (*deps) = append(*deps, target) + (*deps) = append(*deps, target.ImportPath) } - return outErr + return outErrs } func (p *Package) MemPkg() (*gnovm.MemPackage, error) { diff --git a/gnovm/pkg/packages/load_test.go b/gnovm/pkg/packages/load_test.go index 72bcdd1d6c9..27b19dea03a 100644 --- a/gnovm/pkg/packages/load_test.go +++ b/gnovm/pkg/packages/load_test.go @@ -5,6 +5,7 @@ import ( "path/filepath" "testing" + "github.com/gnolang/gno/gnovm/pkg/packages/pkgdownload" "github.com/gnolang/gno/gnovm/pkg/packages/pkgdownload/examplespkgfetcher" "github.com/gnolang/gno/tm2/pkg/testutils" "github.com/stretchr/testify/assert" @@ -123,30 +124,30 @@ func TestSortPkgs(t *testing.T) { }, { desc: "no_dependencies", in: []*Package{ - {ImportPath: "pkg1", Dir: "/path/to/pkg1", Imports: ImportsMap{}}, - {ImportPath: "pkg2", Dir: "/path/to/pkg2", Imports: ImportsMap{}}, - {ImportPath: "pkg3", Dir: "/path/to/pkg3", Imports: ImportsMap{}}, + {ImportPath: "pkg1", Dir: "/path/to/pkg1", Imports: map[FileKind][]string{}}, + {ImportPath: "pkg2", Dir: "/path/to/pkg2", Imports: map[FileKind][]string{}}, + {ImportPath: "pkg3", Dir: "/path/to/pkg3", Imports: map[FileKind][]string{}}, }, expected: []string{"pkg1", "pkg2", "pkg3"}, }, { desc: "circular_dependencies", in: []*Package{ - {ImportPath: "pkg1", Dir: "/path/to/pkg1", Imports: ImportsMap{FileKindPackageSource: []string{"pkg2"}}}, - {ImportPath: "pkg2", Dir: "/path/to/pkg2", Imports: ImportsMap{FileKindPackageSource: []string{"pkg1"}}}, + {ImportPath: "pkg1", Dir: "/path/to/pkg1", Imports: map[FileKind][]string{FileKindPackageSource: {"pkg2"}}}, + {ImportPath: "pkg2", Dir: "/path/to/pkg2", Imports: map[FileKind][]string{FileKindPackageSource: {"pkg1"}}}, }, shouldErr: true, }, { desc: "missing_dependencies", in: []*Package{ - {ImportPath: "pkg1", Dir: "/path/to/pkg1", Imports: ImportsMap{FileKindPackageSource: []string{"pkg2"}}}, + {ImportPath: "pkg1", Dir: "/path/to/pkg1", Imports: map[FileKind][]string{FileKindPackageSource: {"pkg2"}}}, }, shouldErr: true, }, { desc: "valid_dependencies", in: []*Package{ - {ImportPath: "pkg1", Dir: "/path/to/pkg1", Imports: ImportsMap{FileKindPackageSource: []string{"pkg2"}}}, - {ImportPath: "pkg2", Dir: "/path/to/pkg2", Imports: ImportsMap{FileKindPackageSource: []string{"pkg3"}}}, - {ImportPath: "pkg3", Dir: "/path/to/pkg3", Imports: ImportsMap{}}, + {ImportPath: "pkg1", Dir: "/path/to/pkg1", Imports: map[FileKind][]string{FileKindPackageSource: {"pkg2"}}}, + {ImportPath: "pkg2", Dir: "/path/to/pkg2", Imports: map[FileKind][]string{FileKindPackageSource: {"pkg3"}}}, + {ImportPath: "pkg3", Dir: "/path/to/pkg3", Imports: map[FileKind][]string{}}, }, expected: []string{"pkg3", "pkg2", "pkg1"}, }, @@ -164,3 +165,22 @@ func TestSortPkgs(t *testing.T) { }) } } + +func TestLoadNonDraftExamples(t *testing.T) { + examples := filepath.Join("..", "..", "..", "examples", "...") + conf := LoadConfig{ + Deps: true, + Fetcher: pkgdownload.NewNoopFetcher(), + SelfContained: true, + } + + pkgs, err := Load(&conf, examples) + require.NoError(t, err) + + for _, pkg := range pkgs { + if pkg.Draft { + continue + } + require.Empty(t, pkg.Errors) + } +} diff --git a/gnovm/pkg/packages/pkgdownload/pkgfetcher.go b/gnovm/pkg/packages/pkgdownload/pkgfetcher.go index 79a7a6a54e2..b0a790444eb 100644 --- a/gnovm/pkg/packages/pkgdownload/pkgfetcher.go +++ b/gnovm/pkg/packages/pkgdownload/pkgfetcher.go @@ -1,7 +1,21 @@ package pkgdownload -import "github.com/gnolang/gno/gnovm" +import ( + "errors" + + "github.com/gnolang/gno/gnovm" +) type PackageFetcher interface { FetchPackage(pkgPath string) ([]*gnovm.MemFile, error) } + +func NewNoopFetcher() PackageFetcher { + return &noopFetcher{} +} + +type noopFetcher struct{} + +func (nf *noopFetcher) FetchPackage(pkgPath string) ([]*gnovm.MemFile, error) { + return nil, errors.New("noop") +} diff --git a/gnovm/pkg/packages/pkglist.go b/gnovm/pkg/packages/pkglist.go index a0c0106c20b..5344d8ea76f 100644 --- a/gnovm/pkg/packages/pkglist.go +++ b/gnovm/pkg/packages/pkglist.go @@ -38,10 +38,10 @@ func visitPackage(pkg *Package, pkgs []*Package, visited, onStack map[string]boo onStack[pkg.ImportPath] = true // Visit package's dependencies - for _, imp := range pkg.Imports.Merge(FileKindPackageSource) { + for _, imp := range pkg.ImportsSpecs.Merge(FileKindPackageSource) { found := false for _, p := range pkgs { - if p.ImportPath != imp { + if p.ImportPath != imp.PkgPath { continue } if err := visitPackage(p, pkgs, visited, onStack, sortedPkgs); err != nil { @@ -51,7 +51,7 @@ func visitPackage(pkg *Package, pkgs []*Package, visited, onStack map[string]boo break } if !found { - return fmt.Errorf("missing dependency '%s' for package '%s'", imp, pkg.ImportPath) + return fmt.Errorf("missing dependency '%s' for package '%s'", imp.PkgPath, pkg.ImportPath) } } @@ -72,8 +72,8 @@ func (sp SortedPkgList) GetNonDraftPkgs() SortedPkgList { continue } dependsOnDraft := false - for _, req := range pkg.Imports.Merge(FileKindPackageSource) { - if draft[req] { + for _, req := range pkg.ImportsSpecs.Merge(FileKindPackageSource) { + if draft[req.PkgPath] { dependsOnDraft = true draft[pkg.ImportPath] = true break diff --git a/gnovm/pkg/packages/types.go b/gnovm/pkg/packages/types.go index e2988341301..8bc20158f50 100644 --- a/gnovm/pkg/packages/types.go +++ b/gnovm/pkg/packages/types.go @@ -1,6 +1,14 @@ package packages -import "sort" +import ( + "go/scanner" + "go/token" + "path/filepath" + "sort" + "strings" + + "github.com/davecgh/go-spew/spew" +) // ported from https://cs.opensource.google/go/go/+/refs/tags/go1.23.2:src/cmd/go/internal/load/pkg.go type Package struct { @@ -10,13 +18,57 @@ type Package struct { Root string `json:",omitempty"` // Gno root, Gno path dir, or module root dir containing this package ModPath string Match []string `json:",omitempty"` // command-line patterns matching this package - Errors []error `json:",omitempty"` // error loading this package (not dependencies) + Errors []*Error `json:",omitempty"` // error loading this package (not dependencies) Draft bool Files FilesMap - Imports ImportsMap `json:",omitempty"` // import paths used by this package - Deps []string `json:",omitempty"` // all (recursively) imported dependencies + Imports map[FileKind][]string `json:",omitempty"` // import paths used by this package + Deps []string `json:",omitempty"` // all (recursively) imported dependencies + + ImportsSpecs ImportsMap `json:"-"` +} + +type Error struct { + Pos string // "file:line:col" or "file:line" or "" or "-" + Msg string } +type ErrorKind string + +func (err Error) Error() string { + sb := strings.Builder{} + if err.Pos != "" { + sb.WriteString(err.Pos) + sb.WriteString(": ") + } + sb.WriteString(err.Msg) + return sb.String() +} + +func FromErr(err error, fset *token.FileSet, root string, prependRoot bool) []*Error { + spew.Dump(err) + switch err := err.(type) { + case scanner.ErrorList: + res := make([]*Error, 0, len(err)) + for _, e := range err { + pos := e.Pos.String() + if prependRoot { + pos = filepath.Join(root, pos) + } + res = append(res, &Error{Msg: e.Msg, Pos: pos}) + } + return res + default: + return []*Error{{Pos: root, Msg: err.Error()}} + } +} + +const ( + UnknownError ErrorKind = "Unknown" + ListError = "List" + ParseError = "Parse" + TypeError = "Type" +) + type FilesMap map[FileKind][]string func (fm FilesMap) Size() int { diff --git a/gnovm/pkg/test/filetest.go b/gnovm/pkg/test/filetest.go index 1934f429568..df0546f61ed 100644 --- a/gnovm/pkg/test/filetest.go +++ b/gnovm/pkg/test/filetest.go @@ -188,7 +188,7 @@ func (opts *TestOptions) runTest(m *gno.Machine, pkgPath, filename string, conte Files: []*gnovm.MemFile{ {Name: filename, Body: string(content)}, }, - }); err != nil { + }, nil); err != nil { // NOTE: we perform this here, so we can capture the runResult. return runResult{Error: err.Error()} } diff --git a/gnovm/pkg/test/imports.go b/gnovm/pkg/test/imports.go index 8b96400ead8..e7959f7e8e5 100644 --- a/gnovm/pkg/test/imports.go +++ b/gnovm/pkg/test/imports.go @@ -10,7 +10,6 @@ import ( "os" "path/filepath" "runtime/debug" - "strconv" "strings" "time" @@ -40,7 +39,7 @@ func Store( // fmt.Println("getting pkg", pkgPath) if pkgPath == "" { - panic(fmt.Sprintf("invalid zero package path in testStore().pkgGetter")) + panic(errors.New("invalid zero package path in testStore().pkgGetter")) } if withExtern { @@ -48,7 +47,7 @@ func Store( const testPath = "github.com/gnolang/gno/_test/" if strings.HasPrefix(pkgPath, testPath) { baseDir := filepath.Join(rootDir, "gnovm", "tests", "files", "extern", pkgPath[len(testPath):]) - memPkg := gno.MustReadMemPackage(baseDir, pkgPath) + memPkg := gno.MustReadMemPackage(baseDir, pkgPath, nil) send := std.Coins{} ctx := Context(pkgPath, send) m2 := gno.NewMachineWithOptions(gno.MachineOptions{ @@ -139,7 +138,7 @@ func Store( // if known package if pkg, ok := pkgs[pkgPath]; ok { - memPkg := gno.MustReadMemPackage(pkg.Dir, pkgPath) + memPkg := gno.MustReadMemPackage(pkg.Dir, pkgPath, nil) if memPkg.IsEmpty() { panic(fmt.Sprintf("found an empty package %q", pkgPath)) } @@ -198,7 +197,7 @@ func loadStdlib(rootDir, pkgPath string, store gno.Store, stdout io.Writer) (*gn return nil, nil } - memPkg := gno.MustReadMemPackageFromList(files, pkgPath) + memPkg := gno.MustReadMemPackageFromList(files, pkgPath, nil) m2 := gno.NewMachineWithOptions(gno.MachineOptions{ // NOTE: see also pkgs/sdk/vm/builtins.go // Needs PkgPath != its name because TestStore.getPackage is the package @@ -225,7 +224,7 @@ func (e *stackWrappedError) String() string { // from the store. This is mostly useful for "eager import loading", whereby all // imports are pre-loaded in a permanent store, so that the tests can use // ephemeral transaction stores. -func LoadImports(store gno.Store, memPkg *gnovm.MemPackage) (err error) { +func LoadImports(store gno.Store, memPkg *gnovm.MemPackage, fset *token.FileSet) (err error) { defer func() { // This is slightly different from other similar error handling; we do not have a // machine to work with, as this comes from an import; so we need @@ -246,27 +245,24 @@ func LoadImports(store gno.Store, memPkg *gnovm.MemPackage) (err error) { } }() - fset := token.NewFileSet() - importsMap, err := packages.ImportsSpecs(memPkg, fset) + if fset == nil { + fset = token.NewFileSet() + } + + importsMap, err := packages.Imports(memPkg, fset) if err != nil { return err } imports := importsMap.Merge(packages.FileKindPackageSource, packages.FileKindTest, packages.FileKindXTest) for _, im := range imports { - pkgPath, err := strconv.Unquote(im.Path.Value) - if err != nil { - // should not happen - parser.ParseFile should already ensure we get - // a valid string literal here. - panic(fmt.Errorf("%v: unexpected invalid import path: %v", fset.Position(im.Pos()).String(), im.Path.Value)) - } - if gno.IsRealmPath(pkgPath) { + if gno.IsRealmPath(im.PkgPath) { // Don't eagerly load realms. // Realms persist state and can change the state of other realms in initialization. continue } - pkg := store.GetPackage(pkgPath, true) + pkg := store.GetPackage(im.PkgPath, true) if pkg == nil { - return fmt.Errorf("%s: unknown import path %s", fset.Position(im.Pos()).String(), pkgPath) + return fmt.Errorf("%s: unknown import path %s", fset.Position(im.Spec.Pos()).String(), im.PkgPath) } } return nil diff --git a/gnovm/pkg/test/test.go b/gnovm/pkg/test/test.go index 5d1a306c44f..824cf8f2cf9 100644 --- a/gnovm/pkg/test/test.go +++ b/gnovm/pkg/test/test.go @@ -185,7 +185,7 @@ func Test(memPkg *gnovm.MemPackage, fsDir string, opts *TestOptions) error { // fmt.Println("loading imports for", memPkg.Path, fsDir) // Eagerly load imports. - if err := LoadImports(opts.TestStore, memPkg); err != nil { + if err := LoadImports(opts.TestStore, memPkg, nil); err != nil { return err } From fd244862eb9124977c207ba2352308e21f1d38fd Mon Sep 17 00:00:00 2001 From: n0izn0iz Date: Thu, 23 Jan 2025 17:51:52 +0100 Subject: [PATCH 070/143] fix: remove import cycles (#3304) Depends on #3323 - add test in `examples/no_cycles_test.go` to detect import cycles in stdlibs and examples - add `matchString` native injection in the `testing` stdlib to avoid a cycle in `regexp` tests - remove other import cycles Go never allows import cycles. Our stdlibs have a lot of import cycles, and some examples import self which is not allowed in golang either. Keeping support for import cycles in stdlib and importing self will require a lot of hacky and weird logic in generic package loading code so I try to tackle this first. TODO: - [x] fix tests - [x] ~~check cycles with the test stdlibs overlay applied~~ -> be explicit about the lack of support for modifying imports in testing stdlibs overlay --------- Signed-off-by: Norman Meier Signed-off-by: Norman Signed-off-by: Norman Co-authored-by: Morgan Co-authored-by: Norman --- examples/gno.land/p/moul/md/md_test.gno | 2 +- .../gno.land/r/gnoland/faucet/faucet_test.gno | 2 +- examples/no_cycles_test.go | 214 ++++++++++ gnovm/pkg/packages/imports_test.go | 17 +- gnovm/stdlibs/crypto/ed25519/ed25519_test.gno | 2 +- gnovm/stdlibs/crypto/sha256/sha256_test.gno | 2 +- gnovm/stdlibs/encoding/binary/binary_test.gno | 2 +- gnovm/stdlibs/generated.go | 38 ++ gnovm/stdlibs/hash/marshal_test.gno | 2 +- gnovm/stdlibs/io/export_test.gno | 10 + gnovm/stdlibs/io/io_test.gno | 169 ++++---- gnovm/stdlibs/io/multi_test.gno | 81 ++-- gnovm/stdlibs/math/bits/bits_test.gno | 381 +++++++++--------- gnovm/stdlibs/math/bits/export_test.gno | 5 + gnovm/stdlibs/strconv/atob_test.gno | 15 +- gnovm/stdlibs/strconv/atof_test.gno | 275 ++++++------- gnovm/stdlibs/strconv/atoi_test.gno | 315 +++++++-------- gnovm/stdlibs/strconv/decimal_test.gno | 13 +- gnovm/stdlibs/strconv/ftoa_test.gno | 41 +- gnovm/stdlibs/strconv/ftoaryu_test.gno | 7 +- gnovm/stdlibs/strconv/itoa_test.gno | 37 +- gnovm/stdlibs/strconv/quote_test.gno | 59 +-- gnovm/stdlibs/strconv/strconv_test.gno | 33 +- gnovm/stdlibs/strings/printtrie_impl_test.gno | 29 ++ gnovm/stdlibs/strings/printtrie_test.gno | 35 +- gnovm/stdlibs/testing/match.gno | 5 +- gnovm/stdlibs/testing/testing.gno | 3 + gnovm/stdlibs/testing/testing.go | 8 + gnovm/tests/stdlibs/README.md | 2 + gnovm/tests/stdlibs/generated.go | 38 ++ .../tests/stdlibs/testing/native_testing.gno | 2 + gnovm/tests/stdlibs/testing/native_testing.go | 13 +- 32 files changed, 1101 insertions(+), 756 deletions(-) create mode 100644 examples/no_cycles_test.go create mode 100644 gnovm/stdlibs/io/export_test.gno create mode 100644 gnovm/stdlibs/strings/printtrie_impl_test.gno diff --git a/examples/gno.land/p/moul/md/md_test.gno b/examples/gno.land/p/moul/md/md_test.gno index 144ae58d918..3e8e70efaf6 100644 --- a/examples/gno.land/p/moul/md/md_test.gno +++ b/examples/gno.land/p/moul/md/md_test.gno @@ -1,4 +1,4 @@ -package md +package md_test import ( "testing" diff --git a/examples/gno.land/r/gnoland/faucet/faucet_test.gno b/examples/gno.land/r/gnoland/faucet/faucet_test.gno index cecbb2ebcd6..5672f317469 100644 --- a/examples/gno.land/r/gnoland/faucet/faucet_test.gno +++ b/examples/gno.land/r/gnoland/faucet/faucet_test.gno @@ -1,4 +1,4 @@ -package faucet +package faucet_test import ( "std" diff --git a/examples/no_cycles_test.go b/examples/no_cycles_test.go new file mode 100644 index 00000000000..7cc6fbfd183 --- /dev/null +++ b/examples/no_cycles_test.go @@ -0,0 +1,214 @@ +package examples_test + +import ( + "fmt" + "io/fs" + "os" + pathlib "path" + "path/filepath" + "slices" + "strings" + "testing" + + "github.com/gnolang/gno/gnovm" + "github.com/gnolang/gno/gnovm/pkg/gnoenv" + "github.com/gnolang/gno/gnovm/pkg/gnomod" + "github.com/gnolang/gno/gnovm/pkg/packages" + "github.com/stretchr/testify/require" +) + +// XXX: move this into `gno lint` + +var injectedTestingLibs = []string{"encoding/json", "fmt", "os", "internal/os_test"} + +// TestNoCycles checks that there is no import cycles in stdlibs and non-draft examples +func TestNoCycles(t *testing.T) { + // find stdlibs + gnoRoot := gnoenv.RootDir() + pkgs, err := listPkgs(gnomod.Pkg{ + Dir: filepath.Join(gnoRoot, "gnovm", "stdlibs"), + Name: "", + }) + require.NoError(t, err) + + // find examples + examples, err := gnomod.ListPkgs(filepath.Join(gnoRoot, "examples")) + require.NoError(t, err) + for _, example := range examples { + if example.Draft { + continue + } + examplePkgs, err := listPkgs(example) + require.NoError(t, err) + pkgs = append(pkgs, examplePkgs...) + } + + // detect cycles + visited := make(map[string]bool) + for _, p := range pkgs { + require.NoError(t, detectCycles(p, pkgs, visited)) + } +} + +// detectCycles detects import cycles +// +// We need to check +// 3 kinds of nodes +// +// - normal pkg: compiled source +// +// - xtest pkg: external test source (include xtests and filetests), can be treated as their own package +// +// - test pkg: embedded test sources, +// these should not have their corresponding normal package in their dependencies tree +// +// The tricky thing is that we need to split test sources and normal source +// while not considering them as distincitive packages. +// Otherwise we will have false positive for example if we have these edges: +// +// - foo_pkg/foo_test.go imports bar_pkg +// +// - bar_pkg/bar_test.go import foo_pkg +// +// In go, the above example is allowed +// but the following is not +// +// - foo_pkg/foo.go imports bar_pkg +// +// - bar_pkg/bar_test.go imports foo_pkg +func detectCycles(root testPkg, pkgs []testPkg, visited map[string]bool) error { + // check cycles in package's sources + stack := []string{} + if err := visitPackage(root, pkgs, visited, stack); err != nil { + return fmt.Errorf("pkgsrc import: %w", err) + } + // check cycles in external tests' dependencies we might have missed + if err := visitImports([]packages.FileKind{packages.FileKindXTest, packages.FileKindFiletest}, root, pkgs, visited, stack); err != nil { + return fmt.Errorf("xtest import: %w", err) + } + + // check cycles in tests' imports by marking the current package as visited while visiting the tests' imports + // we also consider PackageSource imports here because tests can call package code + visited = map[string]bool{root.PkgPath: true} + stack = []string{root.PkgPath} + if err := visitImports([]packages.FileKind{packages.FileKindPackageSource, packages.FileKindTest}, root, pkgs, visited, stack); err != nil { + return fmt.Errorf("test import: %w", err) + } + + return nil +} + +// visitImports resolves and visits imports by kinds +func visitImports(kinds []packages.FileKind, root testPkg, pkgs []testPkg, visited map[string]bool, stack []string) error { + for _, imp := range root.Imports.Merge(kinds...) { + if slices.Contains(injectedTestingLibs, imp.PkgPath) { + continue + } + idx := slices.IndexFunc(pkgs, func(p testPkg) bool { return p.PkgPath == imp.PkgPath }) + if idx == -1 { + return fmt.Errorf("import %q not found for %q tests", imp.PkgPath, root.PkgPath) + } + if err := visitPackage(pkgs[idx], pkgs, visited, stack); err != nil { + return fmt.Errorf("test import error: %w", err) + } + } + + return nil +} + +// visitNode visits a package and its imports recursively. It only considers imports in PackageSource +func visitPackage(pkg testPkg, pkgs []testPkg, visited map[string]bool, stack []string) error { + if slices.Contains(stack, pkg.PkgPath) { + return fmt.Errorf("cycle detected: %s -> %s", strings.Join(stack, " -> "), pkg.PkgPath) + } + if visited[pkg.PkgPath] { + return nil + } + + visited[pkg.PkgPath] = true + stack = append(stack, pkg.PkgPath) + + if err := visitImports([]packages.FileKind{packages.FileKindPackageSource}, pkg, pkgs, visited, stack); err != nil { + return err + } + + return nil +} + +type testPkg struct { + Dir string + PkgPath string + Imports packages.ImportsMap +} + +// listPkgs lists all packages in rootMod +func listPkgs(rootMod gnomod.Pkg) ([]testPkg, error) { + res := []testPkg{} + rootDir := rootMod.Dir + visited := map[string]struct{}{} + if err := fs.WalkDir(os.DirFS(rootDir), ".", func(path string, d fs.DirEntry, err error) error { + if err != nil { + return err + } + if d.IsDir() { + return nil + } + if !strings.HasSuffix(d.Name(), ".gno") { + return nil + } + subPath := filepath.Dir(path) + dir := filepath.Join(rootDir, subPath) + if _, ok := visited[dir]; ok { + return nil + } + visited[dir] = struct{}{} + + subPkgPath := pathlib.Join(rootMod.Name, subPath) + + pkg := testPkg{ + Dir: dir, + PkgPath: subPkgPath, + } + + memPkg, err := readPkg(pkg.Dir, pkg.PkgPath) + if err != nil { + return fmt.Errorf("read pkg %q: %w", pkg.Dir, err) + } + pkg.Imports, err = packages.Imports(memPkg, nil) + if err != nil { + return fmt.Errorf("list imports of %q: %w", memPkg.Path, err) + } + + res = append(res, pkg) + return nil + }); err != nil { + return nil, fmt.Errorf("walk dirs at %q: %w", rootDir, err) + } + return res, nil +} + +// readPkg reads the sources of a package. It includes all .gno files but ignores the package name +func readPkg(dir string, pkgPath string) (*gnovm.MemPackage, error) { + list, err := os.ReadDir(dir) + if err != nil { + return nil, err + } + memPkg := &gnovm.MemPackage{Path: pkgPath} + for _, entry := range list { + fpath := filepath.Join(dir, entry.Name()) + if !strings.HasSuffix(fpath, ".gno") { + continue + } + fname := filepath.Base(fpath) + bz, err := os.ReadFile(fpath) + if err != nil { + return nil, err + } + memPkg.Files = append(memPkg.Files, + &gnovm.MemFile{ + Name: fname, + Body: string(bz), + }) + } + return memPkg, nil +} diff --git a/gnovm/pkg/packages/imports_test.go b/gnovm/pkg/packages/imports_test.go index f9f58b967c8..444964fbbf3 100644 --- a/gnovm/pkg/packages/imports_test.go +++ b/gnovm/pkg/packages/imports_test.go @@ -1,4 +1,4 @@ -package packages +package packages_test import ( "os" @@ -6,6 +6,7 @@ import ( "testing" "github.com/gnolang/gno/gnovm/pkg/gnolang" + "github.com/gnolang/gno/gnovm/pkg/packages" "github.com/stretchr/testify/require" ) @@ -112,22 +113,22 @@ func TestImports(t *testing.T) { // - ignore subdirs // - ignore duplicate // - should be sorted - expected := map[FileKind][]string{ - FileKindPackageSource: { + expected := map[packages.FileKind][]string{ + packages.FileKindPackageSource: { "gno.land/p/demo/pkg1", "gno.land/p/demo/pkg2", "std", }, - FileKindTest: { + packages.FileKindTest: { "gno.land/p/demo/testpkg", "testing", }, - FileKindXTest: { + packages.FileKindXTest: { "gno.land/p/demo/testpkg", "gno.land/p/demo/xtestdep", "testing", }, - FileKindFiletest: { + packages.FileKindFiletest: { "gno.land/p/demo/filetestdep", }, } @@ -145,11 +146,11 @@ func TestImports(t *testing.T) { pkg, err := gnolang.ReadMemPackage(tmpDir, "test") require.NoError(t, err) - importsMap, err := Imports(pkg, nil) + importsMap, err := packages.Imports(pkg, nil) require.NoError(t, err) // ignore specs - got := map[FileKind][]string{} + got := map[packages.FileKind][]string{} for key, vals := range importsMap { stringVals := make([]string, len(vals)) for i, val := range vals { diff --git a/gnovm/stdlibs/crypto/ed25519/ed25519_test.gno b/gnovm/stdlibs/crypto/ed25519/ed25519_test.gno index 615ea0eb6a7..db0cd1b9c1a 100644 --- a/gnovm/stdlibs/crypto/ed25519/ed25519_test.gno +++ b/gnovm/stdlibs/crypto/ed25519/ed25519_test.gno @@ -1,4 +1,4 @@ -package ed25519 +package ed25519_test import ( "crypto/ed25519" diff --git a/gnovm/stdlibs/crypto/sha256/sha256_test.gno b/gnovm/stdlibs/crypto/sha256/sha256_test.gno index 809f826f007..21e376736a4 100644 --- a/gnovm/stdlibs/crypto/sha256/sha256_test.gno +++ b/gnovm/stdlibs/crypto/sha256/sha256_test.gno @@ -1,4 +1,4 @@ -package sha256 +package sha256_test import ( "crypto/sha256" diff --git a/gnovm/stdlibs/encoding/binary/binary_test.gno b/gnovm/stdlibs/encoding/binary/binary_test.gno index 5407eb5061b..7905c381189 100644 --- a/gnovm/stdlibs/encoding/binary/binary_test.gno +++ b/gnovm/stdlibs/encoding/binary/binary_test.gno @@ -1,4 +1,4 @@ -package binary +package binary_test import ( "bytes" diff --git a/gnovm/stdlibs/generated.go b/gnovm/stdlibs/generated.go index ab35fc6b6bf..6bd45de3589 100644 --- a/gnovm/stdlibs/generated.go +++ b/gnovm/stdlibs/generated.go @@ -800,6 +800,44 @@ var nativeFuncs = [...]NativeFunc{ )) }, }, + { + "testing", + "matchString", + []gno.FieldTypeExpr{ + {Name: gno.N("p0"), Type: gno.X("string")}, + {Name: gno.N("p1"), Type: gno.X("string")}, + }, + []gno.FieldTypeExpr{ + {Name: gno.N("r0"), Type: gno.X("bool")}, + {Name: gno.N("r1"), Type: gno.X("error")}, + }, + false, + func(m *gno.Machine) { + b := m.LastBlock() + var ( + p0 string + rp0 = reflect.ValueOf(&p0).Elem() + p1 string + rp1 = reflect.ValueOf(&p1).Elem() + ) + + gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV, rp0) + gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 1, "")).TV, rp1) + + r0, r1 := libs_testing.X_matchString(p0, p1) + + m.PushValue(gno.Go2GnoValue( + m.Alloc, + m.Store, + reflect.ValueOf(&r0).Elem(), + )) + m.PushValue(gno.Go2GnoValue( + m.Alloc, + m.Store, + reflect.ValueOf(&r1).Elem(), + )) + }, + }, { "time", "now", diff --git a/gnovm/stdlibs/hash/marshal_test.gno b/gnovm/stdlibs/hash/marshal_test.gno index bf823d97cce..d15c02e87b5 100644 --- a/gnovm/stdlibs/hash/marshal_test.gno +++ b/gnovm/stdlibs/hash/marshal_test.gno @@ -6,7 +6,7 @@ // BinaryMarshaler, BinaryUnmarshaler, // and lock in the current representations. -package hash +package hash_test import ( "bytes" diff --git a/gnovm/stdlibs/io/export_test.gno b/gnovm/stdlibs/io/export_test.gno new file mode 100644 index 00000000000..06853f975f5 --- /dev/null +++ b/gnovm/stdlibs/io/export_test.gno @@ -0,0 +1,10 @@ +// Copyright 2020 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package io + +// exported for test +var ErrInvalidWrite = errInvalidWrite +var ErrWhence = errWhence +var ErrOffset = errOffset diff --git a/gnovm/stdlibs/io/io_test.gno b/gnovm/stdlibs/io/io_test.gno index 4915982057b..38e535b3cfb 100644 --- a/gnovm/stdlibs/io/io_test.gno +++ b/gnovm/stdlibs/io/io_test.gno @@ -1,4 +1,4 @@ -package io +package io_test // Copyright 2009 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style @@ -8,6 +8,7 @@ import ( "bytes" "errors" "fmt" + "io" "strings" "testing" ) @@ -15,8 +16,8 @@ import ( // A version of bytes.Buffer without ReadFrom and WriteTo type Buffer struct { bytes.Buffer - ReaderFrom // conflicts with and hides bytes.Buffer's ReaderFrom. - WriterTo // conflicts with and hides bytes.Buffer's WriterTo. + io.ReaderFrom // conflicts with and hides bytes.Buffer's ReaderFrom. + io.WriterTo // conflicts with and hides bytes.Buffer's WriterTo. } // Simple tests, primarily to verify the ReadFrom and WriteTo callouts inside Copy, CopyBuffer and CopyN. @@ -25,7 +26,7 @@ func TestCopy(t *testing.T) { rb := new(Buffer) wb := new(Buffer) rb.WriteString("hello, world.") - Copy(wb, rb) + io.Copy(wb, rb) if wb.String() != "hello, world." { t.Errorf("Copy did not work properly") } @@ -35,12 +36,12 @@ func TestCopyNegative(t *testing.T) { rb := new(Buffer) wb := new(Buffer) rb.WriteString("hello") - Copy(wb, &LimitedReader{R: rb, N: -1}) + io.Copy(wb, &io.LimitedReader{R: rb, N: -1}) if wb.String() != "" { t.Errorf("Copy on LimitedReader with N<0 copied data") } - CopyN(wb, rb, -1) + io.CopyN(wb, rb, -1) if wb.String() != "" { t.Errorf("CopyN with N<0 copied data") } @@ -50,7 +51,7 @@ func TestCopyBuffer(t *testing.T) { rb := new(Buffer) wb := new(Buffer) rb.WriteString("hello, world.") - CopyBuffer(wb, rb, make([]byte, 1)) // Tiny buffer to keep it honest. + io.CopyBuffer(wb, rb, make([]byte, 1)) // Tiny buffer to keep it honest. if wb.String() != "hello, world." { t.Errorf("CopyBuffer did not work properly") } @@ -60,7 +61,7 @@ func TestCopyBufferNil(t *testing.T) { rb := new(Buffer) wb := new(Buffer) rb.WriteString("hello, world.") - CopyBuffer(wb, rb, nil) // Should allocate a buffer. + io.CopyBuffer(wb, rb, nil) // Should allocate a buffer. if wb.String() != "hello, world." { t.Errorf("CopyBuffer did not work properly") } @@ -70,7 +71,7 @@ func TestCopyReadFrom(t *testing.T) { rb := new(Buffer) wb := new(bytes.Buffer) // implements ReadFrom. rb.WriteString("hello, world.") - Copy(wb, rb) + io.Copy(wb, rb) if wb.String() != "hello, world." { t.Errorf("Copy did not work properly") } @@ -80,7 +81,7 @@ func TestCopyWriteTo(t *testing.T) { rb := new(bytes.Buffer) // implements WriteTo. wb := new(Buffer) rb.WriteString("hello, world.") - Copy(wb, rb) + io.Copy(wb, rb) if wb.String() != "hello, world." { t.Errorf("Copy did not work properly") } @@ -92,7 +93,7 @@ type writeToChecker struct { writeToCalled bool } -func (wt *writeToChecker) WriteTo(w Writer) (int64, error) { +func (wt *writeToChecker) WriteTo(w io.Writer) (int64, error) { wt.writeToCalled = true return wt.Buffer.WriteTo(w) } @@ -104,7 +105,7 @@ func TestCopyPriority(t *testing.T) { rb := new(writeToChecker) wb := new(bytes.Buffer) rb.WriteString("hello, world.") - Copy(wb, rb) + io.Copy(wb, rb) if wb.String() != "hello, world." { t.Errorf("Copy did not work properly") } else if !rb.writeToCalled { @@ -134,7 +135,7 @@ func (w errWriter) Write([]byte) (int, error) { func TestCopyReadErrWriteErr(t *testing.T) { er, ew := errors.New("readError"), errors.New("writeError") r, w := zeroErrReader{err: er}, errWriter{err: ew} - n, err := Copy(w, r) + n, err := io.Copy(w, r) if n != 0 || err != ew { t.Errorf("Copy(zeroErrReader, errWriter) = %d, %v; want 0, writeError", n, err) } @@ -144,7 +145,7 @@ func TestCopyN(t *testing.T) { rb := new(Buffer) wb := new(Buffer) rb.WriteString("hello, world.") - CopyN(wb, rb, 5) + io.CopyN(wb, rb, 5) if wb.String() != "hello" { t.Errorf("CopyN did not work properly") } @@ -154,7 +155,7 @@ func TestCopyNReadFrom(t *testing.T) { rb := new(Buffer) wb := new(bytes.Buffer) // implements ReadFrom. rb.WriteString("hello") - CopyN(wb, rb, 5) + io.CopyN(wb, rb, 5) if wb.String() != "hello" { t.Errorf("CopyN did not work properly") } @@ -164,7 +165,7 @@ func TestCopyNWriteTo(t *testing.T) { rb := new(bytes.Buffer) // implements WriteTo. wb := new(Buffer) rb.WriteString("hello, world.") - CopyN(wb, rb, 5) + io.CopyN(wb, rb, 5) if wb.String() != "hello" { t.Errorf("CopyN did not work properly") } @@ -177,7 +178,7 @@ func BenchmarkCopyNSmall(b *testing.B) { b.ResetTimer() for i := 0; i < b.N; i++ { - CopyN(buf, rd, 512) + io.CopyN(buf, rd, 512) rd.Reset(bs) } } @@ -189,13 +190,13 @@ func BenchmarkCopyNLarge(b *testing.B) { b.ResetTimer() for i := 0; i < b.N; i++ { - CopyN(buf, rd, 32*1024) + io.CopyN(buf, rd, 32*1024) rd.Reset(bs) } } type noReadFrom struct { - w Writer + w io.Writer } func (w *noReadFrom) Write(p []byte) (n int, err error) { @@ -214,32 +215,32 @@ func TestCopyNEOF(t *testing.T) { b := new(bytes.Buffer) - n, err := CopyN(&noReadFrom{b}, strings.NewReader("foo"), 3) + n, err := io.CopyN(&noReadFrom{b}, strings.NewReader("foo"), 3) if n != 3 || err != nil { t.Errorf("CopyN(noReadFrom, foo, 3) = %d, %v; want 3, nil", n, err) } - n, err = CopyN(&noReadFrom{b}, strings.NewReader("foo"), 4) - if n != 3 || err != EOF { + n, err = io.CopyN(&noReadFrom{b}, strings.NewReader("foo"), 4) + if n != 3 || err != io.EOF { t.Errorf("CopyN(noReadFrom, foo, 4) = %d, %v; want 3, EOF", n, err) } - n, err = CopyN(b, strings.NewReader("foo"), 3) // b has read from + n, err = io.CopyN(b, strings.NewReader("foo"), 3) // b has read from if n != 3 || err != nil { t.Errorf("CopyN(bytes.Buffer, foo, 3) = %d, %v; want 3, nil", n, err) } - n, err = CopyN(b, strings.NewReader("foo"), 4) // b has read from - if n != 3 || err != EOF { + n, err = io.CopyN(b, strings.NewReader("foo"), 4) // b has read from + if n != 3 || err != io.EOF { t.Errorf("CopyN(bytes.Buffer, foo, 4) = %d, %v; want 3, EOF", n, err) } - n, err = CopyN(b, wantedAndErrReader{}, 5) + n, err = io.CopyN(b, wantedAndErrReader{}, 5) if n != 5 || err != nil { t.Errorf("CopyN(bytes.Buffer, wantedAndErrReader, 5) = %d, %v; want 5, nil", n, err) } - n, err = CopyN(&noReadFrom{b}, wantedAndErrReader{}, 5) + n, err = io.CopyN(&noReadFrom{b}, wantedAndErrReader{}, 5) if n != 5 || err != nil { t.Errorf("CopyN(noReadFrom, wantedAndErrReader, 5) = %d, %v; want 5, nil", n, err) } @@ -267,7 +268,7 @@ func (r *dataAndErrorBuffer) Read(p []byte) (n int, err error) { func TestReadAtLeastWithDataAndEOF(t *testing.T) { var rb dataAndErrorBuffer - rb.err = EOF + rb.err = io.EOF testReadAtLeast(t, &rb) } @@ -277,41 +278,41 @@ func TestReadAtLeastWithDataAndError(t *testing.T) { testReadAtLeast(t, &rb) } -func testReadAtLeast(t *testing.T, rb ReadWriter) { +func testReadAtLeast(t *testing.T, rb io.ReadWriter) { rb.Write([]byte("0123")) buf := make([]byte, 2) - n, err := ReadAtLeast(rb, buf, 2) + n, err := io.ReadAtLeast(rb, buf, 2) if err != nil { t.Error(err) } if n != 2 { t.Errorf("expected to have read 2 bytes, got %v", n) } - n, err = ReadAtLeast(rb, buf, 4) - if err != ErrShortBuffer { + n, err = io.ReadAtLeast(rb, buf, 4) + if err != io.ErrShortBuffer { t.Errorf("expected `ErrShortBuffer` got %v", err) } if n != 0 { t.Errorf("expected to have read 0 bytes, got %v", n) } - n, err = ReadAtLeast(rb, buf, 1) + n, err = io.ReadAtLeast(rb, buf, 1) if err != nil { t.Error(err) } if n != 2 { t.Errorf("expected to have read 2 bytes, got %v", n) } - n, err = ReadAtLeast(rb, buf, 2) - if err != EOF { + n, err = io.ReadAtLeast(rb, buf, 2) + if err != io.EOF { t.Errorf("expected EOF, got %v", err) } if n != 0 { t.Errorf("expected to have read 0 bytes, got %v", n) } rb.Write([]byte("4")) - n, err = ReadAtLeast(rb, buf, 2) - want := ErrUnexpectedEOF - if rb, ok := rb.(*dataAndErrorBuffer); ok && rb.err != EOF { + n, err = io.ReadAtLeast(rb, buf, 2) + want := io.ErrUnexpectedEOF + if rb, ok := rb.(*dataAndErrorBuffer); ok && rb.err != io.EOF { want = rb.err } if err != want { @@ -338,7 +339,7 @@ func TestTeeReader(t *testing.T) { if !bytes.Equal(wb.Bytes(), src) { t.Errorf("bytes written = %q want %q", wb.Bytes(), src) } - if n, err := r.Read(dst); n != 0 || err != EOF { + if n, err := r.Read(dst); n != 0 || err != io.EOF { t.Errorf("r.Read at EOF = %d, %v want 0, EOF", n, err) } rb = bytes.NewBuffer(src) @@ -362,22 +363,22 @@ func TestSectionReader_ReadAt(t *testing.T) { exp string err error }{ - {data: "", off: 0, n: 10, bufLen: 2, at: 0, exp: "", err: EOF}, + {data: "", off: 0, n: 10, bufLen: 2, at: 0, exp: "", err: io.EOF}, {data: dat, off: 0, n: len(dat), bufLen: 0, at: 0, exp: "", err: nil}, - {data: dat, off: len(dat), n: 1, bufLen: 1, at: 0, exp: "", err: EOF}, + {data: dat, off: len(dat), n: 1, bufLen: 1, at: 0, exp: "", err: io.EOF}, {data: dat, off: 0, n: len(dat) + 2, bufLen: len(dat), at: 0, exp: dat, err: nil}, {data: dat, off: 0, n: len(dat), bufLen: len(dat) / 2, at: 0, exp: dat[:len(dat)/2], err: nil}, {data: dat, off: 0, n: len(dat), bufLen: len(dat), at: 0, exp: dat, err: nil}, {data: dat, off: 0, n: len(dat), bufLen: len(dat) / 2, at: 2, exp: dat[2 : 2+len(dat)/2], err: nil}, {data: dat, off: 3, n: len(dat), bufLen: len(dat) / 2, at: 2, exp: dat[5 : 5+len(dat)/2], err: nil}, {data: dat, off: 3, n: len(dat) / 2, bufLen: len(dat)/2 - 2, at: 2, exp: dat[5 : 5+len(dat)/2-2], err: nil}, - {data: dat, off: 3, n: len(dat) / 2, bufLen: len(dat)/2 + 2, at: 2, exp: dat[5 : 5+len(dat)/2-2], err: EOF}, - {data: dat, off: 0, n: 0, bufLen: 0, at: -1, exp: "", err: EOF}, - {data: dat, off: 0, n: 0, bufLen: 0, at: 1, exp: "", err: EOF}, + {data: dat, off: 3, n: len(dat) / 2, bufLen: len(dat)/2 + 2, at: 2, exp: dat[5 : 5+len(dat)/2-2], err: io.EOF}, + {data: dat, off: 0, n: 0, bufLen: 0, at: -1, exp: "", err: io.EOF}, + {data: dat, off: 0, n: 0, bufLen: 0, at: 1, exp: "", err: io.EOF}, } for i, tt := range tests { r := strings.NewReader(tt.data) - s := NewSectionReader(r, int64(tt.off), int64(tt.n)) + s := io.NewSectionReader(r, int64(tt.off), int64(tt.n)) buf := make([]byte, tt.bufLen) if n, err := s.ReadAt(buf, int64(tt.at)); n != len(tt.exp) || string(buf[:n]) != tt.exp || err != tt.err { t.Fatalf("%d: ReadAt(%d) = %q, %v; expected %q, %v", i, tt.at, buf[:n], err, tt.exp, tt.err) @@ -388,9 +389,9 @@ func TestSectionReader_ReadAt(t *testing.T) { func TestSectionReader_Seek(t *testing.T) { // Verifies that NewSectionReader's Seeker behaves like bytes.NewReader (which is like strings.NewReader) br := bytes.NewReader([]byte("foo")) - sr := NewSectionReader(br, 0, int64(len("foo"))) + sr := io.NewSectionReader(br, 0, int64(len("foo"))) - for _, whence := range []int{SeekStart, SeekCurrent, SeekEnd} { + for _, whence := range []int{io.SeekStart, io.SeekCurrent, io.SeekEnd} { for offset := int64(-3); offset <= 4; offset++ { brOff, brErr := br.Seek(offset, whence) srOff, srErr := sr.Seek(offset, whence) @@ -402,13 +403,13 @@ func TestSectionReader_Seek(t *testing.T) { } // And verify we can just seek past the end and get an EOF - got, err := sr.Seek(100, SeekStart) + got, err := sr.Seek(100, io.SeekStart) if err != nil || got != 100 { t.Errorf("Seek = %v, %v; want 100, nil", got, err) } n, err := sr.Read(make([]byte, 10)) - if n != 0 || err != EOF { + if n != 0 || err != io.EOF { t.Errorf("Read = %v, %v; want 0, EOF", n, err) } } @@ -424,7 +425,7 @@ func TestSectionReader_Size(t *testing.T) { for _, tt := range tests { r := strings.NewReader(tt.data) - sr := NewSectionReader(r, 0, int64(len(tt.data))) + sr := io.NewSectionReader(r, 0, int64(len(tt.data))) if got := sr.Size(); got != tt.want { t.Errorf("Size = %v; want %v", got, tt.want) } @@ -442,11 +443,11 @@ func (w largeWriter) Write(p []byte) (int, error) { } func TestCopyLargeWriter(t *testing.T) { - want := errInvalidWrite + want := io.ErrInvalidWrite rb := new(Buffer) wb := largeWriter{} rb.WriteString("hello, world.") - if _, err := Copy(wb, rb); err != want { + if _, err := io.Copy(wb, rb); err != want { t.Errorf("Copy error: got %v, want %v", err, want) } @@ -454,7 +455,7 @@ func TestCopyLargeWriter(t *testing.T) { rb = new(Buffer) wb = largeWriter{err: want} rb.WriteString("hello, world.") - if _, err := Copy(wb, rb); err != want { + if _, err := io.Copy(wb, rb); err != want { t.Errorf("Copy error: got %v, want %v", err, want) } } @@ -462,18 +463,18 @@ func TestCopyLargeWriter(t *testing.T) { func TestNopCloserWriterToForwarding(t *testing.T) { for _, tc := range [...]struct { Name string - r Reader + r io.Reader }{ - {"not a WriterTo", Reader(nil)}, + {"not a WriterTo", io.Reader(nil)}, {"a WriterTo", struct { - Reader - WriterTo + io.Reader + io.WriterTo }{}}, } { - nc := NopCloser(tc.r) + nc := io.NopCloser(tc.r) - _, expected := tc.r.(WriterTo) - _, got := nc.(WriterTo) + _, expected := tc.r.(io.WriterTo) + _, got := nc.(io.WriterTo) if expected != got { t.Errorf("NopCloser incorrectly forwards WriterTo for %s, got %t want %t", tc.Name, got, expected) } @@ -488,28 +489,28 @@ func TestNopCloserWriterToForwarding(t *testing.T) { // t.Fatalf("CreateTemp(%s) failed: %v", tmpfilename, err) // } // defer tmpfile.Close() -// w := NewOffsetWriter(tmpfile, 0) +// w := io.NewOffsetWriter(tmpfile, 0) // // Should throw error errWhence if whence is not valid // t.Run("errWhence", func(t *testing.T) { // for _, whence := range []int{-3, -2, -1, 3, 4, 5} { // var offset int64 = 0 // gotOff, gotErr := w.Seek(offset, whence) -// if gotOff != 0 || gotErr != errWhence { +// if gotOff != 0 || gotErr != io.ErrWhence { // t.Errorf("For whence %d, offset %d, OffsetWriter.Seek got: (%d, %v), want: (%d, %v)", -// whence, offset, gotOff, gotErr, 0, errWhence) +// whence, offset, gotOff, gotErr, 0, io.ErrWhence) // } // } // }) // // Should throw error errOffset if offset is negative // t.Run("errOffset", func(t *testing.T) { -// for _, whence := range []int{SeekStart, SeekCurrent} { +// for _, whence := range []int{io.SeekStart, io.SeekCurrent} { // for offset := int64(-3); offset < 0; offset++ { // gotOff, gotErr := w.Seek(offset, whence) -// if gotOff != 0 || gotErr != errOffset { +// if gotOff != 0 || gotErr != io.ErrOffset { // t.Errorf("For whence %d, offset %d, OffsetWriter.Seek got: (%d, %v), want: (%d, %v)", -// whence, offset, gotOff, gotErr, 0, ErrOffset) +// whence, offset, gotOff, gotErr, 0, io.ErrOffset) // } // } // } @@ -523,12 +524,12 @@ func TestNopCloserWriterToForwarding(t *testing.T) { // returnOff int64 // }{ // // keep in order -// {whence: SeekStart, offset: 1, returnOff: 1}, -// {whence: SeekStart, offset: 2, returnOff: 2}, -// {whence: SeekStart, offset: 3, returnOff: 3}, -// {whence: SeekCurrent, offset: 1, returnOff: 4}, -// {whence: SeekCurrent, offset: 2, returnOff: 6}, -// {whence: SeekCurrent, offset: 3, returnOff: 9}, +// {whence: io.SeekStart, offset: 1, returnOff: 1}, +// {whence: io.SeekStart, offset: 2, returnOff: 2}, +// {whence: io.SeekStart, offset: 3, returnOff: 3}, +// {whence: io.SeekCurrent, offset: 1, returnOff: 4}, +// {whence: io.SeekCurrent, offset: 2, returnOff: 6}, +// {whence: io.SeekCurrent, offset: 3, returnOff: 9}, // } // for idx, tt := range tests { // gotOff, gotErr := w.Seek(tt.offset, tt.whence) @@ -546,28 +547,28 @@ func TestNopCloserWriterToForwarding(t *testing.T) { // to use the original approach instead of this method. (just un-comment the test above) func TestOffsetWriter_Seek(t *testing.T) { buf := new(bytes.Buffer) - w := NewOffsetWriter(testWriterAt{buf}, 0) + w := io.NewOffsetWriter(testWriterAt{buf}, 0) // Should throw error errWhence if whence is not valid t.Run("errWhence", func(t *testing.T) { for _, whence := range []int{-3, -2, -1, 3, 4, 5} { var offset int64 = 0 gotOff, gotErr := w.Seek(offset, whence) - if gotOff != 0 || gotErr != errWhence { + if gotOff != 0 || gotErr != io.ErrWhence { t.Errorf("For whence %d, offset %d, OffsetWriter.Seek got: (%d, %v), want: (%d, %v)", - whence, offset, gotOff, gotErr, 0, errWhence) + whence, offset, gotOff, gotErr, 0, io.ErrWhence) } } }) // Should throw error errOffset if offset is negative t.Run("errOffset", func(t *testing.T) { - for _, whence := range []int{SeekStart, SeekCurrent} { + for _, whence := range []int{io.SeekStart, io.SeekCurrent} { for offset := int64(-3); offset < 0; offset++ { gotOff, gotErr := w.Seek(offset, whence) - if gotOff != 0 || gotErr != errOffset { + if gotOff != 0 || gotErr != io.ErrOffset { t.Errorf("For whence %d, offset %d, OffsetWriter.Seek got: (%d, %v), want: (%d, %v)", - whence, offset, gotOff, gotErr, 0, errOffset) + whence, offset, gotOff, gotErr, 0, io.ErrOffset) } } } @@ -579,12 +580,12 @@ func TestOffsetWriter_Seek(t *testing.T) { whence int returnOff int64 }{ - {whence: SeekStart, offset: 1, returnOff: 1}, - {whence: SeekStart, offset: 2, returnOff: 2}, - {whence: SeekStart, offset: 3, returnOff: 3}, - {whence: SeekCurrent, offset: 1, returnOff: 4}, - {whence: SeekCurrent, offset: 2, returnOff: 6}, - {whence: SeekCurrent, offset: 3, returnOff: 9}, + {whence: io.SeekStart, offset: 1, returnOff: 1}, + {whence: io.SeekStart, offset: 2, returnOff: 2}, + {whence: io.SeekStart, offset: 3, returnOff: 3}, + {whence: io.SeekCurrent, offset: 1, returnOff: 4}, + {whence: io.SeekCurrent, offset: 2, returnOff: 6}, + {whence: io.SeekCurrent, offset: 3, returnOff: 9}, } for idx, tt := range tests { gotOff, gotErr := w.Seek(tt.offset, tt.whence) diff --git a/gnovm/stdlibs/io/multi_test.gno b/gnovm/stdlibs/io/multi_test.gno index f39895ea776..908dffd3cce 100644 --- a/gnovm/stdlibs/io/multi_test.gno +++ b/gnovm/stdlibs/io/multi_test.gno @@ -1,4 +1,4 @@ -package io +package io_test // Copyright 2010 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style @@ -8,6 +8,7 @@ import ( "bytes" "crypto/sha256" "fmt" + "io" "strings" "testing" ) @@ -17,14 +18,14 @@ type Stringer interface { } func TestMultiReader(t *testing.T) { - var mr Reader + var mr io.Reader var buf []byte nread := 0 withFooBar := func(tests func()) { r1 := strings.NewReader("foo ") r2 := strings.NewReader("") r3 := strings.NewReader("bar") - mr = MultiReader(r1, r2, r3) + mr = io.MultiReader(r1, r2, r3) buf = make([]byte, 20) tests() } @@ -50,13 +51,13 @@ func TestMultiReader(t *testing.T) { expectRead(2, "fo", nil) expectRead(5, "o ", nil) expectRead(5, "bar", nil) - expectRead(5, "", EOF) + expectRead(5, "", io.EOF) }) withFooBar(func() { expectRead(4, "foo ", nil) expectRead(1, "b", nil) expectRead(3, "ar", nil) - expectRead(1, "", EOF) + expectRead(1, "", io.EOF) }) withFooBar(func() { expectRead(5, "foo ", nil) @@ -67,7 +68,7 @@ func TestMultiWriter(t *testing.T) { sink := new(bytes.Buffer) // Hide bytes.Buffer's WriteString method: testMultiWriter(t, struct { - Writer + io.Writer Stringer }{sink, sink}) } @@ -82,11 +83,11 @@ func TestMultiWriter_String(t *testing.T) { func TestMultiWriter_WriteStringSingleAlloc(t *testing.T) { var sink1, sink2 bytes.Buffer type simpleWriter struct { // hide bytes.Buffer's WriteString - Writer + io.Writer } - mw := MultiWriter(simpleWriter{&sink1}, simpleWriter{&sink2}) + mw := io.MultiWriter(simpleWriter{&sink1}, simpleWriter{&sink2}) allocs := int(testing.AllocsPerRun2(1000, func() { - WriteString(mw, "foo") + io.WriteString(mw, "foo") })) if allocs != 1 { t.Errorf("num allocations = %d; want 1", allocs) @@ -107,24 +108,24 @@ func (c *writeStringChecker) Write(p []byte) (n int, err error) { func TestMultiWriter_StringCheckCall(t *testing.T) { var c writeStringChecker - mw := MultiWriter(&c) - WriteString(mw, "foo") + mw := io.MultiWriter(&c) + io.WriteString(mw, "foo") if !c.called { t.Error("did not see WriteString call to writeStringChecker") } } func testMultiWriter(t *testing.T, sink interface { - Writer + io.Writer Stringer }, ) { var buf bytes.Buffer - mw := MultiWriter(&buf, sink) + mw := io.MultiWriter(&buf, sink) sourceString := "My input text." source := strings.NewReader(sourceString) - written, err := Copy(mw, source) + written, err := io.Copy(mw, source) if written != int64(len(sourceString)) { t.Errorf("short write of %d, not %d", written, len(sourceString)) @@ -158,7 +159,7 @@ func TestMultiWriterSingleChainFlatten(t *testing.T) { n := runtime.Callers(0, pc) var myDepth = callDepth(pc[:n]) var writeDepth int // will contain the depth from which writerFunc.Writer was called - var w Writer = MultiWriter(writerFunc(func(p []byte) (int, error) { + var w io.Writer = io.MultiWriter(writerFunc(func(p []byte) (int, error) { n := runtime.Callers(1, pc) writeDepth += callDepth(pc[:n]) return 0, nil @@ -167,10 +168,10 @@ func TestMultiWriterSingleChainFlatten(t *testing.T) { mw := w // chain a bunch of multiWriters for i := 0; i < 100; i++ { - mw = MultiWriter(w) + mw = io.MultiWriter(w) } - mw = MultiWriter(w, mw, w, mw) + mw = io.MultiWriter(w, mw, w, mw) mw.Write(nil) // don't care about errors, just want to check the call-depth for Write if writeDepth != 4*(myDepth+2) { // 2 should be multiWriter.Write and writerFunc.Write @@ -182,25 +183,25 @@ func TestMultiWriterSingleChainFlatten(t *testing.T) { func TestMultiWriterError(t *testing.T) { f1 := writerFunc(func(p []byte) (int, error) { - return len(p) / 2, ErrShortWrite + return len(p) / 2, io.ErrShortWrite }) f2 := writerFunc(func(p []byte) (int, error) { t.Errorf("MultiWriter called f2.Write") return len(p), nil }) - w := MultiWriter(f1, f2) + w := io.MultiWriter(f1, f2) n, err := w.Write(make([]byte, 100)) - if n != 50 || err != ErrShortWrite { + if n != 50 || err != io.ErrShortWrite { t.Errorf("Write = %d, %v, want 50, ErrShortWrite", n, err) } } // Test that MultiReader copies the input slice and is insulated from future modification. func TestMultiReaderCopy(t *testing.T) { - slice := []Reader{strings.NewReader("hello world")} - r := MultiReader(slice...) + slice := []io.Reader{strings.NewReader("hello world")} + r := io.MultiReader(slice...) slice[0] = nil - data, err := ReadAll(r) + data, err := io.ReadAll(r) if err != nil || string(data) != "hello world" { t.Errorf("ReadAll() = %q, %v, want %q, nil", data, err, "hello world") } @@ -209,8 +210,8 @@ func TestMultiReaderCopy(t *testing.T) { // Test that MultiWriter copies the input slice and is insulated from future modification. func TestMultiWriterCopy(t *testing.T) { var buf bytes.Buffer - slice := []Writer{&buf} - w := MultiWriter(slice...) + slice := []io.Writer{&buf} + w := io.MultiWriter(slice...) slice[0] = nil n, err := w.Write([]byte("hello world")) if err != nil || n != 11 { @@ -246,7 +247,7 @@ func TestMultiReaderFlatten(t *testing.T) { n := runtime.Callers(0, pc) var myDepth = callDepth(pc[:n]) var readDepth int // will contain the depth from which fakeReader.Read was called - var r Reader = MultiReader(readerFunc(func(p []byte) (int, error) { + var r io.Reader = io.MultiReader(readerFunc(func(p []byte) (int, error) { n := runtime.Callers(1, pc) readDepth = callDepth(pc[:n]) return 0, errors.New("irrelevant") @@ -254,7 +255,7 @@ func TestMultiReaderFlatten(t *testing.T) { // chain a bunch of multiReaders for i := 0; i < 100; i++ { - r = MultiReader(r) + r = io.MultiReader(r) } r.Read(nil) // don't care about errors, just want to check the call-depth for Read @@ -277,12 +278,12 @@ func (b byteAndEOFReader) Read(p []byte) (n int, err error) { panic("unexpected call") } p[0] = byte(b) - return 1, EOF + return 1, io.EOF } // This used to yield bytes forever; issue 16795. func TestMultiReaderSingleByteWithEOF(t *testing.T) { - got, err := ReadAll(LimitReader(MultiReader(byteAndEOFReader('a'), byteAndEOFReader('b')), 10)) + got, err := io.ReadAll(io.LimitReader(io.MultiReader(byteAndEOFReader('a'), byteAndEOFReader('b')), 10)) if err != nil { t.Fatal(err) } @@ -296,17 +297,17 @@ func TestMultiReaderSingleByteWithEOF(t *testing.T) { // chain continues to return EOF on its final read, rather than // yielding a (0, EOF). func TestMultiReaderFinalEOF(t *testing.T) { - r := MultiReader(bytes.NewReader(nil), byteAndEOFReader('a')) + r := io.MultiReader(bytes.NewReader(nil), byteAndEOFReader('a')) buf := make([]byte, 2) n, err := r.Read(buf) - if n != 1 || err != EOF { + if n != 1 || err != io.EOF { t.Errorf("got %v, %v; want 1, EOF", n, err) } } /* func TestMultiReaderFreesExhaustedReaders(t *testing.T) { - var mr Reader + var mr io.Reader closed := make(chan struct{}) // The closure ensures that we don't have a live reference to buf1 // on our stack after MultiReader is inlined (Issue 18819). This @@ -314,14 +315,14 @@ func TestMultiReaderFreesExhaustedReaders(t *testing.T) { func() { buf1 := bytes.NewReader([]byte("foo")) buf2 := bytes.NewReader([]byte("bar")) - mr = MultiReader(buf1, buf2) - runtime.SetFinalizer(buf1, func(*bytes.Reader) { + mr = io.MultiReader(buf1, buf2) + runtime.SetFinalizer(buf1, func(*bytes.io.Reader) { close(closed) }) }() buf := make([]byte, 4) - if n, err := ReadFull(mr, buf); err != nil || string(buf) != "foob" { + if n, err := io.ReadFull(mr, buf); err != nil || string(buf) != "foob" { t.Fatalf(`ReadFull = %d (%q), %v; want 3, "foo", nil`, n, buf[:n], err) } @@ -332,7 +333,7 @@ func TestMultiReaderFreesExhaustedReaders(t *testing.T) { t.Fatal("timeout waiting for collection of buf1") } - if n, err := ReadFull(mr, buf[:2]); err != nil || string(buf[:2]) != "ar" { + if n, err := io.ReadFull(mr, buf[:2]); err != nil || string(buf[:2]) != "ar" { t.Fatalf(`ReadFull = %d (%q), %v; want 2, "ar", nil`, n, buf[:n], err) } } @@ -342,21 +343,21 @@ func TestInterleavedMultiReader(t *testing.T) { r1 := strings.NewReader("123") r2 := strings.NewReader("45678") - mr1 := MultiReader(r1, r2) - mr2 := MultiReader(mr1) + mr1 := io.MultiReader(r1, r2) + mr2 := io.MultiReader(mr1) buf := make([]byte, 4) // Have mr2 use mr1's []Readers. // Consume r1 (and clear it for GC to handle) and consume part of r2. - n, err := ReadFull(mr2, buf) + n, err := io.ReadFull(mr2, buf) if got := string(buf[:n]); got != "1234" || err != nil { t.Errorf(`ReadFull(mr2) = (%q, %v), want ("1234", nil)`, got, err) } // Consume the rest of r2 via mr1. // This should not panic even though mr2 cleared r1. - n, err = ReadFull(mr1, buf) + n, err = io.ReadFull(mr1, buf) if got := string(buf[:n]); got != "5678" || err != nil { t.Errorf(`ReadFull(mr1) = (%q, %v), want ("5678", nil)`, got, err) } diff --git a/gnovm/stdlibs/math/bits/bits_test.gno b/gnovm/stdlibs/math/bits/bits_test.gno index e2834cacf7c..b92de1b94d2 100644 --- a/gnovm/stdlibs/math/bits/bits_test.gno +++ b/gnovm/stdlibs/math/bits/bits_test.gno @@ -2,9 +2,10 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package bits +package bits_test import ( + "math/bits" "testing" ) @@ -14,7 +15,7 @@ func TestLeadingZeros(t *testing.T) { for k := 0; k < 64-8; k++ { x := uint64(i) << uint(k) if x <= 1<<8-1 { - got := LeadingZeros8(uint8(x)) + got := bits.LeadingZeros8(uint8(x)) want := nlz - k + (8 - 8) if x == 0 { want = 8 @@ -25,7 +26,7 @@ func TestLeadingZeros(t *testing.T) { } if x <= 1<<16-1 { - got := LeadingZeros16(uint16(x)) + got := bits.LeadingZeros16(uint16(x)) want := nlz - k + (16 - 8) if x == 0 { want = 16 @@ -36,7 +37,7 @@ func TestLeadingZeros(t *testing.T) { } if x <= 1<<32-1 { - got := LeadingZeros32(uint32(x)) + got := bits.LeadingZeros32(uint32(x)) want := nlz - k + (32 - 8) if x == 0 { want = 32 @@ -44,8 +45,8 @@ func TestLeadingZeros(t *testing.T) { if got != want { t.Fatalf("LeadingZeros32(%#08x) == %d; want %d", x, got, want) } - if UintSize == 32 { - got = LeadingZeros(uint(x)) + if bits.UintSize == 32 { + got = bits.LeadingZeros(uint(x)) if got != want { t.Fatalf("LeadingZeros(%#08x) == %d; want %d", x, got, want) } @@ -53,7 +54,7 @@ func TestLeadingZeros(t *testing.T) { } if x <= 1<<64-1 { - got := LeadingZeros64(uint64(x)) + got := bits.LeadingZeros64(uint64(x)) want := nlz - k + (64 - 8) if x == 0 { want = 64 @@ -61,8 +62,8 @@ func TestLeadingZeros(t *testing.T) { if got != want { t.Fatalf("LeadingZeros64(%#016x) == %d; want %d", x, got, want) } - if UintSize == 64 { - got = LeadingZeros(uint(x)) + if bits.UintSize == 64 { + got = bits.LeadingZeros(uint(x)) if got != want { t.Fatalf("LeadingZeros(%#016x) == %d; want %d", x, got, want) } @@ -75,7 +76,7 @@ func TestLeadingZeros(t *testing.T) { // Exported (global) variable serving as input for some // of the benchmarks to ensure side-effect free calls // are not optimized away. -var Input uint64 = DeBruijn64 +var Input uint64 = bits.DeBruijn64 // Exported (global) variable to store function results // during benchmarking to ensure side-effect free calls @@ -85,7 +86,7 @@ var Output int func BenchmarkLeadingZeros(b *testing.B) { var s int for i := 0; i < b.N; i++ { - s += LeadingZeros(uint(Input) >> (uint(i) % UintSize)) + s += bits.LeadingZeros(uint(Input) >> (uint(i) % bits.UintSize)) } Output = s } @@ -93,7 +94,7 @@ func BenchmarkLeadingZeros(b *testing.B) { func BenchmarkLeadingZeros8(b *testing.B) { var s int for i := 0; i < b.N; i++ { - s += LeadingZeros8(uint8(Input) >> (uint(i) % 8)) + s += bits.LeadingZeros8(uint8(Input) >> (uint(i) % 8)) } Output = s } @@ -101,7 +102,7 @@ func BenchmarkLeadingZeros8(b *testing.B) { func BenchmarkLeadingZeros16(b *testing.B) { var s int for i := 0; i < b.N; i++ { - s += LeadingZeros16(uint16(Input) >> (uint(i) % 16)) + s += bits.LeadingZeros16(uint16(Input) >> (uint(i) % 16)) } Output = s } @@ -109,7 +110,7 @@ func BenchmarkLeadingZeros16(b *testing.B) { func BenchmarkLeadingZeros32(b *testing.B) { var s int for i := 0; i < b.N; i++ { - s += LeadingZeros32(uint32(Input) >> (uint(i) % 32)) + s += bits.LeadingZeros32(uint32(Input) >> (uint(i) % 32)) } Output = s } @@ -117,7 +118,7 @@ func BenchmarkLeadingZeros32(b *testing.B) { func BenchmarkLeadingZeros64(b *testing.B) { var s int for i := 0; i < b.N; i++ { - s += LeadingZeros64(uint64(Input) >> (uint(i) % 64)) + s += bits.LeadingZeros64(uint64(Input) >> (uint(i) % 64)) } Output = s } @@ -129,7 +130,7 @@ func TestTrailingZeros(t *testing.T) { x := uint64(i) << uint(k) want := ntz + k if x <= 1<<8-1 { - got := TrailingZeros8(uint8(x)) + got := bits.TrailingZeros8(uint8(x)) if x == 0 { want = 8 } @@ -139,7 +140,7 @@ func TestTrailingZeros(t *testing.T) { } if x <= 1<<16-1 { - got := TrailingZeros16(uint16(x)) + got := bits.TrailingZeros16(uint16(x)) if x == 0 { want = 16 } @@ -149,15 +150,15 @@ func TestTrailingZeros(t *testing.T) { } if x <= 1<<32-1 { - got := TrailingZeros32(uint32(x)) + got := bits.TrailingZeros32(uint32(x)) if x == 0 { want = 32 } if got != want { t.Fatalf("TrailingZeros32(%#08x) == %d; want %d", x, got, want) } - if UintSize == 32 { - got = TrailingZeros(uint(x)) + if bits.UintSize == 32 { + got = bits.TrailingZeros(uint(x)) if got != want { t.Fatalf("TrailingZeros(%#08x) == %d; want %d", x, got, want) } @@ -165,15 +166,15 @@ func TestTrailingZeros(t *testing.T) { } if x <= 1<<64-1 { - got := TrailingZeros64(uint64(x)) + got := bits.TrailingZeros64(uint64(x)) if x == 0 { want = 64 } if got != want { t.Fatalf("TrailingZeros64(%#016x) == %d; want %d", x, got, want) } - if UintSize == 64 { - got = TrailingZeros(uint(x)) + if bits.UintSize == 64 { + got = bits.TrailingZeros(uint(x)) if got != want { t.Fatalf("TrailingZeros(%#016x) == %d; want %d", x, got, want) } @@ -186,7 +187,7 @@ func TestTrailingZeros(t *testing.T) { func BenchmarkTrailingZeros(b *testing.B) { var s int for i := 0; i < b.N; i++ { - s += TrailingZeros(uint(Input) << (uint(i) % UintSize)) + s += bits.TrailingZeros(uint(Input) << (uint(i) % bits.UintSize)) } Output = s } @@ -194,7 +195,7 @@ func BenchmarkTrailingZeros(b *testing.B) { func BenchmarkTrailingZeros8(b *testing.B) { var s int for i := 0; i < b.N; i++ { - s += TrailingZeros8(uint8(Input) << (uint(i) % 8)) + s += bits.TrailingZeros8(uint8(Input) << (uint(i) % 8)) } Output = s } @@ -202,7 +203,7 @@ func BenchmarkTrailingZeros8(b *testing.B) { func BenchmarkTrailingZeros16(b *testing.B) { var s int for i := 0; i < b.N; i++ { - s += TrailingZeros16(uint16(Input) << (uint(i) % 16)) + s += bits.TrailingZeros16(uint16(Input) << (uint(i) % 16)) } Output = s } @@ -210,7 +211,7 @@ func BenchmarkTrailingZeros16(b *testing.B) { func BenchmarkTrailingZeros32(b *testing.B) { var s int for i := 0; i < b.N; i++ { - s += TrailingZeros32(uint32(Input) << (uint(i) % 32)) + s += bits.TrailingZeros32(uint32(Input) << (uint(i) % 32)) } Output = s } @@ -218,7 +219,7 @@ func BenchmarkTrailingZeros32(b *testing.B) { func BenchmarkTrailingZeros64(b *testing.B) { var s int for i := 0; i < b.N; i++ { - s += TrailingZeros64(uint64(Input) << (uint(i) % 64)) + s += bits.TrailingZeros64(uint64(Input) << (uint(i) % 64)) } Output = s } @@ -244,26 +245,26 @@ func TestOnesCount(t *testing.T) { func testOnesCount(t *testing.T, x uint64, want int) { if x <= 1<<8-1 { - got := OnesCount8(uint8(x)) + got := bits.OnesCount8(uint8(x)) if got != want { t.Fatalf("OnesCount8(%#02x) == %d; want %d", uint8(x), got, want) } } if x <= 1<<16-1 { - got := OnesCount16(uint16(x)) + got := bits.OnesCount16(uint16(x)) if got != want { t.Fatalf("OnesCount16(%#04x) == %d; want %d", uint16(x), got, want) } } if x <= 1<<32-1 { - got := OnesCount32(uint32(x)) + got := bits.OnesCount32(uint32(x)) if got != want { t.Fatalf("OnesCount32(%#08x) == %d; want %d", uint32(x), got, want) } - if UintSize == 32 { - got = OnesCount(uint(x)) + if bits.UintSize == 32 { + got = bits.OnesCount(uint(x)) if got != want { t.Fatalf("OnesCount(%#08x) == %d; want %d", uint32(x), got, want) } @@ -271,12 +272,12 @@ func testOnesCount(t *testing.T, x uint64, want int) { } if x <= 1<<64-1 { - got := OnesCount64(uint64(x)) + got := bits.OnesCount64(uint64(x)) if got != want { t.Fatalf("OnesCount64(%#016x) == %d; want %d", x, got, want) } - if UintSize == 64 { - got = OnesCount(uint(x)) + if bits.UintSize == 64 { + got = bits.OnesCount(uint(x)) if got != want { t.Fatalf("OnesCount(%#016x) == %d; want %d", x, got, want) } @@ -287,7 +288,7 @@ func testOnesCount(t *testing.T, x uint64, want int) { func BenchmarkOnesCount(b *testing.B) { var s int for i := 0; i < b.N; i++ { - s += OnesCount(uint(Input)) + s += bits.OnesCount(uint(Input)) } Output = s } @@ -295,7 +296,7 @@ func BenchmarkOnesCount(b *testing.B) { func BenchmarkOnesCount8(b *testing.B) { var s int for i := 0; i < b.N; i++ { - s += OnesCount8(uint8(Input)) + s += bits.OnesCount8(uint8(Input)) } Output = s } @@ -303,7 +304,7 @@ func BenchmarkOnesCount8(b *testing.B) { func BenchmarkOnesCount16(b *testing.B) { var s int for i := 0; i < b.N; i++ { - s += OnesCount16(uint16(Input)) + s += bits.OnesCount16(uint16(Input)) } Output = s } @@ -311,7 +312,7 @@ func BenchmarkOnesCount16(b *testing.B) { func BenchmarkOnesCount32(b *testing.B) { var s int for i := 0; i < b.N; i++ { - s += OnesCount32(uint32(Input)) + s += bits.OnesCount32(uint32(Input)) } Output = s } @@ -319,78 +320,78 @@ func BenchmarkOnesCount32(b *testing.B) { func BenchmarkOnesCount64(b *testing.B) { var s int for i := 0; i < b.N; i++ { - s += OnesCount64(uint64(Input)) + s += bits.OnesCount64(uint64(Input)) } Output = s } func TestRotateLeft(t *testing.T) { - var m uint64 = DeBruijn64 + var m uint64 = bits.DeBruijn64 for k := uint(0); k < 128; k++ { x8 := uint8(m) - got8 := RotateLeft8(x8, int(k)) + got8 := bits.RotateLeft8(x8, int(k)) want8 := x8<<(k&0x7) | x8>>(8-k&0x7) if got8 != want8 { t.Fatalf("RotateLeft8(%#02x, %d) == %#02x; want %#02x", x8, k, got8, want8) } - got8 = RotateLeft8(want8, -int(k)) + got8 = bits.RotateLeft8(want8, -int(k)) if got8 != x8 { t.Fatalf("RotateLeft8(%#02x, -%d) == %#02x; want %#02x", want8, k, got8, x8) } x16 := uint16(m) - got16 := RotateLeft16(x16, int(k)) + got16 := bits.RotateLeft16(x16, int(k)) want16 := x16<<(k&0xf) | x16>>(16-k&0xf) if got16 != want16 { t.Fatalf("RotateLeft16(%#04x, %d) == %#04x; want %#04x", x16, k, got16, want16) } - got16 = RotateLeft16(want16, -int(k)) + got16 = bits.RotateLeft16(want16, -int(k)) if got16 != x16 { t.Fatalf("RotateLeft16(%#04x, -%d) == %#04x; want %#04x", want16, k, got16, x16) } x32 := uint32(m) - got32 := RotateLeft32(x32, int(k)) + got32 := bits.RotateLeft32(x32, int(k)) want32 := x32<<(k&0x1f) | x32>>(32-k&0x1f) if got32 != want32 { t.Fatalf("RotateLeft32(%#08x, %d) == %#08x; want %#08x", x32, k, got32, want32) } - got32 = RotateLeft32(want32, -int(k)) + got32 = bits.RotateLeft32(want32, -int(k)) if got32 != x32 { t.Fatalf("RotateLeft32(%#08x, -%d) == %#08x; want %#08x", want32, k, got32, x32) } - if UintSize == 32 { + if bits.UintSize == 32 { x := uint(m) - got := RotateLeft(x, int(k)) + got := bits.RotateLeft(x, int(k)) want := x<<(k&0x1f) | x>>(32-k&0x1f) if got != want { t.Fatalf("RotateLeft(%#08x, %d) == %#08x; want %#08x", x, k, got, want) } - got = RotateLeft(want, -int(k)) + got = bits.RotateLeft(want, -int(k)) if got != x { t.Fatalf("RotateLeft(%#08x, -%d) == %#08x; want %#08x", want, k, got, x) } } x64 := uint64(m) - got64 := RotateLeft64(x64, int(k)) + got64 := bits.RotateLeft64(x64, int(k)) want64 := x64<<(k&0x3f) | x64>>(64-k&0x3f) if got64 != want64 { t.Fatalf("RotateLeft64(%#016x, %d) == %#016x; want %#016x", x64, k, got64, want64) } - got64 = RotateLeft64(want64, -int(k)) + got64 = bits.RotateLeft64(want64, -int(k)) if got64 != x64 { t.Fatalf("RotateLeft64(%#016x, -%d) == %#016x; want %#016x", want64, k, got64, x64) } - if UintSize == 64 { + if bits.UintSize == 64 { x := uint(m) - got := RotateLeft(x, int(k)) + got := bits.RotateLeft(x, int(k)) want := x<<(k&0x3f) | x>>(64-k&0x3f) if got != want { t.Fatalf("RotateLeft(%#016x, %d) == %#016x; want %#016x", x, k, got, want) } - got = RotateLeft(want, -int(k)) + got = bits.RotateLeft(want, -int(k)) if got != x { t.Fatalf("RotateLeft(%#08x, -%d) == %#08x; want %#08x", want, k, got, x) } @@ -401,7 +402,7 @@ func TestRotateLeft(t *testing.T) { func BenchmarkRotateLeft(b *testing.B) { var s uint for i := 0; i < b.N; i++ { - s += RotateLeft(uint(Input), i) + s += bits.RotateLeft(uint(Input), i) } Output = int(s) } @@ -409,7 +410,7 @@ func BenchmarkRotateLeft(b *testing.B) { func BenchmarkRotateLeft8(b *testing.B) { var s uint8 for i := 0; i < b.N; i++ { - s += RotateLeft8(uint8(Input), i) + s += bits.RotateLeft8(uint8(Input), i) } Output = int(s) } @@ -417,7 +418,7 @@ func BenchmarkRotateLeft8(b *testing.B) { func BenchmarkRotateLeft16(b *testing.B) { var s uint16 for i := 0; i < b.N; i++ { - s += RotateLeft16(uint16(Input), i) + s += bits.RotateLeft16(uint16(Input), i) } Output = int(s) } @@ -425,7 +426,7 @@ func BenchmarkRotateLeft16(b *testing.B) { func BenchmarkRotateLeft32(b *testing.B) { var s uint32 for i := 0; i < b.N; i++ { - s += RotateLeft32(uint32(Input), i) + s += bits.RotateLeft32(uint32(Input), i) } Output = int(s) } @@ -433,7 +434,7 @@ func BenchmarkRotateLeft32(b *testing.B) { func BenchmarkRotateLeft64(b *testing.B) { var s uint64 for i := 0; i < b.N; i++ { - s += RotateLeft64(uint64(Input), i) + s += bits.RotateLeft64(uint64(Input), i) } Output = int(s) } @@ -474,41 +475,41 @@ func TestReverse(t *testing.T) { func testReverse(t *testing.T, x64, want64 uint64) { x8 := uint8(x64) - got8 := Reverse8(x8) + got8 := bits.Reverse8(x8) want8 := uint8(want64 >> (64 - 8)) if got8 != want8 { t.Fatalf("Reverse8(%#02x) == %#02x; want %#02x", x8, got8, want8) } x16 := uint16(x64) - got16 := Reverse16(x16) + got16 := bits.Reverse16(x16) want16 := uint16(want64 >> (64 - 16)) if got16 != want16 { t.Fatalf("Reverse16(%#04x) == %#04x; want %#04x", x16, got16, want16) } x32 := uint32(x64) - got32 := Reverse32(x32) + got32 := bits.Reverse32(x32) want32 := uint32(want64 >> (64 - 32)) if got32 != want32 { t.Fatalf("Reverse32(%#08x) == %#08x; want %#08x", x32, got32, want32) } - if UintSize == 32 { + if bits.UintSize == 32 { x := uint(x32) - got := Reverse(x) + got := bits.Reverse(x) want := uint(want32) if got != want { t.Fatalf("Reverse(%#08x) == %#08x; want %#08x", x, got, want) } } - got64 := Reverse64(x64) + got64 := bits.Reverse64(x64) if got64 != want64 { t.Fatalf("Reverse64(%#016x) == %#016x; want %#016x", x64, got64, want64) } - if UintSize == 64 { + if bits.UintSize == 64 { x := uint(x64) - got := Reverse(x) + got := bits.Reverse(x) want := uint(want64) if got != want { t.Fatalf("Reverse(%#08x) == %#016x; want %#016x", x, got, want) @@ -519,7 +520,7 @@ func testReverse(t *testing.T, x64, want64 uint64) { func BenchmarkReverse(b *testing.B) { var s uint for i := 0; i < b.N; i++ { - s += Reverse(uint(i)) + s += bits.Reverse(uint(i)) } Output = int(s) } @@ -527,7 +528,7 @@ func BenchmarkReverse(b *testing.B) { func BenchmarkReverse8(b *testing.B) { var s uint8 for i := 0; i < b.N; i++ { - s += Reverse8(uint8(i)) + s += bits.Reverse8(uint8(i)) } Output = int(s) } @@ -535,7 +536,7 @@ func BenchmarkReverse8(b *testing.B) { func BenchmarkReverse16(b *testing.B) { var s uint16 for i := 0; i < b.N; i++ { - s += Reverse16(uint16(i)) + s += bits.Reverse16(uint16(i)) } Output = int(s) } @@ -543,7 +544,7 @@ func BenchmarkReverse16(b *testing.B) { func BenchmarkReverse32(b *testing.B) { var s uint32 for i := 0; i < b.N; i++ { - s += Reverse32(uint32(i)) + s += bits.Reverse32(uint32(i)) } Output = int(s) } @@ -551,7 +552,7 @@ func BenchmarkReverse32(b *testing.B) { func BenchmarkReverse64(b *testing.B) { var s uint64 for i := 0; i < b.N; i++ { - s += Reverse64(uint64(i)) + s += bits.Reverse64(uint64(i)) } Output = int(s) } @@ -577,34 +578,34 @@ func TestReverseBytes(t *testing.T) { func testReverseBytes(t *testing.T, x64, want64 uint64) { x16 := uint16(x64) - got16 := ReverseBytes16(x16) + got16 := bits.ReverseBytes16(x16) want16 := uint16(want64 >> (64 - 16)) if got16 != want16 { t.Fatalf("ReverseBytes16(%#04x) == %#04x; want %#04x", x16, got16, want16) } x32 := uint32(x64) - got32 := ReverseBytes32(x32) + got32 := bits.ReverseBytes32(x32) want32 := uint32(want64 >> (64 - 32)) if got32 != want32 { t.Fatalf("ReverseBytes32(%#08x) == %#08x; want %#08x", x32, got32, want32) } - if UintSize == 32 { + if bits.UintSize == 32 { x := uint(x32) - got := ReverseBytes(x) + got := bits.ReverseBytes(x) want := uint(want32) if got != want { t.Fatalf("ReverseBytes(%#08x) == %#08x; want %#08x", x, got, want) } } - got64 := ReverseBytes64(x64) + got64 := bits.ReverseBytes64(x64) if got64 != want64 { t.Fatalf("ReverseBytes64(%#016x) == %#016x; want %#016x", x64, got64, want64) } - if UintSize == 64 { + if bits.UintSize == 64 { x := uint(x64) - got := ReverseBytes(x) + got := bits.ReverseBytes(x) want := uint(want64) if got != want { t.Fatalf("ReverseBytes(%#016x) == %#016x; want %#016x", x, got, want) @@ -615,7 +616,7 @@ func testReverseBytes(t *testing.T, x64, want64 uint64) { func BenchmarkReverseBytes(b *testing.B) { var s uint for i := 0; i < b.N; i++ { - s += ReverseBytes(uint(i)) + s += bits.ReverseBytes(uint(i)) } Output = int(s) } @@ -623,7 +624,7 @@ func BenchmarkReverseBytes(b *testing.B) { func BenchmarkReverseBytes16(b *testing.B) { var s uint16 for i := 0; i < b.N; i++ { - s += ReverseBytes16(uint16(i)) + s += bits.ReverseBytes16(uint16(i)) } Output = int(s) } @@ -631,7 +632,7 @@ func BenchmarkReverseBytes16(b *testing.B) { func BenchmarkReverseBytes32(b *testing.B) { var s uint32 for i := 0; i < b.N; i++ { - s += ReverseBytes32(uint32(i)) + s += bits.ReverseBytes32(uint32(i)) } Output = int(s) } @@ -639,7 +640,7 @@ func BenchmarkReverseBytes32(b *testing.B) { func BenchmarkReverseBytes64(b *testing.B) { var s uint64 for i := 0; i < b.N; i++ { - s += ReverseBytes64(uint64(i)) + s += bits.ReverseBytes64(uint64(i)) } Output = int(s) } @@ -654,26 +655,26 @@ func TestLen(t *testing.T) { want = length + k } if x <= 1<<8-1 { - got := Len8(uint8(x)) + got := bits.Len8(uint8(x)) if got != want { t.Fatalf("Len8(%#02x) == %d; want %d", x, got, want) } } if x <= 1<<16-1 { - got := Len16(uint16(x)) + got := bits.Len16(uint16(x)) if got != want { t.Fatalf("Len16(%#04x) == %d; want %d", x, got, want) } } if x <= 1<<32-1 { - got := Len32(uint32(x)) + got := bits.Len32(uint32(x)) if got != want { t.Fatalf("Len32(%#08x) == %d; want %d", x, got, want) } - if UintSize == 32 { - got := Len(uint(x)) + if bits.UintSize == 32 { + got := bits.Len(uint(x)) if got != want { t.Fatalf("Len(%#08x) == %d; want %d", x, got, want) } @@ -681,12 +682,12 @@ func TestLen(t *testing.T) { } if x <= 1<<64-1 { - got := Len64(uint64(x)) + got := bits.Len64(uint64(x)) if got != want { t.Fatalf("Len64(%#016x) == %d; want %d", x, got, want) } - if UintSize == 64 { - got := Len(uint(x)) + if bits.UintSize == 64 { + got := bits.Len(uint(x)) if got != want { t.Fatalf("Len(%#016x) == %d; want %d", x, got, want) } @@ -697,7 +698,7 @@ func TestLen(t *testing.T) { } const ( - _M = 1< 0 { panic("overflow") } return x }, func(a, b uint64) uint64 { - x, c := Add64(a, b, 0) + x, c := bits.Add64(a, b, 0) if c != 0 { panic("overflow") } return x }, func(a, b uint64) uint64 { - x, c := Add64(a, b, 0) + x, c := bits.Add64(a, b, 0) if c == 1 { panic("overflow") } return x }, func(a, b uint64) uint64 { - x, c := Add64(a, b, 0) + x, c := bits.Add64(a, b, 0) if c != 1 { return x } panic("overflow") }, func(a, b uint64) uint64 { - x, c := Add64(a, b, 0) + x, c := bits.Add64(a, b, 0) if c == 0 { return x } @@ -863,35 +864,35 @@ func TestSub64OverflowPanic(t *testing.T) { // These are designed to improve coverage of compiler intrinsics. tests := []func(uint64, uint64) uint64{ func(a, b uint64) uint64 { - x, c := Sub64(a, b, 0) + x, c := bits.Sub64(a, b, 0) if c > 0 { panic("overflow") } return x }, func(a, b uint64) uint64 { - x, c := Sub64(a, b, 0) + x, c := bits.Sub64(a, b, 0) if c != 0 { panic("overflow") } return x }, func(a, b uint64) uint64 { - x, c := Sub64(a, b, 0) + x, c := bits.Sub64(a, b, 0) if c == 1 { panic("overflow") } return x }, func(a, b uint64) uint64 { - x, c := Sub64(a, b, 0) + x, c := bits.Sub64(a, b, 0) if c != 1 { return x } panic("overflow") }, func(a, b uint64) uint64 { - x, c := Sub64(a, b, 0) + x, c := bits.Sub64(a, b, 0) if c == 0 { return x } @@ -937,19 +938,19 @@ func TestMulDiv(t *testing.T) { x, y uint hi, lo, r uint }{ - {1 << (UintSize - 1), 2, 1, 0, 1}, + {1 << (bits.UintSize - 1), 2, 1, 0, 1}, {_M, _M, _M - 1, 1, 42}, } { - testMul("Mul", Mul, a.x, a.y, a.hi, a.lo) - testMul("Mul symmetric", Mul, a.y, a.x, a.hi, a.lo) - testDiv("Div", Div, a.hi, a.lo+a.r, a.y, a.x, a.r) - testDiv("Div symmetric", Div, a.hi, a.lo+a.r, a.x, a.y, a.r) + testMul("Mul", bits.Mul, a.x, a.y, a.hi, a.lo) + testMul("Mul symmetric", bits.Mul, a.y, a.x, a.hi, a.lo) + testDiv("Div", bits.Div, a.hi, a.lo+a.r, a.y, a.x, a.r) + testDiv("Div symmetric", bits.Div, a.hi, a.lo+a.r, a.x, a.y, a.r) // The above code can't test intrinsic implementation, because the passed function is not called directly. // The following code uses a closure to test the intrinsic version in case the function is intrinsified. - testMul("Mul intrinsic", func(x, y uint) (uint, uint) { return Mul(x, y) }, a.x, a.y, a.hi, a.lo) - testMul("Mul intrinsic symmetric", func(x, y uint) (uint, uint) { return Mul(x, y) }, a.y, a.x, a.hi, a.lo) - testDiv("Div intrinsic", func(hi, lo, y uint) (uint, uint) { return Div(hi, lo, y) }, a.hi, a.lo+a.r, a.y, a.x, a.r) - testDiv("Div intrinsic symmetric", func(hi, lo, y uint) (uint, uint) { return Div(hi, lo, y) }, a.hi, a.lo+a.r, a.x, a.y, a.r) + testMul("Mul intrinsic", func(x, y uint) (uint, uint) { return bits.Mul(x, y) }, a.x, a.y, a.hi, a.lo) + testMul("Mul intrinsic symmetric", func(x, y uint) (uint, uint) { return bits.Mul(x, y) }, a.y, a.x, a.hi, a.lo) + testDiv("Div intrinsic", func(hi, lo, y uint) (uint, uint) { return bits.Div(hi, lo, y) }, a.hi, a.lo+a.r, a.y, a.x, a.r) + testDiv("Div intrinsic symmetric", func(hi, lo, y uint) (uint, uint) { return bits.Div(hi, lo, y) }, a.hi, a.lo+a.r, a.x, a.y, a.r) } } @@ -974,10 +975,10 @@ func TestMulDiv32(t *testing.T) { {0xc47dfa8c, 50911, 0x98a4, 0x998587f4, 13}, {_M32, _M32, _M32 - 1, 1, 42}, } { - testMul("Mul32", Mul32, a.x, a.y, a.hi, a.lo) - testMul("Mul32 symmetric", Mul32, a.y, a.x, a.hi, a.lo) - testDiv("Div32", Div32, a.hi, a.lo+a.r, a.y, a.x, a.r) - testDiv("Div32 symmetric", Div32, a.hi, a.lo+a.r, a.x, a.y, a.r) + testMul("Mul32", bits.Mul32, a.x, a.y, a.hi, a.lo) + testMul("Mul32 symmetric", bits.Mul32, a.y, a.x, a.hi, a.lo) + testDiv("Div32", bits.Div32, a.hi, a.lo+a.r, a.y, a.x, a.r) + testDiv("Div32 symmetric", bits.Div32, a.hi, a.lo+a.r, a.x, a.y, a.r) } } @@ -1002,16 +1003,16 @@ func TestMulDiv64(t *testing.T) { {0x3626229738a3b9, 0xd8988a9f1cc4a61, 0x2dd0712657fe8, 0x9dd6a3364c358319, 13}, {_M64, _M64, _M64 - 1, 1, 42}, } { - testMul("Mul64", Mul64, a.x, a.y, a.hi, a.lo) - testMul("Mul64 symmetric", Mul64, a.y, a.x, a.hi, a.lo) - testDiv("Div64", Div64, a.hi, a.lo+a.r, a.y, a.x, a.r) - testDiv("Div64 symmetric", Div64, a.hi, a.lo+a.r, a.x, a.y, a.r) + testMul("Mul64", bits.Mul64, a.x, a.y, a.hi, a.lo) + testMul("Mul64 symmetric", bits.Mul64, a.y, a.x, a.hi, a.lo) + testDiv("Div64", bits.Div64, a.hi, a.lo+a.r, a.y, a.x, a.r) + testDiv("Div64 symmetric", bits.Div64, a.hi, a.lo+a.r, a.x, a.y, a.r) // The above code can't test intrinsic implementation, because the passed function is not called directly. // The following code uses a closure to test the intrinsic version in case the function is intrinsified. - testMul("Mul64 intrinsic", func(x, y uint64) (uint64, uint64) { return Mul64(x, y) }, a.x, a.y, a.hi, a.lo) - testMul("Mul64 intrinsic symmetric", func(x, y uint64) (uint64, uint64) { return Mul64(x, y) }, a.y, a.x, a.hi, a.lo) - testDiv("Div64 intrinsic", func(hi, lo, y uint64) (uint64, uint64) { return Div64(hi, lo, y) }, a.hi, a.lo+a.r, a.y, a.x, a.r) - testDiv("Div64 intrinsic symmetric", func(hi, lo, y uint64) (uint64, uint64) { return Div64(hi, lo, y) }, a.hi, a.lo+a.r, a.x, a.y, a.r) + testMul("Mul64 intrinsic", func(x, y uint64) (uint64, uint64) { return bits.Mul64(x, y) }, a.x, a.y, a.hi, a.lo) + testMul("Mul64 intrinsic symmetric", func(x, y uint64) (uint64, uint64) { return bits.Mul64(x, y) }, a.y, a.x, a.hi, a.lo) + testDiv("Div64 intrinsic", func(hi, lo, y uint64) (uint64, uint64) { return bits.Div64(hi, lo, y) }, a.hi, a.lo+a.r, a.y, a.x, a.r) + testDiv("Div64 intrinsic symmetric", func(hi, lo, y uint64) (uint64, uint64) { return bits.Div64(hi, lo, y) }, a.hi, a.lo+a.r, a.x, a.y, a.r) } } @@ -1020,11 +1021,11 @@ func TestDivPanicOverflow(t *testing.T) { defer func() { if err := recover(); err == nil { t.Error("Div should have panicked when y<=hi") - } else if err != overflowError { - t.Errorf("Div expected panic: %q, got: %v ", overflowError, err) + } else if err != bits.OverflowError { + t.Errorf("Div expected panic: %q, got: %v ", bits.OverflowError, err) } }() - q, r := Div(1, 0, 1) + q, r := bits.Div(1, 0, 1) t.Errorf("undefined q, r = %v, %v calculated when Div should have panicked", q, r) } @@ -1033,11 +1034,11 @@ func TestDiv32PanicOverflow(t *testing.T) { defer func() { if err := recover(); err == nil { t.Error("Div32 should have panicked when y<=hi") - } else if err != overflowError { - t.Errorf("Div32 expected panic: %q, got: %v ", overflowError, err) + } else if err != bits.OverflowError { + t.Errorf("Div32 expected panic: %q, got: %v ", bits.OverflowError, err) } }() - q, r := Div32(1, 0, 1) + q, r := bits.Div32(1, 0, 1) t.Errorf("undefined q, r = %v, %v calculated when Div32 should have panicked", q, r) } @@ -1046,11 +1047,11 @@ func TestDiv64PanicOverflow(t *testing.T) { defer func() { if err := recover(); err == nil { t.Error("Div64 should have panicked when y<=hi") - } else if err != overflowError { - t.Errorf("Div64 expected panic: %q, got: %v ", overflowError, err) + } else if err != bits.OverflowError { + t.Errorf("Div64 expected panic: %q, got: %v ", bits.OverflowError, err) } }() - q, r := Div64(1, 0, 1) + q, r := bits.Div64(1, 0, 1) t.Errorf("undefined q, r = %v, %v calculated when Div64 should have panicked", q, r) } @@ -1059,11 +1060,11 @@ func TestDivPanicZero(t *testing.T) { defer func() { if err := recover(); err == nil { t.Error("Div should have panicked when y==0") - } else if err != divideError { - t.Errorf("Div expected panic: %q, got: %q ", divideError, err) + } else if err != bits.DivideError { + t.Errorf("Div expected panic: %q, got: %q ", bits.DivideError, err) } }() - q, r := Div(1, 1, 0) + q, r := bits.Div(1, 1, 0) t.Errorf("undefined q, r = %v, %v calculated when Div should have panicked", q, r) } @@ -1072,11 +1073,11 @@ func TestDiv32PanicZero(t *testing.T) { defer func() { if err := recover(); err == nil { t.Error("Div32 should have panicked when y==0") - } else if err != divideError { - t.Errorf("Div32 expected panic: %q, got: %q ", divideError, err) + } else if err != bits.DivideError { + t.Errorf("Div32 expected panic: %q, got: %q ", bits.DivideError, err) } }() - q, r := Div32(1, 1, 0) + q, r := bits.Div32(1, 1, 0) t.Errorf("undefined q, r = %v, %v calculated when Div32 should have panicked", q, r) } @@ -1085,11 +1086,11 @@ func TestDiv64PanicZero(t *testing.T) { defer func() { if err := recover(); err == nil { t.Error("Div64 should have panicked when y==0") - } else if err != divideError { - t.Errorf("Div64 expected panic: %q, got: %q ", divideError, err) + } else if err != bits.DivideError { + t.Errorf("Div64 expected panic: %q, got: %q ", bits.DivideError, err) } }() - q, r := Div64(1, 1, 0) + q, r := bits.Div64(1, 1, 0) t.Errorf("undefined q, r = %v, %v calculated when Div64 should have panicked", q, r) } @@ -1098,8 +1099,8 @@ func TestRem32(t *testing.T) { // same as the rem returned by Div32 hi, lo, y := uint32(510510), uint32(9699690), uint32(510510+1) // ensure hi < y for i := 0; i < 1000; i++ { - r := Rem32(hi, lo, y) - _, r2 := Div32(hi, lo, y) + r := bits.Rem32(hi, lo, y) + _, r2 := bits.Div32(hi, lo, y) if r != r2 { t.Errorf("Rem32(%v, %v, %v) returned %v, but Div32 returned rem %v", hi, lo, y, r, r2) } @@ -1111,8 +1112,8 @@ func TestRem32Overflow(t *testing.T) { // To trigger a quotient overflow, we need y <= hi hi, lo, y := uint32(510510), uint32(9699690), uint32(7) for i := 0; i < 1000; i++ { - r := Rem32(hi, lo, y) - _, r2 := Div64(0, uint64(hi)<<32|uint64(lo), uint64(y)) + r := bits.Rem32(hi, lo, y) + _, r2 := bits.Div64(0, uint64(hi)<<32|uint64(lo), uint64(y)) if r != uint32(r2) { t.Errorf("Rem32(%v, %v, %v) returned %v, but Div64 returned rem %v", hi, lo, y, r, r2) } @@ -1125,8 +1126,8 @@ func TestRem64(t *testing.T) { // same as the rem returned by Div64 hi, lo, y := uint64(510510), uint64(9699690), uint64(510510+1) // ensure hi < y for i := 0; i < 1000; i++ { - r := Rem64(hi, lo, y) - _, r2 := Div64(hi, lo, y) + r := bits.Rem64(hi, lo, y) + _, r2 := bits.Div64(hi, lo, y) if r != r2 { t.Errorf("Rem64(%v, %v, %v) returned %v, but Div64 returned rem %v", hi, lo, y, r, r2) } @@ -1155,7 +1156,7 @@ func TestRem64Overflow(t *testing.T) { if rt.hi < rt.y { t.Fatalf("Rem64(%v, %v, %v) is not a test with quo overflow", rt.hi, rt.lo, rt.y) } - rem := Rem64(rt.hi, rt.lo, rt.y) + rem := bits.Rem64(rt.hi, rt.lo, rt.y) if rem != rt.rem { t.Errorf("Rem64(%v, %v, %v) returned %v, wanted %v", rt.hi, rt.lo, rt.y, rem, rt.rem) @@ -1166,7 +1167,7 @@ func TestRem64Overflow(t *testing.T) { func BenchmarkAdd(b *testing.B) { var z, c uint for i := 0; i < b.N; i++ { - z, c = Add(uint(Input), uint(i), c) + z, c = bits.Add(uint(Input), uint(i), c) } Output = int(z + c) } @@ -1174,7 +1175,7 @@ func BenchmarkAdd(b *testing.B) { func BenchmarkAdd32(b *testing.B) { var z, c uint32 for i := 0; i < b.N; i++ { - z, c = Add32(uint32(Input), uint32(i), c) + z, c = bits.Add32(uint32(Input), uint32(i), c) } Output = int(z + c) } @@ -1182,7 +1183,7 @@ func BenchmarkAdd32(b *testing.B) { func BenchmarkAdd64(b *testing.B) { var z, c uint64 for i := 0; i < b.N; i++ { - z, c = Add64(uint64(Input), uint64(i), c) + z, c = bits.Add64(uint64(Input), uint64(i), c) } Output = int(z + c) } @@ -1194,10 +1195,10 @@ func BenchmarkAdd64multiple(b *testing.B) { z3 := uint64(Input) for i := 0; i < b.N; i++ { var c uint64 - z0, c = Add64(z0, uint64(i), c) - z1, c = Add64(z1, uint64(i), c) - z2, c = Add64(z2, uint64(i), c) - z3, _ = Add64(z3, uint64(i), c) + z0, c = bits.Add64(z0, uint64(i), c) + z1, c = bits.Add64(z1, uint64(i), c) + z2, c = bits.Add64(z2, uint64(i), c) + z3, _ = bits.Add64(z3, uint64(i), c) } Output = int(z0 + z1 + z2 + z3) } @@ -1205,7 +1206,7 @@ func BenchmarkAdd64multiple(b *testing.B) { func BenchmarkSub(b *testing.B) { var z, c uint for i := 0; i < b.N; i++ { - z, c = Sub(uint(Input), uint(i), c) + z, c = bits.Sub(uint(Input), uint(i), c) } Output = int(z + c) } @@ -1213,7 +1214,7 @@ func BenchmarkSub(b *testing.B) { func BenchmarkSub32(b *testing.B) { var z, c uint32 for i := 0; i < b.N; i++ { - z, c = Sub32(uint32(Input), uint32(i), c) + z, c = bits.Sub32(uint32(Input), uint32(i), c) } Output = int(z + c) } @@ -1221,7 +1222,7 @@ func BenchmarkSub32(b *testing.B) { func BenchmarkSub64(b *testing.B) { var z, c uint64 for i := 0; i < b.N; i++ { - z, c = Sub64(uint64(Input), uint64(i), c) + z, c = bits.Sub64(uint64(Input), uint64(i), c) } Output = int(z + c) } @@ -1233,10 +1234,10 @@ func BenchmarkSub64multiple(b *testing.B) { z3 := uint64(Input) for i := 0; i < b.N; i++ { var c uint64 - z0, c = Sub64(z0, uint64(i), c) - z1, c = Sub64(z1, uint64(i), c) - z2, c = Sub64(z2, uint64(i), c) - z3, _ = Sub64(z3, uint64(i), c) + z0, c = bits.Sub64(z0, uint64(i), c) + z1, c = bits.Sub64(z1, uint64(i), c) + z2, c = bits.Sub64(z2, uint64(i), c) + z3, _ = bits.Sub64(z3, uint64(i), c) } Output = int(z0 + z1 + z2 + z3) } @@ -1244,7 +1245,7 @@ func BenchmarkSub64multiple(b *testing.B) { func BenchmarkMul(b *testing.B) { var hi, lo uint for i := 0; i < b.N; i++ { - hi, lo = Mul(uint(Input), uint(i)) + hi, lo = bits.Mul(uint(Input), uint(i)) } Output = int(hi + lo) } @@ -1252,7 +1253,7 @@ func BenchmarkMul(b *testing.B) { func BenchmarkMul32(b *testing.B) { var hi, lo uint32 for i := 0; i < b.N; i++ { - hi, lo = Mul32(uint32(Input), uint32(i)) + hi, lo = bits.Mul32(uint32(Input), uint32(i)) } Output = int(hi + lo) } @@ -1260,7 +1261,7 @@ func BenchmarkMul32(b *testing.B) { func BenchmarkMul64(b *testing.B) { var hi, lo uint64 for i := 0; i < b.N; i++ { - hi, lo = Mul64(uint64(Input), uint64(i)) + hi, lo = bits.Mul64(uint64(Input), uint64(i)) } Output = int(hi + lo) } @@ -1268,7 +1269,7 @@ func BenchmarkMul64(b *testing.B) { func BenchmarkDiv(b *testing.B) { var q, r uint for i := 0; i < b.N; i++ { - q, r = Div(1, uint(i), uint(Input)) + q, r = bits.Div(1, uint(i), uint(Input)) } Output = int(q + r) } @@ -1276,7 +1277,7 @@ func BenchmarkDiv(b *testing.B) { func BenchmarkDiv32(b *testing.B) { var q, r uint32 for i := 0; i < b.N; i++ { - q, r = Div32(1, uint32(i), uint32(Input)) + q, r = bits.Div32(1, uint32(i), uint32(Input)) } Output = int(q + r) } @@ -1284,7 +1285,7 @@ func BenchmarkDiv32(b *testing.B) { func BenchmarkDiv64(b *testing.B) { var q, r uint64 for i := 0; i < b.N; i++ { - q, r = Div64(1, uint64(i), uint64(Input)) + q, r = bits.Div64(1, uint64(i), uint64(Input)) } Output = int(q + r) } diff --git a/gnovm/stdlibs/math/bits/export_test.gno b/gnovm/stdlibs/math/bits/export_test.gno index 8c6f9332cca..a0165163d8f 100644 --- a/gnovm/stdlibs/math/bits/export_test.gno +++ b/gnovm/stdlibs/math/bits/export_test.gno @@ -4,4 +4,9 @@ package bits +// exported for tests + const DeBruijn64 = deBruijn64 + +var OverflowError = overflowError +var DivideError = divideError diff --git a/gnovm/stdlibs/strconv/atob_test.gno b/gnovm/stdlibs/strconv/atob_test.gno index 39746f8953d..6e6d34e8320 100644 --- a/gnovm/stdlibs/strconv/atob_test.gno +++ b/gnovm/stdlibs/strconv/atob_test.gno @@ -2,10 +2,11 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package strconv +package strconv_test import ( "bytes" + "strconv" "testing" ) @@ -16,8 +17,8 @@ type atobTest struct { } var atobtests = []atobTest{ - {"", false, ErrSyntax}, - {"asdf", false, ErrSyntax}, + {"", false, strconv.ErrSyntax}, + {"asdf", false, strconv.ErrSyntax}, {"0", false, nil}, {"f", false, nil}, {"F", false, nil}, @@ -34,14 +35,14 @@ var atobtests = []atobTest{ func TestParseBool(t *testing.T) { for _, test := range atobtests { - b, e := ParseBool(test.in) + b, e := strconv.ParseBool(test.in) if test.err != nil { // expect an error if e == nil { t.Errorf("ParseBool(%s) = nil; want %s", test.in, test.err) } else { // NumError assertion must succeed; it's the only thing we return. - if e.(*NumError).Err != test.err { + if e.(*strconv.NumError).Err != test.err { t.Errorf("ParseBool(%s) = %s; want %s", test.in, e, test.err) } } @@ -63,7 +64,7 @@ var boolString = map[bool]string{ func TestFormatBool(t *testing.T) { for b, s := range boolString { - if f := FormatBool(b); f != s { + if f := strconv.FormatBool(b); f != s { t.Errorf("FormatBool(%v) = %q; want %q", b, f, s) } } @@ -82,7 +83,7 @@ var appendBoolTests = []appendBoolTest{ func TestAppendBool(t *testing.T) { for _, test := range appendBoolTests { - b := AppendBool(test.in, test.b) + b := strconv.AppendBool(test.in, test.b) if !bytes.Equal(b, test.out) { t.Errorf("AppendBool(%q, %v) = %q; want %q", test.in, test.b, b, test.out) } diff --git a/gnovm/stdlibs/strconv/atof_test.gno b/gnovm/stdlibs/strconv/atof_test.gno index 29d9e4e2f4b..63cf659018b 100644 --- a/gnovm/stdlibs/strconv/atof_test.gno +++ b/gnovm/stdlibs/strconv/atof_test.gno @@ -2,13 +2,14 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package strconv +package strconv_test // XXX: changed not to use dot imports import ( "math" "math/rand" + "strconv" "strings" "testing" ) @@ -20,11 +21,11 @@ type atofTest struct { } var atoftests = []atofTest{ - {"", "0", ErrSyntax}, + {"", "0", strconv.ErrSyntax}, {"1", "1", nil}, {"+1", "1", nil}, - {"1x", "0", ErrSyntax}, - {"1.1.", "0", ErrSyntax}, + {"1x", "0", strconv.ErrSyntax}, + {"1.1.", "0", strconv.ErrSyntax}, {"1e23", "1e+23", nil}, {"1E23", "1e+23", nil}, {"100000000000000000000000", "1e+23", nil}, @@ -55,8 +56,8 @@ var atoftests = []atofTest{ {"-0x2p3", "-16", nil}, {"0x0.fp4", "15", nil}, {"0x0.fp0", "0.9375", nil}, - {"0x1e2", "0", ErrSyntax}, - {"1p2", "0", ErrSyntax}, + {"0x1e2", "0", strconv.ErrSyntax}, + {"1p2", "0", strconv.ErrSyntax}, // zeros {"0", "0", nil}, @@ -131,16 +132,16 @@ var atoftests = []atofTest{ {"-0x.1fffffffffffffp1027", "-1.7976931348623157e+308", nil}, // next float64 - too large - {"1.7976931348623159e308", "+Inf", ErrRange}, - {"-1.7976931348623159e308", "-Inf", ErrRange}, - {"0x1p1024", "+Inf", ErrRange}, - {"-0x1p1024", "-Inf", ErrRange}, - {"0x2p1023", "+Inf", ErrRange}, - {"-0x2p1023", "-Inf", ErrRange}, - {"0x.1p1028", "+Inf", ErrRange}, - {"-0x.1p1028", "-Inf", ErrRange}, - {"0x.2p1027", "+Inf", ErrRange}, - {"-0x.2p1027", "-Inf", ErrRange}, + {"1.7976931348623159e308", "+Inf", strconv.ErrRange}, + {"-1.7976931348623159e308", "-Inf", strconv.ErrRange}, + {"0x1p1024", "+Inf", strconv.ErrRange}, + {"-0x1p1024", "-Inf", strconv.ErrRange}, + {"0x2p1023", "+Inf", strconv.ErrRange}, + {"-0x2p1023", "-Inf", strconv.ErrRange}, + {"0x.1p1028", "+Inf", strconv.ErrRange}, + {"-0x.1p1028", "-Inf", strconv.ErrRange}, + {"0x.2p1027", "+Inf", strconv.ErrRange}, + {"-0x.2p1027", "-Inf", strconv.ErrRange}, // the border is ...158079 // borderline - okay @@ -149,34 +150,34 @@ var atoftests = []atofTest{ {"0x1.fffffffffffff7fffp1023", "1.7976931348623157e+308", nil}, {"-0x1.fffffffffffff7fffp1023", "-1.7976931348623157e+308", nil}, // borderline - too large - {"1.797693134862315808e308", "+Inf", ErrRange}, - {"-1.797693134862315808e308", "-Inf", ErrRange}, - {"0x1.fffffffffffff8p1023", "+Inf", ErrRange}, - {"-0x1.fffffffffffff8p1023", "-Inf", ErrRange}, - {"0x1fffffffffffff.8p+971", "+Inf", ErrRange}, - {"-0x1fffffffffffff8p+967", "-Inf", ErrRange}, - {"0x.1fffffffffffff8p1027", "+Inf", ErrRange}, - {"-0x.1fffffffffffff9p1027", "-Inf", ErrRange}, + {"1.797693134862315808e308", "+Inf", strconv.ErrRange}, + {"-1.797693134862315808e308", "-Inf", strconv.ErrRange}, + {"0x1.fffffffffffff8p1023", "+Inf", strconv.ErrRange}, + {"-0x1.fffffffffffff8p1023", "-Inf", strconv.ErrRange}, + {"0x1fffffffffffff.8p+971", "+Inf", strconv.ErrRange}, + {"-0x1fffffffffffff8p+967", "-Inf", strconv.ErrRange}, + {"0x.1fffffffffffff8p1027", "+Inf", strconv.ErrRange}, + {"-0x.1fffffffffffff9p1027", "-Inf", strconv.ErrRange}, // a little too large {"1e308", "1e+308", nil}, - {"2e308", "+Inf", ErrRange}, - {"1e309", "+Inf", ErrRange}, - {"0x1p1025", "+Inf", ErrRange}, + {"2e308", "+Inf", strconv.ErrRange}, + {"1e309", "+Inf", strconv.ErrRange}, + {"0x1p1025", "+Inf", strconv.ErrRange}, // way too large - {"1e310", "+Inf", ErrRange}, - {"-1e310", "-Inf", ErrRange}, - {"1e400", "+Inf", ErrRange}, - {"-1e400", "-Inf", ErrRange}, - {"1e400000", "+Inf", ErrRange}, - {"-1e400000", "-Inf", ErrRange}, - {"0x1p1030", "+Inf", ErrRange}, - {"0x1p2000", "+Inf", ErrRange}, - {"0x1p2000000000", "+Inf", ErrRange}, - {"-0x1p1030", "-Inf", ErrRange}, - {"-0x1p2000", "-Inf", ErrRange}, - {"-0x1p2000000000", "-Inf", ErrRange}, + {"1e310", "+Inf", strconv.ErrRange}, + {"-1e310", "-Inf", strconv.ErrRange}, + {"1e400", "+Inf", strconv.ErrRange}, + {"-1e400", "-Inf", strconv.ErrRange}, + {"1e400000", "+Inf", strconv.ErrRange}, + {"-1e400000", "-Inf", strconv.ErrRange}, + {"0x1p1030", "+Inf", strconv.ErrRange}, + {"0x1p2000", "+Inf", strconv.ErrRange}, + {"0x1p2000000000", "+Inf", strconv.ErrRange}, + {"-0x1p1030", "-Inf", strconv.ErrRange}, + {"-0x1p2000", "-Inf", strconv.ErrRange}, + {"-0x1p2000000000", "-Inf", strconv.ErrRange}, // denormalized {"1e-305", "1e-305", nil}, @@ -238,29 +239,29 @@ var atoftests = []atofTest{ // try to overflow exponent {"1e-4294967296", "0", nil}, - {"1e+4294967296", "+Inf", ErrRange}, + {"1e+4294967296", "+Inf", strconv.ErrRange}, {"1e-18446744073709551616", "0", nil}, - {"1e+18446744073709551616", "+Inf", ErrRange}, + {"1e+18446744073709551616", "+Inf", strconv.ErrRange}, {"0x1p-4294967296", "0", nil}, - {"0x1p+4294967296", "+Inf", ErrRange}, + {"0x1p+4294967296", "+Inf", strconv.ErrRange}, {"0x1p-18446744073709551616", "0", nil}, - {"0x1p+18446744073709551616", "+Inf", ErrRange}, + {"0x1p+18446744073709551616", "+Inf", strconv.ErrRange}, // Parse errors - {"1e", "0", ErrSyntax}, - {"1e-", "0", ErrSyntax}, - {".e-1", "0", ErrSyntax}, - {"1\x00.2", "0", ErrSyntax}, - {"0x", "0", ErrSyntax}, - {"0x.", "0", ErrSyntax}, - {"0x1", "0", ErrSyntax}, - {"0x.1", "0", ErrSyntax}, - {"0x1p", "0", ErrSyntax}, - {"0x.1p", "0", ErrSyntax}, - {"0x1p+", "0", ErrSyntax}, - {"0x.1p+", "0", ErrSyntax}, - {"0x1p-", "0", ErrSyntax}, - {"0x.1p-", "0", ErrSyntax}, + {"1e", "0", strconv.ErrSyntax}, + {"1e-", "0", strconv.ErrSyntax}, + {".e-1", "0", strconv.ErrSyntax}, + {"1\x00.2", "0", strconv.ErrSyntax}, + {"0x", "0", strconv.ErrSyntax}, + {"0x.", "0", strconv.ErrSyntax}, + {"0x1", "0", strconv.ErrSyntax}, + {"0x.1", "0", strconv.ErrSyntax}, + {"0x1p", "0", strconv.ErrSyntax}, + {"0x.1p", "0", strconv.ErrSyntax}, + {"0x1p+", "0", strconv.ErrSyntax}, + {"0x.1p+", "0", strconv.ErrSyntax}, + {"0x1p-", "0", strconv.ErrSyntax}, + {"0x.1p-", "0", strconv.ErrSyntax}, {"0x1p+2", "4", nil}, {"0x.1p+2", "0.25", nil}, {"0x1p-2", "0.25", nil}, @@ -309,40 +310,40 @@ var atoftests = []atofTest{ // Underscores. {"1_23.50_0_0e+1_2", "1.235e+14", nil}, - {"-_123.5e+12", "0", ErrSyntax}, - {"+_123.5e+12", "0", ErrSyntax}, - {"_123.5e+12", "0", ErrSyntax}, - {"1__23.5e+12", "0", ErrSyntax}, - {"123_.5e+12", "0", ErrSyntax}, - {"123._5e+12", "0", ErrSyntax}, - {"123.5_e+12", "0", ErrSyntax}, - {"123.5__0e+12", "0", ErrSyntax}, - {"123.5e_+12", "0", ErrSyntax}, - {"123.5e+_12", "0", ErrSyntax}, - {"123.5e_-12", "0", ErrSyntax}, - {"123.5e-_12", "0", ErrSyntax}, - {"123.5e+1__2", "0", ErrSyntax}, - {"123.5e+12_", "0", ErrSyntax}, + {"-_123.5e+12", "0", strconv.ErrSyntax}, + {"+_123.5e+12", "0", strconv.ErrSyntax}, + {"_123.5e+12", "0", strconv.ErrSyntax}, + {"1__23.5e+12", "0", strconv.ErrSyntax}, + {"123_.5e+12", "0", strconv.ErrSyntax}, + {"123._5e+12", "0", strconv.ErrSyntax}, + {"123.5_e+12", "0", strconv.ErrSyntax}, + {"123.5__0e+12", "0", strconv.ErrSyntax}, + {"123.5e_+12", "0", strconv.ErrSyntax}, + {"123.5e+_12", "0", strconv.ErrSyntax}, + {"123.5e_-12", "0", strconv.ErrSyntax}, + {"123.5e-_12", "0", strconv.ErrSyntax}, + {"123.5e+1__2", "0", strconv.ErrSyntax}, + {"123.5e+12_", "0", strconv.ErrSyntax}, {"0x_1_2.3_4_5p+1_2", "74565", nil}, - {"-_0x12.345p+12", "0", ErrSyntax}, - {"+_0x12.345p+12", "0", ErrSyntax}, - {"_0x12.345p+12", "0", ErrSyntax}, - {"0x__12.345p+12", "0", ErrSyntax}, - {"0x1__2.345p+12", "0", ErrSyntax}, - {"0x12_.345p+12", "0", ErrSyntax}, - {"0x12._345p+12", "0", ErrSyntax}, - {"0x12.3__45p+12", "0", ErrSyntax}, - {"0x12.345_p+12", "0", ErrSyntax}, - {"0x12.345p_+12", "0", ErrSyntax}, - {"0x12.345p+_12", "0", ErrSyntax}, - {"0x12.345p_-12", "0", ErrSyntax}, - {"0x12.345p-_12", "0", ErrSyntax}, - {"0x12.345p+1__2", "0", ErrSyntax}, - {"0x12.345p+12_", "0", ErrSyntax}, - - {"1e100x", "0", ErrSyntax}, - {"1e1000x", "0", ErrSyntax}, + {"-_0x12.345p+12", "0", strconv.ErrSyntax}, + {"+_0x12.345p+12", "0", strconv.ErrSyntax}, + {"_0x12.345p+12", "0", strconv.ErrSyntax}, + {"0x__12.345p+12", "0", strconv.ErrSyntax}, + {"0x1__2.345p+12", "0", strconv.ErrSyntax}, + {"0x12_.345p+12", "0", strconv.ErrSyntax}, + {"0x12._345p+12", "0", strconv.ErrSyntax}, + {"0x12.3__45p+12", "0", strconv.ErrSyntax}, + {"0x12.345_p+12", "0", strconv.ErrSyntax}, + {"0x12.345p_+12", "0", strconv.ErrSyntax}, + {"0x12.345p+_12", "0", strconv.ErrSyntax}, + {"0x12.345p_-12", "0", strconv.ErrSyntax}, + {"0x12.345p-_12", "0", strconv.ErrSyntax}, + {"0x12.345p+1__2", "0", strconv.ErrSyntax}, + {"0x12.345p+12_", "0", strconv.ErrSyntax}, + + {"1e100x", "0", strconv.ErrSyntax}, + {"1e1000x", "0", strconv.ErrSyntax}, } var atof32tests = []atofTest{ @@ -374,10 +375,10 @@ var atof32tests = []atofTest{ {"-340282346638528859811704183484516925440", "-3.4028235e+38", nil}, {"-0x.ffffffp128", "-3.4028235e+38", nil}, // next float32 - too large - {"3.4028236e38", "+Inf", ErrRange}, - {"-3.4028236e38", "-Inf", ErrRange}, - {"0x1.0p128", "+Inf", ErrRange}, - {"-0x1.0p128", "-Inf", ErrRange}, + {"3.4028236e38", "+Inf", strconv.ErrRange}, + {"-3.4028236e38", "-Inf", strconv.ErrRange}, + {"0x1.0p128", "+Inf", strconv.ErrRange}, + {"-0x1.0p128", "-Inf", strconv.ErrRange}, // the border is 3.40282356779...e+38 // borderline - okay {"3.402823567e38", "3.4028235e+38", nil}, @@ -385,10 +386,10 @@ var atof32tests = []atofTest{ {"0x.ffffff7fp128", "3.4028235e+38", nil}, {"-0x.ffffff7fp128", "-3.4028235e+38", nil}, // borderline - too large - {"3.4028235678e38", "+Inf", ErrRange}, - {"-3.4028235678e38", "-Inf", ErrRange}, - {"0x.ffffff8p128", "+Inf", ErrRange}, - {"-0x.ffffff8p128", "-Inf", ErrRange}, + {"3.4028235678e38", "+Inf", strconv.ErrRange}, + {"-3.4028235678e38", "-Inf", strconv.ErrRange}, + {"0x.ffffff8p128", "+Inf", strconv.ErrRange}, + {"-0x.ffffff8p128", "-Inf", strconv.ErrRange}, // Denormals: less than 2^-126 {"1e-38", "1e-38", nil}, @@ -454,13 +455,13 @@ func initAtofOnce() { for i := range atoftests { test := &atoftests[i] if test.err != nil { - test.err = &NumError{"ParseFloat", test.in, test.err} + test.err = &strconv.NumError{"ParseFloat", test.in, test.err} } } for i := range atof32tests { test := &atof32tests[i] if test.err != nil { - test.err = &NumError{"ParseFloat", test.in, test.err} + test.err = &strconv.NumError{"ParseFloat", test.in, test.err} } } @@ -473,19 +474,19 @@ func initAtofOnce() { for i := range atofRandomTests { n := uint64(rand.Uint32())<<32 | uint64(rand.Uint32()) x := math.Float64frombits(n) - s := FormatFloat(x, 'g', -1, 64) + s := strconv.FormatFloat(x, 'g', -1, 64) atofRandomTests[i] = atofSimpleTest{x, s} } for i := range benchmarksRandomBits { bits := uint64(rand.Uint32())<<32 | uint64(rand.Uint32()) x := math.Float64frombits(bits) - benchmarksRandomBits[i] = FormatFloat(x, 'g', -1, 64) + benchmarksRandomBits[i] = strconv.FormatFloat(x, 'g', -1, 64) } for i := range benchmarksRandomNormal { x := rand.NormFloat64() - benchmarksRandomNormal[i] = FormatFloat(x, 'g', -1, 64) + benchmarksRandomNormal[i] = strconv.FormatFloat(x, 'g', -1, 64) } } @@ -500,7 +501,7 @@ func TestParseFloatPrefix(t *testing.T) { // correctly as "inf" with suffix. for _, suffix := range []string{" ", "q", "+", "-", "<", "=", ">", "(", ")", "i", "init"} { in := test.in + suffix - _, n, err := ParseFloatPrefix(in, 64) + _, n, err := strconv.ParseFloatPrefix(in, 64) if err != nil { t.Errorf("ParseFloatPrefix(%q, 64): err = %v; want no error", in, err) } @@ -530,24 +531,24 @@ func printError(err error) string { func testAtof(t *testing.T, opt bool) { initAtof() - oldopt := SetOptimize(opt) + oldopt := strconv.SetOptimize(opt) for i := 0; i < len(atoftests); i++ { test := &atoftests[i] - out, err := ParseFloat(test.in, 64) - outs := FormatFloat(out, 'g', -1, 64) + out, err := strconv.ParseFloat(test.in, 64) + outs := strconv.FormatFloat(out, 'g', -1, 64) if outs != test.out || !errEqual(err, test.err) { t.Errorf("ParseFloat(%v, 64) = %v, %v want %v, %v", test.in, out, printError(err), test.out, printError(test.err)) } if float64(float32(out)) == out { - out, err := ParseFloat(test.in, 32) + out, err := strconv.ParseFloat(test.in, 32) out32 := float32(out) if float64(out32) != out { t.Errorf("ParseFloat(%v, 32) = %v, not a float32 (closest is %v)", test.in, out, float64(out32)) continue } - outs := FormatFloat(float64(out32), 'g', -1, 32) + outs := strconv.FormatFloat(float64(out32), 'g', -1, 32) if outs != test.out || !errEqual(err, test.err) { t.Errorf("ParseFloat(%v, 32) = %v, %v want %v, %v # %v", test.in, out32, printError(err), test.out, printError(test.err), out) @@ -555,19 +556,19 @@ func testAtof(t *testing.T, opt bool) { } } for _, test := range atof32tests { - out, err := ParseFloat(test.in, 32) + out, err := strconv.ParseFloat(test.in, 32) out32 := float32(out) if float64(out32) != out { t.Errorf("ParseFloat(%v, 32) = %v, not a float32 (closest is %v)", test.in, out, float64(out32)) continue } - outs := FormatFloat(float64(out32), 'g', -1, 32) + outs := strconv.FormatFloat(float64(out32), 'g', -1, 32) if outs != test.out || !errEqual(err, test.err) { t.Errorf("ParseFloat(%v, 32) = %v, %v want %v, %v # %v", test.in, out32, printError(err), test.out, printError(test.err), out) } } - SetOptimize(oldopt) + strconv.SetOptimize(oldopt) } func TestAtof(t *testing.T) { testAtof(t, true) } @@ -577,7 +578,7 @@ func TestAtofSlow(t *testing.T) { testAtof(t, false) } func TestAtofRandom(t *testing.T) { initAtof() for _, test := range atofRandomTests { - x, _ := ParseFloat(test.s, 64) + x, _ := strconv.ParseFloat(test.s, 64) switch { default: t.Errorf("number %s badly parsed as %b (expected %b)", test.s, x, test.x) @@ -604,25 +605,25 @@ var roundTripCases = []struct { func TestRoundTrip(t *testing.T) { for _, tt := range roundTripCases { - old := SetOptimize(false) - s := FormatFloat(tt.f, 'g', -1, 64) + old := strconv.SetOptimize(false) + s := strconv.FormatFloat(tt.f, 'g', -1, 64) if s != tt.s { t.Errorf("no-opt FormatFloat(%b) = %s, want %s", tt.f, s, tt.s) } - f, err := ParseFloat(tt.s, 64) + f, err := strconv.ParseFloat(tt.s, 64) if f != tt.f || err != nil { t.Errorf("no-opt ParseFloat(%s) = %b, %v want %b, nil", tt.s, f, err, tt.f) } - SetOptimize(true) - s = FormatFloat(tt.f, 'g', -1, 64) + strconv.SetOptimize(true) + s = strconv.FormatFloat(tt.f, 'g', -1, 64) if s != tt.s { t.Errorf("opt FormatFloat(%b) = %s, want %s", tt.f, s, tt.s) } - f, err = ParseFloat(tt.s, 64) + f, err = strconv.ParseFloat(tt.s, 64) if f != tt.f || err != nil { t.Errorf("opt ParseFloat(%s) = %b, %v want %b, nil", tt.s, f, err, tt.f) } - SetOptimize(old) + strconv.SetOptimize(old) } } @@ -638,9 +639,9 @@ func TestRoundTrip32(t *testing.T) { if i&1 == 1 { f = -f // negative } - s := FormatFloat(float64(f), 'g', -1, 32) + s := strconv.FormatFloat(float64(f), 'g', -1, 32) - parsed, err := ParseFloat(s, 32) + parsed, err := strconv.ParseFloat(s, 32) parsed32 := float32(parsed) switch { case err != nil: @@ -662,7 +663,7 @@ func TestParseFloatIncorrectBitSize(t *testing.T) { const want = 1.5e308 for _, bitSize := range []int{0, 10, 100, 128} { - f, err := ParseFloat(s, bitSize) + f, err := strconv.ParseFloat(s, bitSize) if err != nil { t.Fatalf("ParseFloat(%q, %d) gave error %s", s, bitSize, err) } @@ -674,25 +675,25 @@ func TestParseFloatIncorrectBitSize(t *testing.T) { func BenchmarkAtof64Decimal(b *testing.B) { for i := 0; i < b.N; i++ { - ParseFloat("33909", 64) + strconv.ParseFloat("33909", 64) } } func BenchmarkAtof64Float(b *testing.B) { for i := 0; i < b.N; i++ { - ParseFloat("339.7784", 64) + strconv.ParseFloat("339.7784", 64) } } func BenchmarkAtof64FloatExp(b *testing.B) { for i := 0; i < b.N; i++ { - ParseFloat("-5.09e75", 64) + strconv.ParseFloat("-5.09e75", 64) } } func BenchmarkAtof64Big(b *testing.B) { for i := 0; i < b.N; i++ { - ParseFloat("123456789123456789123456789", 64) + strconv.ParseFloat("123456789123456789123456789", 64) } } @@ -700,7 +701,7 @@ func BenchmarkAtof64RandomBits(b *testing.B) { initAtof() b.ResetTimer() for i := 0; i < b.N; i++ { - ParseFloat(benchmarksRandomBits[i%1024], 64) + strconv.ParseFloat(benchmarksRandomBits[i%1024], 64) } } @@ -708,7 +709,7 @@ func BenchmarkAtof64RandomFloats(b *testing.B) { initAtof() b.ResetTimer() for i := 0; i < b.N; i++ { - ParseFloat(benchmarksRandomNormal[i%1024], 64) + strconv.ParseFloat(benchmarksRandomNormal[i%1024], 64) } } @@ -716,12 +717,12 @@ func BenchmarkAtof64RandomLongFloats(b *testing.B) { initAtof() samples := make([]string, len(atofRandomTests)) for i, t := range atofRandomTests { - samples[i] = FormatFloat(t.x, 'g', 20, 64) + samples[i] = strconv.FormatFloat(t.x, 'g', 20, 64) } b.ResetTimer() idx := 0 for i := 0; i < b.N; i++ { - ParseFloat(samples[idx], 64) + strconv.ParseFloat(samples[idx], 64) idx++ if idx == len(samples) { idx = 0 @@ -731,19 +732,19 @@ func BenchmarkAtof64RandomLongFloats(b *testing.B) { func BenchmarkAtof32Decimal(b *testing.B) { for i := 0; i < b.N; i++ { - ParseFloat("33909", 32) + strconv.ParseFloat("33909", 32) } } func BenchmarkAtof32Float(b *testing.B) { for i := 0; i < b.N; i++ { - ParseFloat("339.778", 32) + strconv.ParseFloat("339.778", 32) } } func BenchmarkAtof32FloatExp(b *testing.B) { for i := 0; i < b.N; i++ { - ParseFloat("12.3456e32", 32) + strconv.ParseFloat("12.3456e32", 32) } } @@ -752,11 +753,11 @@ func BenchmarkAtof32Random(b *testing.B) { var float32strings [4096]string for i := range float32strings { n = (99991*n + 42) % (0xff << 23) - float32strings[i] = FormatFloat(float64(math.Float32frombits(n)), 'g', -1, 32) + float32strings[i] = strconv.FormatFloat(float64(math.Float32frombits(n)), 'g', -1, 32) } b.ResetTimer() for i := 0; i < b.N; i++ { - ParseFloat(float32strings[i%4096], 32) + strconv.ParseFloat(float32strings[i%4096], 32) } } @@ -765,10 +766,10 @@ func BenchmarkAtof32RandomLong(b *testing.B) { var float32strings [4096]string for i := range float32strings { n = (99991*n + 42) % (0xff << 23) - float32strings[i] = FormatFloat(float64(math.Float32frombits(n)), 'g', 20, 32) + float32strings[i] = strconv.FormatFloat(float64(math.Float32frombits(n)), 'g', 20, 32) } b.ResetTimer() for i := 0; i < b.N; i++ { - ParseFloat(float32strings[i%4096], 32) + strconv.ParseFloat(float32strings[i%4096], 32) } } diff --git a/gnovm/stdlibs/strconv/atoi_test.gno b/gnovm/stdlibs/strconv/atoi_test.gno index cb150628f5c..5dc0577924f 100644 --- a/gnovm/stdlibs/strconv/atoi_test.gno +++ b/gnovm/stdlibs/strconv/atoi_test.gno @@ -2,11 +2,12 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package strconv +package strconv_test import ( "errors" "fmt" + "strconv" "testing" ) @@ -17,23 +18,23 @@ type parseUint64Test struct { } var parseUint64Tests = []parseUint64Test{ - {"", 0, ErrSyntax}, + {"", 0, strconv.ErrSyntax}, {"0", 0, nil}, {"1", 1, nil}, {"12345", 12345, nil}, {"012345", 12345, nil}, - {"12345x", 0, ErrSyntax}, + {"12345x", 0, strconv.ErrSyntax}, {"98765432100", 98765432100, nil}, {"18446744073709551615", 1<<64 - 1, nil}, - {"18446744073709551616", 1<<64 - 1, ErrRange}, - {"18446744073709551620", 1<<64 - 1, ErrRange}, - {"1_2_3_4_5", 0, ErrSyntax}, // base=10 so no underscores allowed - {"_12345", 0, ErrSyntax}, - {"1__2345", 0, ErrSyntax}, - {"12345_", 0, ErrSyntax}, - {"-0", 0, ErrSyntax}, - {"-1", 0, ErrSyntax}, - {"+1", 0, ErrSyntax}, + {"18446744073709551616", 1<<64 - 1, strconv.ErrRange}, + {"18446744073709551620", 1<<64 - 1, strconv.ErrRange}, + {"1_2_3_4_5", 0, strconv.ErrSyntax}, // base=10 so no underscores allowed + {"_12345", 0, strconv.ErrSyntax}, + {"1__2345", 0, strconv.ErrSyntax}, + {"12345_", 0, strconv.ErrSyntax}, + {"-0", 0, strconv.ErrSyntax}, + {"-1", 0, strconv.ErrSyntax}, + {"+1", 0, strconv.ErrSyntax}, } type parseUint64BaseTest struct { @@ -44,91 +45,91 @@ type parseUint64BaseTest struct { } var parseUint64BaseTests = []parseUint64BaseTest{ - {"", 0, 0, ErrSyntax}, + {"", 0, 0, strconv.ErrSyntax}, {"0", 0, 0, nil}, - {"0x", 0, 0, ErrSyntax}, - {"0X", 0, 0, ErrSyntax}, + {"0x", 0, 0, strconv.ErrSyntax}, + {"0X", 0, 0, strconv.ErrSyntax}, {"1", 0, 1, nil}, {"12345", 0, 12345, nil}, {"012345", 0, 012345, nil}, {"0x12345", 0, 0x12345, nil}, {"0X12345", 0, 0x12345, nil}, - {"12345x", 0, 0, ErrSyntax}, - {"0xabcdefg123", 0, 0, ErrSyntax}, - {"123456789abc", 0, 0, ErrSyntax}, + {"12345x", 0, 0, strconv.ErrSyntax}, + {"0xabcdefg123", 0, 0, strconv.ErrSyntax}, + {"123456789abc", 0, 0, strconv.ErrSyntax}, {"98765432100", 0, 98765432100, nil}, {"18446744073709551615", 0, 1<<64 - 1, nil}, - {"18446744073709551616", 0, 1<<64 - 1, ErrRange}, - {"18446744073709551620", 0, 1<<64 - 1, ErrRange}, + {"18446744073709551616", 0, 1<<64 - 1, strconv.ErrRange}, + {"18446744073709551620", 0, 1<<64 - 1, strconv.ErrRange}, {"0xFFFFFFFFFFFFFFFF", 0, 1<<64 - 1, nil}, - {"0x10000000000000000", 0, 1<<64 - 1, ErrRange}, + {"0x10000000000000000", 0, 1<<64 - 1, strconv.ErrRange}, {"01777777777777777777777", 0, 1<<64 - 1, nil}, - {"01777777777777777777778", 0, 0, ErrSyntax}, - {"02000000000000000000000", 0, 1<<64 - 1, ErrRange}, + {"01777777777777777777778", 0, 0, strconv.ErrSyntax}, + {"02000000000000000000000", 0, 1<<64 - 1, strconv.ErrRange}, {"0200000000000000000000", 0, 1 << 61, nil}, - {"0b", 0, 0, ErrSyntax}, - {"0B", 0, 0, ErrSyntax}, + {"0b", 0, 0, strconv.ErrSyntax}, + {"0B", 0, 0, strconv.ErrSyntax}, {"0b101", 0, 5, nil}, {"0B101", 0, 5, nil}, - {"0o", 0, 0, ErrSyntax}, - {"0O", 0, 0, ErrSyntax}, + {"0o", 0, 0, strconv.ErrSyntax}, + {"0O", 0, 0, strconv.ErrSyntax}, {"0o377", 0, 255, nil}, {"0O377", 0, 255, nil}, // underscores allowed with base == 0 only {"1_2_3_4_5", 0, 12345, nil}, // base 0 => 10 - {"_12345", 0, 0, ErrSyntax}, - {"1__2345", 0, 0, ErrSyntax}, - {"12345_", 0, 0, ErrSyntax}, + {"_12345", 0, 0, strconv.ErrSyntax}, + {"1__2345", 0, 0, strconv.ErrSyntax}, + {"12345_", 0, 0, strconv.ErrSyntax}, - {"1_2_3_4_5", 10, 0, ErrSyntax}, // base 10 - {"_12345", 10, 0, ErrSyntax}, - {"1__2345", 10, 0, ErrSyntax}, - {"12345_", 10, 0, ErrSyntax}, + {"1_2_3_4_5", 10, 0, strconv.ErrSyntax}, // base 10 + {"_12345", 10, 0, strconv.ErrSyntax}, + {"1__2345", 10, 0, strconv.ErrSyntax}, + {"12345_", 10, 0, strconv.ErrSyntax}, {"0x_1_2_3_4_5", 0, 0x12345, nil}, // base 0 => 16 - {"_0x12345", 0, 0, ErrSyntax}, - {"0x__12345", 0, 0, ErrSyntax}, - {"0x1__2345", 0, 0, ErrSyntax}, - {"0x1234__5", 0, 0, ErrSyntax}, - {"0x12345_", 0, 0, ErrSyntax}, - - {"1_2_3_4_5", 16, 0, ErrSyntax}, // base 16 - {"_12345", 16, 0, ErrSyntax}, - {"1__2345", 16, 0, ErrSyntax}, - {"1234__5", 16, 0, ErrSyntax}, - {"12345_", 16, 0, ErrSyntax}, + {"_0x12345", 0, 0, strconv.ErrSyntax}, + {"0x__12345", 0, 0, strconv.ErrSyntax}, + {"0x1__2345", 0, 0, strconv.ErrSyntax}, + {"0x1234__5", 0, 0, strconv.ErrSyntax}, + {"0x12345_", 0, 0, strconv.ErrSyntax}, + + {"1_2_3_4_5", 16, 0, strconv.ErrSyntax}, // base 16 + {"_12345", 16, 0, strconv.ErrSyntax}, + {"1__2345", 16, 0, strconv.ErrSyntax}, + {"1234__5", 16, 0, strconv.ErrSyntax}, + {"12345_", 16, 0, strconv.ErrSyntax}, {"0_1_2_3_4_5", 0, 012345, nil}, // base 0 => 8 (0377) - {"_012345", 0, 0, ErrSyntax}, - {"0__12345", 0, 0, ErrSyntax}, - {"01234__5", 0, 0, ErrSyntax}, - {"012345_", 0, 0, ErrSyntax}, + {"_012345", 0, 0, strconv.ErrSyntax}, + {"0__12345", 0, 0, strconv.ErrSyntax}, + {"01234__5", 0, 0, strconv.ErrSyntax}, + {"012345_", 0, 0, strconv.ErrSyntax}, {"0o_1_2_3_4_5", 0, 012345, nil}, // base 0 => 8 (0o377) - {"_0o12345", 0, 0, ErrSyntax}, - {"0o__12345", 0, 0, ErrSyntax}, - {"0o1234__5", 0, 0, ErrSyntax}, - {"0o12345_", 0, 0, ErrSyntax}, + {"_0o12345", 0, 0, strconv.ErrSyntax}, + {"0o__12345", 0, 0, strconv.ErrSyntax}, + {"0o1234__5", 0, 0, strconv.ErrSyntax}, + {"0o12345_", 0, 0, strconv.ErrSyntax}, - {"0_1_2_3_4_5", 8, 0, ErrSyntax}, // base 8 - {"_012345", 8, 0, ErrSyntax}, - {"0__12345", 8, 0, ErrSyntax}, - {"01234__5", 8, 0, ErrSyntax}, - {"012345_", 8, 0, ErrSyntax}, + {"0_1_2_3_4_5", 8, 0, strconv.ErrSyntax}, // base 8 + {"_012345", 8, 0, strconv.ErrSyntax}, + {"0__12345", 8, 0, strconv.ErrSyntax}, + {"01234__5", 8, 0, strconv.ErrSyntax}, + {"012345_", 8, 0, strconv.ErrSyntax}, {"0b_1_0_1", 0, 5, nil}, // base 0 => 2 (0b101) - {"_0b101", 0, 0, ErrSyntax}, - {"0b__101", 0, 0, ErrSyntax}, - {"0b1__01", 0, 0, ErrSyntax}, - {"0b10__1", 0, 0, ErrSyntax}, - {"0b101_", 0, 0, ErrSyntax}, + {"_0b101", 0, 0, strconv.ErrSyntax}, + {"0b__101", 0, 0, strconv.ErrSyntax}, + {"0b1__01", 0, 0, strconv.ErrSyntax}, + {"0b10__1", 0, 0, strconv.ErrSyntax}, + {"0b101_", 0, 0, strconv.ErrSyntax}, - {"1_0_1", 2, 0, ErrSyntax}, // base 2 - {"_101", 2, 0, ErrSyntax}, - {"1_01", 2, 0, ErrSyntax}, - {"10_1", 2, 0, ErrSyntax}, - {"101_", 2, 0, ErrSyntax}, + {"1_0_1", 2, 0, strconv.ErrSyntax}, // base 2 + {"_101", 2, 0, strconv.ErrSyntax}, + {"1_01", 2, 0, strconv.ErrSyntax}, + {"10_1", 2, 0, strconv.ErrSyntax}, + {"101_", 2, 0, strconv.ErrSyntax}, } type parseInt64Test struct { @@ -138,7 +139,7 @@ type parseInt64Test struct { } var parseInt64Tests = []parseInt64Test{ - {"", 0, ErrSyntax}, + {"", 0, strconv.ErrSyntax}, {"0", 0, nil}, {"-0", 0, nil}, {"+0", 0, nil}, @@ -153,16 +154,16 @@ var parseInt64Tests = []parseInt64Test{ {"-98765432100", -98765432100, nil}, {"9223372036854775807", 1<<63 - 1, nil}, {"-9223372036854775807", -(1<<63 - 1), nil}, - {"9223372036854775808", 1<<63 - 1, ErrRange}, + {"9223372036854775808", 1<<63 - 1, strconv.ErrRange}, {"-9223372036854775808", -1 << 63, nil}, - {"9223372036854775809", 1<<63 - 1, ErrRange}, - {"-9223372036854775809", -1 << 63, ErrRange}, - {"-1_2_3_4_5", 0, ErrSyntax}, // base=10 so no underscores allowed - {"-_12345", 0, ErrSyntax}, - {"_12345", 0, ErrSyntax}, - {"1__2345", 0, ErrSyntax}, - {"12345_", 0, ErrSyntax}, - {"123%45", 0, ErrSyntax}, + {"9223372036854775809", 1<<63 - 1, strconv.ErrRange}, + {"-9223372036854775809", -1 << 63, strconv.ErrRange}, + {"-1_2_3_4_5", 0, strconv.ErrSyntax}, // base=10 so no underscores allowed + {"-_12345", 0, strconv.ErrSyntax}, + {"_12345", 0, strconv.ErrSyntax}, + {"1__2345", 0, strconv.ErrSyntax}, + {"12345_", 0, strconv.ErrSyntax}, + {"123%45", 0, strconv.ErrSyntax}, } type parseInt64BaseTest struct { @@ -173,7 +174,7 @@ type parseInt64BaseTest struct { } var parseInt64BaseTests = []parseInt64BaseTest{ - {"", 0, 0, ErrSyntax}, + {"", 0, 0, strconv.ErrSyntax}, {"0", 0, 0, nil}, {"-0", 0, 0, nil}, {"1", 0, 1, nil}, @@ -184,16 +185,16 @@ var parseInt64BaseTests = []parseInt64BaseTest{ {"-012345", 0, -012345, nil}, {"0x12345", 0, 0x12345, nil}, {"-0X12345", 0, -0x12345, nil}, - {"12345x", 0, 0, ErrSyntax}, - {"-12345x", 0, 0, ErrSyntax}, + {"12345x", 0, 0, strconv.ErrSyntax}, + {"-12345x", 0, 0, strconv.ErrSyntax}, {"98765432100", 0, 98765432100, nil}, {"-98765432100", 0, -98765432100, nil}, {"9223372036854775807", 0, 1<<63 - 1, nil}, {"-9223372036854775807", 0, -(1<<63 - 1), nil}, - {"9223372036854775808", 0, 1<<63 - 1, ErrRange}, + {"9223372036854775808", 0, 1<<63 - 1, strconv.ErrRange}, {"-9223372036854775808", 0, -1 << 63, nil}, - {"9223372036854775809", 0, 1<<63 - 1, ErrRange}, - {"-9223372036854775809", 0, -1 << 63, ErrRange}, + {"9223372036854775809", 0, 1<<63 - 1, strconv.ErrRange}, + {"-9223372036854775809", 0, -1 << 63, strconv.ErrRange}, // other bases {"g", 17, 16, nil}, @@ -207,9 +208,9 @@ var parseInt64BaseTests = []parseInt64BaseTest{ {"1010", 2, 10, nil}, {"1000000000000000", 2, 1 << 15, nil}, {"111111111111111111111111111111111111111111111111111111111111111", 2, 1<<63 - 1, nil}, - {"1000000000000000000000000000000000000000000000000000000000000000", 2, 1<<63 - 1, ErrRange}, + {"1000000000000000000000000000000000000000000000000000000000000000", 2, 1<<63 - 1, strconv.ErrRange}, {"-1000000000000000000000000000000000000000000000000000000000000000", 2, -1 << 63, nil}, - {"-1000000000000000000000000000000000000000000000000000000000000001", 2, -1 << 63, ErrRange}, + {"-1000000000000000000000000000000000000000000000000000000000000001", 2, -1 << 63, strconv.ErrRange}, // base 8 {"-10", 8, -8, nil}, @@ -224,27 +225,27 @@ var parseInt64BaseTests = []parseInt64BaseTest{ // underscores {"-0x_1_2_3_4_5", 0, -0x12345, nil}, {"0x_1_2_3_4_5", 0, 0x12345, nil}, - {"-_0x12345", 0, 0, ErrSyntax}, - {"_-0x12345", 0, 0, ErrSyntax}, - {"_0x12345", 0, 0, ErrSyntax}, - {"0x__12345", 0, 0, ErrSyntax}, - {"0x1__2345", 0, 0, ErrSyntax}, - {"0x1234__5", 0, 0, ErrSyntax}, - {"0x12345_", 0, 0, ErrSyntax}, + {"-_0x12345", 0, 0, strconv.ErrSyntax}, + {"_-0x12345", 0, 0, strconv.ErrSyntax}, + {"_0x12345", 0, 0, strconv.ErrSyntax}, + {"0x__12345", 0, 0, strconv.ErrSyntax}, + {"0x1__2345", 0, 0, strconv.ErrSyntax}, + {"0x1234__5", 0, 0, strconv.ErrSyntax}, + {"0x12345_", 0, 0, strconv.ErrSyntax}, {"-0_1_2_3_4_5", 0, -012345, nil}, // octal {"0_1_2_3_4_5", 0, 012345, nil}, // octal - {"-_012345", 0, 0, ErrSyntax}, - {"_-012345", 0, 0, ErrSyntax}, - {"_012345", 0, 0, ErrSyntax}, - {"0__12345", 0, 0, ErrSyntax}, - {"01234__5", 0, 0, ErrSyntax}, - {"012345_", 0, 0, ErrSyntax}, + {"-_012345", 0, 0, strconv.ErrSyntax}, + {"_-012345", 0, 0, strconv.ErrSyntax}, + {"_012345", 0, 0, strconv.ErrSyntax}, + {"0__12345", 0, 0, strconv.ErrSyntax}, + {"01234__5", 0, 0, strconv.ErrSyntax}, + {"012345_", 0, 0, strconv.ErrSyntax}, {"+0xf", 0, 0xf, nil}, {"-0xf", 0, -0xf, nil}, - {"0x+f", 0, 0, ErrSyntax}, - {"0x-f", 0, 0, ErrSyntax}, + {"0x+f", 0, 0, strconv.ErrSyntax}, + {"0x-f", 0, 0, strconv.ErrSyntax}, } type parseUint32Test struct { @@ -254,20 +255,20 @@ type parseUint32Test struct { } var parseUint32Tests = []parseUint32Test{ - {"", 0, ErrSyntax}, + {"", 0, strconv.ErrSyntax}, {"0", 0, nil}, {"1", 1, nil}, {"12345", 12345, nil}, {"012345", 12345, nil}, - {"12345x", 0, ErrSyntax}, + {"12345x", 0, strconv.ErrSyntax}, {"987654321", 987654321, nil}, {"4294967295", 1<<32 - 1, nil}, - {"4294967296", 1<<32 - 1, ErrRange}, - {"1_2_3_4_5", 0, ErrSyntax}, // base=10 so no underscores allowed - {"_12345", 0, ErrSyntax}, - {"_12345", 0, ErrSyntax}, - {"1__2345", 0, ErrSyntax}, - {"12345_", 0, ErrSyntax}, + {"4294967296", 1<<32 - 1, strconv.ErrRange}, + {"1_2_3_4_5", 0, strconv.ErrSyntax}, // base=10 so no underscores allowed + {"_12345", 0, strconv.ErrSyntax}, + {"_12345", 0, strconv.ErrSyntax}, + {"1__2345", 0, strconv.ErrSyntax}, + {"12345_", 0, strconv.ErrSyntax}, } type parseInt32Test struct { @@ -277,7 +278,7 @@ type parseInt32Test struct { } var parseInt32Tests = []parseInt32Test{ - {"", 0, ErrSyntax}, + {"", 0, strconv.ErrSyntax}, {"0", 0, nil}, {"-0", 0, nil}, {"1", 1, nil}, @@ -286,22 +287,22 @@ var parseInt32Tests = []parseInt32Test{ {"-12345", -12345, nil}, {"012345", 12345, nil}, {"-012345", -12345, nil}, - {"12345x", 0, ErrSyntax}, - {"-12345x", 0, ErrSyntax}, + {"12345x", 0, strconv.ErrSyntax}, + {"-12345x", 0, strconv.ErrSyntax}, {"987654321", 987654321, nil}, {"-987654321", -987654321, nil}, {"2147483647", 1<<31 - 1, nil}, {"-2147483647", -(1<<31 - 1), nil}, - {"2147483648", 1<<31 - 1, ErrRange}, + {"2147483648", 1<<31 - 1, strconv.ErrRange}, {"-2147483648", -1 << 31, nil}, - {"2147483649", 1<<31 - 1, ErrRange}, - {"-2147483649", -1 << 31, ErrRange}, - {"-1_2_3_4_5", 0, ErrSyntax}, // base=10 so no underscores allowed - {"-_12345", 0, ErrSyntax}, - {"_12345", 0, ErrSyntax}, - {"1__2345", 0, ErrSyntax}, - {"12345_", 0, ErrSyntax}, - {"123%45", 0, ErrSyntax}, + {"2147483649", 1<<31 - 1, strconv.ErrRange}, + {"-2147483649", -1 << 31, strconv.ErrRange}, + {"-1_2_3_4_5", 0, strconv.ErrSyntax}, // base=10 so no underscores allowed + {"-_12345", 0, strconv.ErrSyntax}, + {"_12345", 0, strconv.ErrSyntax}, + {"1__2345", 0, strconv.ErrSyntax}, + {"12345_", 0, strconv.ErrSyntax}, + {"123%45", 0, strconv.ErrSyntax}, } type numErrorTest struct { @@ -320,37 +321,37 @@ func init() { for i := range parseUint64Tests { test := &parseUint64Tests[i] if test.err != nil { - test.err = &NumError{"ParseUint", test.in, test.err} + test.err = &strconv.NumError{"ParseUint", test.in, test.err} } } for i := range parseUint64BaseTests { test := &parseUint64BaseTests[i] if test.err != nil { - test.err = &NumError{"ParseUint", test.in, test.err} + test.err = &strconv.NumError{"ParseUint", test.in, test.err} } } for i := range parseInt64Tests { test := &parseInt64Tests[i] if test.err != nil { - test.err = &NumError{"ParseInt", test.in, test.err} + test.err = &strconv.NumError{"ParseInt", test.in, test.err} } } for i := range parseInt64BaseTests { test := &parseInt64BaseTests[i] if test.err != nil { - test.err = &NumError{"ParseInt", test.in, test.err} + test.err = &strconv.NumError{"ParseInt", test.in, test.err} } } for i := range parseUint32Tests { test := &parseUint32Tests[i] if test.err != nil { - test.err = &NumError{"ParseUint", test.in, test.err} + test.err = &strconv.NumError{"ParseUint", test.in, test.err} } } for i := range parseInt32Tests { test := &parseInt32Tests[i] if test.err != nil { - test.err = &NumError{"ParseInt", test.in, test.err} + test.err = &strconv.NumError{"ParseInt", test.in, test.err} } } } @@ -358,7 +359,7 @@ func init() { func TestParseUint32(t *testing.T) { for i := range parseUint32Tests { test := &parseUint32Tests[i] - out, err := ParseUint(test.in, 10, 32) + out, err := strconv.ParseUint(test.in, 10, 32) if uint64(test.out) != out || !errEqual(test.err, err) { t.Errorf("ParseUint(%q, 10, 32) = %v, %v want %v, %v", test.in, out, err, test.out, test.err) @@ -369,7 +370,7 @@ func TestParseUint32(t *testing.T) { func TestParseUint64(t *testing.T) { for i := range parseUint64Tests { test := &parseUint64Tests[i] - out, err := ParseUint(test.in, 10, 64) + out, err := strconv.ParseUint(test.in, 10, 64) if test.out != out || !errEqual(test.err, err) { t.Errorf("ParseUint(%q, 10, 64) = %v, %v want %v, %v", test.in, out, err, test.out, test.err) @@ -380,7 +381,7 @@ func TestParseUint64(t *testing.T) { func TestParseUint64Base(t *testing.T) { for i := range parseUint64BaseTests { test := &parseUint64BaseTests[i] - out, err := ParseUint(test.in, test.base, 64) + out, err := strconv.ParseUint(test.in, test.base, 64) if test.out != out || !errEqual(test.err, err) { t.Errorf("ParseUint(%q, %v, 64) = %v, %v want %v, %v", test.in, test.base, out, err, test.out, test.err) @@ -391,7 +392,7 @@ func TestParseUint64Base(t *testing.T) { func TestParseInt32(t *testing.T) { for i := range parseInt32Tests { test := &parseInt32Tests[i] - out, err := ParseInt(test.in, 10, 32) + out, err := strconv.ParseInt(test.in, 10, 32) if int64(test.out) != out || !errEqual(test.err, err) { t.Errorf("ParseInt(%q, 10 ,32) = %v, %v want %v, %v", test.in, out, err, test.out, test.err) @@ -402,7 +403,7 @@ func TestParseInt32(t *testing.T) { func TestParseInt64(t *testing.T) { for i := range parseInt64Tests { test := &parseInt64Tests[i] - out, err := ParseInt(test.in, 10, 64) + out, err := strconv.ParseInt(test.in, 10, 64) if test.out != out || !errEqual(test.err, err) { t.Errorf("ParseInt(%q, 10, 64) = %v, %v want %v, %v", test.in, out, err, test.out, test.err) @@ -413,7 +414,7 @@ func TestParseInt64(t *testing.T) { func TestParseInt64Base(t *testing.T) { for i := range parseInt64BaseTests { test := &parseInt64BaseTests[i] - out, err := ParseInt(test.in, test.base, 64) + out, err := strconv.ParseInt(test.in, test.base, 64) if test.out != out || !errEqual(test.err, err) { t.Errorf("ParseInt(%q, %v, 64) = %v, %v want %v, %v", test.in, test.base, out, err, test.out, test.err) @@ -422,11 +423,11 @@ func TestParseInt64Base(t *testing.T) { } func TestParseUint(t *testing.T) { - switch IntSize { + switch strconv.IntSize { case 32: for i := range parseUint32Tests { test := &parseUint32Tests[i] - out, err := ParseUint(test.in, 10, 0) + out, err := strconv.ParseUint(test.in, 10, 0) if uint64(test.out) != out || !errEqual(test.err, err) { t.Errorf("ParseUint(%q, 10, 0) = %v, %v want %v, %v", test.in, out, err, test.out, test.err) @@ -435,7 +436,7 @@ func TestParseUint(t *testing.T) { case 64: for i := range parseUint64Tests { test := &parseUint64Tests[i] - out, err := ParseUint(test.in, 10, 0) + out, err := strconv.ParseUint(test.in, 10, 0) if test.out != out || !errEqual(test.err, err) { t.Errorf("ParseUint(%q, 10, 0) = %v, %v want %v, %v", test.in, out, err, test.out, test.err) @@ -445,11 +446,11 @@ func TestParseUint(t *testing.T) { } func TestParseInt(t *testing.T) { - switch IntSize { + switch strconv.IntSize { case 32: for i := range parseInt32Tests { test := &parseInt32Tests[i] - out, err := ParseInt(test.in, 10, 0) + out, err := strconv.ParseInt(test.in, 10, 0) if int64(test.out) != out || !errEqual(test.err, err) { t.Errorf("ParseInt(%q, 10, 0) = %v, %v want %v, %v", test.in, out, err, test.out, test.err) @@ -458,7 +459,7 @@ func TestParseInt(t *testing.T) { case 64: for i := range parseInt64Tests { test := &parseInt64Tests[i] - out, err := ParseInt(test.in, 10, 0) + out, err := strconv.ParseInt(test.in, 10, 0) if test.out != out || !errEqual(test.err, err) { t.Errorf("ParseInt(%q, 10, 0) = %v, %v want %v, %v", test.in, out, err, test.out, test.err) @@ -468,14 +469,14 @@ func TestParseInt(t *testing.T) { } func TestAtoi(t *testing.T) { - switch IntSize { + switch strconv.IntSize { case 32: for i := range parseInt32Tests { test := &parseInt32Tests[i] - out, err := Atoi(test.in) + out, err := strconv.Atoi(test.in) var testErr error if test.err != nil { - testErr = &NumError{"Atoi", test.in, test.err.(*NumError).Err} + testErr = &strconv.NumError{"Atoi", test.in, test.err.(*strconv.NumError).Err} } if int(test.out) != out || !errEqual(testErr, err) { t.Errorf("Atoi(%q) = %v, %v want %v, %v", @@ -485,10 +486,10 @@ func TestAtoi(t *testing.T) { case 64: for i := range parseInt64Tests { test := &parseInt64Tests[i] - out, err := Atoi(test.in) + out, err := strconv.Atoi(test.in) var testErr error if test.err != nil { - testErr = &NumError{"Atoi", test.in, test.err.(*NumError).Err} + testErr = &strconv.NumError{"Atoi", test.in, test.err.(*strconv.NumError).Err} } if test.out != int64(out) || !errEqual(testErr, err) { t.Errorf("Atoi(%q) = %v, %v want %v, %v", @@ -499,11 +500,11 @@ func TestAtoi(t *testing.T) { } func bitSizeErrStub(name string, bitSize int) error { - return BitSizeError(name, "0", bitSize) + return strconv.BitSizeError(name, "0", bitSize) } func baseErrStub(name string, base int) error { - return BaseError(name, "0", base) + return strconv.BaseError(name, "0", base) } func noErrStub(name string, arg int) error { @@ -545,7 +546,7 @@ func TestParseIntBitSize(t *testing.T) { for i := range parseBitSizeTests { test := &parseBitSizeTests[i] testErr := test.errStub("ParseInt", test.arg) - _, err := ParseInt("0", 0, test.arg) + _, err := strconv.ParseInt("0", 0, test.arg) if !equalError(testErr, err) { t.Errorf("ParseInt(\"0\", 0, %v) = 0, %v want 0, %v", test.arg, err, testErr) @@ -557,7 +558,7 @@ func TestParseUintBitSize(t *testing.T) { for i := range parseBitSizeTests { test := &parseBitSizeTests[i] testErr := test.errStub("ParseUint", test.arg) - _, err := ParseUint("0", 0, test.arg) + _, err := strconv.ParseUint("0", 0, test.arg) if !equalError(testErr, err) { t.Errorf("ParseUint(\"0\", 0, %v) = 0, %v want 0, %v", test.arg, err, testErr) @@ -569,7 +570,7 @@ func TestParseIntBase(t *testing.T) { for i := range parseBaseTests { test := &parseBaseTests[i] testErr := test.errStub("ParseInt", test.arg) - _, err := ParseInt("0", test.arg, 0) + _, err := strconv.ParseInt("0", test.arg, 0) if !equalError(testErr, err) { t.Errorf("ParseInt(\"0\", %v, 0) = 0, %v want 0, %v", test.arg, err, testErr) @@ -581,7 +582,7 @@ func TestParseUintBase(t *testing.T) { for i := range parseBaseTests { test := &parseBaseTests[i] testErr := test.errStub("ParseUint", test.arg) - _, err := ParseUint("0", test.arg, 0) + _, err := strconv.ParseUint("0", test.arg, 0) if !equalError(testErr, err) { t.Errorf("ParseUint(\"0\", %v, 0) = 0, %v want 0, %v", test.arg, err, testErr) @@ -591,21 +592,21 @@ func TestParseUintBase(t *testing.T) { func TestNumError(t *testing.T) { for _, test := range numErrorTests { - err := &NumError{ + err := &strconv.NumError{ Func: "ParseFloat", Num: test.num, Err: errors.New("failed"), } if got := err.Error(); got != test.want { - t.Errorf(`(&NumError{"ParseFloat", %q, "failed"}).Error() = %v, want %v`, test.num, got, test.want) + t.Errorf(`(&strconv.NumError{"ParseFloat", %q, "failed"}).Error() = %v, want %v`, test.num, got, test.want) } } } /* XXX: add when we support reflection / error un/wrapping. func TestNumErrorUnwrap(t *testing.T) { - err := &NumError{Err: ErrSyntax} - if !errEqual(err, ErrSyntax) { + err := &strconv.NumError{Err: strconv.ErrSyntax} + if !errEqual(err, strconv.ErrSyntax) { t.Error("errors.Is failed, wanted success") } } @@ -637,7 +638,7 @@ func benchmarkParseInt(b *testing.B, neg int) { b.Run(cs.name, func(b *testing.B) { s := fmt.Sprintf("%d", cs.num*int64(neg)) for i := 0; i < b.N; i++ { - out, _ := ParseInt(s, 10, 64) + out, _ := strconv.ParseInt(s, 10, 64) BenchSink += int(out) } }) @@ -659,7 +660,7 @@ func benchmarkAtoi(b *testing.B, neg int) { {"26bit", 1<<26 - 1}, {"31bit", 1<<31 - 1}, } - if IntSize == 64 { + if strconv.IntSize == 64 { cases = append(cases, []benchCase{ {"56bit", 1<<56 - 1}, {"63bit", 1<<63 - 1}, @@ -669,7 +670,7 @@ func benchmarkAtoi(b *testing.B, neg int) { b.Run(cs.name, func(b *testing.B) { s := fmt.Sprintf("%d", cs.num*int64(neg)) for i := 0; i < b.N; i++ { - out, _ := Atoi(s) + out, _ := strconv.Atoi(s) BenchSink += out } }) diff --git a/gnovm/stdlibs/strconv/decimal_test.gno b/gnovm/stdlibs/strconv/decimal_test.gno index 9dc8c997b9c..58036fe69b0 100644 --- a/gnovm/stdlibs/strconv/decimal_test.gno +++ b/gnovm/stdlibs/strconv/decimal_test.gno @@ -2,9 +2,10 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package strconv +package strconv_test import ( + "strconv" "testing" ) @@ -31,7 +32,7 @@ var shifttests = []shiftTest{ func TestDecimalShift(t *testing.T) { for i := 0; i < len(shifttests); i++ { test := &shifttests[i] - d := NewDecimal(test.i) + d := strconv.NewDecimal(test.i) d.Shift(test.shift) s := d.String() if s != test.out { @@ -69,21 +70,21 @@ var roundtests = []roundTest{ func TestDecimalRound(t *testing.T) { for i := 0; i < len(roundtests); i++ { test := &roundtests[i] - d := NewDecimal(test.i) + d := strconv.NewDecimal(test.i) d.RoundDown(test.nd) s := d.String() if s != test.down { t.Errorf("Decimal %v RoundDown %d = %v, want %v", test.i, test.nd, s, test.down) } - d = NewDecimal(test.i) + d = strconv.NewDecimal(test.i) d.Round(test.nd) s = d.String() if s != test.round { t.Errorf("Decimal %v Round %d = %v, want %v", test.i, test.nd, s, test.down) } - d = NewDecimal(test.i) + d = strconv.NewDecimal(test.i) d.RoundUp(test.nd) s = d.String() if s != test.up { @@ -115,7 +116,7 @@ var roundinttests = []roundIntTest{ func TestDecimalRoundedInteger(t *testing.T) { for i := 0; i < len(roundinttests); i++ { test := roundinttests[i] - d := NewDecimal(test.i) + d := strconv.NewDecimal(test.i) d.Shift(test.shift) num := d.RoundedInteger() if num != test.int { diff --git a/gnovm/stdlibs/strconv/ftoa_test.gno b/gnovm/stdlibs/strconv/ftoa_test.gno index df1cc733827..bb860d62278 100644 --- a/gnovm/stdlibs/strconv/ftoa_test.gno +++ b/gnovm/stdlibs/strconv/ftoa_test.gno @@ -2,11 +2,12 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package strconv +package strconv_test import ( "math" "math/rand" + "strconv" "testing" ) @@ -176,20 +177,20 @@ var ftoatests = []ftoaTest{ func TestFtoa(t *testing.T) { for i := 0; i < len(ftoatests); i++ { test := &ftoatests[i] - s := FormatFloat(test.f, test.fmt, test.prec, 64) + s := strconv.FormatFloat(test.f, test.fmt, test.prec, 64) if s != test.s { t.Error("testN=64", test.f, string(test.fmt), test.prec, "want", test.s, "got", s) } - x := AppendFloat([]byte("abc"), test.f, test.fmt, test.prec, 64) + x := strconv.AppendFloat([]byte("abc"), test.f, test.fmt, test.prec, 64) if string(x) != "abc"+test.s { t.Error("AppendFloat testN=64", test.f, string(test.fmt), test.prec, "want", "abc"+test.s, "got", string(x)) } if float64(float32(test.f)) == test.f && test.fmt != 'b' { - s := FormatFloat(test.f, test.fmt, test.prec, 32) + s := strconv.FormatFloat(test.f, test.fmt, test.prec, 32) if s != test.s { t.Error("testN=32", test.f, string(test.fmt), test.prec, "want", test.s, "got", s) } - x := AppendFloat([]byte("abc"), test.f, test.fmt, test.prec, 32) + x := strconv.AppendFloat([]byte("abc"), test.f, test.fmt, test.prec, 32) if string(x) != "abc"+test.s { t.Error("AppendFloat testN=32", test.f, string(test.fmt), test.prec, "want", "abc"+test.s, "got", string(x)) } @@ -201,15 +202,15 @@ func TestFtoaPowersOfTwo(t *testing.T) { for exp := -2048; exp <= 2048; exp++ { f := math.Ldexp(1, exp) if !math.IsInf(f, 0) { - s := FormatFloat(f, 'e', -1, 64) - if x, _ := ParseFloat(s, 64); x != f { + s := strconv.FormatFloat(f, 'e', -1, 64) + if x, _ := strconv.ParseFloat(s, 64); x != f { t.Errorf("failed roundtrip %v => %s => %v", f, s, x) } } f32 := float32(f) if !math.IsInf(float64(f32), 0) { - s := FormatFloat(float64(f32), 'e', -1, 32) - if x, _ := ParseFloat(s, 32); float32(x) != f32 { + s := strconv.FormatFloat(float64(f32), 'e', -1, 32) + if x, _ := strconv.ParseFloat(s, 32); float32(x) != f32 { t.Errorf("failed roundtrip %v => %s => %v", f32, s, float32(x)) } } @@ -226,19 +227,19 @@ func TestFtoaRandom(t *testing.T) { bits := uint64(rand.Uint32())<<32 | uint64(rand.Uint32()) x := math.Float64frombits(bits) - shortFast := FormatFloat(x, 'g', -1, 64) - SetOptimize(false) - shortSlow := FormatFloat(x, 'g', -1, 64) - SetOptimize(true) + shortFast := strconv.FormatFloat(x, 'g', -1, 64) + strconv.SetOptimize(false) + shortSlow := strconv.FormatFloat(x, 'g', -1, 64) + strconv.SetOptimize(true) if shortSlow != shortFast { t.Errorf("%b printed as %s, want %s", x, shortFast, shortSlow) } prec := rand.IntN(12) + 5 - shortFast = FormatFloat(x, 'e', prec, 64) - SetOptimize(false) - shortSlow = FormatFloat(x, 'e', prec, 64) - SetOptimize(true) + shortFast = strconv.FormatFloat(x, 'e', prec, 64) + strconv.SetOptimize(false) + shortSlow = strconv.FormatFloat(x, 'e', prec, 64) + strconv.SetOptimize(true) if shortSlow != shortFast { t.Errorf("%b printed as %s, want %s", x, shortFast, shortSlow) } @@ -251,7 +252,7 @@ func TestFormatFloatInvalidBitSize(t *testing.T) { t.Fatalf("expected panic due to invalid bitSize") } }() - _ = FormatFloat(3.14, 'g', -1, 100) + _ = strconv.FormatFloat(3.14, 'g', -1, 100) } var ftoaBenches = []struct { @@ -305,7 +306,7 @@ func BenchmarkFormatFloat(b *testing.B) { for _, c := range ftoaBenches { b.Run(c.name, func(b *testing.B) { for i := 0; i < b.N; i++ { - FormatFloat(c.float, c.fmt, c.prec, c.bitSize) + strconv.FormatFloat(c.float, c.fmt, c.prec, c.bitSize) } }) } @@ -316,7 +317,7 @@ func BenchmarkAppendFloat(b *testing.B) { for _, c := range ftoaBenches { b.Run(c.name, func(b *testing.B) { for i := 0; i < b.N; i++ { - AppendFloat(dst[:0], c.float, c.fmt, c.prec, c.bitSize) + strconv.AppendFloat(dst[:0], c.float, c.fmt, c.prec, c.bitSize) } }) } diff --git a/gnovm/stdlibs/strconv/ftoaryu_test.gno b/gnovm/stdlibs/strconv/ftoaryu_test.gno index bd969e8e997..8a7cb1fb97d 100644 --- a/gnovm/stdlibs/strconv/ftoaryu_test.gno +++ b/gnovm/stdlibs/strconv/ftoaryu_test.gno @@ -2,16 +2,17 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package strconv +package strconv_test import ( "math" + "strconv" "testing" ) func TestMulByLog2Log10(t *testing.T) { for x := -1600; x <= +1600; x++ { - iMath := MulByLog2Log10(x) + iMath := strconv.MulByLog2Log10(x) fMath := int(math.Floor(float64(x) * math.Ln2 / math.Ln10)) if iMath != fMath { t.Errorf("mulByLog2Log10(%d) failed: %d vs %d\n", x, iMath, fMath) @@ -21,7 +22,7 @@ func TestMulByLog2Log10(t *testing.T) { func TestMulByLog10Log2(t *testing.T) { for x := -500; x <= +500; x++ { - iMath := MulByLog10Log2(x) + iMath := strconv.MulByLog10Log2(x) fMath := int(math.Floor(float64(x) * math.Ln10 / math.Ln2)) if iMath != fMath { t.Errorf("mulByLog10Log2(%d) failed: %d vs %d\n", x, iMath, fMath) diff --git a/gnovm/stdlibs/strconv/itoa_test.gno b/gnovm/stdlibs/strconv/itoa_test.gno index b76acc78183..e463edeb5eb 100644 --- a/gnovm/stdlibs/strconv/itoa_test.gno +++ b/gnovm/stdlibs/strconv/itoa_test.gno @@ -2,9 +2,10 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package strconv +package strconv_test import ( + "strconv" "testing" ) @@ -60,24 +61,24 @@ var itob64tests = []itob64Test{ func TestItoa(t *testing.T) { for _, test := range itob64tests { - s := FormatInt(test.in, test.base) + s := strconv.FormatInt(test.in, test.base) if s != test.out { t.Errorf("FormatInt(%v, %v) = %v want %v", test.in, test.base, s, test.out) } - x := AppendInt([]byte("abc"), test.in, test.base) + x := strconv.AppendInt([]byte("abc"), test.in, test.base) if string(x) != "abc"+test.out { t.Errorf("AppendInt(%q, %v, %v) = %q want %v", "abc", test.in, test.base, x, test.out) } if test.in >= 0 { - s := FormatUint(uint64(test.in), test.base) + s := strconv.FormatUint(uint64(test.in), test.base) if s != test.out { t.Errorf("FormatUint(%v, %v) = %v want %v", test.in, test.base, s, test.out) } - x := AppendUint(nil, uint64(test.in), test.base) + x := strconv.AppendUint(nil, uint64(test.in), test.base) if string(x) != test.out { t.Errorf("AppendUint(%q, %v, %v) = %q want %v", "abc", uint64(test.in), test.base, x, test.out) @@ -85,7 +86,7 @@ func TestItoa(t *testing.T) { } if test.base == 10 && int64(int(test.in)) == test.in { - s := Itoa(int(test.in)) + s := strconv.Itoa(int(test.in)) if s != test.out { t.Errorf("Itoa(%v) = %v want %v", test.in, s, test.out) @@ -99,7 +100,7 @@ func TestItoa(t *testing.T) { t.Fatalf("expected panic due to illegal base") } }() - FormatUint(12345678, 1) + strconv.FormatUint(12345678, 1) } type uitob64Test struct { @@ -119,12 +120,12 @@ var uitob64tests = []uitob64Test{ func TestUitoa(t *testing.T) { for _, test := range uitob64tests { - s := FormatUint(test.in, test.base) + s := strconv.FormatUint(test.in, test.base) if s != test.out { t.Errorf("FormatUint(%v, %v) = %v want %v", test.in, test.base, s, test.out) } - x := AppendUint([]byte("abc"), test.in, test.base) + x := strconv.AppendUint([]byte("abc"), test.in, test.base) if string(x) != "abc"+test.out { t.Errorf("AppendUint(%q, %v, %v) = %q want %v", "abc", test.in, test.base, x, test.out) @@ -161,7 +162,7 @@ var varlenUints = []struct { func TestFormatUintVarlen(t *testing.T) { for _, test := range varlenUints { - s := FormatUint(test.in, 10) + s := strconv.FormatUint(test.in, 10) if s != test.out { t.Errorf("FormatUint(%v, 10) = %v want %v", test.in, s, test.out) } @@ -171,7 +172,7 @@ func TestFormatUintVarlen(t *testing.T) { func BenchmarkFormatInt(b *testing.B) { for i := 0; i < b.N; i++ { for _, test := range itob64tests { - s := FormatInt(test.in, test.base) + s := strconv.FormatInt(test.in, test.base) BenchSink += len(s) } } @@ -181,7 +182,7 @@ func BenchmarkAppendInt(b *testing.B) { dst := make([]byte, 0, 30) for i := 0; i < b.N; i++ { for _, test := range itob64tests { - dst = AppendInt(dst[:0], test.in, test.base) + dst = strconv.AppendInt(dst[:0], test.in, test.base) BenchSink += len(dst) } } @@ -190,7 +191,7 @@ func BenchmarkAppendInt(b *testing.B) { func BenchmarkFormatUint(b *testing.B) { for i := 0; i < b.N; i++ { for _, test := range uitob64tests { - s := FormatUint(test.in, test.base) + s := strconv.FormatUint(test.in, test.base) BenchSink += len(s) } } @@ -200,7 +201,7 @@ func BenchmarkAppendUint(b *testing.B) { dst := make([]byte, 0, 30) for i := 0; i < b.N; i++ { for _, test := range uitob64tests { - dst = AppendUint(dst[:0], test.in, test.base) + dst = strconv.AppendUint(dst[:0], test.in, test.base) BenchSink += len(dst) } } @@ -209,9 +210,9 @@ func BenchmarkAppendUint(b *testing.B) { func BenchmarkFormatIntSmall(b *testing.B) { smallInts := []int64{7, 42} for _, smallInt := range smallInts { - b.Run(Itoa(int(smallInt)), func(b *testing.B) { + b.Run(strconv.Itoa(int(smallInt)), func(b *testing.B) { for i := 0; i < b.N; i++ { - s := FormatInt(smallInt, 10) + s := strconv.FormatInt(smallInt, 10) BenchSink += len(s) } }) @@ -222,7 +223,7 @@ func BenchmarkAppendIntSmall(b *testing.B) { dst := make([]byte, 0, 30) const smallInt = 42 for i := 0; i < b.N; i++ { - dst = AppendInt(dst[:0], smallInt, 10) + dst = strconv.AppendInt(dst[:0], smallInt, 10) BenchSink += len(dst) } } @@ -232,7 +233,7 @@ func BenchmarkAppendUintVarlen(b *testing.B) { b.Run(test.out, func(b *testing.B) { dst := make([]byte, 0, 30) for j := 0; j < b.N; j++ { - dst = AppendUint(dst[:0], test.in, 10) + dst = strconv.AppendUint(dst[:0], test.in, 10) BenchSink += len(dst) } }) diff --git a/gnovm/stdlibs/strconv/quote_test.gno b/gnovm/stdlibs/strconv/quote_test.gno index b11e95461b0..40601365a30 100644 --- a/gnovm/stdlibs/strconv/quote_test.gno +++ b/gnovm/stdlibs/strconv/quote_test.gno @@ -2,9 +2,10 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package strconv +package strconv_test import ( + "strconv" "strings" "testing" "unicode" @@ -14,8 +15,8 @@ import ( func TestIsPrint(t *testing.T) { n := 0 for r := rune(0); r <= unicode.MaxRune; r++ { - if IsPrint(r) != unicode.IsPrint(r) { - t.Errorf("IsPrint(%U)=%t incorrect", r, IsPrint(r)) + if strconv.IsPrint(r) != unicode.IsPrint(r) { + t.Errorf("IsPrint(%U)=%t incorrect", r, strconv.IsPrint(r)) n++ if n > 10 { return @@ -28,8 +29,8 @@ func TestIsPrint(t *testing.T) { func TestIsGraphic(t *testing.T) { n := 0 for r := rune(0); r <= unicode.MaxRune; r++ { - if IsGraphic(r) != unicode.IsGraphic(r) { - t.Errorf("IsGraphic(%U)=%t incorrect", r, IsGraphic(r)) + if strconv.IsGraphic(r) != unicode.IsGraphic(r) { + t.Errorf("IsGraphic(%U)=%t incorrect", r, strconv.IsGraphic(r)) n++ if n > 10 { return @@ -59,10 +60,10 @@ var quotetests = []quoteTest{ func TestQuote(t *testing.T) { for _, tt := range quotetests { - if out := Quote(tt.in); out != tt.out { + if out := strconv.Quote(tt.in); out != tt.out { t.Errorf("Quote(%s) = %s, want %s", tt.in, out, tt.out) } - if out := AppendQuote([]byte("abc"), tt.in); string(out) != "abc"+tt.out { + if out := strconv.AppendQuote([]byte("abc"), tt.in); string(out) != "abc"+tt.out { t.Errorf("AppendQuote(%q, %s) = %s, want %s", "abc", tt.in, out, "abc"+tt.out) } } @@ -70,10 +71,10 @@ func TestQuote(t *testing.T) { func TestQuoteToASCII(t *testing.T) { for _, tt := range quotetests { - if out := QuoteToASCII(tt.in); out != tt.ascii { + if out := strconv.QuoteToASCII(tt.in); out != tt.ascii { t.Errorf("QuoteToASCII(%s) = %s, want %s", tt.in, out, tt.ascii) } - if out := AppendQuoteToASCII([]byte("abc"), tt.in); string(out) != "abc"+tt.ascii { + if out := strconv.AppendQuoteToASCII([]byte("abc"), tt.in); string(out) != "abc"+tt.ascii { t.Errorf("AppendQuoteToASCII(%q, %s) = %s, want %s", "abc", tt.in, out, "abc"+tt.ascii) } } @@ -81,10 +82,10 @@ func TestQuoteToASCII(t *testing.T) { func TestQuoteToGraphic(t *testing.T) { for _, tt := range quotetests { - if out := QuoteToGraphic(tt.in); out != tt.graphic { + if out := strconv.QuoteToGraphic(tt.in); out != tt.graphic { t.Errorf("QuoteToGraphic(%s) = %s, want %s", tt.in, out, tt.graphic) } - if out := AppendQuoteToGraphic([]byte("abc"), tt.in); string(out) != "abc"+tt.graphic { + if out := strconv.AppendQuoteToGraphic([]byte("abc"), tt.in); string(out) != "abc"+tt.graphic { t.Errorf("AppendQuoteToGraphic(%q, %s) = %s, want %s", "abc", tt.in, out, "abc"+tt.graphic) } } @@ -92,13 +93,13 @@ func TestQuoteToGraphic(t *testing.T) { func BenchmarkQuote(b *testing.B) { for i := 0; i < b.N; i++ { - Quote("\a\b\f\r\n\t\v\a\b\f\r\n\t\v\a\b\f\r\n\t\v") + strconv.Quote("\a\b\f\r\n\t\v\a\b\f\r\n\t\v\a\b\f\r\n\t\v") } } func BenchmarkQuoteRune(b *testing.B) { for i := 0; i < b.N; i++ { - QuoteRune('\a') + strconv.QuoteRune('\a') } } @@ -106,7 +107,7 @@ var benchQuoteBuf []byte func BenchmarkAppendQuote(b *testing.B) { for i := 0; i < b.N; i++ { - benchQuoteBuf = AppendQuote(benchQuoteBuf[:0], "\a\b\f\r\n\t\v\a\b\f\r\n\t\v\a\b\f\r\n\t\v") + benchQuoteBuf = strconv.AppendQuote(benchQuoteBuf[:0], "\a\b\f\r\n\t\v\a\b\f\r\n\t\v\a\b\f\r\n\t\v") } } @@ -114,7 +115,7 @@ var benchQuoteRuneBuf []byte func BenchmarkAppendQuoteRune(b *testing.B) { for i := 0; i < b.N; i++ { - benchQuoteRuneBuf = AppendQuoteRune(benchQuoteRuneBuf[:0], '\a') + benchQuoteRuneBuf = strconv.AppendQuoteRune(benchQuoteRuneBuf[:0], '\a') } } @@ -144,10 +145,10 @@ var quoterunetests = []quoteRuneTest{ func TestQuoteRune(t *testing.T) { for _, tt := range quoterunetests { - if out := QuoteRune(tt.in); out != tt.out { + if out := strconv.QuoteRune(tt.in); out != tt.out { t.Errorf("QuoteRune(%U) = %s, want %s", tt.in, out, tt.out) } - if out := AppendQuoteRune([]byte("abc"), tt.in); string(out) != "abc"+tt.out { + if out := strconv.AppendQuoteRune([]byte("abc"), tt.in); string(out) != "abc"+tt.out { t.Errorf("AppendQuoteRune(%q, %U) = %s, want %s", "abc", tt.in, out, "abc"+tt.out) } } @@ -155,10 +156,10 @@ func TestQuoteRune(t *testing.T) { func TestQuoteRuneToASCII(t *testing.T) { for _, tt := range quoterunetests { - if out := QuoteRuneToASCII(tt.in); out != tt.ascii { + if out := strconv.QuoteRuneToASCII(tt.in); out != tt.ascii { t.Errorf("QuoteRuneToASCII(%U) = %s, want %s", tt.in, out, tt.ascii) } - if out := AppendQuoteRuneToASCII([]byte("abc"), tt.in); string(out) != "abc"+tt.ascii { + if out := strconv.AppendQuoteRuneToASCII([]byte("abc"), tt.in); string(out) != "abc"+tt.ascii { t.Errorf("AppendQuoteRuneToASCII(%q, %U) = %s, want %s", "abc", tt.in, out, "abc"+tt.ascii) } } @@ -166,10 +167,10 @@ func TestQuoteRuneToASCII(t *testing.T) { func TestQuoteRuneToGraphic(t *testing.T) { for _, tt := range quoterunetests { - if out := QuoteRuneToGraphic(tt.in); out != tt.graphic { + if out := strconv.QuoteRuneToGraphic(tt.in); out != tt.graphic { t.Errorf("QuoteRuneToGraphic(%U) = %s, want %s", tt.in, out, tt.graphic) } - if out := AppendQuoteRuneToGraphic([]byte("abc"), tt.in); string(out) != "abc"+tt.graphic { + if out := strconv.AppendQuoteRuneToGraphic([]byte("abc"), tt.in); string(out) != "abc"+tt.graphic { t.Errorf("AppendQuoteRuneToGraphic(%q, %U) = %s, want %s", "abc", tt.in, out, "abc"+tt.graphic) } } @@ -228,7 +229,7 @@ var canbackquotetests = []canBackquoteTest{ func TestCanBackquote(t *testing.T) { for _, tt := range canbackquotetests { - if out := CanBackquote(tt.in); out != tt.out { + if out := strconv.CanBackquote(tt.in); out != tt.out { t.Errorf("CanBackquote(%q) = %v, want %v", tt.in, out, tt.out) } } @@ -318,7 +319,7 @@ func TestUnquote(t *testing.T) { testUnquote(t, tt.out, tt.in, nil) } for _, s := range misquoted { - testUnquote(t, s, "", ErrSyntax) + testUnquote(t, s, "", strconv.ErrSyntax) } } @@ -332,7 +333,7 @@ func TestUnquoteInvalidUTF8(t *testing.T) { wantErr error }{ {in: `"foo"`, want: "foo"}, - {in: `"foo`, wantErr: ErrSyntax}, + {in: `"foo`, wantErr: strconv.ErrSyntax}, {in: `"` + "\xc0" + `"`, want: "\xef\xbf\xbd"}, {in: `"a` + "\xc0" + `"`, want: "a\xef\xbf\xbd"}, {in: `"\t` + "\xc0" + `"`, want: "\t\xef\xbf\xbd"}, @@ -344,7 +345,7 @@ func TestUnquoteInvalidUTF8(t *testing.T) { func testUnquote(t *testing.T, in, want string, wantErr error) { // Test Unquote. - got, gotErr := Unquote(in) + got, gotErr := strconv.Unquote(in) if got != want || gotErr != wantErr { t.Errorf("Unquote(%q) = (%q, %v), want (%q, %v)", in, got, gotErr, want, wantErr) } @@ -360,9 +361,9 @@ func testUnquote(t *testing.T, in, want string, wantErr error) { suffix = strings.ReplaceAll(suffix, in[:1], "") } in += suffix - got, gotErr = QuotedPrefix(in) + got, gotErr = strconv.QuotedPrefix(in) if gotErr == nil && wantErr != nil { - _, wantErr = Unquote(got) // original input had trailing junk, reparse with only valid prefix + _, wantErr = strconv.Unquote(got) // original input had trailing junk, reparse with only valid prefix want = got } if got != want || gotErr != wantErr { @@ -372,12 +373,12 @@ func testUnquote(t *testing.T, in, want string, wantErr error) { func BenchmarkUnquoteEasy(b *testing.B) { for i := 0; i < b.N; i++ { - Unquote(`"Give me a rock, paper and scissors and I will move the world."`) + strconv.Unquote(`"Give me a rock, paper and scissors and I will move the world."`) } } func BenchmarkUnquoteHard(b *testing.B) { for i := 0; i < b.N; i++ { - Unquote(`"\x47ive me a \x72ock, \x70aper and \x73cissors and \x49 will move the world."`) + strconv.Unquote(`"\x47ive me a \x72ock, \x70aper and \x73cissors and \x49 will move the world."`) } } diff --git a/gnovm/stdlibs/strconv/strconv_test.gno b/gnovm/stdlibs/strconv/strconv_test.gno index 5421ae84a93..e80eca207f7 100644 --- a/gnovm/stdlibs/strconv/strconv_test.gno +++ b/gnovm/stdlibs/strconv/strconv_test.gno @@ -2,9 +2,10 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package strconv +package strconv_test import ( + "strconv" "strings" "testing" ) @@ -21,24 +22,24 @@ var ( }{ {0, `AppendInt(localBuf[:0], 123, 10)`, func() { var localBuf [64]byte - AppendInt(localBuf[:0], 123, 10) + strconv.AppendInt(localBuf[:0], 123, 10) }}, - {0, `AppendInt(globalBuf[:0], 123, 10)`, func() { AppendInt(globalBuf[:0], 123, 10) }}, + {0, `AppendInt(globalBuf[:0], 123, 10)`, func() { strconv.AppendInt(globalBuf[:0], 123, 10) }}, {0, `AppendFloat(localBuf[:0], 1.23, 'g', 5, 64)`, func() { var localBuf [64]byte - AppendFloat(localBuf[:0], 1.23, 'g', 5, 64) + strconv.AppendFloat(localBuf[:0], 1.23, 'g', 5, 64) }}, - {0, `AppendFloat(globalBuf[:0], 1.23, 'g', 5, 64)`, func() { AppendFloat(globalBuf[:0], 1.23, 'g', 5, 64) }}, + {0, `AppendFloat(globalBuf[:0], 1.23, 'g', 5, 64)`, func() { strconv.AppendFloat(globalBuf[:0], 1.23, 'g', 5, 64) }}, // In practice we see 7 for the next one, but allow some slop. // Before pre-allocation in appendQuotedWith, we saw 39. - {10, `AppendQuoteToASCII(nil, oneMB)`, func() { AppendQuoteToASCII(nil, string(oneMB)) }}, - {0, `ParseFloat("123.45", 64)`, func() { ParseFloat("123.45", 64) }}, - {0, `ParseFloat("123.456789123456789", 64)`, func() { ParseFloat("123.456789123456789", 64) }}, + {10, `AppendQuoteToASCII(nil, oneMB)`, func() { strconv.AppendQuoteToASCII(nil, string(oneMB)) }}, + {0, `ParseFloat("123.45", 64)`, func() { strconv.ParseFloat("123.45", 64) }}, + {0, `ParseFloat("123.456789123456789", 64)`, func() { strconv.ParseFloat("123.456789123456789", 64) }}, {0, `ParseFloat("1.000000000000000111022302462515654042363166809082031251", 64)`, func() { - ParseFloat("1.000000000000000111022302462515654042363166809082031251", 64) + strconv.ParseFloat("1.000000000000000111022302462515654042363166809082031251", 64) }}, {0, `ParseFloat("1.0000000000000001110223024625156540423631668090820312500...001", 64)`, func() { - ParseFloat(nextToOne, 64) + strconv.ParseFloat(nextToOne, 64) }}, } ) @@ -125,11 +126,11 @@ func TestAllocationsFromBytes(t *testing.T) { */ func TestErrorPrefixes(t *testing.T) { - _, errInt := Atoi("INVALID") - _, errBool := ParseBool("INVALID") - _, errFloat := ParseFloat("INVALID", 64) - _, errInt64 := ParseInt("INVALID", 10, 64) - _, errUint64 := ParseUint("INVALID", 10, 64) + _, errInt := strconv.Atoi("INVALID") + _, errBool := strconv.ParseBool("INVALID") + _, errFloat := strconv.ParseFloat("INVALID", 64) + _, errInt64 := strconv.ParseInt("INVALID", 10, 64) + _, errUint64 := strconv.ParseUint("INVALID", 10, 64) vectors := []struct { err error // Input error @@ -143,7 +144,7 @@ func TestErrorPrefixes(t *testing.T) { } for _, v := range vectors { - nerr, ok := v.err.(*NumError) + nerr, ok := v.err.(*strconv.NumError) if !ok { t.Errorf("test %s, error was not a *NumError", v.want) continue diff --git a/gnovm/stdlibs/strings/printtrie_impl_test.gno b/gnovm/stdlibs/strings/printtrie_impl_test.gno new file mode 100644 index 00000000000..2a16c012eeb --- /dev/null +++ b/gnovm/stdlibs/strings/printtrie_impl_test.gno @@ -0,0 +1,29 @@ +package strings + +func (r *Replacer) PrintTrie() string { + r.buildOnce() + gen := r.r.(*genericReplacer) + return gen.printNode(&gen.root, 0) +} + +func (r *genericReplacer) printNode(t *trieNode, depth int) (s string) { + if t.priority > 0 { + s += "+" + } else { + s += "-" + } + s += "\n" + + if t.prefix != "" { + s += Repeat(".", depth) + t.prefix + s += r.printNode(t.next, depth+len(t.prefix)) + } else if t.table != nil { + for b, m := range r.mapping { + if int(m) != r.tableSize && t.table[m] != nil { + s += Repeat(".", depth) + string([]byte{byte(b)}) + s += r.printNode(t.table[m], depth+1) + } + } + } + return +} diff --git a/gnovm/stdlibs/strings/printtrie_test.gno b/gnovm/stdlibs/strings/printtrie_test.gno index b5b387b9bca..a51208f4756 100644 --- a/gnovm/stdlibs/strings/printtrie_test.gno +++ b/gnovm/stdlibs/strings/printtrie_test.gno @@ -1,37 +1,10 @@ -package strings +package strings_test import ( + "strings" "testing" ) -func (r *Replacer) PrintTrie() string { - r.buildOnce() - gen := r.r.(*genericReplacer) - return gen.printNode(&gen.root, 0) -} - -func (r *genericReplacer) printNode(t *trieNode, depth int) (s string) { - if t.priority > 0 { - s += "+" - } else { - s += "-" - } - s += "\n" - - if t.prefix != "" { - s += Repeat(".", depth) + t.prefix - s += r.printNode(t.next, depth+len(t.prefix)) - } else if t.table != nil { - for b, m := range r.mapping { - if int(m) != r.tableSize && t.table[m] != nil { - s += Repeat(".", depth) + string([]byte{byte(b)}) - s += r.printNode(t.table[m], depth+1) - } - } - } - return -} - func TestGenericTrieBuilding(t *testing.T) { testCases := []struct{ in, out string }{ {"abc;abdef;abdefgh;xx;xy;z", `- @@ -79,13 +52,13 @@ func TestGenericTrieBuilding(t *testing.T) { } for _, tc := range testCases { - keys := Split(tc.in, ";") + keys := strings.Split(tc.in, ";") args := make([]string, len(keys)*2) for i, key := range keys { args[i*2] = key } - got := NewReplacer(args...).PrintTrie() + got := strings.NewReplacer(args...).PrintTrie() // Remove tabs from tc.out wantbuf := make([]byte, 0, len(tc.out)) for i := 0; i < len(tc.out); i++ { diff --git a/gnovm/stdlibs/testing/match.gno b/gnovm/stdlibs/testing/match.gno index 8b099f37624..fae9391dc79 100644 --- a/gnovm/stdlibs/testing/match.gno +++ b/gnovm/stdlibs/testing/match.gno @@ -8,7 +8,6 @@ package testing import ( "fmt" - "regexp" "strings" "unicode" ) @@ -35,7 +34,7 @@ func (m simpleMatch) matches(name []string) (ok, partial bool) { if i >= len(m) { break } - if ok, _ := regexp.MatchString(m[i], s); !ok { + if ok, _ := matchString(m[i], s); !ok { return false, false } } @@ -48,7 +47,7 @@ func (m simpleMatch) verify(name string) error { } // Verify filters before doing any processing. for i, s := range m { - if _, err := regexp.MatchString(s, "non-empty"); err != nil { + if _, err := matchString(s, "non-empty"); err != nil { return fmt.Errorf("element %d of %s (%q): %s", i, name, s, err) } } diff --git a/gnovm/stdlibs/testing/testing.gno b/gnovm/stdlibs/testing/testing.gno index fdafd9652ba..08082894178 100644 --- a/gnovm/stdlibs/testing/testing.gno +++ b/gnovm/stdlibs/testing/testing.gno @@ -322,6 +322,9 @@ func formatDur(dur int64) string { // used to calculate execution times; only present in testing stdlibs func unixNano() int64 +// used to filter tests, we can't directly use regexp here due to a cyclic import; only present in testing stdlibs +func matchString(pat, str string) (result bool, err error) + func tRunner(t *T, fn testingFunc, verbose bool) { if !t.shouldRun(t.name) { return diff --git a/gnovm/stdlibs/testing/testing.go b/gnovm/stdlibs/testing/testing.go index 2c2e1d69904..b7408b7cceb 100644 --- a/gnovm/stdlibs/testing/testing.go +++ b/gnovm/stdlibs/testing/testing.go @@ -1,6 +1,14 @@ package testing +import ( + "errors" +) + func X_unixNano() int64 { // only implemented in testing stdlibs return 0 } + +func X_matchString(pat, str string) (result bool, err error) { + return false, errors.New("only implemented in testing stdlibs") +} diff --git a/gnovm/tests/stdlibs/README.md b/gnovm/tests/stdlibs/README.md index 16d5d171342..8742447e59a 100644 --- a/gnovm/tests/stdlibs/README.md +++ b/gnovm/tests/stdlibs/README.md @@ -4,3 +4,5 @@ This directory contains test-specific standard libraries. These are only available when testing gno code in `_test.gno` and `_filetest.gno` files. Re-declarations of functions already existing override the definitions of the normal stdlibs directory. + +Adding imports that don't exist in the corresponding normal stdlib is undefined behavior \ No newline at end of file diff --git a/gnovm/tests/stdlibs/generated.go b/gnovm/tests/stdlibs/generated.go index db5ecdec05d..4445d2467e8 100644 --- a/gnovm/tests/stdlibs/generated.go +++ b/gnovm/tests/stdlibs/generated.go @@ -333,6 +333,44 @@ var nativeFuncs = [...]NativeFunc{ )) }, }, + { + "testing", + "matchString", + []gno.FieldTypeExpr{ + {Name: gno.N("p0"), Type: gno.X("string")}, + {Name: gno.N("p1"), Type: gno.X("string")}, + }, + []gno.FieldTypeExpr{ + {Name: gno.N("r0"), Type: gno.X("bool")}, + {Name: gno.N("r1"), Type: gno.X("error")}, + }, + false, + func(m *gno.Machine) { + b := m.LastBlock() + var ( + p0 string + rp0 = reflect.ValueOf(&p0).Elem() + p1 string + rp1 = reflect.ValueOf(&p1).Elem() + ) + + gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV, rp0) + gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 1, "")).TV, rp1) + + r0, r1 := testlibs_testing.X_matchString(p0, p1) + + m.PushValue(gno.Go2GnoValue( + m.Alloc, + m.Store, + reflect.ValueOf(&r0).Elem(), + )) + m.PushValue(gno.Go2GnoValue( + m.Alloc, + m.Store, + reflect.ValueOf(&r1).Elem(), + )) + }, + }, { "unicode", "IsPrint", diff --git a/gnovm/tests/stdlibs/testing/native_testing.gno b/gnovm/tests/stdlibs/testing/native_testing.gno index 3076a679e77..64ed5edd61d 100644 --- a/gnovm/tests/stdlibs/testing/native_testing.gno +++ b/gnovm/tests/stdlibs/testing/native_testing.gno @@ -1,3 +1,5 @@ package testing func unixNano() int64 + +func matchString(pat, str string) (result bool, err error) diff --git a/gnovm/tests/stdlibs/testing/native_testing.go b/gnovm/tests/stdlibs/testing/native_testing.go index db681ad8a62..bde9bbca7a4 100644 --- a/gnovm/tests/stdlibs/testing/native_testing.go +++ b/gnovm/tests/stdlibs/testing/native_testing.go @@ -1,7 +1,18 @@ package testing -import "time" +import ( + "regexp" + "time" +) func X_unixNano() int64 { return time.Now().UnixNano() } + +func X_matchString(pat, str string) (result bool, err error) { + var matchRe *regexp.Regexp + if matchRe, err = regexp.Compile(pat); err != nil { + return + } + return matchRe.MatchString(str), nil +} From 8e1c532801fac9e282cbe0995d612cf460739921 Mon Sep 17 00:00:00 2001 From: Guilhem Fanton <8671905+gfanton@users.noreply.github.com> Date: Thu, 23 Jan 2025 18:47:43 +0100 Subject: [PATCH 071/143] fix(p2p): infinity loop if no peer in queue (#3593) --- tm2/pkg/p2p/switch.go | 28 ++++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/tm2/pkg/p2p/switch.go b/tm2/pkg/p2p/switch.go index 5c1c37f7729..0dd087026dd 100644 --- a/tm2/pkg/p2p/switch.go +++ b/tm2/pkg/p2p/switch.go @@ -72,8 +72,9 @@ type MultiplexSwitch struct { privatePeers sync.Map // ID -> nothing; lookup table of peers who are not shared transport Transport - dialQueue *dial.Queue - events *events.Events + dialQueue *dial.Queue + dialNotify chan struct{} + events *events.Events } // NewMultiplexSwitch creates a new MultiplexSwitch with the given config. @@ -88,6 +89,7 @@ func NewMultiplexSwitch( peers: newSet(), transport: transport, dialQueue: dial.NewQueue(), + dialNotify: make(chan struct{}, 1), events: events.New(), maxInboundPeers: defaultCfg.MaxNumInboundPeers, maxOutboundPeers: defaultCfg.MaxNumOutboundPeers, @@ -262,13 +264,15 @@ func (sw *MultiplexSwitch) runDialLoop(ctx context.Context) { select { case <-ctx.Done(): sw.Logger.Debug("dial context canceled") - return + default: // Grab a dial item item := sw.dialQueue.Peek() if item == nil { - // Nothing to dial + // Nothing to dial, wait until something is + // added to the queue + sw.waitForPeersToDial(ctx) continue } @@ -565,6 +569,7 @@ func (sw *MultiplexSwitch) DialPeers(peerAddrs ...*types.NetAddress) { } sw.dialQueue.Push(item) + sw.notifyAddPeerToDial() } } @@ -588,6 +593,7 @@ func (sw *MultiplexSwitch) dialItems(dialItems ...dial.Item) { } sw.dialQueue.Push(dialItem) + sw.notifyAddPeerToDial() } } @@ -698,6 +704,20 @@ func (sw *MultiplexSwitch) addPeer(p PeerConn) error { return nil } +func (sw *MultiplexSwitch) notifyAddPeerToDial() { + select { + case sw.dialNotify <- struct{}{}: + default: + } +} + +func (sw *MultiplexSwitch) waitForPeersToDial(ctx context.Context) { + select { + case <-ctx.Done(): + case <-sw.dialNotify: + } +} + // logTelemetry logs the switch telemetry data // to global metrics funnels func (sw *MultiplexSwitch) logTelemetry() { From 98c43534de031a25335f7c233ea603eabbc16c2b Mon Sep 17 00:00:00 2001 From: Emmanuel T Odeke Date: Fri, 24 Jan 2025 06:25:42 +0200 Subject: [PATCH 072/143] test(fuzz): appease goimports by renaming added .go corpus files to *go_fuzz (#3602) goimports doesn't provide an ignore pattern except by reading the $GOPATH/.goimportsignore which would be too cumbersome and non-standard to muck around with so instead this change modifies testdata/corpra/*.go to testdata/corpora/*.go_fuzz so that goimports won't throw a fit against CI/CD for this project. Fixes #3601 --- gnovm/pkg/gnolang/fuzz_test.go | 4 ++-- .../{corpra/parsefile/a.go => corpora/parsefile/a.go_fuzz} | 0 .../{corpra/parsefile/b.go => corpora/parsefile/b.go_fuzz} | 0 .../bug_3013.go => corpora/parsefile/bug_3013.go_fuzz} | 0 .../parsefile/bug_3014_redefine.go_fuzz} | 0 .../parsefile/bug_3014_redefine2.go_fuzz} | 0 .../parsefile/bug_3014_redefine3.go_fuzz} | 0 .../parsefile/bug_3014_redefine4.go_fuzz} | 0 .../parsefile/bug_3014_redefine5.go_fuzz} | 0 .../parsefile/bug_3014_redefine6.go_fuzz} | 0 10 files changed, 2 insertions(+), 2 deletions(-) rename gnovm/pkg/gnolang/testdata/{corpra/parsefile/a.go => corpora/parsefile/a.go_fuzz} (100%) rename gnovm/pkg/gnolang/testdata/{corpra/parsefile/b.go => corpora/parsefile/b.go_fuzz} (100%) rename gnovm/pkg/gnolang/testdata/{corpra/parsefile/bug_3013.go => corpora/parsefile/bug_3013.go_fuzz} (100%) rename gnovm/pkg/gnolang/testdata/{corpra/parsefile/bug_3014_redefine.go => corpora/parsefile/bug_3014_redefine.go_fuzz} (100%) rename gnovm/pkg/gnolang/testdata/{corpra/parsefile/bug_3014_redefine2.go => corpora/parsefile/bug_3014_redefine2.go_fuzz} (100%) rename gnovm/pkg/gnolang/testdata/{corpra/parsefile/bug_3014_redefine3.go => corpora/parsefile/bug_3014_redefine3.go_fuzz} (100%) rename gnovm/pkg/gnolang/testdata/{corpra/parsefile/bug_3014_redefine4.go => corpora/parsefile/bug_3014_redefine4.go_fuzz} (100%) rename gnovm/pkg/gnolang/testdata/{corpra/parsefile/bug_3014_redefine5.go => corpora/parsefile/bug_3014_redefine5.go_fuzz} (100%) rename gnovm/pkg/gnolang/testdata/{corpra/parsefile/bug_3014_redefine6.go => corpora/parsefile/bug_3014_redefine6.go_fuzz} (100%) diff --git a/gnovm/pkg/gnolang/fuzz_test.go b/gnovm/pkg/gnolang/fuzz_test.go index 977c7453b90..f19c35a3da1 100644 --- a/gnovm/pkg/gnolang/fuzz_test.go +++ b/gnovm/pkg/gnolang/fuzz_test.go @@ -50,8 +50,8 @@ func FuzzConvertUntypedBigdecToFloat(f *testing.F) { func FuzzParseFile(f *testing.F) { // 1. Add the corpra. - parseFileDir := filepath.Join("testdata", "corpra", "parsefile") - paths, err := filepath.Glob(filepath.Join(parseFileDir, "*.go")) + parseFileDir := filepath.Join("testdata", "corpora", "parsefile") + paths, err := filepath.Glob(filepath.Join(parseFileDir, "*.go_fuzz")) if err != nil { f.Fatal(err) } diff --git a/gnovm/pkg/gnolang/testdata/corpra/parsefile/a.go b/gnovm/pkg/gnolang/testdata/corpora/parsefile/a.go_fuzz similarity index 100% rename from gnovm/pkg/gnolang/testdata/corpra/parsefile/a.go rename to gnovm/pkg/gnolang/testdata/corpora/parsefile/a.go_fuzz diff --git a/gnovm/pkg/gnolang/testdata/corpra/parsefile/b.go b/gnovm/pkg/gnolang/testdata/corpora/parsefile/b.go_fuzz similarity index 100% rename from gnovm/pkg/gnolang/testdata/corpra/parsefile/b.go rename to gnovm/pkg/gnolang/testdata/corpora/parsefile/b.go_fuzz diff --git a/gnovm/pkg/gnolang/testdata/corpra/parsefile/bug_3013.go b/gnovm/pkg/gnolang/testdata/corpora/parsefile/bug_3013.go_fuzz similarity index 100% rename from gnovm/pkg/gnolang/testdata/corpra/parsefile/bug_3013.go rename to gnovm/pkg/gnolang/testdata/corpora/parsefile/bug_3013.go_fuzz diff --git a/gnovm/pkg/gnolang/testdata/corpra/parsefile/bug_3014_redefine.go b/gnovm/pkg/gnolang/testdata/corpora/parsefile/bug_3014_redefine.go_fuzz similarity index 100% rename from gnovm/pkg/gnolang/testdata/corpra/parsefile/bug_3014_redefine.go rename to gnovm/pkg/gnolang/testdata/corpora/parsefile/bug_3014_redefine.go_fuzz diff --git a/gnovm/pkg/gnolang/testdata/corpra/parsefile/bug_3014_redefine2.go b/gnovm/pkg/gnolang/testdata/corpora/parsefile/bug_3014_redefine2.go_fuzz similarity index 100% rename from gnovm/pkg/gnolang/testdata/corpra/parsefile/bug_3014_redefine2.go rename to gnovm/pkg/gnolang/testdata/corpora/parsefile/bug_3014_redefine2.go_fuzz diff --git a/gnovm/pkg/gnolang/testdata/corpra/parsefile/bug_3014_redefine3.go b/gnovm/pkg/gnolang/testdata/corpora/parsefile/bug_3014_redefine3.go_fuzz similarity index 100% rename from gnovm/pkg/gnolang/testdata/corpra/parsefile/bug_3014_redefine3.go rename to gnovm/pkg/gnolang/testdata/corpora/parsefile/bug_3014_redefine3.go_fuzz diff --git a/gnovm/pkg/gnolang/testdata/corpra/parsefile/bug_3014_redefine4.go b/gnovm/pkg/gnolang/testdata/corpora/parsefile/bug_3014_redefine4.go_fuzz similarity index 100% rename from gnovm/pkg/gnolang/testdata/corpra/parsefile/bug_3014_redefine4.go rename to gnovm/pkg/gnolang/testdata/corpora/parsefile/bug_3014_redefine4.go_fuzz diff --git a/gnovm/pkg/gnolang/testdata/corpra/parsefile/bug_3014_redefine5.go b/gnovm/pkg/gnolang/testdata/corpora/parsefile/bug_3014_redefine5.go_fuzz similarity index 100% rename from gnovm/pkg/gnolang/testdata/corpra/parsefile/bug_3014_redefine5.go rename to gnovm/pkg/gnolang/testdata/corpora/parsefile/bug_3014_redefine5.go_fuzz diff --git a/gnovm/pkg/gnolang/testdata/corpra/parsefile/bug_3014_redefine6.go b/gnovm/pkg/gnolang/testdata/corpora/parsefile/bug_3014_redefine6.go_fuzz similarity index 100% rename from gnovm/pkg/gnolang/testdata/corpra/parsefile/bug_3014_redefine6.go rename to gnovm/pkg/gnolang/testdata/corpora/parsefile/bug_3014_redefine6.go_fuzz From f67f186930f96b4c43e1d37769dc2875d16917a0 Mon Sep 17 00:00:00 2001 From: Manfred Touron <94029+moul@users.noreply.github.com> Date: Fri, 24 Jan 2025 11:58:23 +0100 Subject: [PATCH 073/143] chore: reorganize gnovm/cmd/gno (#3587) - Increase Go-Gno compatibility. - Sort alphabetically commands. - Move Gno-specific commands under `gno tool `. - Improve `ShortHelp`. - Use `embedmd` in `gnovm/cmd/gno`'s README as an example of how we should use it more frequently. --------- Signed-off-by: moul <94029+moul@users.noreply.github.com> --- .github/workflows/examples.yml | 2 +- .gitignore | 3 + CONTRIBUTING.md | 2 +- docs/reference/go-gno-compatibility.md | 56 +++++++++---------- examples/Makefile | 6 +- gnovm/Makefile | 10 +++- gnovm/cmd/gno/README.md | 24 ++++++-- gnovm/cmd/gno/bug.go | 2 +- gnovm/cmd/gno/clean.go | 2 +- gnovm/cmd/gno/doc.go | 3 +- gnovm/cmd/gno/env.go | 2 +- gnovm/cmd/gno/fmt.go | 2 +- gnovm/cmd/gno/main.go | 29 +++++----- gnovm/cmd/gno/mod.go | 6 +- gnovm/cmd/gno/run.go | 2 +- gnovm/cmd/gno/test.go | 2 +- gnovm/cmd/gno/testdata/lint/bad_import.txtar | 4 +- gnovm/cmd/gno/testdata/lint/file_error.txtar | 4 +- gnovm/cmd/gno/testdata/lint/no_error.txtar | 4 +- gnovm/cmd/gno/testdata/lint/no_gnomod.txtar | 4 +- .../cmd/gno/testdata/lint/not_declared.txtar | 4 +- .../transpile/gobuild_flag_build_error.txtar | 4 +- .../transpile/gobuild_flag_parse_error.txtar | 4 +- .../testdata/transpile/invalid_import.txtar | 4 +- .../cmd/gno/testdata/transpile/no_args.txtar | 4 +- .../gno/testdata/transpile/parse_error.txtar | 4 +- .../testdata/transpile/valid_empty_dir.txtar | 4 +- .../transpile/valid_gobuild_file.txtar | 4 +- .../transpile/valid_gobuild_flag.txtar | 6 +- .../transpile/valid_output_flag.txtar | 8 +-- .../transpile/valid_output_gobuild.txtar | 8 +-- .../transpile/valid_transpile_file.txtar | 6 +- .../transpile/valid_transpile_package.txtar | 4 +- .../transpile/valid_transpile_tree.txtar | 4 +- gnovm/cmd/gno/tool.go | 39 +++++++++++++ gnovm/cmd/gno/{lint.go => tool_lint.go} | 0 .../gno/{lint_test.go => tool_lint_test.go} | 20 +++---- gnovm/cmd/gno/{repl.go => tool_repl.go} | 0 .../gno/{repl_test.go => tool_repl_test.go} | 0 .../gno/{transpile.go => tool_transpile.go} | 0 ...anspile_test.go => tool_transpile_test.go} | 0 gnovm/tests/challenges/unused0.gno | 2 +- 42 files changed, 181 insertions(+), 117 deletions(-) create mode 100644 gnovm/cmd/gno/tool.go rename gnovm/cmd/gno/{lint.go => tool_lint.go} (100%) rename gnovm/cmd/gno/{lint_test.go => tool_lint_test.go} (70%) rename gnovm/cmd/gno/{repl.go => tool_repl.go} (100%) rename gnovm/cmd/gno/{repl_test.go => tool_repl_test.go} (100%) rename gnovm/cmd/gno/{transpile.go => tool_transpile.go} (100%) rename gnovm/cmd/gno/{transpile_test.go => tool_transpile_test.go} (100%) diff --git a/.github/workflows/examples.yml b/.github/workflows/examples.yml index 5d606a2a663..ffe40c2db7e 100644 --- a/.github/workflows/examples.yml +++ b/.github/workflows/examples.yml @@ -29,7 +29,7 @@ jobs: with: go-version: ${{ matrix.goversion }} - run: go install -v ./gnovm/cmd/gno - - run: go run ./gnovm/cmd/gno transpile -v --gobuild ./examples + - run: go run ./gnovm/cmd/gno tool transpile -v --gobuild ./examples test: strategy: fail-fast: false diff --git a/.gitignore b/.gitignore index 7d3f3f92b41..82cc109887e 100644 --- a/.gitignore +++ b/.gitignore @@ -34,3 +34,6 @@ coverage.out *.swp *.swo *.bak + +.tmp/ +wal/ diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b58d63c6c75..9562a531227 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -198,7 +198,7 @@ Additionally, it's not possible to use `gofumpt` for code formatting with (flycheck-define-checker gno-lint "A GNO syntax checker using the gno lint tool." - :command ("gno" "lint" source-original) + :command ("gno" "tool" "lint" source-original) :error-patterns (;; ./file.gno:32: error message (code=1) (error line-start (file-name) ":" line ": " (message) " (code=" (id (one-or-more digit)) ")." line-end)) ;; Ensure the file is saved, to work around diff --git a/docs/reference/go-gno-compatibility.md b/docs/reference/go-gno-compatibility.md index 9f9d611e4fd..87c734d935f 100644 --- a/docs/reference/go-gno-compatibility.md +++ b/docs/reference/go-gno-compatibility.md @@ -297,31 +297,31 @@ Legend: ## Tooling (`gno` binary) -| go command | gno command | comment | -|-------------------|---------------------------|-----------------------------------------------------------------------| -| go bug | gno bug | same behavior | -| go build | gno transpile -gobuild | same intention, limited compatibility | -| go clean | gno clean | same intention, limited compatibility | -| go doc | gno doc | limited compatibility; see https://github.com/gnolang/gno/issues/522 | -| go env | gno env | | -| go fix | | | -| go fmt | gno fmt | gofmt (& similar tools, like gofumpt) works on gno code. | -| go generate | | | -| go get | | see `gno mod download`. | -| go help | gno $cmd --help | ie. `gno doc --help` | -| go install | | | -| go list | | | -| go mod | gno mod | | -| + go mod init | gno mod init | same behavior | -| + go mod download | gno mod download | same behavior | -| + go mod tidy | gno mod tidy | same behavior | -| + go mod why | gno mod why | same intention | -| | gno transpile | | -| go work | | | -| | gno repl | | -| go run | gno run | | -| go test | gno test | limited compatibility | -| go tool | | | -| go version | | | -| go vet | | | -| golint | gno lint | same intention | +| go command | gno command | comment | +|-------------------|------------------------------|-----------------------------------------------------------------------| +| go bug | gno bug | same behavior | +| go build | gno tool transpile -gobuild | same intention, limited compatibility | +| go clean | gno clean | same intention, limited compatibility | +| go doc | gno doc | limited compatibility; see https://github.com/gnolang/gno/issues/522 | +| go env | gno env | | +| go fix | | | +| go fmt | gno fmt | gofmt (& similar tools, like gofumpt) works on gno code. | +| go generate | | | +| go get | | see `gno mod download`. | +| go help | gno $cmd --help | ie. `gno doc --help` | +| go install | | | +| go list | | | +| go mod | gno mod | | +| + go mod init | gno mod init | same behavior | +| + go mod download | gno mod download | same behavior | +| + go mod tidy | gno mod tidy | same behavior | +| + go mod why | gno mod why | same intention | +| | gno tool transpile | | +| go work | | | +| | gno tool repl | | +| go run | gno run | | +| go test | gno test | limited compatibility | +| go tool | | | +| go version | | | +| go vet | | | +| golint | gno tool lint | same intention | diff --git a/examples/Makefile b/examples/Makefile index cdc73ee6b3a..63a20f78eb9 100644 --- a/examples/Makefile +++ b/examples/Makefile @@ -33,11 +33,11 @@ OFFICIAL_PACKAGES += ./gno.land/r/gov # Dev tools .PHONY: transpile transpile: - go run ../gnovm/cmd/gno transpile -v . + go run ../gnovm/cmd/gno tool transpile -v . .PHONY: build build: - go run ../gnovm/cmd/gno transpile -v --gobuild . + go run ../gnovm/cmd/gno tool transpile -v --gobuild . .PHONY: test test: @@ -45,7 +45,7 @@ test: .PHONY: lint lint: - go run ../gnovm/cmd/gno lint -v $(OFFICIAL_PACKAGES) + go run ../gnovm/cmd/gno tool lint -v $(OFFICIAL_PACKAGES) .PHONY: test.sync test.sync: diff --git a/gnovm/Makefile b/gnovm/Makefile index babb7ad74ca..ce745e44aae 100644 --- a/gnovm/Makefile +++ b/gnovm/Makefile @@ -54,8 +54,8 @@ lint: .PHONY: fmt fmt: - go run ./cmd/gno fmt $(GNOFMT_FLAGS) ./stdlibs/... ./tests/stdlibs/... $(rundep) mvdan.cc/gofumpt $(GOFMT_FLAGS) . + go run ./cmd/gno fmt $(GNOFMT_FLAGS) ./stdlibs/... ./tests/stdlibs/... .PHONY: imports imports: @@ -125,7 +125,13 @@ _test.filetest:; # TODO: move _dev.stringer to go:generate instructions, simplify generate # to just go generate. .PHONY: generate -generate: _dev.stringer _dev.generate +generate: _dev.stringer _dev.generate _dev.docs + +.PHONY: _dev.docs +_dev.docs: + mkdir -p .tmp + (go run ./cmd/gno -h 2>&1 || true) | grep -v "exit status 1" > .tmp/gno-help.txt + $(rundep) github.com/campoy/embedmd -w `find . -name "*.md"` stringer_cmd=$(rundep) golang.org/x/tools/cmd/stringer .PHONY: _dev.stringer diff --git a/gnovm/cmd/gno/README.md b/gnovm/cmd/gno/README.md index f900b164783..81b45622c05 100644 --- a/gnovm/cmd/gno/README.md +++ b/gnovm/cmd/gno/README.md @@ -6,13 +6,25 @@ `gno [arguments]` -## Commands +## Usage -* `gno run` - run a Gno file -* `gno transpile` - transpile .gno to .go -* `gno test` - test a gno package -* `gno mod` - manages dependencies -* `gno repl` start a GnoVM REPL +[embedmd]:#(../../.tmp/gno-help.txt) +```txt +USAGE + gno [arguments] + +SUBCOMMANDS + bug start a bug report + clean remove generated and cached data + doc show documentation for package or symbol + env print gno environment information + fmt gnofmt (reformat) package sources + mod module maintenance + run run gno packages + test test packages + tool run specified gno tool + +``` ## Install diff --git a/gnovm/cmd/gno/bug.go b/gnovm/cmd/gno/bug.go index a1273d9ad59..7a4345fb1ed 100644 --- a/gnovm/cmd/gno/bug.go +++ b/gnovm/cmd/gno/bug.go @@ -61,7 +61,7 @@ func newBugCmd(io commands.IO) *commands.Command { commands.Metadata{ Name: "bug", ShortUsage: "bug", - ShortHelp: "Start a bug report", + ShortHelp: "start a bug report", LongHelp: `opens https://github.com/gnolang/gno/issues in a browser. The new issue body is prefilled for you with the following information: diff --git a/gnovm/cmd/gno/clean.go b/gnovm/cmd/gno/clean.go index 0ca2e940d58..7c318c3957e 100644 --- a/gnovm/cmd/gno/clean.go +++ b/gnovm/cmd/gno/clean.go @@ -26,7 +26,7 @@ func newCleanCmd(io commands.IO) *commands.Command { commands.Metadata{ Name: "clean", ShortUsage: "clean [flags]", - ShortHelp: "removes generated files and cached data", + ShortHelp: "remove generated and cached data", }, cfg, func(ctx context.Context, args []string) error { diff --git a/gnovm/cmd/gno/doc.go b/gnovm/cmd/gno/doc.go index 794dd1ba7bb..c34ed984d9c 100644 --- a/gnovm/cmd/gno/doc.go +++ b/gnovm/cmd/gno/doc.go @@ -29,7 +29,8 @@ func newDocCmd(io commands.IO) *commands.Command { commands.Metadata{ Name: "doc", ShortUsage: "doc [flags] ", - ShortHelp: "get documentation for the specified package or symbol (type, function, method, or variable/constant)", + ShortHelp: "show documentation for package or symbol", + LongHelp: "get documentation for the specified package or symbol (type, function, method, or variable/constant)", }, c, func(_ context.Context, args []string) error { diff --git a/gnovm/cmd/gno/env.go b/gnovm/cmd/gno/env.go index 9c601a270c7..20d32c85ec0 100644 --- a/gnovm/cmd/gno/env.go +++ b/gnovm/cmd/gno/env.go @@ -18,7 +18,7 @@ func newEnvCmd(io commands.IO) *commands.Command { commands.Metadata{ Name: "env", ShortUsage: "env [flags] ", - ShortHelp: "`env` prints Gno environment information", + ShortHelp: "print gno environment information", }, c, func(_ context.Context, args []string) error { diff --git a/gnovm/cmd/gno/fmt.go b/gnovm/cmd/gno/fmt.go index de6c28c4df0..e37c50a079f 100644 --- a/gnovm/cmd/gno/fmt.go +++ b/gnovm/cmd/gno/fmt.go @@ -35,7 +35,7 @@ func newFmtCmd(io commands.IO) *commands.Command { commands.Metadata{ Name: "fmt", ShortUsage: "gno fmt [flags] [path ...]", - ShortHelp: "Run gno file formatter.", + ShortHelp: "gnofmt (reformat) package sources", LongHelp: "The `gno fmt` tool processes, formats, and cleans up `gno` source files.", }, cfg, diff --git a/gnovm/cmd/gno/main.go b/gnovm/cmd/gno/main.go index 7a5799f2835..5f8bb7b522e 100644 --- a/gnovm/cmd/gno/main.go +++ b/gnovm/cmd/gno/main.go @@ -16,33 +16,32 @@ func main() { func newGnocliCmd(io commands.IO) *commands.Command { cmd := commands.NewCommand( commands.Metadata{ - ShortUsage: " [flags] [...]", - LongHelp: "Runs the gno development toolkit", + ShortUsage: "gno [arguments]", }, commands.NewEmptyConfig(), commands.HelpExec, ) cmd.AddSubCommands( - newModCmd(io), - newTestCmd(io), - newLintCmd(io), - newRunCmd(io), - newTranspileCmd(io), + newBugCmd(io), + // build newCleanCmd(io), - newReplCmd(), newDocCmd(io), newEnvCmd(io), - newBugCmd(io), + // fix newFmtCmd(io), - // graph - // vendor -- download deps from the chain in vendor/ - // list -- list packages - // render -- call render()? - // publish/release // generate - // "vm" -- starts an in-memory chain that can be interacted with? + // get + // install + // list -- list packages + newModCmd(io), + // work + newRunCmd(io), + // telemetry + newTestCmd(io), + newToolCmd(io), // version -- show cmd/gno, golang versions + // vet ) return cmd diff --git a/gnovm/cmd/gno/mod.go b/gnovm/cmd/gno/mod.go index 5479d934ce6..f303908d8ee 100644 --- a/gnovm/cmd/gno/mod.go +++ b/gnovm/cmd/gno/mod.go @@ -25,7 +25,7 @@ func newModCmd(io commands.IO) *commands.Command { commands.Metadata{ Name: "mod", ShortUsage: "mod ", - ShortHelp: "manage gno.mod", + ShortHelp: "module maintenance", }, commands.NewEmptyConfig(), commands.HelpExec, @@ -33,8 +33,12 @@ func newModCmd(io commands.IO) *commands.Command { cmd.AddSubCommands( newModDownloadCmd(io), + // edit + // graph newModInitCmd(), newModTidy(io), + // vendor + // verify newModWhy(io), ) diff --git a/gnovm/cmd/gno/run.go b/gnovm/cmd/gno/run.go index 9a9beac5cd1..489016aa3d4 100644 --- a/gnovm/cmd/gno/run.go +++ b/gnovm/cmd/gno/run.go @@ -32,7 +32,7 @@ func newRunCmd(io commands.IO) *commands.Command { commands.Metadata{ Name: "run", ShortUsage: "run [flags] [...]", - ShortHelp: "runs the specified gno files", + ShortHelp: "run gno packages", }, cfg, func(_ context.Context, args []string) error { diff --git a/gnovm/cmd/gno/test.go b/gnovm/cmd/gno/test.go index ea06b25d8e2..ae67d69bc90 100644 --- a/gnovm/cmd/gno/test.go +++ b/gnovm/cmd/gno/test.go @@ -35,7 +35,7 @@ func newTestCmd(io commands.IO) *commands.Command { commands.Metadata{ Name: "test", ShortUsage: "test [flags] [...]", - ShortHelp: "runs the tests for the specified packages", + ShortHelp: "test packages", LongHelp: `Runs the tests for the specified packages. 'gno test' recompiles each package along with any files with names matching the diff --git a/gnovm/cmd/gno/testdata/lint/bad_import.txtar b/gnovm/cmd/gno/testdata/lint/bad_import.txtar index e2c0431443c..117a699fa6c 100644 --- a/gnovm/cmd/gno/testdata/lint/bad_import.txtar +++ b/gnovm/cmd/gno/testdata/lint/bad_import.txtar @@ -1,6 +1,6 @@ -# testing gno lint command: bad import error +# testing gno tool lint command: bad import error -! gno lint ./bad_file.gno +! gno tool lint ./bad_file.gno cmp stdout stdout.golden cmp stderr stderr.golden diff --git a/gnovm/cmd/gno/testdata/lint/file_error.txtar b/gnovm/cmd/gno/testdata/lint/file_error.txtar index 4fa50c6da81..f3301b46757 100644 --- a/gnovm/cmd/gno/testdata/lint/file_error.txtar +++ b/gnovm/cmd/gno/testdata/lint/file_error.txtar @@ -1,6 +1,6 @@ -# gno lint: test file error +# gno tool lint: test file error -! gno lint ./i_have_error_test.gno +! gno tool lint ./i_have_error_test.gno cmp stdout stdout.golden cmp stderr stderr.golden diff --git a/gnovm/cmd/gno/testdata/lint/no_error.txtar b/gnovm/cmd/gno/testdata/lint/no_error.txtar index 5dd3b164952..5033e054ac6 100644 --- a/gnovm/cmd/gno/testdata/lint/no_error.txtar +++ b/gnovm/cmd/gno/testdata/lint/no_error.txtar @@ -1,6 +1,6 @@ -# testing simple gno lint command with any error +# testing simple gno tool lint command with any error -gno lint ./good_file.gno +gno tool lint ./good_file.gno cmp stdout stdout.golden cmp stdout stderr.golden diff --git a/gnovm/cmd/gno/testdata/lint/no_gnomod.txtar b/gnovm/cmd/gno/testdata/lint/no_gnomod.txtar index b5a046a7095..4564f2d9fff 100644 --- a/gnovm/cmd/gno/testdata/lint/no_gnomod.txtar +++ b/gnovm/cmd/gno/testdata/lint/no_gnomod.txtar @@ -1,6 +1,6 @@ -# gno lint: no gnomod +# gno tool lint: no gnomod -! gno lint . +! gno tool lint . cmp stdout stdout.golden cmp stderr stderr.golden diff --git a/gnovm/cmd/gno/testdata/lint/not_declared.txtar b/gnovm/cmd/gno/testdata/lint/not_declared.txtar index ac56b27e0df..3c3c304797e 100644 --- a/gnovm/cmd/gno/testdata/lint/not_declared.txtar +++ b/gnovm/cmd/gno/testdata/lint/not_declared.txtar @@ -1,6 +1,6 @@ -# testing gno lint command: not declared error +# testing gno tool lint command: not declared error -! gno lint ./bad_file.gno +! gno tool lint ./bad_file.gno cmp stdout stdout.golden cmp stderr stderr.golden diff --git a/gnovm/cmd/gno/testdata/transpile/gobuild_flag_build_error.txtar b/gnovm/cmd/gno/testdata/transpile/gobuild_flag_build_error.txtar index 145fe796c09..0b41319a190 100644 --- a/gnovm/cmd/gno/testdata/transpile/gobuild_flag_build_error.txtar +++ b/gnovm/cmd/gno/testdata/transpile/gobuild_flag_build_error.txtar @@ -1,7 +1,7 @@ -# Run gno transpile with -gobuild flag +# Run gno tool transpile with -gobuild flag # The error messages changed sometime in go1.23, so this avoids errors -! gno transpile -gobuild . +! gno tool transpile -gobuild . ! stdout .+ stderr '^main.gno:4:6: .*declared and not used' diff --git a/gnovm/cmd/gno/testdata/transpile/gobuild_flag_parse_error.txtar b/gnovm/cmd/gno/testdata/transpile/gobuild_flag_parse_error.txtar index 629de2b7f3d..1008d45adb2 100644 --- a/gnovm/cmd/gno/testdata/transpile/gobuild_flag_parse_error.txtar +++ b/gnovm/cmd/gno/testdata/transpile/gobuild_flag_parse_error.txtar @@ -1,6 +1,6 @@ -# Run gno transpile with -gobuild flag on file with parse error +# Run gno tool transpile with -gobuild flag on file with parse error -! gno transpile -gobuild . +! gno tool transpile -gobuild . ! stdout .+ stderr '^main.gno:3:1: expected declaration, found invalid$' diff --git a/gnovm/cmd/gno/testdata/transpile/invalid_import.txtar b/gnovm/cmd/gno/testdata/transpile/invalid_import.txtar index 0c51012feb7..23e9e805fe5 100644 --- a/gnovm/cmd/gno/testdata/transpile/invalid_import.txtar +++ b/gnovm/cmd/gno/testdata/transpile/invalid_import.txtar @@ -1,6 +1,6 @@ -# Run gno transpile with gno files with an invalid import path +# Run gno tool transpile with gno files with an invalid import path -! gno transpile . +! gno tool transpile . ! stdout .+ stderr '^main.gno:5:2: import "xxx" does not exist$' diff --git a/gnovm/cmd/gno/testdata/transpile/no_args.txtar b/gnovm/cmd/gno/testdata/transpile/no_args.txtar index 6d8592d4e3b..30ea24fbf73 100644 --- a/gnovm/cmd/gno/testdata/transpile/no_args.txtar +++ b/gnovm/cmd/gno/testdata/transpile/no_args.txtar @@ -1,6 +1,6 @@ -# Run gno transpile without args +# Run gno tool transpile without args -! gno transpile +! gno tool transpile ! stdout .+ stderr 'USAGE' diff --git a/gnovm/cmd/gno/testdata/transpile/parse_error.txtar b/gnovm/cmd/gno/testdata/transpile/parse_error.txtar index b94b86992af..c19276ef273 100644 --- a/gnovm/cmd/gno/testdata/transpile/parse_error.txtar +++ b/gnovm/cmd/gno/testdata/transpile/parse_error.txtar @@ -1,6 +1,6 @@ -# Run gno transpile with gno files with parse errors +# Run gno tool transpile with gno files with parse errors -! gno transpile . +! gno tool transpile . ! stdout .+ stderr '^main.gno:3:1: expected declaration, found invalid$' diff --git a/gnovm/cmd/gno/testdata/transpile/valid_empty_dir.txtar b/gnovm/cmd/gno/testdata/transpile/valid_empty_dir.txtar index 2bd1841d2b4..f9ccd8d8b53 100644 --- a/gnovm/cmd/gno/testdata/transpile/valid_empty_dir.txtar +++ b/gnovm/cmd/gno/testdata/transpile/valid_empty_dir.txtar @@ -1,6 +1,6 @@ -# Run gno transpile on an empty dir +# Run gno tool transpile on an empty dir -gno transpile . +gno tool transpile . ! stdout .+ ! stderr .+ diff --git a/gnovm/cmd/gno/testdata/transpile/valid_gobuild_file.txtar b/gnovm/cmd/gno/testdata/transpile/valid_gobuild_file.txtar index 40bb1ecb98a..8e95c5994d0 100644 --- a/gnovm/cmd/gno/testdata/transpile/valid_gobuild_file.txtar +++ b/gnovm/cmd/gno/testdata/transpile/valid_gobuild_file.txtar @@ -1,6 +1,6 @@ -# Run gno transpile with -gobuild flag on an individual file +# Run gno tool transpile with -gobuild flag on an individual file -gno transpile -gobuild -v main.gno +gno tool transpile -gobuild -v main.gno ! stdout .+ cmp stderr stderr.golden diff --git a/gnovm/cmd/gno/testdata/transpile/valid_gobuild_flag.txtar b/gnovm/cmd/gno/testdata/transpile/valid_gobuild_flag.txtar index 2eacfb9de60..72ca7a4f2f4 100644 --- a/gnovm/cmd/gno/testdata/transpile/valid_gobuild_flag.txtar +++ b/gnovm/cmd/gno/testdata/transpile/valid_gobuild_flag.txtar @@ -1,6 +1,6 @@ -# Run gno transpile with -gobuild flag +# Run gno tool transpile with -gobuild flag -gno transpile -gobuild -v . +gno tool transpile -gobuild -v . ! stdout .+ cmp stderr stderr.golden @@ -12,7 +12,7 @@ cmp sub/sub.gno.gen.go sub/sub.gno.gen.go.golden rm mai.gno.gen.gosub/sub.gno.gen.go # Re-try, but use an absolute path. -gno transpile -gobuild -v $WORK +gno tool transpile -gobuild -v $WORK ! stdout .+ cmpenv stderr stderr2.golden diff --git a/gnovm/cmd/gno/testdata/transpile/valid_output_flag.txtar b/gnovm/cmd/gno/testdata/transpile/valid_output_flag.txtar index b1a63890f46..62953e96fb7 100644 --- a/gnovm/cmd/gno/testdata/transpile/valid_output_flag.txtar +++ b/gnovm/cmd/gno/testdata/transpile/valid_output_flag.txtar @@ -1,6 +1,6 @@ -# Run gno transpile with valid gno files, using the -output flag. +# Run gno tool transpile with valid gno files, using the -output flag. -gno transpile -v -output directory/hello/ . +gno tool transpile -v -output directory/hello/ . ! stdout .+ cmp stderr stderr1.golden @@ -9,7 +9,7 @@ exists directory/hello/main.gno.gen.go rm directory # Try running using the absolute path to the directory. -gno transpile -v -output directory/hello $WORK +gno tool transpile -v -output directory/hello $WORK ! stdout .+ cmpenv stderr stderr2.golden @@ -20,7 +20,7 @@ rm directory # Try running in subdirectory, using a "relative non-local path." (ie. has "../") mkdir subdir cd subdir -gno transpile -v -output hello .. +gno tool transpile -v -output hello .. ! stdout .+ cmpenv stderr ../stderr3.golden diff --git a/gnovm/cmd/gno/testdata/transpile/valid_output_gobuild.txtar b/gnovm/cmd/gno/testdata/transpile/valid_output_gobuild.txtar index 3540e865f3e..c948e53ebb3 100644 --- a/gnovm/cmd/gno/testdata/transpile/valid_output_gobuild.txtar +++ b/gnovm/cmd/gno/testdata/transpile/valid_output_gobuild.txtar @@ -1,6 +1,6 @@ -# Run gno transpile with valid gno files, using the -output and -gobuild flags together. +# Run gno tool transpile with valid gno files, using the -output and -gobuild flags together. -gno transpile -v -output directory/hello/ -gobuild . +gno tool transpile -v -output directory/hello/ -gobuild . ! stdout .+ cmp stderr stderr1.golden @@ -9,7 +9,7 @@ exists directory/hello/main.gno.gen.go rm directory # Try running using the absolute path to the directory. -gno transpile -v -output directory/hello -gobuild $WORK +gno tool transpile -v -output directory/hello -gobuild $WORK ! stdout .+ cmpenv stderr stderr2.golden @@ -20,7 +20,7 @@ rm directory # Try running in subdirectory, using a "relative non-local path." (ie. has "../") mkdir subdir cd subdir -gno transpile -v -output hello -gobuild .. +gno tool transpile -v -output hello -gobuild .. ! stdout .+ cmpenv stderr ../stderr3.golden diff --git a/gnovm/cmd/gno/testdata/transpile/valid_transpile_file.txtar b/gnovm/cmd/gno/testdata/transpile/valid_transpile_file.txtar index 86cc6f12f7a..906c43cc41b 100644 --- a/gnovm/cmd/gno/testdata/transpile/valid_transpile_file.txtar +++ b/gnovm/cmd/gno/testdata/transpile/valid_transpile_file.txtar @@ -1,8 +1,8 @@ -# Run gno transpile with an individual file. +# Run gno tool transpile with an individual file. # Running transpile on the current directory should only precompile # main.gno. -gno transpile -v . +gno tool transpile -v . ! stdout .+ stderr ^\.$ @@ -12,7 +12,7 @@ exists main.gno.gen.go rm main.gno.gen.go # Running it using individual filenames should precompile hello_test.gno, as well. -gno transpile -v main.gno hello_test.gno +gno tool transpile -v main.gno hello_test.gno ! stdout .+ cmp stderr transpile-files-stderr.golden diff --git a/gnovm/cmd/gno/testdata/transpile/valid_transpile_package.txtar b/gnovm/cmd/gno/testdata/transpile/valid_transpile_package.txtar index 2a24423598a..74923b9846a 100644 --- a/gnovm/cmd/gno/testdata/transpile/valid_transpile_package.txtar +++ b/gnovm/cmd/gno/testdata/transpile/valid_transpile_package.txtar @@ -1,6 +1,6 @@ -# Run gno transpile with valid gno files +# Run gno tool transpile with valid gno files -gno transpile . +gno tool transpile . ! stdout .+ ! stderr .+ diff --git a/gnovm/cmd/gno/testdata/transpile/valid_transpile_tree.txtar b/gnovm/cmd/gno/testdata/transpile/valid_transpile_tree.txtar index a765ab5093b..e0c42d986e4 100644 --- a/gnovm/cmd/gno/testdata/transpile/valid_transpile_tree.txtar +++ b/gnovm/cmd/gno/testdata/transpile/valid_transpile_tree.txtar @@ -1,7 +1,7 @@ -# Run gno transpile with dependencies +# Run gno tool transpile with dependencies env GNOROOT=$WORK -gno transpile -v ./examples +gno tool transpile -v ./examples ! stdout .+ cmpenv stderr stderr.golden diff --git a/gnovm/cmd/gno/tool.go b/gnovm/cmd/gno/tool.go new file mode 100644 index 00000000000..0e4f7ff51d7 --- /dev/null +++ b/gnovm/cmd/gno/tool.go @@ -0,0 +1,39 @@ +package main + +import ( + "github.com/gnolang/gno/tm2/pkg/commands" +) + +func newToolCmd(io commands.IO) *commands.Command { + cmd := commands.NewCommand( + commands.Metadata{ + Name: "tool", + ShortUsage: "gno tool command [args...]", + ShortHelp: "run specified gno tool", + }, + commands.NewEmptyConfig(), + commands.HelpExec, + ) + + cmd.AddSubCommands( + // go equivalent commands: + // + // compile + // transpile + // pprof + // trace + // vet + + // gno specific commands: + // + // ast + newLintCmd(io), + // publish/release + // render -- call render()? + newReplCmd(), + newTranspileCmd(io), + // "vm" -- starts an in-memory chain that can be interacted with? + ) + + return cmd +} diff --git a/gnovm/cmd/gno/lint.go b/gnovm/cmd/gno/tool_lint.go similarity index 100% rename from gnovm/cmd/gno/lint.go rename to gnovm/cmd/gno/tool_lint.go diff --git a/gnovm/cmd/gno/lint_test.go b/gnovm/cmd/gno/tool_lint_test.go similarity index 70% rename from gnovm/cmd/gno/lint_test.go rename to gnovm/cmd/gno/tool_lint_test.go index 4589fc55f92..85b625fa367 100644 --- a/gnovm/cmd/gno/lint_test.go +++ b/gnovm/cmd/gno/tool_lint_test.go @@ -8,26 +8,26 @@ import ( func TestLintApp(t *testing.T) { tc := []testMainCase{ { - args: []string{"lint"}, + args: []string{"tool", "lint"}, errShouldBe: "flag: help requested", }, { - args: []string{"lint", "../../tests/integ/run_main/"}, + args: []string{"tool", "lint", "../../tests/integ/run_main/"}, stderrShouldContain: "./../../tests/integ/run_main: gno.mod file not found in current or any parent directory (code=1)", errShouldBe: "exit code: 1", }, { - args: []string{"lint", "../../tests/integ/undefined_variable_test/undefined_variables_test.gno"}, + args: []string{"tool", "lint", "../../tests/integ/undefined_variable_test/undefined_variables_test.gno"}, stderrShouldContain: "undefined_variables_test.gno:6:28: name toto not declared (code=2)", errShouldBe: "exit code: 1", }, { - args: []string{"lint", "../../tests/integ/package_not_declared/main.gno"}, + args: []string{"tool", "lint", "../../tests/integ/package_not_declared/main.gno"}, stderrShouldContain: "main.gno:4:2: name fmt not declared (code=2)", errShouldBe: "exit code: 1", }, { - args: []string{"lint", "../../tests/integ/several-lint-errors/main.gno"}, + args: []string{"tool", "lint", "../../tests/integ/several-lint-errors/main.gno"}, stderrShouldContain: "../../tests/integ/several-lint-errors/main.gno:5:5: expected ';', found example (code=2)\n../../tests/integ/several-lint-errors/main.gno:6", errShouldBe: "exit code: 1", }, { - args: []string{"lint", "../../tests/integ/several-files-multiple-errors/main.gno"}, + args: []string{"tool", "lint", "../../tests/integ/several-files-multiple-errors/main.gno"}, stderrShouldContain: func() string { lines := []string{ "../../tests/integ/several-files-multiple-errors/file2.gno:3:5: expected 'IDENT', found '{' (code=2)", @@ -39,17 +39,17 @@ func TestLintApp(t *testing.T) { }(), errShouldBe: "exit code: 1", }, { - args: []string{"lint", "../../tests/integ/minimalist_gnomod/"}, + args: []string{"tool", "lint", "../../tests/integ/minimalist_gnomod/"}, // TODO: raise an error because there is a gno.mod, but no .gno files }, { - args: []string{"lint", "../../tests/integ/invalid_module_name/"}, + args: []string{"tool", "lint", "../../tests/integ/invalid_module_name/"}, // TODO: raise an error because gno.mod is invalid }, { - args: []string{"lint", "../../tests/integ/invalid_gno_file/"}, + args: []string{"tool", "lint", "../../tests/integ/invalid_gno_file/"}, stderrShouldContain: "../../tests/integ/invalid_gno_file/invalid.gno:1:1: expected 'package', found packag (code=2)", errShouldBe: "exit code: 1", }, { - args: []string{"lint", "../../tests/integ/typecheck_missing_return/"}, + args: []string{"tool", "lint", "../../tests/integ/typecheck_missing_return/"}, stderrShouldContain: "../../tests/integ/typecheck_missing_return/main.gno:5:1: missing return (code=4)", errShouldBe: "exit code: 1", }, diff --git a/gnovm/cmd/gno/repl.go b/gnovm/cmd/gno/tool_repl.go similarity index 100% rename from gnovm/cmd/gno/repl.go rename to gnovm/cmd/gno/tool_repl.go diff --git a/gnovm/cmd/gno/repl_test.go b/gnovm/cmd/gno/tool_repl_test.go similarity index 100% rename from gnovm/cmd/gno/repl_test.go rename to gnovm/cmd/gno/tool_repl_test.go diff --git a/gnovm/cmd/gno/transpile.go b/gnovm/cmd/gno/tool_transpile.go similarity index 100% rename from gnovm/cmd/gno/transpile.go rename to gnovm/cmd/gno/tool_transpile.go diff --git a/gnovm/cmd/gno/transpile_test.go b/gnovm/cmd/gno/tool_transpile_test.go similarity index 100% rename from gnovm/cmd/gno/transpile_test.go rename to gnovm/cmd/gno/tool_transpile_test.go diff --git a/gnovm/tests/challenges/unused0.gno b/gnovm/tests/challenges/unused0.gno index d5b0ff75c17..dc9aa950dec 100644 --- a/gnovm/tests/challenges/unused0.gno +++ b/gnovm/tests/challenges/unused0.gno @@ -1,7 +1,7 @@ package main // NOTE: instead of patching the vm code, this should be handled by an -// integration of `gno transpile --gobuild`, which uses `go build` under +// integration of `gno tool transpile --gobuild`, which uses `go build` under // the hood and already detects unused variables. func main() { var x int From ad93c332a639c730b34e7448d87d8bcbfb23c076 Mon Sep 17 00:00:00 2001 From: Manfred Touron <94029+moul@users.noreply.github.com> Date: Mon, 27 Jan 2025 09:19:28 +0100 Subject: [PATCH 074/143] feat(gnoweb): add gnoconnect html meta entries (#3609) Adds before the ``: ```html ``` Blocks #3610 Signed-off-by: moul <94029+moul@users.noreply.github.com> --- gno.land/pkg/gnoweb/components/layout_index.go | 2 ++ gno.land/pkg/gnoweb/components/layouts/head.html | 4 ++++ gno.land/pkg/gnoweb/handler.go | 2 ++ 3 files changed, 8 insertions(+) diff --git a/gno.land/pkg/gnoweb/components/layout_index.go b/gno.land/pkg/gnoweb/components/layout_index.go index 8b49e8f8ada..f56ddfe1514 100644 --- a/gno.land/pkg/gnoweb/components/layout_index.go +++ b/gno.land/pkg/gnoweb/components/layout_index.go @@ -15,6 +15,8 @@ type HeadData struct { ChromaPath string AssetsPath string Analytics bool + Remote string + ChainId string } type IndexData struct { diff --git a/gno.land/pkg/gnoweb/components/layouts/head.html b/gno.land/pkg/gnoweb/components/layouts/head.html index 1b78eeeb324..72f255ebdf6 100644 --- a/gno.land/pkg/gnoweb/components/layouts/head.html +++ b/gno.land/pkg/gnoweb/components/layouts/head.html @@ -40,5 +40,9 @@ + + + + {{ end }} diff --git a/gno.land/pkg/gnoweb/handler.go b/gno.land/pkg/gnoweb/handler.go index 3fdfc33909c..cdaaa63e1bc 100644 --- a/gno.land/pkg/gnoweb/handler.go +++ b/gno.land/pkg/gnoweb/handler.go @@ -90,6 +90,8 @@ func (h *WebHandler) Get(w http.ResponseWriter, r *http.Request) { HeadData: components.HeadData{ AssetsPath: h.Static.AssetsPath, ChromaPath: h.Static.ChromaPath, + ChainId: h.Static.ChainId, + Remote: h.Static.RemoteHelp, }, FooterData: components.FooterData{ Analytics: h.Static.Analytics, From a885c78da2ef749b711241b578b525f9bf4155a1 Mon Sep 17 00:00:00 2001 From: 6h057 <15034695+omarsy@users.noreply.github.com> Date: Tue, 28 Jan 2025 13:06:05 +0100 Subject: [PATCH 075/143] fix(gnovm/softfloat): replace copy.sh with Go generator (#3584) Co-authored-by: Morgan Co-authored-by: Manfred Touron <94029+moul@users.noreply.github.com> --- gnovm/pkg/gnolang/internal/softfloat/copy.sh | 32 ----- .../gnolang/internal/softfloat/gen/main.go | 116 ++++++++++++++++++ .../internal/softfloat/runtime_softfloat64.go | 2 +- .../softfloat/runtime_softfloat64_test.go | 6 +- .../gnolang/internal/softfloat/softfloat.go | 2 +- 5 files changed, 121 insertions(+), 37 deletions(-) delete mode 100644 gnovm/pkg/gnolang/internal/softfloat/copy.sh create mode 100644 gnovm/pkg/gnolang/internal/softfloat/gen/main.go diff --git a/gnovm/pkg/gnolang/internal/softfloat/copy.sh b/gnovm/pkg/gnolang/internal/softfloat/copy.sh deleted file mode 100644 index 6d2a8f80462..00000000000 --- a/gnovm/pkg/gnolang/internal/softfloat/copy.sh +++ /dev/null @@ -1,32 +0,0 @@ -#!/bin/sh - -# softfloat64.go: -# - add header -# - change package name -cat > runtime_softfloat64.go << EOF -// Code generated by copy.sh. DO NOT EDIT. -// This file is copied from \$GOROOT/src/runtime/softfloat64.go. -// It is the software floating point implementation used by the Go runtime. - -EOF -cat "$GOROOT/src/runtime/softfloat64.go" >> ./runtime_softfloat64.go -sed -i 's/^package runtime$/package softfloat/' runtime_softfloat64.go - -# softfloat64_test.go: -# - add header -# - change package name -# - change import to right package -# - change GOARCH to runtime.GOARCH, and import the "runtime" package -cat > runtime_softfloat64_test.go << EOF -// Code generated by copy.sh. DO NOT EDIT. -// This file is copied from \$GOROOT/src/runtime/softfloat64_test.go. -// It is the tests for the software floating point implementation -// used by the Go runtime. - -EOF -cat "$GOROOT/src/runtime/softfloat64_test.go" >> ./runtime_softfloat64_test.go -sed -i 's/^package runtime_test$/package softfloat_test/ -s#^\t\. "runtime"$#\t. "github.com/gnolang/gno/gnovm/pkg/gnolang/internal/softfloat"# -s/GOARCH/runtime.GOARCH/g -16a\ - "runtime"' runtime_softfloat64_test.go \ No newline at end of file diff --git a/gnovm/pkg/gnolang/internal/softfloat/gen/main.go b/gnovm/pkg/gnolang/internal/softfloat/gen/main.go new file mode 100644 index 00000000000..7c89ff9b5a9 --- /dev/null +++ b/gnovm/pkg/gnolang/internal/softfloat/gen/main.go @@ -0,0 +1,116 @@ +package main + +import ( + "errors" + "fmt" + "log" + "os" + "os/exec" + "path/filepath" + "runtime" + "strings" +) + +func main() { + // Process softfloat64.go file + processSoftFloat64File() + + // Process softfloat64_test.go file + processSoftFloat64TestFile() + + // Run mvdan.cc/gofumpt + gofumpt() + + fmt.Println("Files processed successfully.") +} + +func processSoftFloat64File() { + // Read source file + content, err := os.ReadFile(fmt.Sprintf("%s/src/runtime/softfloat64.go", runtime.GOROOT())) + if err != nil { + log.Fatal("Error reading source file:", err) + } + + // Prepare header + header := `// Code generated by github.com/gnolang/gno/gnovm/pkg/gnolang/internal/softfloat/gen. DO NOT EDIT. +// This file is copied from $GOROOT/src/runtime/softfloat64.go. +// It is the software floating point implementation used by the Go runtime. + +` + + // Combine header with content + newContent := header + string(content) + + // Replace package name + newContent = strings.Replace(newContent, "package runtime", "package softfloat", 1) + + // Write to destination file + err = os.WriteFile("runtime_softfloat64.go", []byte(newContent), 0o644) + if err != nil { + log.Fatal("Error writing to destination file:", err) + } +} + +func processSoftFloat64TestFile() { + // Read source test file + content, err := os.ReadFile(fmt.Sprintf("%s/src/runtime/softfloat64_test.go", runtime.GOROOT())) + if err != nil { + log.Fatal("Error reading source test file:", err) + } + + // Prepare header + header := `// Code generated by github.com/gnolang/gno/gnovm/pkg/gnolang/internal/softfloat/gen. DO NOT EDIT. +// This file is copied from $GOROOT/src/runtime/softfloat64_test.go. +// It is the tests for the software floating point implementation +// used by the Go runtime. + +` + + // Combine header with content + newContent := header + string(content) + + // Replace package name and imports + newContent = strings.Replace(newContent, "package runtime_test", "package softfloat_test", 1) + newContent = strings.Replace(newContent, "\t. \"runtime\"", "\t\"runtime\"", 1) + newContent = strings.Replace(newContent, "GOARCH", "runtime.GOARCH", 1) + + newContent = strings.Replace(newContent, "import (", "import (\n\t. \"github.com/gnolang/gno/gnovm/pkg/gnolang/internal/softfloat\"", 1) + + // Write to destination file + err = os.WriteFile("runtime_softfloat64_test.go", []byte(newContent), 0o644) + if err != nil { + log.Fatal("Error writing to destination test file:", err) + } +} + +func gitRoot() (string, error) { + wd, err := os.Getwd() + if err != nil { + return "", err + } + p := wd + for { + if s, e := os.Stat(filepath.Join(p, ".git")); e == nil && s.IsDir() { + return p, nil + } + + if strings.HasSuffix(p, string(filepath.Separator)) { + return "", errors.New("root git not found") + } + + p = filepath.Dir(p) + } +} + +func gofumpt() { + rootPath, err := gitRoot() + if err != nil { + log.Fatal("error finding git root:", err) + } + + cmd := exec.Command("go", "run", "-modfile", filepath.Join(strings.TrimSpace(rootPath), "misc/devdeps/go.mod"), "mvdan.cc/gofumpt", "-w", ".") + _, err = cmd.Output() + if err != nil { + log.Fatal("error gofumpt:", err) + } +} diff --git a/gnovm/pkg/gnolang/internal/softfloat/runtime_softfloat64.go b/gnovm/pkg/gnolang/internal/softfloat/runtime_softfloat64.go index cf2ad5afd8a..7623b9c2077 100644 --- a/gnovm/pkg/gnolang/internal/softfloat/runtime_softfloat64.go +++ b/gnovm/pkg/gnolang/internal/softfloat/runtime_softfloat64.go @@ -1,4 +1,4 @@ -// Code generated by copy.sh. DO NOT EDIT. +// Code generated by github.com/gnolang/gno/gnovm/pkg/gnolang/internal/softfloat/gen. DO NOT EDIT. // This file is copied from $GOROOT/src/runtime/softfloat64.go. // It is the software floating point implementation used by the Go runtime. diff --git a/gnovm/pkg/gnolang/internal/softfloat/runtime_softfloat64_test.go b/gnovm/pkg/gnolang/internal/softfloat/runtime_softfloat64_test.go index c57fe08b0ef..8b5d34650f1 100644 --- a/gnovm/pkg/gnolang/internal/softfloat/runtime_softfloat64_test.go +++ b/gnovm/pkg/gnolang/internal/softfloat/runtime_softfloat64_test.go @@ -1,4 +1,4 @@ -// Code generated by copy.sh. DO NOT EDIT. +// Code generated by github.com/gnolang/gno/gnovm/pkg/gnolang/internal/softfloat/gen. DO NOT EDIT. // This file is copied from $GOROOT/src/runtime/softfloat64_test.go. // It is the tests for the software floating point implementation // used by the Go runtime. @@ -10,11 +10,11 @@ package softfloat_test import ( + . "github.com/gnolang/gno/gnovm/pkg/gnolang/internal/softfloat" "math" "math/rand" - . "github.com/gnolang/gno/gnovm/pkg/gnolang/internal/softfloat" + "runtime" "testing" - "runtime" ) // turn uint64 op into float64 op diff --git a/gnovm/pkg/gnolang/internal/softfloat/softfloat.go b/gnovm/pkg/gnolang/internal/softfloat/softfloat.go index 30f66dff620..89dcd04d8fb 100644 --- a/gnovm/pkg/gnolang/internal/softfloat/softfloat.go +++ b/gnovm/pkg/gnolang/internal/softfloat/softfloat.go @@ -17,7 +17,7 @@ package softfloat // This file mostly exports the functions from runtime_softfloat64.go -//go:generate sh copy.sh +//go:generate go run github.com/gnolang/gno/gnovm/pkg/gnolang/internal/softfloat/gen const ( mask = 0x7FF From df4113d75450066a72c627f584686b28d2a10cbc Mon Sep 17 00:00:00 2001 From: Leon Hudak <33522493+leohhhn@users.noreply.github.com> Date: Wed, 29 Jan 2025 10:44:49 +0100 Subject: [PATCH 076/143] feat(examples): update leon's config & home (#3603) --- examples/gno.land/r/leon/config/config.gno | 119 +++++++++++++++------ examples/gno.land/r/leon/hof/hof.gno | 22 ++-- examples/gno.land/r/leon/home/home.gno | 42 ++++---- 3 files changed, 119 insertions(+), 64 deletions(-) diff --git a/examples/gno.land/r/leon/config/config.gno b/examples/gno.land/r/leon/config/config.gno index bc800ec8263..bb90a6c21d7 100644 --- a/examples/gno.land/r/leon/config/config.gno +++ b/examples/gno.land/r/leon/config/config.gno @@ -3,61 +3,116 @@ package config import ( "errors" "std" + "strconv" + "strings" + "time" + + "gno.land/p/demo/avl" + p "gno.land/p/demo/avl/pager" + "gno.land/p/demo/ownable" + "gno.land/p/demo/ufmt" + "gno.land/p/moul/md" + "gno.land/p/moul/realmpath" ) var ( - main std.Address // leon's main address - backup std.Address // backup address + configs = avl.NewTree() + pager = p.NewPager(configs, 10, false) + banner = "---\n[[Leon's Home page]](/r/leon/home) | [[GitHub: @leohhhn]](https://github.com/leohhhn)\n\n---" + absPath = strings.TrimPrefix(std.CurrentRealm().PkgPath(), std.GetChainDomain()) + + // SafeObjects + OwnableMain = ownable.NewWithAddress("g125em6arxsnj49vx35f0n0z34putv5ty3376fg5") + OwnableBackup = ownable.NewWithAddress("g1lavlav7zwsjqlzzl3qdl3nl242qtf638vnhdjh") - ErrInvalidAddr = errors.New("leon's config: invalid address") ErrUnauthorized = errors.New("leon's config: unauthorized") ) -func init() { - main = "g125em6arxsnj49vx35f0n0z34putv5ty3376fg5" +type Config struct { + lines string + updated time.Time } -func Address() std.Address { - return main -} +func AddConfig(name, lines string) { + if !IsAuthorized(std.PrevRealm().Addr()) { + panic(ErrUnauthorized) + } -func Backup() std.Address { - return backup + configs.Set(name, Config{ + lines: lines, + updated: time.Now(), + }) // no overwrite check } -func SetAddress(a std.Address) error { - if !a.IsValid() { - return ErrInvalidAddr +func RemoveConfig(name string) { + if !IsAuthorized(std.PrevRealm().Addr()) { + panic(ErrUnauthorized) } - if err := checkAuthorized(); err != nil { - return err + if _, ok := configs.Remove(name); !ok { + panic("no config with that name") } - - main = a - return nil } -func SetBackup(a std.Address) error { - if !a.IsValid() { - return ErrInvalidAddr +func UpdateBanner(newBanner string) { + if !IsAuthorized(std.PrevRealm().Addr()) { + panic(ErrUnauthorized) } - if err := checkAuthorized(); err != nil { - return err - } + banner = newBanner +} - backup = a - return nil +func IsAuthorized(addr std.Address) bool { + return addr == OwnableMain.Owner() || addr == OwnableBackup.Owner() } -func checkAuthorized() error { - caller := std.PrevRealm().Addr() - isAuthorized := caller == main || caller == backup +func Banner() string { + return banner +} + +func Render(path string) (out string) { + req := realmpath.Parse(path) + if req.Path == "" { + out += md.H1("Leon's config package") + + out += ufmt.Sprintf("Leon's main address: %s\n\n", OwnableMain.Owner().String()) + out += ufmt.Sprintf("Leon's backup address: %s\n\n", OwnableBackup.Owner().String()) - if !isAuthorized { - return ErrUnauthorized + out += md.H2("Leon's configs") + + if configs.Size() == 0 { + out += "No configs yet :c\n\n" + } + + page := pager.MustGetPageByPath(path) + for _, item := range page.Items { + out += ufmt.Sprintf("- [%s](%s:%s)\n\n", item.Key, absPath, item.Key) + } + + out += page.Picker() + out += "\n\n" + out += "Page " + strconv.Itoa(page.PageNumber) + " of " + strconv.Itoa(page.TotalPages) + "\n\n" + + out += Banner() + + return out } - return nil + return renderConfPage(req.Path) +} + +func renderConfPage(confName string) (out string) { + raw, ok := configs.Get(confName) + if !ok { + out += md.H1("404") + out += "That config does not exist :/" + return out + } + + conf := raw.(Config) + out += md.H1(confName) + out += ufmt.Sprintf("```\n%s\n```\n\n", conf.lines) + out += ufmt.Sprintf("_Last updated on %s_", conf.updated.Format("02 Jan, 2006")) + + return out } diff --git a/examples/gno.land/r/leon/hof/hof.gno b/examples/gno.land/r/leon/hof/hof.gno index 147a0dd1a95..96266ffe380 100644 --- a/examples/gno.land/r/leon/hof/hof.gno +++ b/examples/gno.land/r/leon/hof/hof.gno @@ -10,6 +10,8 @@ import ( "gno.land/p/demo/ownable" "gno.land/p/demo/pausable" "gno.land/p/demo/seqid" + + "gno.land/r/leon/config" ) var ( @@ -24,7 +26,7 @@ type ( Exhibition struct { itemCounter seqid.ID description string - items *avl.Tree // pkgPath > Item + items *avl.Tree // pkgPath > &Item itemsSorted *avl.Tree // same data but sorted, storing pointers } @@ -43,7 +45,7 @@ func init() { itemsSorted: avl.NewTree(), } - Ownable = ownable.NewWithAddress(std.Address("g125em6arxsnj49vx35f0n0z34putv5ty3376fg5")) + Ownable = ownable.NewWithAddress(config.OwnableMain.Owner()) // OrigSendOwnable? Pausable = pausable.NewFromOwnable(Ownable) } @@ -85,14 +87,14 @@ func Register() { func Upvote(pkgpath string) { rawItem, ok := exhibition.items.Get(pkgpath) if !ok { - panic(ErrNoSuchItem.Error()) + panic(ErrNoSuchItem) } item := rawItem.(*Item) caller := std.PrevRealm().Addr().String() if item.upvote.Has(caller) { - panic(ErrDoubleUpvote.Error()) + panic(ErrDoubleUpvote) } item.upvote.Set(caller, struct{}{}) @@ -101,14 +103,14 @@ func Upvote(pkgpath string) { func Downvote(pkgpath string) { rawItem, ok := exhibition.items.Get(pkgpath) if !ok { - panic(ErrNoSuchItem.Error()) + panic(ErrNoSuchItem) } item := rawItem.(*Item) caller := std.PrevRealm().Addr().String() if item.downvote.Has(caller) { - panic(ErrDoubleDownvote.Error()) + panic(ErrDoubleDownvote) } item.downvote.Set(caller, struct{}{}) @@ -116,19 +118,19 @@ func Downvote(pkgpath string) { func Delete(pkgpath string) { if !Ownable.CallerIsOwner() { - panic(ownable.ErrUnauthorized.Error()) + panic(ownable.ErrUnauthorized) } i, ok := exhibition.items.Get(pkgpath) if !ok { - panic(ErrNoSuchItem.Error()) + panic(ErrNoSuchItem) } if _, removed := exhibition.itemsSorted.Remove(i.(*Item).id.String()); !removed { - panic(ErrNoSuchItem.Error()) + panic(ErrNoSuchItem) } if _, removed := exhibition.items.Remove(pkgpath); !removed { - panic(ErrNoSuchItem.Error()) + panic(ErrNoSuchItem) } } diff --git a/examples/gno.land/r/leon/home/home.gno b/examples/gno.land/r/leon/home/home.gno index cf33260cc6b..aef261fcd60 100644 --- a/examples/gno.land/r/leon/home/home.gno +++ b/examples/gno.land/r/leon/home/home.gno @@ -19,7 +19,24 @@ var ( abtMe [2]string ) +func Render(path string) string { + out := "# Leon's Homepage\n\n" + + out += renderAboutMe() + out += renderBlogPosts() + out += "\n\n" + out += renderArt() + out += "\n\n" + out += config.Banner() + out += "\n\n" + + return out +} + func init() { + hof.Register() + mirror.Register(std.CurrentRealm().PkgPath(), Render) + pfp = "https://i.imgflip.com/91vskx.jpg" pfpCaption = "[My favourite painting & pfp](https://en.wikipedia.org/wiki/Wanderer_above_the_Sea_of_Fog)" abtMe = [2]string{ @@ -30,16 +47,12 @@ life-long learner, and sharer of knowledge.`, My contributions to gno.land can mainly be found [here](https://github.com/gnolang/gno/issues?q=sort:updated-desc+author:leohhhn). -TODO import r/gh -`, +TODO import r/gh`, } - - hof.Register() - mirror.Register(std.CurrentRealm().PkgPath(), Render) } func UpdatePFP(url, caption string) { - if !isAuthorized(std.PrevRealm().Addr()) { + if !config.IsAuthorized(std.PrevRealm().Addr()) { panic(config.ErrUnauthorized) } @@ -48,7 +61,7 @@ func UpdatePFP(url, caption string) { } func UpdateAboutMe(col1, col2 string) { - if !isAuthorized(std.PrevRealm().Addr()) { + if !config.IsAuthorized(std.PrevRealm().Addr()) { panic(config.ErrUnauthorized) } @@ -56,17 +69,6 @@ func UpdateAboutMe(col1, col2 string) { abtMe[1] = col2 } -func Render(path string) string { - out := "# Leon's Homepage\n\n" - - out += renderAboutMe() - out += renderBlogPosts() - out += "\n\n" - out += renderArt() - - return out -} - func renderBlogPosts() string { out := "" //out += "## Leon's Blog Posts" @@ -130,7 +132,3 @@ func renderMillipede() string { return out } - -func isAuthorized(addr std.Address) bool { - return addr == config.Address() || addr == config.Backup() -} From 21fe65624a39fce3c589c1dd2d897b02b720f292 Mon Sep 17 00:00:00 2001 From: Leon Hudak <33522493+leohhhn@users.noreply.github.com> Date: Wed, 29 Jan 2025 10:55:05 +0100 Subject: [PATCH 077/143] feat(r/docs): pager + render paths (#3608) --- .../r/docs/avl_pager_with_params/gno.mod | 1 + .../r/docs/avl_pager_with_params/render.gno | 86 +++++++++++++++++++ examples/gno.land/r/docs/docs.gno | 1 + 3 files changed, 88 insertions(+) create mode 100644 examples/gno.land/r/docs/avl_pager_with_params/gno.mod create mode 100644 examples/gno.land/r/docs/avl_pager_with_params/render.gno diff --git a/examples/gno.land/r/docs/avl_pager_with_params/gno.mod b/examples/gno.land/r/docs/avl_pager_with_params/gno.mod new file mode 100644 index 00000000000..aeb5b047762 --- /dev/null +++ b/examples/gno.land/r/docs/avl_pager_with_params/gno.mod @@ -0,0 +1 @@ +module gno.land/r/docs/avl_pager_params diff --git a/examples/gno.land/r/docs/avl_pager_with_params/render.gno b/examples/gno.land/r/docs/avl_pager_with_params/render.gno new file mode 100644 index 00000000000..108f5735b65 --- /dev/null +++ b/examples/gno.land/r/docs/avl_pager_with_params/render.gno @@ -0,0 +1,86 @@ +package avl_pager_params + +import ( + "gno.land/p/demo/avl" + "gno.land/p/demo/avl/pager" + "gno.land/p/demo/seqid" + "gno.land/p/demo/ufmt" + "gno.land/p/moul/realmpath" +) + +// We'll keep some demo data in an AVL tree to showcase pagination. +var ( + items *avl.Tree + idCounter seqid.ID +) + +func init() { + items = avl.NewTree() + // Populate the tree with 15 sample items for demonstration. + for i := 1; i <= 15; i++ { + id := idCounter.Next().String() + items.Set(id, "Some item value: "+id) + } +} + +func Render(path string) string { + // 1) Parse the incoming path to split route vs. query. + req := realmpath.Parse(path) + // - req.Path contains everything *before* ? or $ (? - query params, $ - gnoweb params) + // - The remaining part (page=2, size=5, etc.) is not in req.Path. + + // 2) If no specific route is provided (req.Path == ""), we’ll show a “home” page + // that displays a list of configs in paginated form. + if req.Path == "" { + return renderHome(path) + } + + // 3) If a route *is* provided (e.g. :SomeKey), + // we will interpret it as a request for a specific page. + return renderConfigItem(req.Path) +} + +// renderHome shows a paginated list of config items if route == "". +func renderHome(fullPath string) string { + // Create a Pager for our config tree, with a default page size of 5. + p := pager.NewPager(items, 5, false) + + // MustGetPageByPath uses the *entire* path (including query parts: ?page=2, etc.) + page := p.MustGetPageByPath(fullPath) + + // Start building the output (plain text or markdown). + out := "# AVL Pager + Render paths\n\n" + out += `This realm showcases how to maintain a paginated list while properly parsing render paths. +You can see how a single page can include a paginated element (like the example below), and how clicking +an item can take you to a dedicated page for that specific item. + +No matter how you browse through the paginated list, the introductory text (this section) remains the same. + +` + + out += ufmt.Sprintf("Showing page %d of %d\n\n", page.PageNumber, page.TotalPages) + + // List items for this page. + for _, item := range page.Items { + // Link each item to a details page: e.g. ":Config01" + out += ufmt.Sprintf("- [Item %s](/r/docs/avl_pager_params:%s)\n", item.Key, item.Key) + } + + // Insert pagination controls (previous/next links, etc.). + out += "\n" + page.Picker() + "\n\n" + out += "### [Go back to r/docs](/r/docs)" + + return out +} + +// renderConfigItem shows details for a single item, e.g. ":item001". +func renderConfigItem(itemName string) string { + value, ok := items.Get(itemName) + if !ok { + return ufmt.Sprintf("**No item found** for key: %s", itemName) + } + + out := ufmt.Sprintf("# Item %s\n\n%s\n\n", itemName, value.(string)) + out += "[Go back](/r/docs/avl_pager_params)" + return out +} diff --git a/examples/gno.land/r/docs/docs.gno b/examples/gno.land/r/docs/docs.gno index 28bac4171b5..be9a58e1c53 100644 --- a/examples/gno.land/r/docs/docs.gno +++ b/examples/gno.land/r/docs/docs.gno @@ -13,6 +13,7 @@ Explore various examples to learn more about Gno functionality and usage. - [Source](/r/docs/source) - View realm source code. - [Buttons](/r/docs/buttons) - Add buttons to your realm's render. - [AVL Pager](/r/docs/avl_pager) - Paginate through AVL tree items. +- [AVL Pager + Render paths](/r/docs/avl_pager_params) - Handle render arguments with pagination. - [Img Embed](/r/docs/img_embed) - Demonstrates how to embed an image. - ... From 4d0000e8e10b13934e18a11b1220c27fd607926f Mon Sep 17 00:00:00 2001 From: Leon Hudak <33522493+leohhhn@users.noreply.github.com> Date: Wed, 29 Jan 2025 11:07:26 +0100 Subject: [PATCH 078/143] feat(gnoweb): "No render" page/component (#3611) ## Description Defining a `Render()` function in realms is optional. Currently gnoweb presents an error if a realm that doesn't have a render func is requested. This should not be the case. This PR also adds a VM error, `RenderNotDeclared`, which is to be returned when `vm/qrender` is called on a realm which does not have a `Render()` function declared. I updated the status component to return the following in the aforementioned case: Screenshot 2025-01-25 at 16 30 55 Also adds another `r/docs` realm mentioning that a render function is optional in `r/`. --- examples/gno.land/r/docs/docs.gno | 1 + .../gno.land/r/docs/optional_render/gno.mod | 1 + .../docs/optional_render/optional_render.gno | 7 +++ gno.land/pkg/gnoweb/app_test.go | 1 + gno.land/pkg/gnoweb/components/view_status.go | 34 +++++++++++++-- .../pkg/gnoweb/components/views/status.html | 10 +++-- gno.land/pkg/gnoweb/handler.go | 18 +++++--- gno.land/pkg/gnoweb/handler_test.go | 43 ++++++++++++++++++- gno.land/pkg/gnoweb/webclient.go | 3 +- gno.land/pkg/gnoweb/webclient_html.go | 5 +++ gno.land/pkg/gnoweb/webclient_mock.go | 25 ++++++++++- gno.land/pkg/sdk/vm/errors.go | 2 + gno.land/pkg/sdk/vm/handler.go | 4 ++ gno.land/pkg/sdk/vm/package.go | 1 + 14 files changed, 139 insertions(+), 16 deletions(-) create mode 100644 examples/gno.land/r/docs/optional_render/gno.mod create mode 100644 examples/gno.land/r/docs/optional_render/optional_render.gno diff --git a/examples/gno.land/r/docs/docs.gno b/examples/gno.land/r/docs/docs.gno index be9a58e1c53..b4c78205c0a 100644 --- a/examples/gno.land/r/docs/docs.gno +++ b/examples/gno.land/r/docs/docs.gno @@ -15,6 +15,7 @@ Explore various examples to learn more about Gno functionality and usage. - [AVL Pager](/r/docs/avl_pager) - Paginate through AVL tree items. - [AVL Pager + Render paths](/r/docs/avl_pager_params) - Handle render arguments with pagination. - [Img Embed](/r/docs/img_embed) - Demonstrates how to embed an image. +- [Optional Render](/r/docs/optional_render) - Render() is optional in realms. - ... diff --git a/examples/gno.land/r/docs/optional_render/gno.mod b/examples/gno.land/r/docs/optional_render/gno.mod new file mode 100644 index 00000000000..4c8162ca46d --- /dev/null +++ b/examples/gno.land/r/docs/optional_render/gno.mod @@ -0,0 +1 @@ +module gno.land/r/docs/optional_render diff --git a/examples/gno.land/r/docs/optional_render/optional_render.gno b/examples/gno.land/r/docs/optional_render/optional_render.gno new file mode 100644 index 00000000000..77da30609b3 --- /dev/null +++ b/examples/gno.land/r/docs/optional_render/optional_render.gno @@ -0,0 +1,7 @@ +package optional_render + +func Info() string { + return `Having a Render() function in your realm is optional! +If you do decide to have a Render() function, it must have the following signature: +func Render(path string) string { ... }` +} diff --git a/gno.land/pkg/gnoweb/app_test.go b/gno.land/pkg/gnoweb/app_test.go index 6fb69c6d984..eb17ee4d0e9 100644 --- a/gno.land/pkg/gnoweb/app_test.go +++ b/gno.land/pkg/gnoweb/app_test.go @@ -47,6 +47,7 @@ func TestRoutes(t *testing.T) { {"/game-of-realms", found, "/contribute"}, {"/gor", found, "/contribute"}, {"/blog", found, "/r/gnoland/blog"}, + {"/r/docs/optional_render", http.StatusNoContent, "No Render"}, {"/r/not/found/", notFound, ""}, {"/404/not/found", notFound, ""}, {"/아스키문자가아닌경로", notFound, ""}, diff --git a/gno.land/pkg/gnoweb/components/view_status.go b/gno.land/pkg/gnoweb/components/view_status.go index 46f998c45cb..56477a4db0a 100644 --- a/gno.land/pkg/gnoweb/components/view_status.go +++ b/gno.land/pkg/gnoweb/components/view_status.go @@ -2,10 +2,38 @@ package components const StatusViewType ViewType = "status-view" +// StatusData holds the dynamic fields for the "status" template type StatusData struct { - Message string + Title string + Body string + ButtonURL string + ButtonText string } -func StatusComponent(message string) *View { - return NewTemplateView(StatusViewType, "status", StatusData{message}) +// StatusErrorComponent returns a view for error scenarios +func StatusErrorComponent(message string) *View { + return NewTemplateView( + StatusViewType, + "status", + StatusData{ + Title: "Error: " + message, + Body: "Something went wrong.", + ButtonURL: "/", + ButtonText: "Go Back Home", + }, + ) +} + +// StatusNoRenderComponent returns a view for non-error notifications +func StatusNoRenderComponent(pkgPath string) *View { + return NewTemplateView( + StatusViewType, + "status", + StatusData{ + Title: "No Render", + Body: "This realm does not implement a Render() function.", + ButtonURL: pkgPath + "$source", + ButtonText: "View Realm Source", + }, + ) } diff --git a/gno.land/pkg/gnoweb/components/views/status.html b/gno.land/pkg/gnoweb/components/views/status.html index ab068cbf7e4..f4533275789 100644 --- a/gno.land/pkg/gnoweb/components/views/status.html +++ b/gno.land/pkg/gnoweb/components/views/status.html @@ -1,8 +1,12 @@ {{ define "status" }}
gno land -

Error: {{ .Message }}

-

Something went wrong. Let’s find our way back!

- Go Back Home +

+ {{ .Title }} +

+

{{ .Body }}

+ + {{ .ButtonText }} +
{{ end }} diff --git a/gno.land/pkg/gnoweb/handler.go b/gno.land/pkg/gnoweb/handler.go index cdaaa63e1bc..822fd50fa1b 100644 --- a/gno.land/pkg/gnoweb/handler.go +++ b/gno.land/pkg/gnoweb/handler.go @@ -114,7 +114,7 @@ func (h *WebHandler) prepareIndexBodyView(r *http.Request, indexData *components gnourl, err := ParseGnoURL(r.URL) if err != nil { h.Logger.Warn("unable to parse url path", "path", r.URL.Path, "error", err) - return http.StatusNotFound, components.StatusComponent("invalid path") + return http.StatusNotFound, components.StatusErrorComponent("invalid path") } breadcrumb := generateBreadcrumbPaths(gnourl) @@ -130,7 +130,7 @@ func (h *WebHandler) prepareIndexBodyView(r *http.Request, indexData *components return h.GetPackageView(gnourl) default: h.Logger.Debug("invalid path: path is neither a pure package or a realm") - return http.StatusBadRequest, components.StatusComponent("invalid path") + return http.StatusBadRequest, components.StatusErrorComponent("invalid path") } } @@ -160,6 +160,10 @@ func (h *WebHandler) GetRealmView(gnourl *GnoURL) (int, *components.View) { meta, err := h.Client.RenderRealm(&content, gnourl.Path, gnourl.EncodeArgs()) if err != nil { + if errors.Is(err, ErrRenderNotDeclared) { + return http.StatusNoContent, components.StatusNoRenderComponent(gnourl.Path) + } + h.Logger.Error("unable to render realm", "error", err, "path", gnourl.EncodeURL()) return GetClientErrorStatusPage(gnourl, err) } @@ -223,7 +227,7 @@ func (h *WebHandler) GetSourceView(gnourl *GnoURL) (int, *components.View) { if len(files) == 0 { h.Logger.Debug("no files available", "path", gnourl.Path) - return http.StatusOK, components.StatusComponent("no files available") + return http.StatusOK, components.StatusErrorComponent("no files available") } var fileName string @@ -266,7 +270,7 @@ func (h *WebHandler) GetDirectoryView(gnourl *GnoURL) (int, *components.View) { if len(files) == 0 { h.Logger.Debug("no files available", "path", gnourl.Path) - return http.StatusOK, components.StatusComponent("no files available") + return http.StatusOK, components.StatusErrorComponent("no files available") } return http.StatusOK, components.DirectoryView(components.DirData{ @@ -283,13 +287,13 @@ func GetClientErrorStatusPage(_ *GnoURL, err error) (int, *components.View) { switch { case errors.Is(err, ErrClientPathNotFound): - return http.StatusNotFound, components.StatusComponent(err.Error()) + return http.StatusNotFound, components.StatusErrorComponent(err.Error()) case errors.Is(err, ErrClientBadRequest): - return http.StatusInternalServerError, components.StatusComponent("bad request") + return http.StatusInternalServerError, components.StatusErrorComponent("bad request") case errors.Is(err, ErrClientResponse): fallthrough // XXX: for now fallback as internal error default: - return http.StatusInternalServerError, components.StatusComponent("internal error") + return http.StatusInternalServerError, components.StatusErrorComponent("internal error") } } diff --git a/gno.land/pkg/gnoweb/handler_test.go b/gno.land/pkg/gnoweb/handler_test.go index 624e3390a97..e85434a6f41 100644 --- a/gno.land/pkg/gnoweb/handler_test.go +++ b/gno.land/pkg/gnoweb/handler_test.go @@ -24,12 +24,13 @@ func (t *testingLogger) Write(b []byte) (n int, err error) { // TestWebHandler_Get tests the Get method of WebHandler using table-driven tests. func TestWebHandler_Get(t *testing.T) { + t.Parallel() // Set up a mock package with some files and functions mockPackage := &gnoweb.MockPackage{ Domain: "example.com", Path: "/r/mock/path", Files: map[string]string{ - "render.gno": `package main; func Render(path string) { return "one more time" }`, + "render.gno": `package main; func Render(path string) string { return "one more time" }`, "gno.mod": `module example.com/r/mock/path`, "LicEnse": `my super license`, }, @@ -37,6 +38,10 @@ func TestWebHandler_Get(t *testing.T) { {FuncName: "SuperRenderFunction", Params: []vm.NamedType{ {Name: "my_super_arg", Type: "string"}, }}, + { + FuncName: "Render", Params: []vm.NamedType{{Name: "path", Type: "string"}}, + Results: []vm.NamedType{{Name: "", Type: "string"}}, + }, }, } @@ -82,6 +87,7 @@ func TestWebHandler_Get(t *testing.T) { for _, tc := range cases { t.Run(strings.TrimPrefix(tc.Path, "/"), func(t *testing.T) { + t.Parallel() t.Logf("input: %+v", tc) // Initialize testing logger @@ -110,3 +116,38 @@ func TestWebHandler_Get(t *testing.T) { }) } } + +// TestWebHandler_NoRender checks if gnoweb displays the `No Render` page properly. +// This happens when the render being queried does not have a Render function declared. +func TestWebHandler_NoRender(t *testing.T) { + t.Parallel() + + mockPath := "/r/mock/path" + mockPackage := &gnoweb.MockPackage{ + Domain: "gno.land", + Path: "/r/mock/path", + Files: map[string]string{ + "render.gno": `package main; func init() {}`, + "gno.mod": `module gno.land/r/mock/path`, + }, + } + + webclient := gnoweb.NewMockWebClient(mockPackage) + config := gnoweb.WebHandlerConfig{ + WebClient: webclient, + } + + logger := slog.New(slog.NewTextHandler(&testingLogger{t}, &slog.HandlerOptions{})) + handler, err := gnoweb.NewWebHandler(logger, config) + require.NoError(t, err, "failed to create WebHandler") + + req, err := http.NewRequest(http.MethodGet, mockPath, nil) + require.NoError(t, err, "failed to create HTTP request") + + rr := httptest.NewRecorder() + handler.ServeHTTP(rr, req) + + assert.Equal(t, http.StatusNoContent, rr.Code, "unexpected status code") + assert.Containsf(t, rr.Body.String(), "", "rendered body should contain: %q", "No Render") + assert.Containsf(t, rr.Body.String(), "", "rendered body should contain: %q", "This realm does not implement a Render() function.") +} diff --git a/gno.land/pkg/gnoweb/webclient.go b/gno.land/pkg/gnoweb/webclient.go index de44303f352..1def3bc3812 100644 --- a/gno.land/pkg/gnoweb/webclient.go +++ b/gno.land/pkg/gnoweb/webclient.go @@ -10,6 +10,7 @@ import ( var ( ErrClientPathNotFound = errors.New("package not found") + ErrRenderNotDeclared = errors.New("render function not declared") ErrClientBadRequest = errors.New("bad request") ErrClientResponse = errors.New("node response error") ) @@ -23,7 +24,7 @@ type RealmMeta struct { Toc md.Toc } -// WebClient is an interface for interacting with package and node ressources. +// WebClient is an interface for interacting with package and node resources. type WebClient interface { // RenderRealm renders the content of a realm from a given path and // arguments into the giver `writer`. The method should ensures the rendered diff --git a/gno.land/pkg/gnoweb/webclient_html.go b/gno.land/pkg/gnoweb/webclient_html.go index d856c6f87a0..c04a7f9e457 100644 --- a/gno.land/pkg/gnoweb/webclient_html.go +++ b/gno.land/pkg/gnoweb/webclient_html.go @@ -177,6 +177,7 @@ func (s *HTMLWebClient) RenderRealm(w io.Writer, pkgPath string, args string) (* pkgPath = strings.Trim(pkgPath, "/") data := fmt.Sprintf("%s/%s:%s", s.domain, pkgPath, args) + rawres, err := s.query(qpath, []byte(data)) if err != nil { return nil, err @@ -213,6 +214,10 @@ func (s *HTMLWebClient) query(qpath string, data []byte) ([]byte, error) { return nil, ErrClientPathNotFound } + if errors.Is(err, vm.NoRenderDeclError{}) { + return nil, ErrRenderNotDeclared + } + s.logger.Error("response error", "path", qpath, "log", qres.Response.Log) return nil, fmt.Errorf("%w: %s", ErrClientResponse, err.Error()) } diff --git a/gno.land/pkg/gnoweb/webclient_mock.go b/gno.land/pkg/gnoweb/webclient_mock.go index 451f5e237c3..8a037c181e0 100644 --- a/gno.land/pkg/gnoweb/webclient_mock.go +++ b/gno.land/pkg/gnoweb/webclient_mock.go @@ -31,13 +31,18 @@ func NewMockWebClient(pkgs ...*MockPackage) *MockWebClient { return &MockWebClient{Packages: mpkgs} } -// Render simulates rendering a package by writing its content to the writer. +// RenderRealm simulates rendering a package by writing its content to the writer. func (m *MockWebClient) RenderRealm(w io.Writer, path string, args string) (*RealmMeta, error) { pkg, exists := m.Packages[path] if !exists { return nil, ErrClientPathNotFound } + if !pkgHasRender(pkg) { + return nil, ErrRenderNotDeclared + } + + // Write to the realm render fmt.Fprintf(w, "[%s]%s:", pkg.Domain, pkg.Path) // Return a dummy RealmMeta for simplicity @@ -89,3 +94,21 @@ func (m *MockWebClient) Sources(path string) ([]string, error) { return fileNames, nil } + +func pkgHasRender(pkg *MockPackage) bool { + if len(pkg.Functions) == 0 { + return false + } + + for _, fn := range pkg.Functions { + if fn.FuncName == "Render" && + len(fn.Params) == 1 && + len(fn.Results) == 1 && + fn.Params[0].Type == "string" && + fn.Results[0].Type == "string" { + return true + } + } + + return false +} diff --git a/gno.land/pkg/sdk/vm/errors.go b/gno.land/pkg/sdk/vm/errors.go index c8d6da98970..208fb074f7e 100644 --- a/gno.land/pkg/sdk/vm/errors.go +++ b/gno.land/pkg/sdk/vm/errors.go @@ -16,6 +16,7 @@ func (abciError) AssertABCIError() {} // NOTE: these are meant to be used in conjunction with pkgs/errors. type ( InvalidPkgPathError struct{ abciError } + NoRenderDeclError struct{ abciError } PkgExistError struct{ abciError } InvalidStmtError struct{ abciError } InvalidExprError struct{ abciError } @@ -27,6 +28,7 @@ type ( ) func (e InvalidPkgPathError) Error() string { return "invalid package path" } +func (e NoRenderDeclError) Error() string { return "render function not declared" } func (e PkgExistError) Error() string { return "package already exists" } func (e InvalidStmtError) Error() string { return "invalid statement" } func (e InvalidExprError) Error() string { return "invalid expression" } diff --git a/gno.land/pkg/sdk/vm/handler.go b/gno.land/pkg/sdk/vm/handler.go index c484e07e887..5aebf1afe46 100644 --- a/gno.land/pkg/sdk/vm/handler.go +++ b/gno.land/pkg/sdk/vm/handler.go @@ -129,9 +129,13 @@ func (vh vmHandler) queryRender(ctx sdk.Context, req abci.RequestQuery) (res abc expr := fmt.Sprintf("Render(%q)", path) result, err := vh.vm.QueryEvalString(ctx, pkgPath, expr) if err != nil { + if strings.Contains(err.Error(), "Render not declared") { + err = NoRenderDeclError{} + } res = sdk.ABCIResponseQueryFromError(err) return } + res.Data = []byte(result) return } diff --git a/gno.land/pkg/sdk/vm/package.go b/gno.land/pkg/sdk/vm/package.go index 0359061ccea..95e97648dac 100644 --- a/gno.land/pkg/sdk/vm/package.go +++ b/gno.land/pkg/sdk/vm/package.go @@ -20,6 +20,7 @@ var Package = amino.RegisterPackage(amino.NewPackage( // errors InvalidPkgPathError{}, "InvalidPkgPathError", + NoRenderDeclError{}, "NoRenderDeclError", PkgExistError{}, "PkgExistError", InvalidStmtError{}, "InvalidStmtError", InvalidExprError{}, "InvalidExprError", From 533ae676090c26bc6c9efc81aec5322f10d05e90 Mon Sep 17 00:00:00 2001 From: Leon Hudak <33522493+leohhhn@users.noreply.github.com> Date: Wed, 29 Jan 2025 11:51:50 +0100 Subject: [PATCH 079/143] fix(gnoweb): NoRender response & test (#3634) ## Description Changes the NoRender response to 200, since 204 does not allow for content body. Also fixes a test that didn't catch this. --- gno.land/pkg/gnoweb/app_test.go | 2 +- gno.land/pkg/gnoweb/handler.go | 2 +- gno.land/pkg/gnoweb/handler_test.go | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/gno.land/pkg/gnoweb/app_test.go b/gno.land/pkg/gnoweb/app_test.go index eb17ee4d0e9..ce10cae12d5 100644 --- a/gno.land/pkg/gnoweb/app_test.go +++ b/gno.land/pkg/gnoweb/app_test.go @@ -47,7 +47,7 @@ func TestRoutes(t *testing.T) { {"/game-of-realms", found, "/contribute"}, {"/gor", found, "/contribute"}, {"/blog", found, "/r/gnoland/blog"}, - {"/r/docs/optional_render", http.StatusNoContent, "No Render"}, + {"/r/docs/optional_render", http.StatusOK, "No Render"}, {"/r/not/found/", notFound, ""}, {"/404/not/found", notFound, ""}, {"/아스키문자가아닌경로", notFound, ""}, diff --git a/gno.land/pkg/gnoweb/handler.go b/gno.land/pkg/gnoweb/handler.go index 822fd50fa1b..ac39f4ce0f9 100644 --- a/gno.land/pkg/gnoweb/handler.go +++ b/gno.land/pkg/gnoweb/handler.go @@ -161,7 +161,7 @@ func (h *WebHandler) GetRealmView(gnourl *GnoURL) (int, *components.View) { meta, err := h.Client.RenderRealm(&content, gnourl.Path, gnourl.EncodeArgs()) if err != nil { if errors.Is(err, ErrRenderNotDeclared) { - return http.StatusNoContent, components.StatusNoRenderComponent(gnourl.Path) + return http.StatusOK, components.StatusNoRenderComponent(gnourl.Path) } h.Logger.Error("unable to render realm", "error", err, "path", gnourl.EncodeURL()) diff --git a/gno.land/pkg/gnoweb/handler_test.go b/gno.land/pkg/gnoweb/handler_test.go index e85434a6f41..8321ad24be2 100644 --- a/gno.land/pkg/gnoweb/handler_test.go +++ b/gno.land/pkg/gnoweb/handler_test.go @@ -147,7 +147,7 @@ func TestWebHandler_NoRender(t *testing.T) { rr := httptest.NewRecorder() handler.ServeHTTP(rr, req) - assert.Equal(t, http.StatusNoContent, rr.Code, "unexpected status code") - assert.Containsf(t, rr.Body.String(), "", "rendered body should contain: %q", "No Render") - assert.Containsf(t, rr.Body.String(), "", "rendered body should contain: %q", "This realm does not implement a Render() function.") + assert.Equal(t, http.StatusOK, rr.Code, "unexpected status code") + expectedBody := "This realm does not implement a Render() function." + assert.Contains(t, rr.Body.String(), expectedBody, "rendered body should contain: %q", expectedBody) } From 15d119fbf21817bd667b9107966a10502e09f605 Mon Sep 17 00:00:00 2001 From: Antoine Eddi <5222525+aeddi@users.noreply.github.com> Date: Wed, 29 Jan 2025 14:21:07 +0100 Subject: [PATCH 080/143] feat: optimize jitter factor calculation (#3629) --- tm2/pkg/p2p/switch.go | 90 ++++++++++++++++++++------------------ tm2/pkg/p2p/switch_test.go | 67 ++++++++++++++++++++++++++++ 2 files changed, 114 insertions(+), 43 deletions(-) diff --git a/tm2/pkg/p2p/switch.go b/tm2/pkg/p2p/switch.go index 0dd087026dd..7d9e768dd4b 100644 --- a/tm2/pkg/p2p/switch.go +++ b/tm2/pkg/p2p/switch.go @@ -1,11 +1,12 @@ package p2p import ( + "bytes" "context" "crypto/rand" + "encoding/binary" "fmt" "math" - "math/big" "sync" "time" @@ -356,7 +357,7 @@ func (sw *MultiplexSwitch) runRedialLoop(ctx context.Context) { type backoffItem struct { lastDialTime time.Time - attempts int + attempts uint } var ( @@ -482,65 +483,68 @@ func (sw *MultiplexSwitch) runRedialLoop(ctx context.Context) { } } -// calculateBackoff calculates a backoff time, -// based on the number of attempts and range limits +// calculateBackoff calculates the backoff interval by exponentiating the base interval +// by the number of attempts. The returned interval is capped at maxInterval and has a +// jitter factor applied to it (+/- 10% of interval, max 10 sec). func calculateBackoff( - attempts int, - minTimeout time.Duration, - maxTimeout time.Duration, + attempts uint, + baseInterval time.Duration, + maxInterval time.Duration, ) time.Duration { - var ( - minTime = time.Second * 1 - maxTime = time.Second * 60 - multiplier = float64(2) // exponential + const ( + defaultBaseInterval = time.Second * 1 + defaultMaxInterval = time.Second * 60 ) - // Check the min limit - if minTimeout > 0 { - minTime = minTimeout + // Sanitize base interval parameter. + if baseInterval <= 0 { + baseInterval = defaultBaseInterval } - // Check the max limit - if maxTimeout > 0 { - maxTime = maxTimeout + // Sanitize max interval parameter. + if maxInterval <= 0 { + maxInterval = defaultMaxInterval } - // Sanity check the range - if minTime >= maxTime { - return maxTime + // Calculate the interval by exponentiating the base interval by the number of attempts. + interval := baseInterval << attempts + + // Cap the interval to the maximum interval. + if interval > maxInterval { + interval = maxInterval } - // Calculate the backoff duration - var ( - base = float64(minTime) - calculated = base * math.Pow(multiplier, float64(attempts)) - ) + // Below is the code to add a jitter factor to the interval. + // Read random bytes into an 8 bytes buffer (size of an int64). + var randBytes [8]byte + if _, err := rand.Read(randBytes[:]); err != nil { + return interval + } - // Attempt to calculate the jitter factor - n, err := rand.Int(rand.Reader, big.NewInt(math.MaxInt64)) - if err == nil { - jitterFactor := float64(n.Int64()) / float64(math.MaxInt64) // range [0, 1] + // Convert the random bytes to an int64. + var randInt64 int64 + _ = binary.Read(bytes.NewReader(randBytes[:]), binary.NativeEndian, &randInt64) - calculated = jitterFactor*(calculated-base) + base - } + // Calculate the random jitter multiplier (float between -1 and 1). + jitterMultiplier := float64(randInt64) / float64(math.MaxInt64) - // Prevent overflow for int64 (duration) cast - if calculated > float64(math.MaxInt64) { - return maxTime - } + const ( + maxJitterDuration = 10 * time.Second + maxJitterPercentage = 10 // 10% + ) - duration := time.Duration(calculated) + // Calculate the maximum jitter based on interval percentage. + maxJitter := interval * maxJitterPercentage / 100 - // Clamp the duration within bounds - if duration < minTime { - return minTime + // Cap the maximum jitter to the maximum duration. + if maxJitter > maxJitterDuration { + maxJitter = maxJitterDuration } - if duration > maxTime { - return maxTime - } + // Calculate the jitter. + jitter := time.Duration(float64(maxJitter) * jitterMultiplier) - return duration + return interval + jitter } // DialPeers adds the peers to the dial queue for async dialing. diff --git a/tm2/pkg/p2p/switch_test.go b/tm2/pkg/p2p/switch_test.go index 19a5db2efa5..cf0a0c41bb5 100644 --- a/tm2/pkg/p2p/switch_test.go +++ b/tm2/pkg/p2p/switch_test.go @@ -823,3 +823,70 @@ func TestMultiplexSwitch_DialPeers(t *testing.T) { } }) } + +func TestCalculateBackoff(t *testing.T) { + t.Parallel() + + checkJitterRange := func(t *testing.T, expectedAbs, actual time.Duration) { + t.Helper() + require.LessOrEqual(t, actual, expectedAbs) + require.GreaterOrEqual(t, actual, expectedAbs*-1) + } + + // Test that the default jitter factor is 10% of the backoff duration. + t.Run("percentage jitter", func(t *testing.T) { + t.Parallel() + + for i := 0; i < 1000; i++ { + checkJitterRange(t, 100*time.Millisecond, calculateBackoff(0, time.Second, 10*time.Minute)-time.Second) + checkJitterRange(t, 200*time.Millisecond, calculateBackoff(1, time.Second, 10*time.Minute)-2*time.Second) + checkJitterRange(t, 400*time.Millisecond, calculateBackoff(2, time.Second, 10*time.Minute)-4*time.Second) + checkJitterRange(t, 800*time.Millisecond, calculateBackoff(3, time.Second, 10*time.Minute)-8*time.Second) + checkJitterRange(t, 1600*time.Millisecond, calculateBackoff(4, time.Second, 10*time.Minute)-16*time.Second) + } + }) + + // Test that the jitter factor is capped at 10 sec. + t.Run("capped jitter", func(t *testing.T) { + t.Parallel() + + for i := 0; i < 1000; i++ { + checkJitterRange(t, 10*time.Second, calculateBackoff(7, time.Second, 10*time.Minute)-128*time.Second) + checkJitterRange(t, 10*time.Second, calculateBackoff(10, time.Second, 20*time.Minute)-1024*time.Second) + checkJitterRange(t, 10*time.Second, calculateBackoff(20, time.Second, 300*time.Hour)-1048576*time.Second) + } + }) + + // Test that the backoff interval is based on the baseInterval. + t.Run("base interval", func(t *testing.T) { + t.Parallel() + + for i := 0; i < 1000; i++ { + checkJitterRange(t, 4800*time.Millisecond, calculateBackoff(4, 3*time.Second, 10*time.Minute)-48*time.Second) + checkJitterRange(t, 8*time.Second, calculateBackoff(3, 10*time.Second, 10*time.Minute)-80*time.Second) + checkJitterRange(t, 10*time.Second, calculateBackoff(5, 3*time.Hour, 100*time.Hour)-96*time.Hour) + } + }) + + // Test that the backoff interval is capped at maxInterval +/- jitter factor. + t.Run("max interval", func(t *testing.T) { + t.Parallel() + + for i := 0; i < 1000; i++ { + checkJitterRange(t, 100*time.Millisecond, calculateBackoff(10, 10*time.Hour, time.Second)-time.Second) + checkJitterRange(t, 1600*time.Millisecond, calculateBackoff(10, 10*time.Hour, 16*time.Second)-16*time.Second) + checkJitterRange(t, 10*time.Second, calculateBackoff(10, 10*time.Hour, 128*time.Second)-128*time.Second) + } + }) + + // Test parameters sanitization for base and max intervals. + t.Run("parameters sanitization", func(t *testing.T) { + t.Parallel() + + for i := 0; i < 1000; i++ { + checkJitterRange(t, 100*time.Millisecond, calculateBackoff(0, -10, -10)-time.Second) + checkJitterRange(t, 1600*time.Millisecond, calculateBackoff(4, -10, -10)-16*time.Second) + checkJitterRange(t, 10*time.Second, calculateBackoff(7, -10, 10*time.Minute)-128*time.Second) + } + }) +} From b392287f0d2c8262b5a020cd045b79a16ccb41fd Mon Sep 17 00:00:00 2001 From: Manfred Touron <94029+moul@users.noreply.github.com> Date: Wed, 29 Jan 2025 17:53:57 +0100 Subject: [PATCH 081/143] feat: gno mod graph (#3588) Basic initial version compatible with `go mod graph` in terms of output, while allowing the specification of folders through an optional argument. - [x] implement - [x] tests - [x] share examples Depends on #3587 --------- Signed-off-by: moul <94029+moul@users.noreply.github.com> --- gnovm/cmd/gno/mod.go | 49 ++++++++++++++++++++++++++++++++++++++- gnovm/cmd/gno/mod_test.go | 28 ++++++++++++++++++++++ 2 files changed, 76 insertions(+), 1 deletion(-) diff --git a/gnovm/cmd/gno/mod.go b/gnovm/cmd/gno/mod.go index f303908d8ee..e394684561f 100644 --- a/gnovm/cmd/gno/mod.go +++ b/gnovm/cmd/gno/mod.go @@ -34,7 +34,7 @@ func newModCmd(io commands.IO) *commands.Command { cmd.AddSubCommands( newModDownloadCmd(io), // edit - // graph + newModGraphCmd(io), newModInitCmd(), newModTidy(io), // vendor @@ -61,6 +61,21 @@ func newModDownloadCmd(io commands.IO) *commands.Command { ) } +func newModGraphCmd(io commands.IO) *commands.Command { + cfg := &modGraphCfg{} + return commands.NewCommand( + commands.Metadata{ + Name: "graph", + ShortUsage: "graph [path]", + ShortHelp: "print module requirement graph", + }, + cfg, + func(_ context.Context, args []string) error { + return execModGraph(cfg, args, io) + }, + ) +} + func newModInitCmd() *commands.Command { return commands.NewCommand( commands.Metadata{ @@ -144,6 +159,38 @@ func (c *modDownloadCfg) RegisterFlags(fs *flag.FlagSet) { ) } +type modGraphCfg struct{} + +func (c *modGraphCfg) RegisterFlags(fs *flag.FlagSet) { + // /out std + // /out remote + // /out _test processing + // ... +} + +func execModGraph(cfg *modGraphCfg, args []string, io commands.IO) error { + // default to current directory if no args provided + if len(args) == 0 { + args = []string{"."} + } + if len(args) > 1 { + return flag.ErrHelp + } + + stdout := io.Out() + + pkgs, err := gnomod.ListPkgs(args[0]) + if err != nil { + return err + } + for _, pkg := range pkgs { + for _, dep := range pkg.Imports { + fmt.Fprintf(stdout, "%s %s\n", pkg.Name, dep) + } + } + return nil +} + func execModDownload(cfg *modDownloadCfg, args []string, io commands.IO) error { if len(args) > 0 { return flag.ErrHelp diff --git a/gnovm/cmd/gno/mod_test.go b/gnovm/cmd/gno/mod_test.go index afce25597cd..e6fdce50a86 100644 --- a/gnovm/cmd/gno/mod_test.go +++ b/gnovm/cmd/gno/mod_test.go @@ -210,6 +210,34 @@ func TestModApp(t *testing.T) { # gno.land/p/demo/avl valid.gno +`, + }, + + // test `gno mod graph` + { + args: []string{"mod", "graph"}, + testDir: "../../tests/integ/minimalist_gnomod", + simulateExternalRepo: true, + stdoutShouldBe: ``, + }, + { + args: []string{"mod", "graph"}, + testDir: "../../tests/integ/valid1", + simulateExternalRepo: true, + stdoutShouldBe: ``, + }, + { + args: []string{"mod", "graph"}, + testDir: "../../tests/integ/valid2", + simulateExternalRepo: true, + stdoutShouldBe: `gno.land/p/integ/valid gno.land/p/demo/avl +`, + }, + { + args: []string{"mod", "graph"}, + testDir: "../../tests/integ/require_remote_module", + simulateExternalRepo: true, + stdoutShouldBe: `gno.land/tests/importavl gno.land/p/demo/avl `, }, } From 57da32437daa07a76e9478b1704832c0a211cfd2 Mon Sep 17 00:00:00 2001 From: Jeff Thompson Date: Thu, 30 Jan 2025 17:51:43 +0100 Subject: [PATCH 082/143] chore: Trigger CI tests on changes to the main go.mod (#3648) Resolves https://github.com/gnolang/gno/issues/3312 After a dependabot commit, the dependabot-tidy workflow [runs `make tidy`](https://github.com/gnolang/gno/blob/b392287f0d2c8262b5a020cd045b79a16ccb41fd/.github/workflows/dependabot-tidy.yml#L33). This changes `go.mod` in the subfolders of various tools, but these do not trigger the main CI test workflows. However, note that `make tidy` often also changes the main `go.mod` file. In this case, and other cases, it makes sense that a change in the main `go.mod` file should trigger the testing workflows since a change to dependency versions could effect test results. This PR updates workflow yml files for the global tests to trigger on a change the main `go.mod` file. (Thanks to feedback from @zivkovicmilos.) Signed-off-by: Jeff Thompson --- .github/workflows/gnoland.yml | 3 +++ .github/workflows/gnovm.yml | 3 +++ .github/workflows/tm2.yml | 3 +++ 3 files changed, 9 insertions(+) diff --git a/.github/workflows/gnoland.yml b/.github/workflows/gnoland.yml index b02e7b364e6..c4bc26a45fc 100644 --- a/.github/workflows/gnoland.yml +++ b/.github/workflows/gnoland.yml @@ -14,6 +14,9 @@ on: # Changes to examples/ can create failures in gno.land, eg. txtars, # see: https://github.com/gnolang/gno/pull/3590 - examples/** + # We trigger the testing workflow for changes to the main go.mod, + # since this can affect test results + - go.mod workflow_dispatch: jobs: diff --git a/.github/workflows/gnovm.yml b/.github/workflows/gnovm.yml index 7a015b74e09..08b0b66c4e8 100644 --- a/.github/workflows/gnovm.yml +++ b/.github/workflows/gnovm.yml @@ -8,6 +8,9 @@ on: paths: - gnovm/** - tm2/** # GnoVM has a dependency on TM2 types + # We trigger the testing workflow for changes to the main go.mod, + # since this can affect test results + - go.mod workflow_dispatch: jobs: diff --git a/.github/workflows/tm2.yml b/.github/workflows/tm2.yml index 757391eab8c..d2157eb8828 100644 --- a/.github/workflows/tm2.yml +++ b/.github/workflows/tm2.yml @@ -7,6 +7,9 @@ on: pull_request: paths: - tm2/** + # We trigger the testing workflow for changes to the main go.mod, + # since this can affect test results + - go.mod workflow_dispatch: jobs: From d3774cefc766b6e5a9b464e4974e484a876408e9 Mon Sep 17 00:00:00 2001 From: 0xtekgrinder <72015889+0xtekgrinder@users.noreply.github.com> Date: Fri, 31 Jan 2025 09:04:56 -0500 Subject: [PATCH 083/143] feat: ownable2step realm (#3594) Addresses: #3520 Add a ownable variant realm to have a transfer of ownership with two functions where the new owner needs to accept ownership to be sure no mistake can be made. --------- Co-authored-by: Leon Hudak <33522493+leohhhn@users.noreply.github.com> --- .../p/oxtekgrinder/ownable2step/errors.gno | 10 ++ .../p/oxtekgrinder/ownable2step/gno.mod | 1 + .../p/oxtekgrinder/ownable2step/ownable.gno | 98 +++++++++++ .../ownable2step/ownable_test.gno | 156 ++++++++++++++++++ 4 files changed, 265 insertions(+) create mode 100644 examples/gno.land/p/oxtekgrinder/ownable2step/errors.gno create mode 100644 examples/gno.land/p/oxtekgrinder/ownable2step/gno.mod create mode 100644 examples/gno.land/p/oxtekgrinder/ownable2step/ownable.gno create mode 100644 examples/gno.land/p/oxtekgrinder/ownable2step/ownable_test.gno diff --git a/examples/gno.land/p/oxtekgrinder/ownable2step/errors.gno b/examples/gno.land/p/oxtekgrinder/ownable2step/errors.gno new file mode 100644 index 00000000000..6d91c9eb24b --- /dev/null +++ b/examples/gno.land/p/oxtekgrinder/ownable2step/errors.gno @@ -0,0 +1,10 @@ +package ownable2step + +import "errors" + +var ( + ErrNoPendingOwner = errors.New("ownable2step: no pending owner") + ErrUnauthorized = errors.New("ownable2step: caller is not owner") + ErrPendingUnauthorized = errors.New("ownable2step: caller is not pending owner") + ErrInvalidAddress = errors.New("ownable2step: new owner address is invalid") +) diff --git a/examples/gno.land/p/oxtekgrinder/ownable2step/gno.mod b/examples/gno.land/p/oxtekgrinder/ownable2step/gno.mod new file mode 100644 index 00000000000..0132a03418c --- /dev/null +++ b/examples/gno.land/p/oxtekgrinder/ownable2step/gno.mod @@ -0,0 +1 @@ +module gno.land/p/oxtekgrinder/ownable2step diff --git a/examples/gno.land/p/oxtekgrinder/ownable2step/ownable.gno b/examples/gno.land/p/oxtekgrinder/ownable2step/ownable.gno new file mode 100644 index 00000000000..43afa1cd141 --- /dev/null +++ b/examples/gno.land/p/oxtekgrinder/ownable2step/ownable.gno @@ -0,0 +1,98 @@ +package ownable2step + +import ( + "std" +) + +const OwnershipTransferEvent = "OwnershipTransfer" + +// Ownable2Step is a two-step ownership transfer package +// It allows the current owner to set a new owner and the new owner will need to accept the ownership before it is transferred +type Ownable2Step struct { + owner std.Address + pendingOwner std.Address +} + +func New() *Ownable2Step { + return &Ownable2Step{ + owner: std.PrevRealm().Addr(), + pendingOwner: "", + } +} + +func NewWithAddress(addr std.Address) *Ownable2Step { + return &Ownable2Step{ + owner: addr, + pendingOwner: "", + } +} + +// TransferOwnership initiate the transfer of the ownership to a new address by setting the PendingOwner +func (o *Ownable2Step) TransferOwnership(newOwner std.Address) error { + if !o.CallerIsOwner() { + return ErrUnauthorized + } + if !newOwner.IsValid() { + return ErrInvalidAddress + } + + o.pendingOwner = newOwner + return nil +} + +// AcceptOwnership accepts the pending ownership transfer +func (o *Ownable2Step) AcceptOwnership() error { + if o.pendingOwner.String() == "" { + return ErrNoPendingOwner + } + if std.PrevRealm().Addr() != o.pendingOwner { + return ErrPendingUnauthorized + } + + o.owner = o.pendingOwner + o.pendingOwner = "" + + return nil +} + +// DropOwnership removes the owner, effectively disabling any owner-related actions +// Top-level usage: disables all only-owner actions/functions, +// Embedded usage: behaves like a burn functionality, removing the owner from the struct +func (o *Ownable2Step) DropOwnership() error { + if !o.CallerIsOwner() { + return ErrUnauthorized + } + + prevOwner := o.owner + o.owner = "" + + std.Emit( + OwnershipTransferEvent, + "from", prevOwner.String(), + "to", "", + ) + + return nil +} + +// Owner returns the owner address from Ownable +func (o *Ownable2Step) Owner() std.Address { + return o.owner +} + +// PendingOwner returns the pending owner address from Ownable2Step +func (o *Ownable2Step) PendingOwner() std.Address { + return o.pendingOwner +} + +// CallerIsOwner checks if the caller of the function is the Realm's owner +func (o *Ownable2Step) CallerIsOwner() bool { + return std.PrevRealm().Addr() == o.owner +} + +// AssertCallerIsOwner panics if the caller is not the owner +func (o *Ownable2Step) AssertCallerIsOwner() { + if std.PrevRealm().Addr() != o.owner { + panic(ErrUnauthorized) + } +} diff --git a/examples/gno.land/p/oxtekgrinder/ownable2step/ownable_test.gno b/examples/gno.land/p/oxtekgrinder/ownable2step/ownable_test.gno new file mode 100644 index 00000000000..4cca03b6ef5 --- /dev/null +++ b/examples/gno.land/p/oxtekgrinder/ownable2step/ownable_test.gno @@ -0,0 +1,156 @@ +package ownable2step + +import ( + "std" + "testing" + + "gno.land/p/demo/testutils" + "gno.land/p/demo/uassert" + "gno.land/p/demo/urequire" +) + +var ( + alice = testutils.TestAddress("alice") + bob = testutils.TestAddress("bob") +) + +func TestNew(t *testing.T) { + std.TestSetRealm(std.NewUserRealm(alice)) + std.TestSetOrigCaller(alice) + + o := New() + got := o.Owner() + pendingOwner := o.PendingOwner() + + uassert.Equal(t, got, alice) + uassert.Equal(t, pendingOwner.String(), "") +} + +func TestNewWithAddress(t *testing.T) { + o := NewWithAddress(alice) + + got := o.Owner() + pendingOwner := o.PendingOwner() + + uassert.Equal(t, got, alice) + uassert.Equal(t, pendingOwner.String(), "") +} + +func TestInitiateTransferOwnership(t *testing.T) { + std.TestSetRealm(std.NewUserRealm(alice)) + std.TestSetOrigCaller(alice) + + o := New() + + err := o.TransferOwnership(bob) + urequire.NoError(t, err) + + owner := o.Owner() + pendingOwner := o.PendingOwner() + + uassert.Equal(t, owner, alice) + uassert.Equal(t, pendingOwner, bob) +} + +func TestTransferOwnership(t *testing.T) { + std.TestSetRealm(std.NewUserRealm(alice)) + std.TestSetOrigCaller(alice) + + o := New() + + err := o.TransferOwnership(bob) + urequire.NoError(t, err) + + owner := o.Owner() + pendingOwner := o.PendingOwner() + + uassert.Equal(t, owner, alice) + uassert.Equal(t, pendingOwner, bob) + + std.TestSetRealm(std.NewUserRealm(bob)) + std.TestSetOrigCaller(bob) + + err = o.AcceptOwnership() + urequire.NoError(t, err) + + owner = o.Owner() + pendingOwner = o.PendingOwner() + + uassert.Equal(t, owner, bob) + uassert.Equal(t, pendingOwner.String(), "") +} + +func TestCallerIsOwner(t *testing.T) { + std.TestSetRealm(std.NewUserRealm(alice)) + std.TestSetOrigCaller(alice) + + o := New() + unauthorizedCaller := bob + + std.TestSetRealm(std.NewUserRealm(unauthorizedCaller)) + std.TestSetOrigCaller(unauthorizedCaller) + + uassert.False(t, o.CallerIsOwner()) +} + +func TestDropOwnership(t *testing.T) { + std.TestSetRealm(std.NewUserRealm(alice)) + + o := New() + + err := o.DropOwnership() + urequire.NoError(t, err, "DropOwnership failed") + + owner := o.Owner() + uassert.Empty(t, owner, "owner should be empty") +} + +// Errors + +func TestErrUnauthorized(t *testing.T) { + std.TestSetRealm(std.NewUserRealm(alice)) + std.TestSetOrigCaller(alice) + + o := New() + + std.TestSetRealm(std.NewUserRealm(bob)) + std.TestSetOrigCaller(bob) + + uassert.ErrorContains(t, o.TransferOwnership(alice), ErrUnauthorized.Error()) + uassert.ErrorContains(t, o.DropOwnership(), ErrUnauthorized.Error()) +} + +func TestErrInvalidAddress(t *testing.T) { + std.TestSetRealm(std.NewUserRealm(alice)) + + o := New() + + err := o.TransferOwnership("") + uassert.ErrorContains(t, err, ErrInvalidAddress.Error()) + + err = o.TransferOwnership("10000000001000000000100000000010000000001000000000") + uassert.ErrorContains(t, err, ErrInvalidAddress.Error()) +} + +func TestErrNoPendingOwner(t *testing.T) { + std.TestSetRealm(std.NewUserRealm(alice)) + + o := New() + + err := o.AcceptOwnership() + uassert.ErrorContains(t, err, ErrNoPendingOwner.Error()) +} + +func TestErrPendingUnauthorized(t *testing.T) { + std.TestSetRealm(std.NewUserRealm(alice)) + + o := New() + + err := o.TransferOwnership(bob) + urequire.NoError(t, err) + + std.TestSetRealm(std.NewUserRealm(alice)) + + err = o.AcceptOwnership() + uassert.ErrorContains(t, err, ErrPendingUnauthorized.Error()) +} From 5c8dcfd3c368b6451f6da09b19aa6a08e884ea80 Mon Sep 17 00:00:00 2001 From: Emmanuel T Odeke Date: Fri, 31 Jan 2025 22:05:09 +0200 Subject: [PATCH 084/143] fix(tm2/pkg/bft/node): make Node.startRPC not leak listeners on any error (#3640) Once Node.startRPC encounters an error, previously it was discarding all the created listeners and leaking them unclosed. This change fixes that using Go's named return values. Fixes #3639 --- tm2/pkg/bft/node/node.go | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/tm2/pkg/bft/node/node.go b/tm2/pkg/bft/node/node.go index c1afb2996fa..08f1b3c58f9 100644 --- a/tm2/pkg/bft/node/node.go +++ b/tm2/pkg/bft/node/node.go @@ -713,7 +713,17 @@ func (n *Node) configureRPC() { rpccore.SetConfig(*n.config.RPC) } -func (n *Node) startRPC() ([]net.Listener, error) { +func (n *Node) startRPC() (listeners []net.Listener, err error) { + defer func() { + if err != nil { + // Close all the created listeners on any error, instead of + // leaking them: https://github.com/gnolang/gno/issues/3639 + for _, ln := range listeners { + ln.Close() + } + } + }() + listenAddrs := splitAndTrimEmpty(n.config.RPC.ListenAddress, ",", " ") config := rpcserver.DefaultConfig() @@ -729,8 +739,8 @@ func (n *Node) startRPC() ([]net.Listener, error) { // we may expose the rpc over both a unix and tcp socket var rebuildAddresses bool - listeners := make([]net.Listener, len(listenAddrs)) - for i, listenAddr := range listenAddrs { + listeners = make([]net.Listener, 0, len(listenAddrs)) + for _, listenAddr := range listenAddrs { mux := http.NewServeMux() rpcLogger := n.Logger.With("module", "rpc-server") wmLogger := rpcLogger.With("protocol", "websocket") @@ -782,7 +792,7 @@ func (n *Node) startRPC() ([]net.Listener, error) { ) } - listeners[i] = listener + listeners = append(listeners, listener) } if rebuildAddresses { n.config.RPC.ListenAddress = joinListenerAddresses(listeners) From 7992a29f706f152dc2f3470399236e0a33859b1e Mon Sep 17 00:00:00 2001 From: Emmanuel T Odeke Date: Fri, 31 Jan 2025 22:24:29 +0200 Subject: [PATCH 085/143] feat(tm2/pkg/iavl): add FuzzIterateRange and modernize FuzzMutableTree (#3548) This change hooks MutableTree fuzzing to Go's native fuzzing that's more intelligent and coverage guided to mutate inputs instead of naive random program generation. While here also added FuzzIterateRange. Updates #3087 --- tm2/pkg/iavl/tree_fuzz_test.go | 229 ++++++++++++++++++++++++++++----- 1 file changed, 199 insertions(+), 30 deletions(-) diff --git a/tm2/pkg/iavl/tree_fuzz_test.go b/tm2/pkg/iavl/tree_fuzz_test.go index 08645414fbf..ba709cc9da2 100644 --- a/tm2/pkg/iavl/tree_fuzz_test.go +++ b/tm2/pkg/iavl/tree_fuzz_test.go @@ -1,8 +1,14 @@ package iavl import ( + "encoding/json" "fmt" + "io" + "io/fs" "math/rand" + "os" + "path/filepath" + "strings" "testing" "github.com/gnolang/gno/tm2/pkg/db/memdb" @@ -14,28 +20,36 @@ import ( // A program is a list of instructions. type program struct { - instructions []instruction + Instructions []instruction `json:"instructions"` } func (p *program) Execute(tree *MutableTree) (err error) { var errLine int defer func() { - if r := recover(); r != nil { - var str string - - for i, instr := range p.instructions { - prefix := " " - if i == errLine { - prefix = ">> " - } - str += prefix + instr.String() + "\n" + r := recover() + if r == nil { + return + } + + // These are simply input errors and shouldn't be reported as actual logical issues. + if containsAny(fmt.Sprint(r), "Unrecognized op:", "Attempt to store nil value at key") { + return + } + + var str string + + for i, instr := range p.Instructions { + prefix := " " + if i == errLine { + prefix = ">> " } - err = fmt.Errorf("Program panicked with: %s\n%s", r, str) + str += prefix + instr.String() + "\n" } + err = fmt.Errorf("Program panicked with: %s\n%s", r, str) }() - for i, instr := range p.instructions { + for i, instr := range p.Instructions { errLine = i instr.Execute(tree) } @@ -43,39 +57,39 @@ func (p *program) Execute(tree *MutableTree) (err error) { } func (p *program) addInstruction(i instruction) { - p.instructions = append(p.instructions, i) + p.Instructions = append(p.Instructions, i) } func (p *program) size() int { - return len(p.instructions) + return len(p.Instructions) } type instruction struct { - op string - k, v []byte - version int64 + Op string + K, V []byte + Version int64 } func (i instruction) Execute(tree *MutableTree) { - switch i.op { + switch i.Op { case "SET": - tree.Set(i.k, i.v) + tree.Set(i.K, i.V) case "REMOVE": - tree.Remove(i.k) + tree.Remove(i.K) case "SAVE": tree.SaveVersion() case "DELETE": - tree.DeleteVersion(i.version) + tree.DeleteVersion(i.Version) default: - panic("Unrecognized op: " + i.op) + panic("Unrecognized op: " + i.Op) } } func (i instruction) String() string { - if i.version > 0 { - return fmt.Sprintf("%-8s %-8s %-8s %-8d", i.op, i.k, i.v, i.version) + if i.Version > 0 { + return fmt.Sprintf("%-8s %-8s %-8s %-8d", i.Op, i.K, i.V, i.Version) } - return fmt.Sprintf("%-8s %-8s %-8s", i.op, i.k, i.v) + return fmt.Sprintf("%-8s %-8s %-8s", i.Op, i.K, i.V) } // Generate a random program of the given size. @@ -88,15 +102,15 @@ func genRandomProgram(size int) *program { switch rand.Int() % 7 { case 0, 1, 2: - p.addInstruction(instruction{op: "SET", k: k, v: v}) + p.addInstruction(instruction{Op: "SET", K: k, V: v}) case 3, 4: - p.addInstruction(instruction{op: "REMOVE", k: k}) + p.addInstruction(instruction{Op: "REMOVE", K: k}) case 5: - p.addInstruction(instruction{op: "SAVE", version: int64(nextVersion)}) + p.addInstruction(instruction{Op: "SAVE", Version: int64(nextVersion)}) nextVersion++ case 6: if rv := rand.Int() % nextVersion; rv < nextVersion && rv > 0 { - p.addInstruction(instruction{op: "DELETE", version: int64(rv)}) + p.addInstruction(instruction{Op: "DELETE", Version: int64(rv)}) } } } @@ -107,19 +121,174 @@ func genRandomProgram(size int) *program { func TestMutableTreeFuzz(t *testing.T) { t.Parallel() + runThenGenerateMutableTreeFuzzSeeds(t, false) +} + +var pathForMutableTreeProgramSeeds = filepath.Join("testdata", "corpora", "mutable_tree_programs") + +func runThenGenerateMutableTreeFuzzSeeds(tb testing.TB, writeSeedsToFileSystem bool) { + tb.Helper() + + if testing.Short() { + tb.Skip("Running in -short mode") + } + maxIterations := testFuzzIterations progsPerIteration := 100000 iterations := 0 + if writeSeedsToFileSystem { + if err := os.MkdirAll(pathForMutableTreeProgramSeeds, 0o755); err != nil { + tb.Fatal(err) + } + } + for size := 5; iterations < maxIterations; size++ { for i := 0; i < progsPerIteration/size; i++ { tree := NewMutableTree(memdb.NewMemDB(), 0) program := genRandomProgram(size) err := program.Execute(tree) if err != nil { - t.Fatalf("Error after %d iterations (size %d): %s\n%s", iterations, size, err.Error(), tree.String()) + tb.Fatalf("Error after %d iterations (size %d): %s\n%s", iterations, size, err.Error(), tree.String()) } iterations++ + + if !writeSeedsToFileSystem { + continue + } + + // Otherwise write them to the testdata/corpra directory. + programJSON, err := json.Marshal(program) + if err != nil { + tb.Fatal(err) + } + path := filepath.Join(pathForMutableTreeProgramSeeds, fmt.Sprintf("%d", i+1)) + if err := os.WriteFile(path, programJSON, 0o755); err != nil { + tb.Fatal(err) + } + } + } +} + +type treeRange struct { + Start []byte + End []byte + Forward bool +} + +var basicRecords = []struct { + key, value string +}{ + {"abc", "123"}, + {"low", "high"}, + {"fan", "456"}, + {"foo", "a"}, + {"foobaz", "c"}, + {"good", "bye"}, + {"foobang", "d"}, + {"foobar", "b"}, + {"food", "e"}, + {"foml", "f"}, +} + +// Allows hooking into Go's fuzzers and then for continuous fuzzing +// enriched with coverage guided mutations, instead of naive mutations. +func FuzzIterateRange(f *testing.F) { + if testing.Short() { + f.Skip("Skipping in -short mode") + } + + // 1. Add the seeds. + seeds := []*treeRange{ + {[]byte("foo"), []byte("goo"), true}, + {[]byte("aaa"), []byte("abb"), true}, + {nil, []byte("flap"), true}, + {[]byte("foob"), nil, true}, + {[]byte("very"), nil, true}, + {[]byte("very"), nil, false}, + {[]byte("fooba"), []byte("food"), true}, + {[]byte("fooba"), []byte("food"), false}, + {[]byte("g"), nil, false}, + } + for _, seed := range seeds { + blob, err := json.Marshal(seed) + if err != nil { + f.Fatal(err) + } + f.Add(blob) + } + + db := memdb.NewMemDB() + tree := NewMutableTree(db, 0) + for _, br := range basicRecords { + tree.Set([]byte(br.key), []byte(br.value)) + } + + var trav traverser + + // 2. Run the fuzzer. + f.Fuzz(func(t *testing.T, rangeJSON []byte) { + tr := new(treeRange) + if err := json.Unmarshal(rangeJSON, tr); err != nil { + return + } + + tree.IterateRange(tr.Start, tr.End, tr.Forward, trav.view) + }) +} + +func containsAny(s string, anyOf ...string) bool { + for _, q := range anyOf { + if strings.Contains(s, q) { + return true + } + } + return false +} + +func FuzzMutableTreeInstructions(f *testing.F) { + if testing.Short() { + f.Skip("Skipping in -short mode") + } + + // 0. Generate then add the seeds. + runThenGenerateMutableTreeFuzzSeeds(f, true) + + // 1. Add the seeds. + dir := os.DirFS("testdata") + err := fs.WalkDir(dir, ".", func(path string, de fs.DirEntry, err error) error { + if de.IsDir() { + return err + } + + ff, err := dir.Open(path) + if err != nil { + return err + } + defer ff.Close() + + blob, err := io.ReadAll(ff) + if err != nil { + return err } + f.Add(blob) + return nil + }) + if err != nil { + f.Fatal(err) } + + // 2. Run the fuzzer. + f.Fuzz(func(t *testing.T, programJSON []byte) { + program := new(program) + if err := json.Unmarshal(programJSON, program); err != nil { + return + } + + tree := NewMutableTree(memdb.NewMemDB(), 0) + err := program.Execute(tree) + if err != nil { + t.Fatal(err) + } + }) } From c24f69fdae397cb9c7e833bf9a2d74c95edd4a78 Mon Sep 17 00:00:00 2001 From: Emmanuel T Odeke Date: Fri, 31 Jan 2025 22:37:43 +0200 Subject: [PATCH 086/143] test(tm2/pkg/cmap): add benchmarks to show true impact of contention (#3540) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Go standard library's sync.Map is touted as great for cases with high load and is commonly known knowledge but the benchmark that I am committing shows otherwise that for this library's usage, it is so much more expensive hence this benchmark will avoid someone committing sync.Map without seeing the true implications. ```shell $ benchstat map_w_mutex.txt stdlib_sync_map.txt name old time/op new time/op delta CMapConcurrentInsertsDeletesHas-8 1.72s ±11% 1.92s ± 3% +11.66% (p=0.000 n=10+9) CMapHas-8 109ns ± 9% 118ns ± 3% +8.26% (p=0.002 n=10+8) name old alloc/op new alloc/op delta CMapConcurrentInsertsDeletesHas-8 1.18GB ± 2% 3.21GB ± 3% +172.09% (p=0.000 n=10+10) CMapHas-8 16.0B ± 0% 16.0B ± 0% ~ (all equal) name old allocs/op new allocs/op delta CMapConcurrentInsertsDeletesHas-8 824k ± 0% 4433k ± 0% +437.89% (p=0.000 n=10+10) CMapHas-8 2.00 ± 0% 1.60 ±38% ~ (p=0.065 n=9+10) ``` Updates #3505 --- tm2/pkg/cmap/cmap_test.go | 57 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/tm2/pkg/cmap/cmap_test.go b/tm2/pkg/cmap/cmap_test.go index d9051ea18d6..ebeb601633d 100644 --- a/tm2/pkg/cmap/cmap_test.go +++ b/tm2/pkg/cmap/cmap_test.go @@ -2,7 +2,9 @@ package cmap import ( "fmt" + "runtime" "strings" + "sync" "testing" "github.com/stretchr/testify/assert" @@ -56,6 +58,61 @@ func TestContains(t *testing.T) { assert.Nil(t, cmap.Get("key2")) } +var sink any = nil + +func BenchmarkCMapConcurrentInsertsDeletesHas(b *testing.B) { + cm := NewCMap() + keys := make([]string, 100000) + for i := range keys { + keys[i] = fmt.Sprintf("key%d", i) + } + b.ResetTimer() + + for i := 0; i < b.N; i++ { + var wg sync.WaitGroup + semaCh := make(chan bool) + nCPU := runtime.NumCPU() + for j := 0; j < nCPU; j++ { + wg.Add(1) + go func() { + defer wg.Done() + + // Make sure that all the goroutines run at the + // exact same time for true concurrent tests. + <-semaCh + + for i, key := range keys { + if (j+i)%2 == 0 { + cm.Has(key) + } else { + cm.Set(key, j) + } + _ = cm.Size() + if (i+1)%3 == 0 { + cm.Delete(key) + } + + if (i+1)%327 == 0 { + cm.Clear() + } + _ = cm.Size() + _ = cm.Keys() + } + _ = cm.Values() + }() + } + close(semaCh) + wg.Wait() + + sink = semaCh + } + + if sink == nil { + b.Fatal("Benchmark did not run!") + } + sink = nil +} + func BenchmarkCMapHas(b *testing.B) { m := NewCMap() for i := 0; i < 1000; i++ { From 053bfb4a47aba3f876e3cbcb3e95d0704ba96868 Mon Sep 17 00:00:00 2001 From: Norman Date: Sun, 2 Feb 2025 03:00:50 +0100 Subject: [PATCH 087/143] fix: adapt after merge Signed-off-by: Norman --- gnovm/cmd/benchops/run.go | 4 +-- gnovm/cmd/gno/list.go | 2 +- gnovm/cmd/gno/mod.go | 8 ++--- gnovm/cmd/gno/test.go | 2 +- gnovm/cmd/gno/testdata/lint/bad_import.txtar | 2 +- gnovm/cmd/gno/tool_lint.go | 30 ++++++++++++------ gnovm/cmd/gno/tool_lint_test.go | 27 +++++++++-------- gnovm/pkg/gnolang/debugger_test.go | 15 +++++---- gnovm/pkg/gnolang/files_test.go | 5 ++- gnovm/pkg/gnomod/parse.go | 2 +- gnovm/pkg/packages/analyze_packages.go | 2 ++ gnovm/pkg/packages/load.go | 4 +++ gnovm/pkg/packages/load_test.go | 18 +++++------ gnovm/pkg/packages/pkglist.go | 20 ++++++++++++ gnovm/pkg/packages/types.go | 32 +++++++++++++++++--- gnovm/pkg/test/imports.go | 6 ++-- gnovm/pkg/test/test.go | 6 ++-- 17 files changed, 122 insertions(+), 63 deletions(-) diff --git a/gnovm/cmd/benchops/run.go b/gnovm/cmd/benchops/run.go index e01fbc1cb6b..b09062ba3c5 100644 --- a/gnovm/cmd/benchops/run.go +++ b/gnovm/cmd/benchops/run.go @@ -103,7 +103,7 @@ func addPackage(gstore gno.Store, dir string, pkgPath string) *gno.PackageValue }) defer m.Release() - memPkg := gno.MustReadMemPackage(dir, pkgPath) + memPkg := gno.MustReadMemPackage(dir, pkgPath, nil) // pare the file, create pn, pv and save the values in m.store _, pv := m.RunMemPackage(memPkg, true) @@ -122,7 +122,7 @@ func loadStdlibs(bstore BenchStore) { return nil, nil } - memPkg := gno.MustReadMemPackage(stdlibPath, pkgPath) + memPkg := gno.MustReadMemPackage(stdlibPath, pkgPath, nil) if memPkg.IsEmpty() { // no gno files are present, skip this package return nil, nil diff --git a/gnovm/cmd/gno/list.go b/gnovm/cmd/gno/list.go index 5012b4b2e41..dcf8591d9f7 100644 --- a/gnovm/cmd/gno/list.go +++ b/gnovm/cmd/gno/list.go @@ -59,7 +59,7 @@ func execList(cfg *listCfg, args []string, io commands.IO) error { pkgs, err := packages.Load(conf, args...) if err != nil { - fmt.Println(err) + io.ErrPrintln(err) os.Exit(1) } diff --git a/gnovm/cmd/gno/mod.go b/gnovm/cmd/gno/mod.go index 8f920366516..b611ae190de 100644 --- a/gnovm/cmd/gno/mod.go +++ b/gnovm/cmd/gno/mod.go @@ -172,7 +172,7 @@ func (c *modGraphCfg) RegisterFlags(fs *flag.FlagSet) { func execModGraph(cfg *modGraphCfg, args []string, io commands.IO) error { // default to current directory if no args provided if len(args) == 0 { - args = []string{"."} + args = []string{"./..."} } if len(args) > 1 { return flag.ErrHelp @@ -180,13 +180,13 @@ func execModGraph(cfg *modGraphCfg, args []string, io commands.IO) error { stdout := io.Out() - pkgs, err := gnomod.ListPkgs(args[0]) + pkgs, err := packages.Load(&packages.LoadConfig{}, args...) if err != nil { return err } for _, pkg := range pkgs { - for _, dep := range pkg.Imports { - fmt.Fprintf(stdout, "%s %s\n", pkg.Name, dep) + for _, dep := range pkg.ImportsSpecs.Merge(packages.FileKindPackageSource) { + fmt.Fprintf(stdout, "%s %s\n", pkg.ImportPath, dep.PkgPath) } } return nil diff --git a/gnovm/cmd/gno/test.go b/gnovm/cmd/gno/test.go index f45c6a5bab1..cbbd0108c97 100644 --- a/gnovm/cmd/gno/test.go +++ b/gnovm/cmd/gno/test.go @@ -183,7 +183,7 @@ func execTest(cfg *testCfg, args []string, io commands.IO) error { if cfg.verbose { stdout = io.Out() } - opts := test.NewTestOptions(cfg.rootDir, pkgsMap, io.In(), stdout, io.Err()) + opts := test.NewTestOptions(cfg.rootDir, pkgs, io.In(), stdout, io.Err()) opts.RunFlag = cfg.run opts.Sync = cfg.updateGoldenTests opts.Verbose = cfg.verbose diff --git a/gnovm/cmd/gno/testdata/lint/bad_import.txtar b/gnovm/cmd/gno/testdata/lint/bad_import.txtar index 891a5b7d858..836374999bd 100644 --- a/gnovm/cmd/gno/testdata/lint/bad_import.txtar +++ b/gnovm/cmd/gno/testdata/lint/bad_import.txtar @@ -19,4 +19,4 @@ module gno.land/p/test -- stdout.golden -- -- stderr.golden -- -bad_file.gno:3:8: package python is not in std (/Users/norman/Code/gno/gnovm/stdlibs/python) +bad_file.gno:3:8: package python is not in std (/Users/norman/Code/gno/gnovm/stdlibs/python) (code=5) diff --git a/gnovm/cmd/gno/tool_lint.go b/gnovm/cmd/gno/tool_lint.go index e44055a9ba4..69ab14d20e2 100644 --- a/gnovm/cmd/gno/tool_lint.go +++ b/gnovm/cmd/gno/tool_lint.go @@ -13,6 +13,7 @@ import ( "regexp" "strings" + "github.com/davecgh/go-spew/spew" "github.com/gnolang/gno/gnovm" "github.com/gnolang/gno/gnovm/pkg/gnoenv" gno "github.com/gnolang/gno/gnovm/pkg/gnolang" @@ -62,6 +63,7 @@ const ( lintGnoError lintParserError lintTypeCheckError + lintLoadPkgs // TODO: add new linter codes here. ) @@ -110,7 +112,7 @@ func execLint(cfg *lintCfg, args []string, io commands.IO) error { hasError := false bs, ts := test.Store( - rootDir, pkgsMap, false, + rootDir, pkgs, false, nopReader{}, goio.Discard, goio.Discard, ) @@ -130,7 +132,12 @@ func execLint(cfg *lintCfg, args []string, io commands.IO) error { } for _, err := range pkg.Errors { - io.ErrPrintln(tryRelativize(err.Error())) + io.ErrPrintln(lintIssue{ + Code: lintLoadPkgs, + Confidence: 1, + Location: err.Pos, + Msg: err.Msg, + }) hasError = true } if hasError { @@ -162,16 +169,15 @@ func execLint(cfg *lintCfg, args []string, io commands.IO) error { packages.Inject(pkgsMap, deps) // read mempkg - memPkgPath := pkg.ImportPath - if memPkgPath == "" || memPkgPath == "command-line-arguments" { - memPkgPath = pkg.Dir - } - memPkg, err := gno.ReadMemPackage(pkg.Dir, memPkgPath, loadCfg.Fset) + memPkg, err := pkg.MemPkg() if err != nil { + spew.Fdump(os.Stderr, loadDepsErr) io.ErrPrintln(issueFromError(pkg.Dir, err).String()) hasError = true continue } + // override memPkg path for pretty errors + memPkg.Path = pkg.Dir // Perform imports using the parent store. if err := test.LoadImports(ts, memPkg, loadCfg.Fset); err != nil { @@ -189,7 +195,7 @@ func execLint(cfg *lintCfg, args []string, io commands.IO) error { // Run type checking if !pkg.Draft { - foundErr, err := lintTypeCheck(io, pkg.Dir, memPkg, gs) + foundErr, err := lintTypeCheck(io, memPkg, pkgs) if err != nil { io.ErrPrintln(err) hasError = true @@ -199,6 +205,9 @@ func execLint(cfg *lintCfg, args []string, io commands.IO) error { } else if verbose { io.ErrPrintfln("%s: module is draft, skipping type check", logName) } + if hasError { + return + } tm := test.Machine(gs, goio.Discard, memPkg.Path) defer tm.Release() @@ -211,6 +220,7 @@ func execLint(cfg *lintCfg, args []string, io commands.IO) error { tm.RunFiles(testFiles.Files...) }) + if hasRuntimeErr { hasError = true } @@ -223,8 +233,8 @@ func execLint(cfg *lintCfg, args []string, io commands.IO) error { return nil } -func lintTypeCheck(io commands.IO, pkgDir string, memPkg *gnovm.MemPackage, testStore gno.Store) (errorsFound bool, err error) { - tcErr := gno.TypeCheckMemPackageTest(pkgDir, memPkg, testStore) +func lintTypeCheck(io commands.IO, memPkg *gnovm.MemPackage, getter gno.MemPackageGetter) (errorsFound bool, err error) { + tcErr := gno.TypeCheckMemPackageTest(memPkg, getter) if tcErr == nil { return false, nil } diff --git a/gnovm/cmd/gno/tool_lint_test.go b/gnovm/cmd/gno/tool_lint_test.go index 85b625fa367..59788cc1717 100644 --- a/gnovm/cmd/gno/tool_lint_test.go +++ b/gnovm/cmd/gno/tool_lint_test.go @@ -12,7 +12,7 @@ func TestLintApp(t *testing.T) { errShouldBe: "flag: help requested", }, { args: []string{"tool", "lint", "../../tests/integ/run_main/"}, - stderrShouldContain: "./../../tests/integ/run_main: gno.mod file not found in current or any parent directory (code=1)", + stderrShouldContain: "../../tests/integ/run_main: gno.mod file not found in current or any parent directory (code=1)", errShouldBe: "exit code: 1", }, { args: []string{"tool", "lint", "../../tests/integ/undefined_variable_test/undefined_variables_test.gno"}, @@ -20,12 +20,12 @@ func TestLintApp(t *testing.T) { errShouldBe: "exit code: 1", }, { args: []string{"tool", "lint", "../../tests/integ/package_not_declared/main.gno"}, - stderrShouldContain: "main.gno:4:2: name fmt not declared (code=2)", + stderrShouldContain: "../../tests/integ/package_not_declared/main.gno:4:2: undefined: fmt (code=4)\n", errShouldBe: "exit code: 1", }, { - args: []string{"tool", "lint", "../../tests/integ/several-lint-errors/main.gno"}, - stderrShouldContain: "../../tests/integ/several-lint-errors/main.gno:5:5: expected ';', found example (code=2)\n../../tests/integ/several-lint-errors/main.gno:6", - errShouldBe: "exit code: 1", + args: []string{"tool", "lint", "../../tests/integ/several-lint-errors/main.gno"}, + stderrShouldBe: "../../tests/integ/several-lint-errors/main.gno:5:5: expected ';', found example (code=3)\n../../tests/integ/several-lint-errors/main.gno:6:2: expected '}', found 'EOF' (code=3)\n", + errShouldBe: "exit code: 1", }, { args: []string{"tool", "lint", "../../tests/integ/several-files-multiple-errors/main.gno"}, stderrShouldContain: func() string { @@ -42,16 +42,17 @@ func TestLintApp(t *testing.T) { args: []string{"tool", "lint", "../../tests/integ/minimalist_gnomod/"}, // TODO: raise an error because there is a gno.mod, but no .gno files }, { - args: []string{"tool", "lint", "../../tests/integ/invalid_module_name/"}, - // TODO: raise an error because gno.mod is invalid + args: []string{"tool", "lint", "../../tests/integ/invalid_module_name/"}, + stderrShouldBe: "../../tests/integ/invalid_module_name/gno.mod:1: usage: module module/path (code=5)\n", + errShouldBe: "exit code: 1", }, { - args: []string{"tool", "lint", "../../tests/integ/invalid_gno_file/"}, - stderrShouldContain: "../../tests/integ/invalid_gno_file/invalid.gno:1:1: expected 'package', found packag (code=2)", - errShouldBe: "exit code: 1", + args: []string{"tool", "lint", "../../tests/integ/invalid_gno_file/"}, + stderrShouldBe: "../../tests/integ/invalid_gno_file/invalid.gno:1:1: expected 'package', found packag (code=5)\n", + errShouldBe: "exit code: 1", }, { - args: []string{"tool", "lint", "../../tests/integ/typecheck_missing_return/"}, - stderrShouldContain: "../../tests/integ/typecheck_missing_return/main.gno:5:1: missing return (code=4)", - errShouldBe: "exit code: 1", + args: []string{"tool", "lint", "../../tests/integ/typecheck_missing_return/"}, + stderrShouldBe: "../../tests/integ/typecheck_missing_return/main.gno:5:1: missing return (code=4)\n", + errShouldBe: "exit code: 1", }, // TODO: 'gno mod' is valid? diff --git a/gnovm/pkg/gnolang/debugger_test.go b/gnovm/pkg/gnolang/debugger_test.go index 9c1a5c193ad..8f1e922a970 100644 --- a/gnovm/pkg/gnolang/debugger_test.go +++ b/gnovm/pkg/gnolang/debugger_test.go @@ -27,7 +27,7 @@ type writeNopCloser struct{ io.Writer } func (writeNopCloser) Close() error { return nil } // TODO (Marc): move evalTest to gnovm/tests package and remove code duplicates -func evalTest(debugAddr, in, file string, pkgs packages.PackagesMap) (out, err string) { +func evalTest(debugAddr, in, file string, getter gnolang.MemPackageGetter) (out, err string) { bout := bytes.NewBufferString("") berr := bytes.NewBufferString("") stdin := bytes.NewBufferString(in) @@ -42,7 +42,7 @@ func evalTest(debugAddr, in, file string, pkgs packages.PackagesMap) (out, err s err = strings.TrimSpace(strings.ReplaceAll(err, "../../tests/files/", "files/")) }() - _, testStore := test.Store(gnoenv.RootDir(), pkgs, false, stdin, stdout, stderr) + _, testStore := test.Store(gnoenv.RootDir(), getter, false, stdin, stdout, stderr) f := gnolang.MustReadFile(file) @@ -71,12 +71,12 @@ func evalTest(debugAddr, in, file string, pkgs packages.PackagesMap) (out, err s return } -func runDebugTest(t *testing.T, targetPath string, tests []dtest, pkgs packages.PackagesMap) { +func runDebugTest(t *testing.T, targetPath string, tests []dtest, getter gnolang.MemPackageGetter) { t.Helper() for _, test := range tests { t.Run("", func(t *testing.T) { - out, err := evalTest("", test.in, targetPath, pkgs) + out, err := evalTest("", test.in, targetPath, getter) t.Log("in:", test.in, "out:", out, "err:", err) if !strings.Contains(out, test.out) { t.Errorf("unexpected output\nwant\"%s\"\n got \"%s\"", test.out, out) @@ -92,7 +92,6 @@ func TestDebug(t *testing.T) { pkgs, err := packages.Load(&packages.LoadConfig{}, filepath.FromSlash("../../../examples/...")) require.NoError(t, err) - pkgsMap := packages.NewPackagesMap(pkgs...) runDebugTest(t, debugTarget, []dtest{ {in: "\n", out: "Welcome to the Gnovm debugger. Type 'help' for list of commands."}, @@ -150,18 +149,18 @@ func TestDebug(t *testing.T) { {in: "b 27\nc\np b\n", out: `("!zero" string)`}, {in: "b 22\nc\np t.A[3]\n", out: "Command failed: &{(\"slice index out of bounds: 3 (len=3)\" string) }"}, {in: "b 43\nc\nc\nc\np i\ndetach\n", out: "(1 int)"}, - }, pkgsMap) + }, pkgs) runDebugTest(t, "../../tests/files/a1.gno", []dtest{ {in: "l\n", out: "unknown source file"}, {in: "b 5\n", out: "unknown source file"}, - }, pkgsMap) + }, pkgs) runDebugTest(t, "../../tests/integ/debugger/sample2.gno", []dtest{ {in: "s\np tests\n", out: "(package(tests gno.land/p/demo/tests) package{})"}, {in: "s\np tests.World\n", out: `("world" string)`}, {in: "s\np tests.xxx\n", out: "Command failed: invalid selector: xxx"}, - }, pkgsMap) + }, pkgs) } const debugAddress = "localhost:17358" diff --git a/gnovm/pkg/gnolang/files_test.go b/gnovm/pkg/gnolang/files_test.go index a3bad970727..f31e261c79c 100644 --- a/gnovm/pkg/gnolang/files_test.go +++ b/gnovm/pkg/gnolang/files_test.go @@ -41,7 +41,6 @@ func TestFiles(t *testing.T) { pkgs, err := packages.Load(&packages.LoadConfig{}, filepath.Join(rootDir, "examples", "....")) require.NoError(t, err) - pkgsMap := packages.NewPackagesMap(pkgs...) newOpts := func() *test.TestOptions { o := &test.TestOptions{ @@ -51,7 +50,7 @@ func TestFiles(t *testing.T) { Sync: *withSync, } o.BaseStore, o.TestStore = test.Store( - rootDir, pkgsMap, true, + rootDir, pkgs, true, nopReader{}, o.WriterForStore(), io.Discard, ) return o @@ -126,7 +125,7 @@ func TestStdlibs(t *testing.T) { capture = new(bytes.Buffer) out = capture } - opts = test.NewTestOptions(rootDir, make(map[string]*packages.Package), nopReader{}, out, out) + opts = test.NewTestOptions(rootDir, nil, nopReader{}, out, out) opts.Verbose = true return } diff --git a/gnovm/pkg/gnomod/parse.go b/gnovm/pkg/gnomod/parse.go index a9cd65aedd1..0b24244e468 100644 --- a/gnovm/pkg/gnomod/parse.go +++ b/gnovm/pkg/gnomod/parse.go @@ -50,7 +50,7 @@ func ParseAt(dir string) (*File, error) { func ParseGnoMod(fname string) (*File, error) { file, err := os.Stat(fname) if err != nil { - return nil, fmt.Errorf("%s: stat: %w", fname, err) + return nil, fmt.Errorf("%s: could not read gno.mod file", fname) } if file.IsDir() { return nil, fmt.Errorf("%s: gno.mod is a directory", fname) diff --git a/gnovm/pkg/packages/analyze_packages.go b/gnovm/pkg/packages/analyze_packages.go index d93de6b80be..d4073b7e9e7 100644 --- a/gnovm/pkg/packages/analyze_packages.go +++ b/gnovm/pkg/packages/analyze_packages.go @@ -9,6 +9,7 @@ import ( "path/filepath" "strings" + "github.com/davecgh/go-spew/spew" "github.com/gnolang/gno/gnovm" "github.com/gnolang/gno/gnovm/pkg/gnoenv" "github.com/gnolang/gno/gnovm/pkg/gnolang" @@ -187,6 +188,7 @@ func readPkgFiles(pkg *Package, files []string, fset *token.FileSet) *Package { modFpath := filepath.Join(pkg.Root, "gno.mod") mod, err := gnomod.ParseGnoMod(modFpath) if err != nil { + spew.Dump(err) pkg.Errors = append(pkg.Errors, FromErr(err, fset, modFpath, false)...) return pkg } diff --git a/gnovm/pkg/packages/load.go b/gnovm/pkg/packages/load.go index 6cdf4bea756..4db2609e0aa 100644 --- a/gnovm/pkg/packages/load.go +++ b/gnovm/pkg/packages/load.go @@ -7,6 +7,7 @@ import ( "os" "path/filepath" "slices" + "strings" "github.com/gnolang/gno/gnovm" "github.com/gnolang/gno/gnovm/pkg/gnoenv" @@ -215,6 +216,9 @@ func (p *Package) MemPkg() (*gnovm.MemPackage, error) { files := []*gnovm.MemFile{} for _, cat := range p.Files { for _, f := range cat { + if !strings.HasSuffix(f, ".gno") { + continue + } body, err := os.ReadFile(filepath.Join(p.Dir, f)) if err != nil { return nil, err diff --git a/gnovm/pkg/packages/load_test.go b/gnovm/pkg/packages/load_test.go index e5ac659d380..27b19dea03a 100644 --- a/gnovm/pkg/packages/load_test.go +++ b/gnovm/pkg/packages/load_test.go @@ -124,30 +124,30 @@ func TestSortPkgs(t *testing.T) { }, { desc: "no_dependencies", in: []*Package{ - {ImportPath: "pkg1", Dir: "/path/to/pkg1", Imports: ImportsMap{}}, - {ImportPath: "pkg2", Dir: "/path/to/pkg2", Imports: ImportsMap{}}, - {ImportPath: "pkg3", Dir: "/path/to/pkg3", Imports: ImportsMap{}}, + {ImportPath: "pkg1", Dir: "/path/to/pkg1", Imports: map[FileKind][]string{}}, + {ImportPath: "pkg2", Dir: "/path/to/pkg2", Imports: map[FileKind][]string{}}, + {ImportPath: "pkg3", Dir: "/path/to/pkg3", Imports: map[FileKind][]string{}}, }, expected: []string{"pkg1", "pkg2", "pkg3"}, }, { desc: "circular_dependencies", in: []*Package{ - {ImportPath: "pkg1", Dir: "/path/to/pkg1", Imports: ImportsMap{FileKindPackageSource: {{PkgPath: "pkg2"}}}}, - {ImportPath: "pkg2", Dir: "/path/to/pkg2", Imports: ImportsMap{FileKindPackageSource: {{PkgPath: "pkg1"}}}}, + {ImportPath: "pkg1", Dir: "/path/to/pkg1", Imports: map[FileKind][]string{FileKindPackageSource: {"pkg2"}}}, + {ImportPath: "pkg2", Dir: "/path/to/pkg2", Imports: map[FileKind][]string{FileKindPackageSource: {"pkg1"}}}, }, shouldErr: true, }, { desc: "missing_dependencies", in: []*Package{ - {ImportPath: "pkg1", Dir: "/path/to/pkg1", Imports: ImportsMap{FileKindPackageSource: {{PkgPath: "pkg2"}}}}, + {ImportPath: "pkg1", Dir: "/path/to/pkg1", Imports: map[FileKind][]string{FileKindPackageSource: {"pkg2"}}}, }, shouldErr: true, }, { desc: "valid_dependencies", in: []*Package{ - {ImportPath: "pkg1", Dir: "/path/to/pkg1", Imports: ImportsMap{FileKindPackageSource: {{PkgPath: "pkg2"}}}}, - {ImportPath: "pkg2", Dir: "/path/to/pkg2", Imports: ImportsMap{FileKindPackageSource: {{PkgPath: "pkg3"}}}}, - {ImportPath: "pkg3", Dir: "/path/to/pkg3", Imports: ImportsMap{}}, + {ImportPath: "pkg1", Dir: "/path/to/pkg1", Imports: map[FileKind][]string{FileKindPackageSource: {"pkg2"}}}, + {ImportPath: "pkg2", Dir: "/path/to/pkg2", Imports: map[FileKind][]string{FileKindPackageSource: {"pkg3"}}}, + {ImportPath: "pkg3", Dir: "/path/to/pkg3", Imports: map[FileKind][]string{}}, }, expected: []string{"pkg3", "pkg2", "pkg1"}, }, diff --git a/gnovm/pkg/packages/pkglist.go b/gnovm/pkg/packages/pkglist.go index 2f7ab075b42..a19d11e7888 100644 --- a/gnovm/pkg/packages/pkglist.go +++ b/gnovm/pkg/packages/pkglist.go @@ -2,6 +2,11 @@ package packages import ( "fmt" + "os" + + "github.com/davecgh/go-spew/spew" + "github.com/gnolang/gno/gnovm" + "github.com/gnolang/gno/gnovm/pkg/gnolang" ) type ( @@ -9,6 +14,8 @@ type ( SortedPkgList []*Package ) +var _ gnolang.MemPackageGetter = (*PkgList)(nil) + func (pl PkgList) Get(pkgPath string) *Package { for _, p := range pl { if p.ImportPath == pkgPath { @@ -18,6 +25,19 @@ func (pl PkgList) Get(pkgPath string) *Package { return nil } +func (pl PkgList) GetMemPackage(pkgPath string) *gnovm.MemPackage { + pkg := pl.Get(pkgPath) + if pkg == nil { + return nil + } + memPkg, err := pkg.MemPkg() + if err != nil { + spew.Fdump(os.Stderr, "get err", err) + panic(err) + } + return memPkg +} + // sortPkgs sorts the given packages by their dependencies. func (pl PkgList) Sort() (SortedPkgList, error) { visited := make(map[string]bool) diff --git a/gnovm/pkg/packages/types.go b/gnovm/pkg/packages/types.go index f1cce1dabb5..b9b08bc01de 100644 --- a/gnovm/pkg/packages/types.go +++ b/gnovm/pkg/packages/types.go @@ -1,13 +1,14 @@ package packages import ( + "fmt" "go/scanner" "go/token" "path/filepath" "sort" "strings" - "github.com/davecgh/go-spew/spew" + "golang.org/x/mod/modfile" ) // ported from https://cs.opensource.google/go/go/+/refs/tags/go1.23.2:src/cmd/go/internal/load/pkg.go @@ -21,8 +22,8 @@ type Package struct { Errors []*Error `json:",omitempty"` // error loading this package (not dependencies) Draft bool Files FilesMap - Imports ImportsMap `json:",omitempty"` // import paths used by this package - Deps []string `json:",omitempty"` // all (recursively) imported dependencies + Imports map[FileKind][]string `json:",omitempty"` // import paths used by this package + Deps []string `json:",omitempty"` // all (recursively) imported dependencies ImportsSpecs ImportsMap `json:"-"` } @@ -45,7 +46,6 @@ func (err Error) Error() string { } func FromErr(err error, fset *token.FileSet, root string, prependRoot bool) []*Error { - spew.Dump(err) switch err := err.(type) { case scanner.ErrorList: res := make([]*Error, 0, len(err)) @@ -57,6 +57,30 @@ func FromErr(err error, fset *token.FileSet, root string, prependRoot bool) []*E res = append(res, &Error{Msg: e.Msg, Pos: pos}) } return res + case modfile.ErrorList: + res := make([]*Error, 0, len(err)) + for _, e := range err { + var pos string + if e.Pos.LineRune > 1 { + // Don't print LineRune if it's 1 (beginning of line). + // It's always 1 except in scanner errors, which are rare. + pos = fmt.Sprintf("%s:%d:%d", e.Filename, e.Pos.Line, e.Pos.LineRune) + } else if e.Pos.Line > 0 { + pos = fmt.Sprintf("%s:%d", e.Filename, e.Pos.Line) + } else if e.Filename != "" { + pos = e.Filename + } + + var directive string + if e.ModPath != "" { + directive = fmt.Sprintf("%s %s: ", e.Verb, e.ModPath) + } else if e.Verb != "" { + directive = fmt.Sprintf("%s: ", e.Verb) + } + + res = append(res, &Error{Msg: directive + e.Err.Error(), Pos: pos}) + } + return res default: return []*Error{{Pos: root, Msg: err.Error()}} } diff --git a/gnovm/pkg/test/imports.go b/gnovm/pkg/test/imports.go index e7959f7e8e5..142d7e5bfb0 100644 --- a/gnovm/pkg/test/imports.go +++ b/gnovm/pkg/test/imports.go @@ -14,6 +14,7 @@ import ( "time" "github.com/gnolang/gno/gnovm" + "github.com/gnolang/gno/gnovm/pkg/gnolang" gno "github.com/gnolang/gno/gnovm/pkg/gnolang" "github.com/gnolang/gno/gnovm/pkg/packages" teststdlibs "github.com/gnolang/gno/gnovm/tests/stdlibs" @@ -27,7 +28,7 @@ import ( // NOTE: this isn't safe, should only be used for testing. func Store( rootDir string, - pkgs map[string]*packages.Package, + getter gnolang.MemPackageGetter, withExtern bool, stdin io.Reader, stdout, stderr io.Writer, @@ -137,8 +138,7 @@ func Store( } // if known package - if pkg, ok := pkgs[pkgPath]; ok { - memPkg := gno.MustReadMemPackage(pkg.Dir, pkgPath, nil) + if memPkg := getter.GetMemPackage(pkgPath); memPkg != nil { if memPkg.IsEmpty() { panic(fmt.Sprintf("found an empty package %q", pkgPath)) } diff --git a/gnovm/pkg/test/test.go b/gnovm/pkg/test/test.go index 824cf8f2cf9..f4bf1dd2790 100644 --- a/gnovm/pkg/test/test.go +++ b/gnovm/pkg/test/test.go @@ -16,8 +16,8 @@ import ( "time" "github.com/gnolang/gno/gnovm" + "github.com/gnolang/gno/gnovm/pkg/gnolang" gno "github.com/gnolang/gno/gnovm/pkg/gnolang" - "github.com/gnolang/gno/gnovm/pkg/packages" "github.com/gnolang/gno/gnovm/stdlibs" teststd "github.com/gnolang/gno/gnovm/tests/stdlibs/std" "github.com/gnolang/gno/tm2/pkg/crypto" @@ -134,14 +134,14 @@ func (opts *TestOptions) WriterForStore() io.Writer { } // NewTestOptions sets up TestOptions, filling out all "required" parameters. -func NewTestOptions(rootDir string, pkgs map[string]*packages.Package, stdin io.Reader, stdout, stderr io.Writer) *TestOptions { +func NewTestOptions(rootDir string, getter gnolang.MemPackageGetter, stdin io.Reader, stdout, stderr io.Writer) *TestOptions { opts := &TestOptions{ RootDir: rootDir, Output: stdout, Error: stderr, } opts.BaseStore, opts.TestStore = Store( - rootDir, pkgs, false, + rootDir, getter, false, stdin, opts.WriterForStore(), stderr, ) return opts From 33f90ca0f884b81247edc943eb3e9af7b7d15328 Mon Sep 17 00:00:00 2001 From: Norman Date: Sun, 2 Feb 2025 03:14:11 +0100 Subject: [PATCH 088/143] chore: remove merge artifact Signed-off-by: Norman --- gnovm/stdlibs/strings/printtrie.gno | 29 ----------------------------- 1 file changed, 29 deletions(-) delete mode 100644 gnovm/stdlibs/strings/printtrie.gno diff --git a/gnovm/stdlibs/strings/printtrie.gno b/gnovm/stdlibs/strings/printtrie.gno deleted file mode 100644 index 2a16c012eeb..00000000000 --- a/gnovm/stdlibs/strings/printtrie.gno +++ /dev/null @@ -1,29 +0,0 @@ -package strings - -func (r *Replacer) PrintTrie() string { - r.buildOnce() - gen := r.r.(*genericReplacer) - return gen.printNode(&gen.root, 0) -} - -func (r *genericReplacer) printNode(t *trieNode, depth int) (s string) { - if t.priority > 0 { - s += "+" - } else { - s += "-" - } - s += "\n" - - if t.prefix != "" { - s += Repeat(".", depth) + t.prefix - s += r.printNode(t.next, depth+len(t.prefix)) - } else if t.table != nil { - for b, m := range r.mapping { - if int(m) != r.tableSize && t.table[m] != nil { - s += Repeat(".", depth) + string([]byte{byte(b)}) - s += r.printNode(t.table[m], depth+1) - } - } - } - return -} From 627eab289076cae5834037ecaf4ea7e921c73a2d Mon Sep 17 00:00:00 2001 From: Leon Hudak <33522493+leohhhn@users.noreply.github.com> Date: Sun, 2 Feb 2025 13:56:43 +0100 Subject: [PATCH 089/143] feat(examples/test): quality of life improvements (#3661) ## Description This PR does two things: 1. Replaces the word "Fail" from test function names with `NotSucceed` - allows for easier ctrl+f search of the output locally and in the CI when something fails during `examples/make test` 2. Disables stress tests for `p/demo/diff` & `p/demo/btree`, cutting the examples/ test time by ~80% (baseline M2 mbp): ``` // master > time make test make test 140.04s user 3.09s system 217% cpu 1:05.95 total // PR > time make test make test 27.74s user 1.42s system 157% cpu 18.529 total ``` --------- Co-authored-by: Manfred Touron <94029+moul@users.noreply.github.com> --- examples/gno.land/p/demo/btree/btree_test.gno | 171 +++++++++--------- examples/gno.land/p/demo/diff/diff_test.gno | 13 +- examples/gno.land/p/demo/json/node_test.gno | 16 +- .../gno.land/p/demo/simpledao/dao_test.gno | 2 +- 4 files changed, 103 insertions(+), 99 deletions(-) diff --git a/examples/gno.land/p/demo/btree/btree_test.gno b/examples/gno.land/p/demo/btree/btree_test.gno index 871e8c25e1d..959fa7a4254 100644 --- a/examples/gno.land/p/demo/btree/btree_test.gno +++ b/examples/gno.land/p/demo/btree/btree_test.gno @@ -523,91 +523,14 @@ func TestBTree(t *testing.T) { } } -func TestStress(t *testing.T) { - // Loop through creating B-Trees with a range of degrees from 3 to 12, stepping by 3. - // Insert 1000 records into each tree, then search for each record. - // Delete half of the records, skipping every other one, then search for each record. - - for degree := 3; degree <= 12; degree += 3 { - t.Logf("Testing B-Tree of degree %d\n", degree) - tree := New(WithDegree(degree)) - - // Insert 1000 records - t.Logf("Inserting 1000 records\n") - for i := 0; i < 1000; i++ { - content := Content{Key: i, Value: fmt.Sprintf("Value_%d", i)} - tree.Insert(content) - } - - // Search for all records - for i := 0; i < 1000; i++ { - content := Content{Key: i, Value: fmt.Sprintf("Value_%d", i)} - val := tree.Get(content) - if val == nil { - t.Errorf("Expected key %v, but didn't find it", content.Key) - } - } - - // Delete half of the records - for i := 0; i < 1000; i += 2 { - content := Content{Key: i, Value: fmt.Sprintf("Value_%d", i)} - tree.Delete(content) - } - - // Search for all records - for i := 0; i < 1000; i++ { - content := Content{Key: i, Value: fmt.Sprintf("Value_%d", i)} - val := tree.Get(content) - if i%2 == 0 { - if val != nil { - t.Errorf("Didn't expect key %v, but found key:value %v:%v", content.Key, val.(Content).Key, val.(Content).Value) - } - } else { - if val == nil { - t.Errorf("Expected key %v, but didn't find it", content.Key) - } - } - } - } - - // Now create a very large tree, with 100000 records - // Then delete roughly one third of them, using a very basic random number generation scheme - // (implement it right here) to determine which records to delete. - // Print a few lines using Logf to let the user know what's happening. - - t.Logf("Testing B-Tree of degree 10 with 100000 records\n") - tree := New(WithDegree(10)) - - // Insert 100000 records - t.Logf("Inserting 100000 records\n") - for i := 0; i < 100000; i++ { - content := Content{Key: i, Value: fmt.Sprintf("Value_%d", i)} - tree.Insert(content) - } - - // Implement a very basic random number generator - seed := 0 - random := func() int { - seed = (seed*1103515245 + 12345) & 0x7fffffff - return seed - } - - // Delete one third of the records - t.Logf("Deleting one third of the records\n") - for i := 0; i < 35000; i++ { - content := Content{Key: random() % 100000, Value: fmt.Sprintf("Value_%d", i)} - tree.Delete(content) - } -} - -// Write a test that populates a large B-Tree with 10000 records. +// Write a test that populates a large B-Tree with 1000 records. // It should then `Clone` the tree, make some changes to both the original and the clone, // And then clone the clone, and make some changes to all three trees, and then check that the changes are isolated // to the tree they were made in. - func TestBTreeCloneIsolation(t *testing.T) { - t.Logf("Creating B-Tree of degree 10 with 10000 records\n") - tree := genericSeeding(New(WithDegree(10)), 10000) + t.Logf("Creating B-Tree of degree 10 with 1000 records\n") + size := 1000 + tree := genericSeeding(New(WithDegree(10)), size) // Clone the tree t.Logf("Cloning the tree\n") @@ -615,7 +538,7 @@ func TestBTreeCloneIsolation(t *testing.T) { // Make some changes to the original and the clone t.Logf("Making changes to the original and the clone\n") - for i := 0; i < 10000; i += 2 { + for i := 0; i < size; i += 2 { content := Content{Key: i, Value: fmt.Sprintf("Value_%d", i)} tree.Delete(content) content = Content{Key: i + 1, Value: fmt.Sprintf("Value_%d", i+1)} @@ -628,7 +551,7 @@ func TestBTreeCloneIsolation(t *testing.T) { // Make some changes to all three trees t.Logf("Making changes to all three trees\n") - for i := 0; i < 10000; i += 3 { + for i := 0; i < size; i += 3 { content := Content{Key: i, Value: fmt.Sprintf("Value_%d", i)} tree.Delete(content) content = Content{Key: i, Value: fmt.Sprintf("Value_%d", i+1)} @@ -639,7 +562,7 @@ func TestBTreeCloneIsolation(t *testing.T) { // Check that the changes are isolated to the tree they were made in t.Logf("Checking that the changes are isolated to the tree they were made in\n") - for i := 0; i < 10000; i++ { + for i := 0; i < size; i++ { content := Content{Key: i, Value: fmt.Sprintf("Value_%d", i)} val := tree.Get(content) @@ -676,3 +599,83 @@ func TestBTreeCloneIsolation(t *testing.T) { } } } + +// -------------------- +// Stress tests. Disabled for testing performance + +//func TestStress(t *testing.T) { +// // Loop through creating B-Trees with a range of degrees from 3 to 12, stepping by 3. +// // Insert 1000 records into each tree, then search for each record. +// // Delete half of the records, skipping every other one, then search for each record. +// +// for degree := 3; degree <= 12; degree += 3 { +// t.Logf("Testing B-Tree of degree %d\n", degree) +// tree := New(WithDegree(degree)) +// +// // Insert 1000 records +// t.Logf("Inserting 1000 records\n") +// for i := 0; i < 1000; i++ { +// content := Content{Key: i, Value: fmt.Sprintf("Value_%d", i)} +// tree.Insert(content) +// } +// +// // Search for all records +// for i := 0; i < 1000; i++ { +// content := Content{Key: i, Value: fmt.Sprintf("Value_%d", i)} +// val := tree.Get(content) +// if val == nil { +// t.Errorf("Expected key %v, but didn't find it", content.Key) +// } +// } +// +// // Delete half of the records +// for i := 0; i < 1000; i += 2 { +// content := Content{Key: i, Value: fmt.Sprintf("Value_%d", i)} +// tree.Delete(content) +// } +// +// // Search for all records +// for i := 0; i < 1000; i++ { +// content := Content{Key: i, Value: fmt.Sprintf("Value_%d", i)} +// val := tree.Get(content) +// if i%2 == 0 { +// if val != nil { +// t.Errorf("Didn't expect key %v, but found key:value %v:%v", content.Key, val.(Content).Key, val.(Content).Value) +// } +// } else { +// if val == nil { +// t.Errorf("Expected key %v, but didn't find it", content.Key) +// } +// } +// } +// } +// +// // Now create a very large tree, with 100000 records +// // Then delete roughly one third of them, using a very basic random number generation scheme +// // (implement it right here) to determine which records to delete. +// // Print a few lines using Logf to let the user know what's happening. +// +// t.Logf("Testing B-Tree of degree 10 with 100000 records\n") +// tree := New(WithDegree(10)) +// +// // Insert 100000 records +// t.Logf("Inserting 100000 records\n") +// for i := 0; i < 100000; i++ { +// content := Content{Key: i, Value: fmt.Sprintf("Value_%d", i)} +// tree.Insert(content) +// } +// +// // Implement a very basic random number generator +// seed := 0 +// random := func() int { +// seed = (seed*1103515245 + 12345) & 0x7fffffff +// return seed +// } +// +// // Delete one third of the records +// t.Logf("Deleting one third of the records\n") +// for i := 0; i < 35000; i++ { +// content := Content{Key: random() % 100000, Value: fmt.Sprintf("Value_%d", i)} +// tree.Delete(content) +// } +//} diff --git a/examples/gno.land/p/demo/diff/diff_test.gno b/examples/gno.land/p/demo/diff/diff_test.gno index bbf4fcdf3e0..3993c91664a 100644 --- a/examples/gno.land/p/demo/diff/diff_test.gno +++ b/examples/gno.land/p/demo/diff/diff_test.gno @@ -162,12 +162,13 @@ func TestMyersDiff(t *testing.T) { new: strings.Repeat("b", 1000), expected: "[-" + strings.Repeat("a", 1000) + "][+" + strings.Repeat("b", 1000) + "]", }, - { - name: "Very long strings", - old: strings.Repeat("a", 10000) + "b" + strings.Repeat("a", 10000), - new: strings.Repeat("a", 10000) + "c" + strings.Repeat("a", 10000), - expected: strings.Repeat("a", 10000) + "[-b][+c]" + strings.Repeat("a", 10000), - }, + //{ // disabled for testing performance + // XXX: consider adding a flag to run such tests, not like `-short`, or switching to a `-bench`, maybe. + // name: "Very long strings", + // old: strings.Repeat("a", 10000) + "b" + strings.Repeat("a", 10000), + // new: strings.Repeat("a", 10000) + "c" + strings.Repeat("a", 10000), + // expected: strings.Repeat("a", 10000) + "[-b][+c]" + strings.Repeat("a", 10000), + //}, } for _, tc := range tests { diff --git a/examples/gno.land/p/demo/json/node_test.gno b/examples/gno.land/p/demo/json/node_test.gno index dbc82369f68..c364187ac86 100644 --- a/examples/gno.land/p/demo/json/node_test.gno +++ b/examples/gno.land/p/demo/json/node_test.gno @@ -285,7 +285,7 @@ func TestNode_GetBool(t *testing.T) { } } -func TestNode_GetBool_Fail(t *testing.T) { +func TestNode_GetBool_NotSucceed(t *testing.T) { tests := []simpleNode{ {"nil node", (*Node)(nil)}, {"literally null node", NullNode("")}, @@ -357,7 +357,7 @@ func TestNode_GetNull(t *testing.T) { } } -func TestNode_GetNull_Fail(t *testing.T) { +func TestNode_GetNull_NotSucceed(t *testing.T) { tests := []simpleNode{ {"nil node", (*Node)(nil)}, {"number node is null", NumberNode("", 42)}, @@ -435,7 +435,7 @@ func TestNode_GetNumeric_With_Unmarshal(t *testing.T) { } } -func TestNode_GetNumeric_Fail(t *testing.T) { +func TestNode_GetNumeric_NotSucceed(t *testing.T) { tests := []simpleNode{ {"nil node", (*Node)(nil)}, {"null node", NullNode("")}, @@ -467,7 +467,7 @@ func TestNode_GetString(t *testing.T) { } } -func TestNode_GetString_Fail(t *testing.T) { +func TestNode_GetString_NotSucceed(t *testing.T) { tests := []simpleNode{ {"nil node", (*Node)(nil)}, {"null node", NullNode("")}, @@ -577,7 +577,7 @@ func TestNode_GetArray(t *testing.T) { } } -func TestNode_GetArray_Fail(t *testing.T) { +func TestNode_GetArray_NotSucceed(t *testing.T) { tests := []simpleNode{ {"nil node", (*Node)(nil)}, {"null node", NullNode("")}, @@ -736,7 +736,7 @@ func TestNode_Index(t *testing.T) { } } -func TestNode_Index_Fail(t *testing.T) { +func TestNode_Index_NotSucceed(t *testing.T) { tests := []struct { name string node *Node @@ -854,7 +854,7 @@ func TestNode_GetKey(t *testing.T) { } } -func TestNode_GetKey_Fail(t *testing.T) { +func TestNode_GetKey_NotSucceed(t *testing.T) { tests := []simpleNode{ {"nil node", (*Node)(nil)}, {"null node", NullNode("")}, @@ -998,7 +998,7 @@ func TestNode_GetObject(t *testing.T) { } } -func TestNode_GetObject_Fail(t *testing.T) { +func TestNode_GetObject_NotSucceed(t *testing.T) { tests := []simpleNode{ {"nil node", (*Node)(nil)}, {"get object from null node", NullNode("")}, diff --git a/examples/gno.land/p/demo/simpledao/dao_test.gno b/examples/gno.land/p/demo/simpledao/dao_test.gno index 46251e24dad..275455d1479 100644 --- a/examples/gno.land/p/demo/simpledao/dao_test.gno +++ b/examples/gno.land/p/demo/simpledao/dao_test.gno @@ -752,7 +752,7 @@ func TestSimpleDAO_ExecuteProposal(t *testing.T) { dao.ExecutionSuccessful, }, { - "execution failed", + "execution not succeeded", dao.ExecutionFailed, }, } From 01abd50bb5ec0348e8c02a94e66c43eba079def9 Mon Sep 17 00:00:00 2001 From: Leon Hudak <33522493+leohhhn@users.noreply.github.com> Date: Sun, 2 Feb 2025 14:26:00 +0100 Subject: [PATCH 090/143] chore(examples): modify pausable (#3628) ## Description Prevously, the `pausable` object embedded the `ownable` object directly, and with the intended usage of both packages being the following, `ownable` functions were being duplicated: ```go var ( Ownable = ownable.NewWithAddress(std.Address("xyz")) Pausable = pausable.NewFromOwnable(Ownable) ) ``` This PR names the `ownable` inside `pausable` as a private field and exposes a getter in case someone needs it for some reason. It also removes the `New()` function, which doesn't make too much sense right now as the pausable needs to be paired with `ownable` - this might change later but for now this should be the way. --------- Signed-off-by: moul <94029+moul@users.noreply.github.com> Co-authored-by: moul <94029+moul@users.noreply.github.com> --- examples/gno.land/p/demo/ownable/ownable.gno | 18 +++++-- .../gno.land/p/demo/ownable/ownable_test.gno | 48 +++++++++++++++++++ .../gno.land/p/demo/pausable/pausable.gno | 27 +++++------ .../p/demo/pausable/pausable_test.gno | 42 +++++++--------- 4 files changed, 91 insertions(+), 44 deletions(-) diff --git a/examples/gno.land/p/demo/ownable/ownable.gno b/examples/gno.land/p/demo/ownable/ownable.gno index f565e27c0f2..a8cb5ea95a7 100644 --- a/examples/gno.land/p/demo/ownable/ownable.gno +++ b/examples/gno.land/p/demo/ownable/ownable.gno @@ -65,18 +65,28 @@ func (o *Ownable) DropOwnership() error { } // Owner returns the owner address from Ownable -func (o Ownable) Owner() std.Address { +func (o *Ownable) Owner() std.Address { + if o == nil { + return std.Address("") + } return o.owner } // CallerIsOwner checks if the caller of the function is the Realm's owner -func (o Ownable) CallerIsOwner() bool { +func (o *Ownable) CallerIsOwner() bool { + if o == nil { + return false + } return std.PrevRealm().Addr() == o.owner } // AssertCallerIsOwner panics if the caller is not the owner -func (o Ownable) AssertCallerIsOwner() { - if std.PrevRealm().Addr() != o.owner { +func (o *Ownable) AssertCallerIsOwner() { + if o == nil { + panic(ErrUnauthorized) + } + caller := std.PrevRealm().Addr() + if caller != o.owner { panic(ErrUnauthorized) } } diff --git a/examples/gno.land/p/demo/ownable/ownable_test.gno b/examples/gno.land/p/demo/ownable/ownable_test.gno index f58af9642c6..d8b7f9a8e3a 100644 --- a/examples/gno.land/p/demo/ownable/ownable_test.gno +++ b/examples/gno.land/p/demo/ownable/ownable_test.gno @@ -93,3 +93,51 @@ func TestErrInvalidAddress(t *testing.T) { err = o.TransferOwnership("10000000001000000000100000000010000000001000000000") uassert.ErrorContains(t, err, ErrInvalidAddress.Error()) } + +func TestAssertCallerIsOwner(t *testing.T) { + std.TestSetRealm(std.NewUserRealm(alice)) + std.TestSetOrigCaller(alice) + + o := New() + + // Should not panic when caller is owner + o.AssertCallerIsOwner() + + // Should panic when caller is not owner + std.TestSetRealm(std.NewUserRealm(bob)) + std.TestSetOrigCaller(bob) + + defer func() { + r := recover() + if r == nil { + t.Error("expected panic but got none") + } + if r != ErrUnauthorized { + t.Errorf("expected ErrUnauthorized but got %v", r) + } + }() + o.AssertCallerIsOwner() +} + +func TestNilReceiver(t *testing.T) { + var o *Ownable + + owner := o.Owner() + if owner != std.Address("") { + t.Errorf("expected empty address but got %v", owner) + } + + isOwner := o.CallerIsOwner() + uassert.False(t, isOwner) + + defer func() { + r := recover() + if r == nil { + t.Error("expected panic but got none") + } + if r != ErrUnauthorized { + t.Errorf("expected ErrUnauthorized but got %v", r) + } + }() + o.AssertCallerIsOwner() +} diff --git a/examples/gno.land/p/demo/pausable/pausable.gno b/examples/gno.land/p/demo/pausable/pausable.gno index e6a85771fa6..fa3962cab41 100644 --- a/examples/gno.land/p/demo/pausable/pausable.gno +++ b/examples/gno.land/p/demo/pausable/pausable.gno @@ -7,23 +7,15 @@ import ( ) type Pausable struct { - *ownable.Ownable + o *ownable.Ownable paused bool } -// New returns a new Pausable struct with non-paused state as default -func New() *Pausable { - return &Pausable{ - Ownable: ownable.New(), - paused: false, - } -} - // NewFromOwnable is the same as New, but with a pre-existing top-level ownable func NewFromOwnable(ownable *ownable.Ownable) *Pausable { return &Pausable{ - Ownable: ownable, - paused: false, + o: ownable, + paused: false, } } @@ -34,24 +26,29 @@ func (p Pausable) IsPaused() bool { // Pause sets the state of Pausable to true, meaning all pausable functions are paused func (p *Pausable) Pause() error { - if !p.CallerIsOwner() { + if !p.o.CallerIsOwner() { return ownable.ErrUnauthorized } p.paused = true - std.Emit("Paused", "account", p.Owner().String()) + std.Emit("Paused", "account", p.o.Owner().String()) return nil } // Unpause sets the state of Pausable to false, meaning all pausable functions are resumed func (p *Pausable) Unpause() error { - if !p.CallerIsOwner() { + if !p.o.CallerIsOwner() { return ownable.ErrUnauthorized } p.paused = false - std.Emit("Unpaused", "account", p.Owner().String()) + std.Emit("Unpaused", "account", p.o.Owner().String()) return nil } + +// Ownable returns the underlying ownable +func (p *Pausable) Ownable() *ownable.Ownable { + return p.o +} diff --git a/examples/gno.land/p/demo/pausable/pausable_test.gno b/examples/gno.land/p/demo/pausable/pausable_test.gno index c9557245bdf..47028cd85c8 100644 --- a/examples/gno.land/p/demo/pausable/pausable_test.gno +++ b/examples/gno.land/p/demo/pausable/pausable_test.gno @@ -5,57 +5,49 @@ import ( "testing" "gno.land/p/demo/ownable" + "gno.land/p/demo/uassert" "gno.land/p/demo/urequire" ) var ( - firstCaller = std.Address("g1l9aypkr8xfvs82zeux486ddzec88ty69lue9de") - secondCaller = std.Address("g127jydsh6cms3lrtdenydxsckh23a8d6emqcvfa") + firstCaller = std.Address("g1l9aypkr8xfvs82zeux486ddzec88ty69lue9de") + o = ownable.NewWithAddress(firstCaller) ) -func TestNew(t *testing.T) { - std.TestSetOrigCaller(firstCaller) - - result := New() - - urequire.False(t, result.paused, "Expected result to be unpaused") - urequire.Equal(t, firstCaller.String(), result.Owner().String()) -} - func TestNewFromOwnable(t *testing.T) { std.TestSetOrigCaller(firstCaller) - o := ownable.New() - std.TestSetOrigCaller(secondCaller) result := NewFromOwnable(o) - - urequire.Equal(t, firstCaller.String(), result.Owner().String()) + urequire.Equal(t, firstCaller.String(), result.Ownable().Owner().String()) } func TestSetUnpaused(t *testing.T) { std.TestSetOrigCaller(firstCaller) + result := NewFromOwnable(o) - result := New() result.Unpause() - - urequire.False(t, result.IsPaused(), "Expected result to be unpaused") + uassert.False(t, result.IsPaused(), "Expected result to be unpaused") } func TestSetPaused(t *testing.T) { std.TestSetOrigCaller(firstCaller) + result := NewFromOwnable(o) - result := New() result.Pause() - - urequire.True(t, result.IsPaused(), "Expected result to be paused") + uassert.True(t, result.IsPaused(), "Expected result to be paused") } func TestIsPaused(t *testing.T) { - std.TestSetOrigCaller(firstCaller) - - result := New() + result := NewFromOwnable(o) urequire.False(t, result.IsPaused(), "Expected result to be unpaused") + std.TestSetOrigCaller(firstCaller) result.Pause() - urequire.True(t, result.IsPaused(), "Expected result to be paused") + uassert.True(t, result.IsPaused(), "Expected result to be paused") +} + +func TestOwnable(t *testing.T) { + result := NewFromOwnable(o) + + uassert.Equal(t, result.Ownable().Owner(), o.Owner()) } From d5a083bc9a85d2922f9432810613fec060943060 Mon Sep 17 00:00:00 2001 From: Norman Date: Mon, 3 Feb 2025 05:52:15 +0100 Subject: [PATCH 091/143] fix: tests Signed-off-by: Norman --- gno.land/pkg/gnoland/genesis.go | 7 +- gno.land/pkg/keyscli/addpkg.go | 2 +- gno.land/pkg/keyscli/run.go | 2 +- gno.land/pkg/sdk/vm/keeper.go | 6 +- gno.land/pkg/sdk/vm/msgs.go | 4 +- gnovm/cmd/gno/mod.go | 4 +- gnovm/cmd/gno/run.go | 16 ++-- gnovm/cmd/gno/test.go | 30 +++---- .../cmd/gno/testdata/lint/not_declared.txtar | 1 - gnovm/cmd/gno/testdata/test/empty_gno1.txtar | 4 +- .../gno/testdata/test/no_path_empty_dir.txtar | 4 +- .../gno/testdata/test/no_path_empty_gno.txtar | 4 +- .../gno/testdata/test/realm_boundmethod.txtar | 23 +++--- .../gno/testdata/test/unknown_package.txtar | 2 +- gnovm/cmd/gno/tool_lint.go | 19 +---- gnovm/cmd/gno/tool_lint_test.go | 10 +-- gnovm/pkg/doc/dirs.go | 78 ++++++++++++++++--- gnovm/pkg/doc/dirs_test.go | 4 +- gnovm/pkg/doc/pkg.go | 4 +- gnovm/pkg/gnolang/files_test.go | 4 +- gnovm/pkg/gnomod/parse_test.go | 4 +- gnovm/pkg/packages/analyze_packages.go | 20 ++--- gnovm/pkg/packages/expand_patterns.go | 1 + gnovm/pkg/packages/load.go | 27 +++++-- gnovm/pkg/packages/pkglist.go | 26 ++++++- gnovm/pkg/test/imports.go | 4 + 26 files changed, 189 insertions(+), 121 deletions(-) diff --git a/gno.land/pkg/gnoland/genesis.go b/gno.land/pkg/gnoland/genesis.go index 02fea917cee..2b283f5a17f 100644 --- a/gno.land/pkg/gnoland/genesis.go +++ b/gno.land/pkg/gnoland/genesis.go @@ -173,9 +173,12 @@ func LoadPackage(pkg *packages.Package, creator bft.Address, fee std.Fee, deposi var tx std.Tx // Open files in directory as MemPackage. - memPkg := gnolang.MustReadMemPackage(pkg.Dir, pkg.ImportPath) - err := memPkg.Validate() + memPkg, err := pkg.MemPkg() if err != nil { + return tx, fmt.Errorf("read package: %w", err) + } + + if err := memPkg.Validate(); err != nil { return tx, fmt.Errorf("invalid package: %w", err) } diff --git a/gno.land/pkg/keyscli/addpkg.go b/gno.land/pkg/keyscli/addpkg.go index 5308d9d2ac4..1e447924fb4 100644 --- a/gno.land/pkg/keyscli/addpkg.go +++ b/gno.land/pkg/keyscli/addpkg.go @@ -102,7 +102,7 @@ func execMakeAddPkg(cfg *MakeAddPkgCfg, args []string, io commands.IO) error { } // open files in directory as MemPackage. - memPkg := gno.MustReadMemPackage(cfg.PkgDir, cfg.PkgPath) + memPkg := gno.MustReadMemPackage(cfg.PkgDir, cfg.PkgPath, nil) if memPkg.IsEmpty() { panic(fmt.Sprintf("found an empty package %q", cfg.PkgPath)) } diff --git a/gno.land/pkg/keyscli/run.go b/gno.land/pkg/keyscli/run.go index 00b2be585c6..bae4fae591d 100644 --- a/gno.land/pkg/keyscli/run.go +++ b/gno.land/pkg/keyscli/run.go @@ -92,7 +92,7 @@ func execMakeRun(cfg *MakeRunCfg, args []string, cmdio commands.IO) error { return fmt.Errorf("could not read source path: %q, %w", sourcePath, err) } if info.IsDir() { - memPkg = gno.MustReadMemPackage(sourcePath, "") + memPkg = gno.MustReadMemPackage(sourcePath, "", nil) } else { // is file b, err := os.ReadFile(sourcePath) if err != nil { diff --git a/gno.land/pkg/sdk/vm/keeper.go b/gno.land/pkg/sdk/vm/keeper.go index 5b588a8524d..e60daa73332 100644 --- a/gno.land/pkg/sdk/vm/keeper.go +++ b/gno.land/pkg/sdk/vm/keeper.go @@ -187,7 +187,7 @@ func loadStdlibPackage(pkgPath, stdlibDir string, store gno.Store) { // does not exist. panic(fmt.Sprintf("failed loading stdlib %q: does not exist", pkgPath)) } - memPkg := gno.MustReadMemPackage(stdlibPath, pkgPath) + memPkg := gno.MustReadMemPackage(stdlibPath, pkgPath, nil) if memPkg.IsEmpty() { // no gno files are present panic(fmt.Sprintf("failed loading stdlib %q: not a valid MemPackage", pkgPath)) @@ -349,7 +349,7 @@ func (vm *VMKeeper) AddPackage(ctx sdk.Context, msg MsgAddPackage) (err error) { // Validate Gno syntax and type check. format := true - if err := gno.TypeCheckMemPackage("", memPkg, gnostore, format); err != nil { + if err := gno.TypeCheckMemPackage(memPkg, gnostore, format); err != nil { return ErrTypeCheck(err) } @@ -564,7 +564,7 @@ func (vm *VMKeeper) Run(ctx sdk.Context, msg MsgRun) (res string, err error) { // Validate Gno syntax and type check. format := false - if err = gno.TypeCheckMemPackage("", memPkg, gnostore, format); err != nil { + if err = gno.TypeCheckMemPackage(memPkg, gnostore, format); err != nil { return "", ErrTypeCheck(err) } diff --git a/gno.land/pkg/sdk/vm/msgs.go b/gno.land/pkg/sdk/vm/msgs.go index 38f35ab7110..1ab3a817d08 100644 --- a/gno.land/pkg/sdk/vm/msgs.go +++ b/gno.land/pkg/sdk/vm/msgs.go @@ -29,7 +29,7 @@ func NewMsgAddPackage(creator crypto.Address, pkgPath string, files []*gnovm.Mem var pkgName string for _, file := range files { if strings.HasSuffix(file.Name, ".gno") { - pkgName = string(gno.MustPackageNameFromFileBody(file.Name, file.Body)) + pkgName = string(gno.MustPackageNameFromFileBody(file.Name, file.Body, nil)) break } } @@ -156,7 +156,7 @@ var _ std.Msg = MsgRun{} func NewMsgRun(caller crypto.Address, send std.Coins, files []*gnovm.MemFile) MsgRun { for _, file := range files { if strings.HasSuffix(file.Name, ".gno") { - pkgName := string(gno.MustPackageNameFromFileBody(file.Name, file.Body)) + pkgName := string(gno.MustPackageNameFromFileBody(file.Name, file.Body, nil)) if pkgName != "main" { panic("package name should be 'main'") } diff --git a/gnovm/cmd/gno/mod.go b/gnovm/cmd/gno/mod.go index b611ae190de..058f6c121fa 100644 --- a/gnovm/cmd/gno/mod.go +++ b/gnovm/cmd/gno/mod.go @@ -185,8 +185,8 @@ func execModGraph(cfg *modGraphCfg, args []string, io commands.IO) error { return err } for _, pkg := range pkgs { - for _, dep := range pkg.ImportsSpecs.Merge(packages.FileKindPackageSource) { - fmt.Fprintf(stdout, "%s %s\n", pkg.ImportPath, dep.PkgPath) + for _, imp := range pkg.Imports[packages.FileKindPackageSource] { + fmt.Fprintf(stdout, "%s %s\n", pkg.ImportPath, imp) } } return nil diff --git a/gnovm/cmd/gno/run.go b/gnovm/cmd/gno/run.go index d322d31cb96..c69f46d7939 100644 --- a/gnovm/cmd/gno/run.go +++ b/gnovm/cmd/gno/run.go @@ -10,6 +10,7 @@ import ( "github.com/gnolang/gno/gnovm/pkg/gnoenv" "github.com/gnolang/gno/gnovm/pkg/gnofiles" gno "github.com/gnolang/gno/gnovm/pkg/gnolang" + "github.com/gnolang/gno/gnovm/pkg/packages" "github.com/gnolang/gno/gnovm/pkg/test" "github.com/gnolang/gno/tm2/pkg/commands" "github.com/gnolang/gno/tm2/pkg/std" @@ -89,18 +90,23 @@ func execRun(cfg *runCfg, args []string, io commands.IO) error { stdout := io.Out() stderr := io.Err() + if len(args) == 0 { + args = []string{"./..."} + } + + pkgs, err := packages.Load(&packages.LoadConfig{Fetcher: testPackageFetcher, Deps: true}, args...) + if err != nil { + return err + } + // init store and machine _, testStore := test.Store( - cfg.rootDir, nil, false, + cfg.rootDir, pkgs, false, stdin, stdout, stderr) if cfg.verbose { testStore.SetLogStoreOps(true) } - if len(args) == 0 { - args = []string{"."} - } - // read files files, err := parseFiles(args, stderr) if err != nil { diff --git a/gnovm/cmd/gno/test.go b/gnovm/cmd/gno/test.go index cbbd0108c97..02f12b872f9 100644 --- a/gnovm/cmd/gno/test.go +++ b/gnovm/cmd/gno/test.go @@ -157,20 +157,13 @@ func execTest(cfg *testCfg, args []string, io commands.IO) error { IO: io, Fetcher: testPackageFetcher, DepsPatterns: []string{"./..."}, + Deps: true, } pkgs, err := packages.Load(conf, args...) if err != nil { - return fmt.Errorf("list targets from patterns: %w", err) + return err } - if len(pkgs) == 0 { - io.ErrPrintln("no packages to test") - return nil - } - - pkgsMap := map[string]*packages.Package{} - packages.Inject(pkgsMap, pkgs) - if cfg.timeout > 0 { go func() { time.Sleep(cfg.timeout) @@ -198,6 +191,14 @@ func execTest(cfg *testCfg, args []string, io commands.IO) error { continue } + if len(pkg.Errors) != 0 { + for _, err := range pkg.Errors { + io.ErrPrintln(err) + buildErrCount++ + } + continue + } + if !pkg.Draft && pkg.Files.Size() == 0 { return fmt.Errorf("no Gno files in %s", pkg.Dir) } @@ -212,17 +213,6 @@ func execTest(cfg *testCfg, args []string, io commands.IO) error { continue } - depsConf := *conf - depsConf.Deps = true - depsConf.Cache = pkgsMap - deps, loadDepsErr := packages.Load(&depsConf, pkg.Dir) - if loadDepsErr != nil { - io.ErrPrintfln("%s: load deps: %v", label, err) - buildErrCount++ - continue - } - packages.Inject(pkgsMap, deps) - memPkg, err := gno.ReadMemPackage(pkg.Dir, label, conf.Fset) if err != nil { io.ErrPrintln(err) diff --git a/gnovm/cmd/gno/testdata/lint/not_declared.txtar b/gnovm/cmd/gno/testdata/lint/not_declared.txtar index 3c3c304797e..30f7a37e2ce 100644 --- a/gnovm/cmd/gno/testdata/lint/not_declared.txtar +++ b/gnovm/cmd/gno/testdata/lint/not_declared.txtar @@ -19,4 +19,3 @@ module gno.land/p/demo/hello -- stdout.golden -- -- stderr.golden -- bad_file.gno:4:2: undefined: hello (code=4) -bad_file.gno:4:2: name hello not declared (code=2) diff --git a/gnovm/cmd/gno/testdata/test/empty_gno1.txtar b/gnovm/cmd/gno/testdata/test/empty_gno1.txtar index 2cdff48c9e8..e5c7934e632 100644 --- a/gnovm/cmd/gno/testdata/test/empty_gno1.txtar +++ b/gnovm/cmd/gno/testdata/test/empty_gno1.txtar @@ -1,8 +1,8 @@ # Test empty gno1 -gno test . +! gno test . ! stdout .+ -stderr '\? \. \[no test files\]' +stderr 'empty\.gno:1:1: expected ''package'', found ''EOF''' -- empty.gno -- diff --git a/gnovm/cmd/gno/testdata/test/no_path_empty_dir.txtar b/gnovm/cmd/gno/testdata/test/no_path_empty_dir.txtar index 6f8b54d7ea4..70026d6e28c 100644 --- a/gnovm/cmd/gno/testdata/test/no_path_empty_dir.txtar +++ b/gnovm/cmd/gno/testdata/test/no_path_empty_dir.txtar @@ -1,6 +1,6 @@ # Run gno test without path argument on an empty dir -gno test +! gno test ! stdout .+ -stderr '[no test files]' \ No newline at end of file +stderr '^no Gno files in .+$' \ No newline at end of file diff --git a/gnovm/cmd/gno/testdata/test/no_path_empty_gno.txtar b/gnovm/cmd/gno/testdata/test/no_path_empty_gno.txtar index 846ce5bbd88..469103f7f4c 100644 --- a/gnovm/cmd/gno/testdata/test/no_path_empty_gno.txtar +++ b/gnovm/cmd/gno/testdata/test/no_path_empty_gno.txtar @@ -1,8 +1,8 @@ # Test empty gno without path argument -gno test +! gno test ! stdout .+ -stderr '\? \. \[no test files\]' +stderr 'empty\.gno:1:1: expected ''package'', found ''EOF''' -- empty.gno -- \ No newline at end of file diff --git a/gnovm/cmd/gno/testdata/test/realm_boundmethod.txtar b/gnovm/cmd/gno/testdata/test/realm_boundmethod.txtar index e53cde13667..f2281ae6f3b 100644 --- a/gnovm/cmd/gno/testdata/test/realm_boundmethod.txtar +++ b/gnovm/cmd/gno/testdata/test/realm_boundmethod.txtar @@ -1,9 +1,4 @@ -# Set up GNOROOT in the current directory. -mkdir $WORK/gnovm -symlink $WORK/gnovm/stdlibs -> $GNOROOT/gnovm/stdlibs -env GNOROOT=$WORK - -gno test -v ./examples/gno.land/r/demo/realm2 +gno test -v ./r/demo/realm2 stderr '=== RUN TestDo' stderr '--- PASS: TestDo.*' @@ -11,10 +6,10 @@ stderr '--- PASS: TestDo.*' stderr '=== RUN file/realm2_filetest.gno' stderr '--- PASS: file/realm2_filetest.*' --- examples/gno.land/p/demo/counter/gno.mod -- +-- p/demo/counter/gno.mod -- module gno.land/p/demo/counter --- examples/gno.land/p/demo/counter/counter.gno -- +-- p/demo/counter/counter.gno -- package counter type Counter struct { @@ -25,10 +20,10 @@ func (c *Counter) Inc() { c.n++ } --- examples/gno.land/r/demo/realm1/gno.mod -- +-- r/demo/realm1/gno.mod -- module gno.land/r/demo/realm1 --- examples/gno.land/r/demo/realm1/realm1.gno -- +-- r/demo/realm1/realm1.gno -- package realm1 import "gno.land/p/demo/counter" @@ -39,10 +34,10 @@ func GetCounter() *counter.Counter { return &c } --- examples/gno.land/r/demo/realm2/gno.mod -- +-- r/demo/realm2/gno.mod -- module gno.land/r/demo/realm2 --- examples/gno.land/r/demo/realm2/realm2.gno -- +-- r/demo/realm2/realm2.gno -- package realm2 import ( @@ -53,7 +48,7 @@ func Do() { realm1.GetCounter().Inc() } --- examples/gno.land/r/demo/realm2/realm2_filetest.gno -- +-- r/demo/realm2/realm2_filetest.gno -- // PKGPATH: gno.land/r/tests package tests @@ -67,7 +62,7 @@ func main() { // Output: // OK --- examples/gno.land/r/demo/realm2/realm2_test.gno -- +-- r/demo/realm2/realm2_test.gno -- package realm2 import "testing" diff --git a/gnovm/cmd/gno/testdata/test/unknown_package.txtar b/gnovm/cmd/gno/testdata/test/unknown_package.txtar index 0611d3440a4..0affcedb8d8 100644 --- a/gnovm/cmd/gno/testdata/test/unknown_package.txtar +++ b/gnovm/cmd/gno/testdata/test/unknown_package.txtar @@ -3,7 +3,7 @@ ! gno test -v . ! stdout .+ -stderr 'contract.gno:3:8: unknown import path foobarbaz' +stderr 'contract.gno:3:8: package foobarbaz is not in std' -- contract.gno -- package contract diff --git a/gnovm/cmd/gno/tool_lint.go b/gnovm/cmd/gno/tool_lint.go index 69ab14d20e2..20f7c7c6f74 100644 --- a/gnovm/cmd/gno/tool_lint.go +++ b/gnovm/cmd/gno/tool_lint.go @@ -13,7 +13,6 @@ import ( "regexp" "strings" - "github.com/davecgh/go-spew/spew" "github.com/gnolang/gno/gnovm" "github.com/gnolang/gno/gnovm/pkg/gnoenv" gno "github.com/gnolang/gno/gnovm/pkg/gnolang" @@ -106,9 +105,6 @@ func execLint(cfg *lintCfg, args []string, io commands.IO) error { return fmt.Errorf("list packages from args: %w", err) } - pkgsMap := map[string]*packages.Package{} - packages.Inject(pkgsMap, pkgs) - hasError := false bs, ts := test.Store( @@ -156,22 +152,9 @@ func execLint(cfg *lintCfg, args []string, io commands.IO) error { hasError = true } - // load deps - loadDepsCfg := *loadCfg - loadDepsCfg.Deps = true - loadDepsCfg.Cache = pkgsMap - deps, loadDepsErr := packages.Load(&loadDepsCfg, pkg.Dir) - if loadDepsErr != nil { - io.ErrPrintln(issueFromError(pkg.Dir, err).String()) - hasError = true - continue - } - packages.Inject(pkgsMap, deps) - // read mempkg memPkg, err := pkg.MemPkg() if err != nil { - spew.Fdump(os.Stderr, loadDepsErr) io.ErrPrintln(issueFromError(pkg.Dir, err).String()) hasError = true continue @@ -195,7 +178,7 @@ func execLint(cfg *lintCfg, args []string, io commands.IO) error { // Run type checking if !pkg.Draft { - foundErr, err := lintTypeCheck(io, memPkg, pkgs) + foundErr, err := lintTypeCheck(io, memPkg, ts) if err != nil { io.ErrPrintln(err) hasError = true diff --git a/gnovm/cmd/gno/tool_lint_test.go b/gnovm/cmd/gno/tool_lint_test.go index 59788cc1717..9912583d1b7 100644 --- a/gnovm/cmd/gno/tool_lint_test.go +++ b/gnovm/cmd/gno/tool_lint_test.go @@ -27,13 +27,13 @@ func TestLintApp(t *testing.T) { stderrShouldBe: "../../tests/integ/several-lint-errors/main.gno:5:5: expected ';', found example (code=3)\n../../tests/integ/several-lint-errors/main.gno:6:2: expected '}', found 'EOF' (code=3)\n", errShouldBe: "exit code: 1", }, { - args: []string{"tool", "lint", "../../tests/integ/several-files-multiple-errors/main.gno"}, + args: []string{"tool", "lint", "../../tests/integ/several-files-multiple-errors"}, stderrShouldContain: func() string { lines := []string{ - "../../tests/integ/several-files-multiple-errors/file2.gno:3:5: expected 'IDENT', found '{' (code=2)", - "../../tests/integ/several-files-multiple-errors/file2.gno:5:1: expected type, found '}' (code=2)", - "../../tests/integ/several-files-multiple-errors/main.gno:5:5: expected ';', found example (code=2)", - "../../tests/integ/several-files-multiple-errors/main.gno:6:2: expected '}', found 'EOF' (code=2)", + "../../tests/integ/several-files-multiple-errors/file2.gno:3:5: expected 'IDENT', found '{' (code=3)", + "../../tests/integ/several-files-multiple-errors/file2.gno:5:1: expected type, found '}' (code=3)", + "../../tests/integ/several-files-multiple-errors/main.gno:5:5: expected ';', found example (code=3)", + "../../tests/integ/several-files-multiple-errors/main.gno:6:2: expected '}', found 'EOF' (code=3)", } return strings.Join(lines, "\n") + "\n" }(), diff --git a/gnovm/pkg/doc/dirs.go b/gnovm/pkg/doc/dirs.go index 7411a59839c..2536a28cd72 100644 --- a/gnovm/pkg/doc/dirs.go +++ b/gnovm/pkg/doc/dirs.go @@ -9,11 +9,15 @@ import ( "os" "path" "path/filepath" + "slices" "sort" "strings" + "github.com/gnolang/gno/gnovm" + "github.com/gnolang/gno/gnovm/pkg/gnolang" "github.com/gnolang/gno/gnovm/pkg/gnomod" "github.com/gnolang/gno/gnovm/pkg/packages" + "golang.org/x/mod/module" ) // A bfsDir describes a directory holding code by specifying @@ -61,31 +65,83 @@ func newDirs(dirs []string, modDirs []string) *bfsDirs { dir: mdir, importPath: gm.Module.Mod.Path, }) - roots = append(roots, getGnoModDirs(mdir)...) + roots = append(roots, getGnoModDirs(gm, mdir)...) } go d.walk(roots) return d } -func getGnoModDirs(root string) []bfsDir { - pkgs, err := packages.Load(&packages.LoadConfig{Deps: true}, filepath.Join(root, "...")) - if err != nil { - log.Println("open source directories from import:", err) - return nil - } +func getGnoModDirs(gm *gnomod.File, root string) []bfsDir { + // cmd/go makes use of the go list command, we don't have that here. - dirs := make([]bfsDir, 0, len(pkgs)) - for _, pkg := range pkgs { + imports := packageImportsRecursive(root, gm.Module.Mod.Path) + + dirs := make([]bfsDir, 0, len(imports)) + for _, r := range imports { + mv := gm.Resolve(module.Version{Path: r}) + path := gnomod.PackageDir("", mv) + if _, err := os.Stat(path); err != nil { + // only give directories which actually exist and don't give + // an error when accessing + if !os.IsNotExist(err) { + log.Println("open source directories from import:", err) + } + continue + } dirs = append(dirs, bfsDir{ - importPath: pkg.ImportPath, - dir: pkg.Dir, + importPath: mv.Path, + dir: path, }) } return dirs } +func packageImportsRecursive(root string, pkgPath string) []string { + pkg, err := gnolang.ReadMemPackage(root, pkgPath, nil) + if err != nil { + // ignore invalid packages + pkg = &gnovm.MemPackage{} + } + + importsMap, err := packages.Imports(pkg, nil) + if err != nil { + // ignore packages with invalid imports + importsMap = nil + } + resRaw := importsMap.Merge(packages.FileKindPackageSource, packages.FileKindTest, packages.FileKindXTest) + res := make([]string, len(resRaw)) + for idx, imp := range resRaw { + res[idx] = imp.PkgPath + } + + entries, err := os.ReadDir(root) + if err != nil { + // ignore unreadable dirs + entries = nil + } + + for _, entry := range entries { + if !entry.IsDir() { + continue + } + + dirName := entry.Name() + sub := packageImportsRecursive(filepath.Join(root, dirName), path.Join(pkgPath, dirName)) + + for _, imp := range sub { + if !slices.Contains(res, imp) { + res = append(res, imp) //nolint:makezero + } + } + } + + sort.Strings(res) + + return res +} + // Reset puts the scan back at the beginning. func (d *bfsDirs) Reset() { d.offset = 0 diff --git a/gnovm/pkg/doc/dirs_test.go b/gnovm/pkg/doc/dirs_test.go index 3139298a7ae..45562acdd48 100644 --- a/gnovm/pkg/doc/dirs_test.go +++ b/gnovm/pkg/doc/dirs_test.go @@ -41,7 +41,7 @@ func TestNewDirs_nonExisting(t *testing.T) { assert.Empty(t, d.hist, "hist should be empty") assert.Equal(t, strings.Count(buf.String(), "\n"), 2, "output should contain 2 lines") assert.Contains(t, buf.String(), "non/existing/dir: no such file or directory") - assert.Contains(t, buf.String(), "this/one/neither/gno.mod: no such file or directory") + assert.Contains(t, buf.String(), "this/one/neither/gno.mod: could not read gno.mod file") assert.NotContains(t, buf.String(), "dirsempty: no such file or directory") } @@ -57,7 +57,7 @@ func TestNewDirs_invalidModDir(t *testing.T) { log.Default().SetOutput(old) assert.Empty(t, d.hist, "hist should be len 0 (testdata/dirs is not a valid mod dir)") assert.Equal(t, strings.Count(buf.String(), "\n"), 1, "output should contain 1 line") - assert.Contains(t, buf.String(), "gno.mod: no such file or directory") + assert.Contains(t, buf.String(), "gno.mod: could not read gno.mod file") } func tNewDirs(t *testing.T) (string, *bfsDirs) { diff --git a/gnovm/pkg/doc/pkg.go b/gnovm/pkg/doc/pkg.go index 618af5af708..71e1a50f299 100644 --- a/gnovm/pkg/doc/pkg.go +++ b/gnovm/pkg/doc/pkg.go @@ -210,14 +210,14 @@ func (pkg *pkgData) docPackage(opts *WriteDocumentationOptions) (*ast.Package, * // Compute package documentation. // Assign to blank to ignore errors that can happen due to unresolved identifiers. - astpkg, _ := ast.NewPackage(pkg.fset, fileMap, simplepackages, nil) + astpkg, _ := ast.NewPackage(pkg.fset, fileMap, simpleImporter, nil) p := doc.New(astpkg, pkg.dir.importPath, mode) // TODO: classifyExamples(p, Examples(testGoFiles...)) return astpkg, p, nil } -func simplepackages(imports map[string]*ast.Object, path string) (*ast.Object, error) { +func simpleImporter(imports map[string]*ast.Object, path string) (*ast.Object, error) { pkg := imports[path] if pkg == nil { // note that strings.LastIndex returns -1 if there is no "/" diff --git a/gnovm/pkg/gnolang/files_test.go b/gnovm/pkg/gnolang/files_test.go index f31e261c79c..01c7a30141e 100644 --- a/gnovm/pkg/gnolang/files_test.go +++ b/gnovm/pkg/gnolang/files_test.go @@ -36,10 +36,10 @@ func (nopReader) Read(p []byte) (int, error) { return 0, io.EOF } func TestFiles(t *testing.T) { t.Parallel() - rootDir, err := filepath.Abs("../../../") + rootDir, err := filepath.Abs(filepath.FromSlash("../../..")) require.NoError(t, err) - pkgs, err := packages.Load(&packages.LoadConfig{}, filepath.Join(rootDir, "examples", "....")) + pkgs, err := packages.Load(&packages.LoadConfig{}, filepath.Join(rootDir, "examples", "...")) require.NoError(t, err) newOpts := func() *test.TestOptions { diff --git a/gnovm/pkg/gnomod/parse_test.go b/gnovm/pkg/gnomod/parse_test.go index a2410cec8d8..4086df16e6d 100644 --- a/gnovm/pkg/gnomod/parse_test.go +++ b/gnovm/pkg/gnomod/parse_test.go @@ -181,7 +181,7 @@ func TestParseGnoMod(t *testing.T) { desc: "file not exists", modData: `module foo`, modPath: filepath.Join(pkgDir, "mod.gno"), - errShouldContain: "could not read gno.mod file:", + errShouldContain: "mod.gno: could not read gno.mod file", }, { desc: "file path is dir", @@ -204,7 +204,7 @@ func TestParseGnoMod(t *testing.T) { desc: "error bad module directive", modData: `module foo v0.0.0`, modPath: filepath.Join(pkgDir, "gno.mod"), - errShouldContain: "error parsing gno.mod file at", + errShouldContain: "gno.mod:1: usage: module module/path", }, { desc: "error gno.mod without module", diff --git a/gnovm/pkg/packages/analyze_packages.go b/gnovm/pkg/packages/analyze_packages.go index d4073b7e9e7..c31bd064971 100644 --- a/gnovm/pkg/packages/analyze_packages.go +++ b/gnovm/pkg/packages/analyze_packages.go @@ -9,14 +9,13 @@ import ( "path/filepath" "strings" - "github.com/davecgh/go-spew/spew" "github.com/gnolang/gno/gnovm" "github.com/gnolang/gno/gnovm/pkg/gnoenv" "github.com/gnolang/gno/gnovm/pkg/gnolang" "github.com/gnolang/gno/gnovm/pkg/gnomod" ) -func readPackages(matches []*pkgMatch, fset *token.FileSet) ([]*Package, error) { +func readPackages(matches []*pkgMatch, known PkgList, fset *token.FileSet) (PkgList, error) { if fset == nil { fset = token.NewFileSet() } @@ -29,6 +28,8 @@ func readPackages(matches []*pkgMatch, fset *token.FileSet) ([]*Package, error) if err != nil { return nil, err } + } else if known.GetByDir(pkgMatch.Dir) != nil { + continue } else { pkg = readPkgDir(pkgMatch.Dir, "", fset) } @@ -146,13 +147,6 @@ func readPkgFiles(pkg *Package, files []string, fset *token.FileSet) *Package { pkg.Files[fileKind] = append(pkg.Files[fileKind], base) } - var err error - pkg.ImportsSpecs, err = Imports(&mempkg, fset) - if err != nil { - pkg.Errors = append(pkg.Errors, FromErr(err, fset, pkg.Dir, true)...) - } - pkg.Imports = pkg.ImportsSpecs.ToStrings() - // we use the ReadMemPkg utils to get the package name because we want name resolution like the vm nameFiles := pkg.Files.Merge(FileKindPackageSource, FileKindTest, FileKindXTest, FileKindFiletest) absFiles := make([]string, 0, len(nameFiles)) @@ -163,10 +157,17 @@ func readPkgFiles(pkg *Package, files []string, fset *token.FileSet) *Package { minMempkg, err := gnolang.ReadMemPackageFromList(absFiles, "", fset) if err != nil { pkg.Errors = append(pkg.Errors, FromErr(err, fset, pkg.Dir, true)...) + return pkg } else { pkg.Name = minMempkg.Name } + pkg.ImportsSpecs, err = Imports(&mempkg, fset) + if err != nil { + pkg.Errors = append(pkg.Errors, FromErr(err, fset, pkg.Dir, true)...) + } + pkg.Imports = pkg.ImportsSpecs.ToStrings() + // TODO: check if stdlib if pkg.ImportPath == "command-line-arguments" { @@ -188,7 +189,6 @@ func readPkgFiles(pkg *Package, files []string, fset *token.FileSet) *Package { modFpath := filepath.Join(pkg.Root, "gno.mod") mod, err := gnomod.ParseGnoMod(modFpath) if err != nil { - spew.Dump(err) pkg.Errors = append(pkg.Errors, FromErr(err, fset, modFpath, false)...) return pkg } diff --git a/gnovm/pkg/packages/expand_patterns.go b/gnovm/pkg/packages/expand_patterns.go index a4525bdd2a0..43b7e36e973 100644 --- a/gnovm/pkg/packages/expand_patterns.go +++ b/gnovm/pkg/packages/expand_patterns.go @@ -92,6 +92,7 @@ func expandPatterns(conf *LoadConfig, patterns ...string) ([]*pkgMatch, error) { return nil, fmt.Errorf("%s: recursive remote patterns are not supported", match) case patternKindRemote: + // XXX: weird if conf.SelfContained { return nil, fmt.Errorf("%s: remote patterns are not supported in self-contained mode", match) } diff --git a/gnovm/pkg/packages/load.go b/gnovm/pkg/packages/load.go index 4db2609e0aa..525ff6acfb6 100644 --- a/gnovm/pkg/packages/load.go +++ b/gnovm/pkg/packages/load.go @@ -23,7 +23,7 @@ type LoadConfig struct { IO commands.IO Fetcher pkgdownload.PackageFetcher Deps bool - Cache PackagesMap + Cache PkgList SelfContained bool AllowEmpty bool DepsPatterns []string @@ -43,14 +43,25 @@ func (conf *LoadConfig) applyDefaults() { if conf.Fetcher == nil { conf.Fetcher = rpcpkgfetcher.New(nil) } - if conf.Cache == nil { - conf.Cache = map[string]*Package{} - } if conf.Fset == nil { conf.Fset = token.NewFileSet() } } +func LoadWorkspace(conf *LoadConfig, dir string) (PkgList, error) { + absDir, err := filepath.Abs(dir) + if err != nil { + return nil, err + } + + workRoot, err := gnomod.FindRootDir(absDir) + if err != nil { + return nil, err + } + + return Load(conf, filepath.Join(workRoot, "...")) +} + func Load(conf *LoadConfig, patterns ...string) (PkgList, error) { if conf == nil { conf = &LoadConfig{} @@ -62,7 +73,7 @@ func Load(conf *LoadConfig, patterns ...string) (PkgList, error) { return nil, err } - pkgs, err := readPackages(expanded, conf.Fset) + pkgs, err := readPackages(expanded, nil, conf.Fset) if err != nil { return nil, err } @@ -85,13 +96,13 @@ func Load(conf *LoadConfig, patterns ...string) (PkgList, error) { m.Match = []string{} } - extraPkgs, err := readPackages(extra, conf.Fset) + extraPkgs, err := readPackages(extra, pkgs, conf.Fset) if err != nil { return nil, err } extraMap := NewPackagesMap(extraPkgs...) - toVisit := pkgs + toVisit := []*Package(pkgs) queuedByPkgPath := NewPackagesMap(pkgs...) markForVisit := func(pkg *Package) { queuedByPkgPath.Add(pkg) @@ -122,7 +133,7 @@ func Load(conf *LoadConfig, patterns ...string) (PkgList, error) { } // check if we have it in config cache - if cached, ok := conf.Cache[imp.PkgPath]; ok { + if cached := conf.Cache.Get(imp.PkgPath); cached != nil { markForVisit(cached) continue } diff --git a/gnovm/pkg/packages/pkglist.go b/gnovm/pkg/packages/pkglist.go index a19d11e7888..5258401241a 100644 --- a/gnovm/pkg/packages/pkglist.go +++ b/gnovm/pkg/packages/pkglist.go @@ -25,6 +25,26 @@ func (pl PkgList) Get(pkgPath string) *Package { return nil } +func (pl PkgList) GetByDir(dir string) *Package { + for _, p := range pl { + if p.Dir == dir { + return p + } + } + return nil +} + +func (pl PkgList) Roots() PkgList { + res := PkgList{} + for _, p := range pl { + if len(p.Match) == 0 { + continue + } + res = append(res, p) + } + return res +} + func (pl PkgList) GetMemPackage(pkgPath string) *gnovm.MemPackage { pkg := pl.Get(pkgPath) if pkg == nil { @@ -67,10 +87,10 @@ func visitPackage(pkg *Package, pkgs []*Package, visited, onStack map[string]boo onStack[pkg.ImportPath] = true // Visit package's dependencies - for _, imp := range pkg.ImportsSpecs.Merge(FileKindPackageSource) { + for _, imp := range pkg.Imports[FileKindPackageSource] { found := false for _, p := range pkgs { - if p.ImportPath != imp.PkgPath { + if p.ImportPath != imp { continue } if err := visitPackage(p, pkgs, visited, onStack, sortedPkgs); err != nil { @@ -80,7 +100,7 @@ func visitPackage(pkg *Package, pkgs []*Package, visited, onStack map[string]boo break } if !found { - return fmt.Errorf("missing dependency '%s' for package '%s'", imp.PkgPath, pkg.ImportPath) + return fmt.Errorf("missing dependency '%s' for package '%s'", imp, pkg.ImportPath) } } diff --git a/gnovm/pkg/test/imports.go b/gnovm/pkg/test/imports.go index 142d7e5bfb0..7ecddd4934e 100644 --- a/gnovm/pkg/test/imports.go +++ b/gnovm/pkg/test/imports.go @@ -36,6 +36,9 @@ func Store( baseStore storetypes.CommitStore, resStore gno.Store, ) { + if getter == nil { + getter = packages.PkgList{} + } getPackage := func(pkgPath string, store gno.Store) (pn *gno.PackageNode, pv *gno.PackageValue) { // fmt.Println("getting pkg", pkgPath) @@ -140,6 +143,7 @@ func Store( // if known package if memPkg := getter.GetMemPackage(pkgPath); memPkg != nil { if memPkg.IsEmpty() { + fmt.Fprintln(os.Stderr, string(debug.Stack())) panic(fmt.Sprintf("found an empty package %q", pkgPath)) } From 282b8185dcea72c0e31b67f383b7cc09c3a20042 Mon Sep 17 00:00:00 2001 From: Norman Date: Mon, 3 Feb 2025 05:53:41 +0100 Subject: [PATCH 092/143] fix: remove dev artifact Signed-off-by: Norman --- gnovm/blip/foo.go | 5 ----- gnovm/blip/sub/thing.go | 9 --------- 2 files changed, 14 deletions(-) delete mode 100644 gnovm/blip/foo.go delete mode 100644 gnovm/blip/sub/thing.go diff --git a/gnovm/blip/foo.go b/gnovm/blip/foo.go deleted file mode 100644 index 0f7cb0697e1..00000000000 --- a/gnovm/blip/foo.go +++ /dev/null @@ -1,5 +0,0 @@ -package bloup - -import ( - "whatever" -) diff --git a/gnovm/blip/sub/thing.go b/gnovm/blip/sub/thing.go deleted file mode 100644 index 7342d0b05d4..00000000000 --- a/gnovm/blip/sub/thing.go +++ /dev/null @@ -1,9 +0,0 @@ -package sub - -import ( - "gno.land/lel/lol" -) - -func main() { - lol.Foo() -} From d35e1ffc0fd6ca7f2a9b80431a91c0ac420d76c4 Mon Sep 17 00:00:00 2001 From: Norman Date: Mon, 3 Feb 2025 06:07:07 +0100 Subject: [PATCH 093/143] fix: revert transpile invocation Signed-off-by: Norman --- examples/Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/Makefile b/examples/Makefile index dd05045b7ec..b949afb69cf 100644 --- a/examples/Makefile +++ b/examples/Makefile @@ -33,11 +33,11 @@ OFFICIAL_PACKAGES += ./gno.land/r/gov/... # Dev tools .PHONY: transpile transpile: - go run ../gnovm/cmd/gno tool transpile -v ./... + go run ../gnovm/cmd/gno tool transpile -v . .PHONY: build build: - go run ../gnovm/cmd/gno tool transpile -v --gobuild ./... + go run ../gnovm/cmd/gno tool transpile -v --gobuild . .PHONY: test test: From 62f0f9dc2895ceab9d121560d1d7c187d64ac4fa Mon Sep 17 00:00:00 2001 From: Norman Date: Mon, 3 Feb 2025 06:22:58 +0100 Subject: [PATCH 094/143] fix: include workspace modules in gno test Signed-off-by: Norman --- gnovm/cmd/gno/test.go | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/gnovm/cmd/gno/test.go b/gnovm/cmd/gno/test.go index 02f12b872f9..87bd4b00ce0 100644 --- a/gnovm/cmd/gno/test.go +++ b/gnovm/cmd/gno/test.go @@ -5,10 +5,13 @@ import ( "flag" "fmt" goio "io" + "os" + "path/filepath" "time" "github.com/gnolang/gno/gnovm/pkg/gnoenv" gno "github.com/gnolang/gno/gnovm/pkg/gnolang" + "github.com/gnolang/gno/gnovm/pkg/gnomod" "github.com/gnolang/gno/gnovm/pkg/packages" "github.com/gnolang/gno/gnovm/pkg/test" "github.com/gnolang/gno/tm2/pkg/commands" @@ -152,11 +155,24 @@ func execTest(cfg *testCfg, args []string, io commands.IO) error { cfg.rootDir = gnoenv.RootDir() } + depsPatterns := []string{} + + // include local modules + workspaceDir := "." + cwd, err := os.Getwd() + if err == nil { + workspaceDir, err = gnomod.FindRootDir(cwd) + if err != nil { + workspaceDir = "." + } + } + depsPatterns = append(depsPatterns, filepath.Join(workspaceDir, "...")) + // Find targets for test. conf := &packages.LoadConfig{ IO: io, Fetcher: testPackageFetcher, - DepsPatterns: []string{"./..."}, + DepsPatterns: depsPatterns, Deps: true, } pkgs, err := packages.Load(conf, args...) From 44bff74aa209927429ec4bf3b098650465f94789 Mon Sep 17 00:00:00 2001 From: Norman Date: Mon, 3 Feb 2025 06:24:25 +0100 Subject: [PATCH 095/143] chore: revert transpile invocation change Signed-off-by: Norman --- .github/workflows/examples.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/examples.yml b/.github/workflows/examples.yml index 7e5846e8fcc..ffe40c2db7e 100644 --- a/.github/workflows/examples.yml +++ b/.github/workflows/examples.yml @@ -29,7 +29,7 @@ jobs: with: go-version: ${{ matrix.goversion }} - run: go install -v ./gnovm/cmd/gno - - run: go run ./gnovm/cmd/gno tool transpile -v --gobuild ./examples/... + - run: go run ./gnovm/cmd/gno tool transpile -v --gobuild ./examples test: strategy: fail-fast: false From 8fe26c8a29ff1487269f889028c9b9173d3dd7d5 Mon Sep 17 00:00:00 2001 From: Norman Date: Mon, 3 Feb 2025 06:29:17 +0100 Subject: [PATCH 096/143] fix: test Signed-off-by: Norman --- examples/no_cycles_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/no_cycles_test.go b/examples/no_cycles_test.go index ba90a6072ac..ef9322833b5 100644 --- a/examples/no_cycles_test.go +++ b/examples/no_cycles_test.go @@ -85,7 +85,7 @@ func detectCycles(root *packages.Package, pkgs packages.PkgList, visited map[str // visitImports resolves and visits imports by kinds func visitImports(kinds []packages.FileKind, root *packages.Package, pkgs packages.PkgList, visited map[string]bool, stack []string) error { - for _, imp := range root.Imports.Merge(kinds...) { + for _, imp := range root.ImportsSpecs.Merge(kinds...) { if slices.Contains(injectedTestingLibs, imp.PkgPath) { continue } From c6b1f3b9d17f2871b62b50f9340faf5fe9b3f18a Mon Sep 17 00:00:00 2001 From: Norman Date: Mon, 3 Feb 2025 06:29:24 +0100 Subject: [PATCH 097/143] chore: lint Signed-off-by: Norman --- gnovm/pkg/packages/imports.go | 6 +++--- gnovm/pkg/test/imports.go | 3 +-- gnovm/pkg/test/test.go | 3 +-- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/gnovm/pkg/packages/imports.go b/gnovm/pkg/packages/imports.go index d3c39ea5302..431990d9355 100644 --- a/gnovm/pkg/packages/imports.go +++ b/gnovm/pkg/packages/imports.go @@ -82,9 +82,9 @@ type FileImport struct { type ImportsMap map[FileKind][]*FileImport -func (im ImportsMap) ToStrings() map[FileKind][]string { - res := make(map[FileKind][]string, len(im)) - for k, v := range im { +func (imap ImportsMap) ToStrings() map[FileKind][]string { + res := make(map[FileKind][]string, len(imap)) + for k, v := range imap { c := make([]string, 0, len(v)) for _, x := range v { c = append(c, x.PkgPath) diff --git a/gnovm/pkg/test/imports.go b/gnovm/pkg/test/imports.go index 7ecddd4934e..e0969271811 100644 --- a/gnovm/pkg/test/imports.go +++ b/gnovm/pkg/test/imports.go @@ -14,7 +14,6 @@ import ( "time" "github.com/gnolang/gno/gnovm" - "github.com/gnolang/gno/gnovm/pkg/gnolang" gno "github.com/gnolang/gno/gnovm/pkg/gnolang" "github.com/gnolang/gno/gnovm/pkg/packages" teststdlibs "github.com/gnolang/gno/gnovm/tests/stdlibs" @@ -28,7 +27,7 @@ import ( // NOTE: this isn't safe, should only be used for testing. func Store( rootDir string, - getter gnolang.MemPackageGetter, + getter gno.MemPackageGetter, withExtern bool, stdin io.Reader, stdout, stderr io.Writer, diff --git a/gnovm/pkg/test/test.go b/gnovm/pkg/test/test.go index f4bf1dd2790..0cb1b26e174 100644 --- a/gnovm/pkg/test/test.go +++ b/gnovm/pkg/test/test.go @@ -16,7 +16,6 @@ import ( "time" "github.com/gnolang/gno/gnovm" - "github.com/gnolang/gno/gnovm/pkg/gnolang" gno "github.com/gnolang/gno/gnovm/pkg/gnolang" "github.com/gnolang/gno/gnovm/stdlibs" teststd "github.com/gnolang/gno/gnovm/tests/stdlibs/std" @@ -134,7 +133,7 @@ func (opts *TestOptions) WriterForStore() io.Writer { } // NewTestOptions sets up TestOptions, filling out all "required" parameters. -func NewTestOptions(rootDir string, getter gnolang.MemPackageGetter, stdin io.Reader, stdout, stderr io.Writer) *TestOptions { +func NewTestOptions(rootDir string, getter gno.MemPackageGetter, stdin io.Reader, stdout, stderr io.Writer) *TestOptions { opts := &TestOptions{ RootDir: rootDir, Output: stdout, From 315ac5d3255c61a5bad0ef4bd57bb1db7f6ea1b5 Mon Sep 17 00:00:00 2001 From: Norman Date: Mon, 3 Feb 2025 06:33:37 +0100 Subject: [PATCH 098/143] chore: remove dev artifact Signed-off-by: Norman --- gnovm/blipgno/foo.gno | 5 ----- gnovm/blipgno/gno.mod | 1 - gnovm/blipgno/sub/thing.gno | 9 --------- 3 files changed, 15 deletions(-) delete mode 100644 gnovm/blipgno/foo.gno delete mode 100644 gnovm/blipgno/gno.mod delete mode 100644 gnovm/blipgno/sub/thing.gno diff --git a/gnovm/blipgno/foo.gno b/gnovm/blipgno/foo.gno deleted file mode 100644 index 0f7cb0697e1..00000000000 --- a/gnovm/blipgno/foo.gno +++ /dev/null @@ -1,5 +0,0 @@ -package bloup - -import ( - "whatever" -) diff --git a/gnovm/blipgno/gno.mod b/gnovm/blipgno/gno.mod deleted file mode 100644 index 9e3ddf6d8aa..00000000000 --- a/gnovm/blipgno/gno.mod +++ /dev/null @@ -1 +0,0 @@ -module gno.land/p/demo/blip diff --git a/gnovm/blipgno/sub/thing.gno b/gnovm/blipgno/sub/thing.gno deleted file mode 100644 index 7342d0b05d4..00000000000 --- a/gnovm/blipgno/sub/thing.gno +++ /dev/null @@ -1,9 +0,0 @@ -package sub - -import ( - "gno.land/lel/lol" -) - -func main() { - lol.Foo() -} From dc0b608d46ab41896e582c4620d000362fc65ff3 Mon Sep 17 00:00:00 2001 From: Alexis Colin Date: Mon, 3 Feb 2025 17:53:47 +0900 Subject: [PATCH 099/143] feat(gnoweb): enable strikethrough UI (#3670) --- gno.land/pkg/gnoweb/webclient_html.go | 1 + 1 file changed, 1 insertion(+) diff --git a/gno.land/pkg/gnoweb/webclient_html.go b/gno.land/pkg/gnoweb/webclient_html.go index c04a7f9e457..72b1b3f8b06 100644 --- a/gno.land/pkg/gnoweb/webclient_html.go +++ b/gno.land/pkg/gnoweb/webclient_html.go @@ -49,6 +49,7 @@ func NewDefaultHTMLWebClientConfig(client *client.RPCClient) *HTMLWebClientConfi markdown.NewHighlighting( markdown.WithFormatOptions(chromaOptions...), ), + extension.Strikethrough, extension.Table, ), } From 815cf51273a20cd7752c72d5903b02d373584a65 Mon Sep 17 00:00:00 2001 From: Stefan Nikolic Date: Mon, 3 Feb 2025 15:35:21 +0100 Subject: [PATCH 100/143] feat: add FOMO3D game implementation (#3344) # Description This PR introduces FOMO3D, a blockchain-based game that combines lottery and investment mechanics, implemented as a Gno realm. The game creates an engaging economic model where players compete to be the last key purchaser while earning dividends from subsequent purchases. ### Key Features - Players purchase keys using GNOT tokens - Each key purchase: - Extends the game timer - Increases key price by 1% - Makes buyer potential jackpot winner - Distributes dividends to existing key holders - Automatic prize distribution: - 47% to jackpot (winner) - 28% as dividends to key holders - 20% to next round's starting pot - 5% as a fee to the contract owner - Full test coverage ### Technical Implementation - Utilizes AVL tree for player data storage - Implements dividend distribution system - Includes comprehensive test suite - Features markdown-formatted render functions for game state visualization - Mints a unique FOMO3D NFT to the winner of each round ### How to Use 1. Start game with `StartGame()` 2. Purchase keys with `BuyKeys()` 3. Claim dividends with `ClaimDividends()` 4. View game status via render functions 5. Winner automatically receives jackpot when timer expires ### Testing All core functionalities are covered by unit tests including: - Full game flow - Key purchasing mechanics - Dividend distribution - Game ending conditions Inspired by the original Ethereum FOMO3D game but rebuilt for the Gno platform. ## Note The test checks will not pass until [gnolang/gno#3495](https://github.com/gnolang/gno/pull/3495) is merged. In case this PR is not approved, I will refactor the NFT feature accordingly. --------- Signed-off-by: gfanton <8671905+gfanton@users.noreply.github.com> Signed-off-by: Norman Meier Signed-off-by: Norman Signed-off-by: Norman Co-authored-by: Nathan Toups <612924+n2p5@users.noreply.github.com> Co-authored-by: Leon Hudak <33522493+leohhhn@users.noreply.github.com> Co-authored-by: Alexis Colin Co-authored-by: gfanton <8671905+gfanton@users.noreply.github.com> Co-authored-by: Mustapha <102119509+mous1985@users.noreply.github.com> Co-authored-by: Morgan Co-authored-by: Blake <104744707+r3v4s@users.noreply.github.com> Co-authored-by: n0izn0iz Co-authored-by: Norman --- examples/gno.land/r/stefann/fomo3d/errors.gno | 30 ++ examples/gno.land/r/stefann/fomo3d/events.gno | 94 +++++ examples/gno.land/r/stefann/fomo3d/fomo3d.gno | 358 ++++++++++++++++++ .../gno.land/r/stefann/fomo3d/fomo3d_test.gno | 294 ++++++++++++++ examples/gno.land/r/stefann/fomo3d/gno.mod | 1 + examples/gno.land/r/stefann/fomo3d/nft.gno | 88 +++++ examples/gno.land/r/stefann/fomo3d/render.gno | 138 +++++++ 7 files changed, 1003 insertions(+) create mode 100644 examples/gno.land/r/stefann/fomo3d/errors.gno create mode 100644 examples/gno.land/r/stefann/fomo3d/events.gno create mode 100644 examples/gno.land/r/stefann/fomo3d/fomo3d.gno create mode 100644 examples/gno.land/r/stefann/fomo3d/fomo3d_test.gno create mode 100644 examples/gno.land/r/stefann/fomo3d/gno.mod create mode 100644 examples/gno.land/r/stefann/fomo3d/nft.gno create mode 100644 examples/gno.land/r/stefann/fomo3d/render.gno diff --git a/examples/gno.land/r/stefann/fomo3d/errors.gno b/examples/gno.land/r/stefann/fomo3d/errors.gno new file mode 100644 index 00000000000..df70ab08c55 --- /dev/null +++ b/examples/gno.land/r/stefann/fomo3d/errors.gno @@ -0,0 +1,30 @@ +package fomo3d + +import "errors" + +var ( + // Game state errors + ErrGameInProgress = errors.New("fomo3d: game already in progress") + ErrGameNotInProgress = errors.New("fomo3d: game not in progress") + ErrGameEnded = errors.New("fomo3d: game has ended") + ErrGameTimeExpired = errors.New("fomo3d: game time expired") + ErrNoKeysPurchased = errors.New("fomo3d: no keys purchased") + ErrPlayerNotInGame = errors.New("fomo3d: player is not in the game") + + // Payment errors + ErrInvalidPayment = errors.New("fomo3d: must send ugnot only") + ErrInsufficientPayment = errors.New("fomo3d: insufficient payment for key") + + // Dividend errors + ErrNoDividendsToClaim = errors.New("fomo3d: no dividends to claim") + + // Fee errors + ErrNoFeesToClaim = errors.New("fomo3d: no owner fees to claim") + + // Resolution errors + ErrInvalidAddressOrName = errors.New("fomo3d: invalid address or unregistered username") + + // NFT errors + ErrUnauthorizedMint = errors.New("fomo3d: only the Fomo3D game realm can mint winner NFTs") + ErrZeroAddress = errors.New("fomo3d: zero address") +) diff --git a/examples/gno.land/r/stefann/fomo3d/events.gno b/examples/gno.land/r/stefann/fomo3d/events.gno new file mode 100644 index 00000000000..ea404466955 --- /dev/null +++ b/examples/gno.land/r/stefann/fomo3d/events.gno @@ -0,0 +1,94 @@ +package fomo3d + +import ( + "std" + + "gno.land/p/demo/ufmt" +) + +// Event names +const ( + // Game events + GameStartedEvent = "GameStarted" + GameEndedEvent = "GameEnded" + KeysPurchasedEvent = "KeysPurchased" + + // Player events + DividendsClaimedEvent = "DividendsClaimed" + + // Admin events + OwnerFeeClaimedEvent = "OwnerFeeClaimed" +) + +// Event keys +const ( + // Common keys + EventRoundKey = "round" + EventAmountKey = "amount" + + // Game keys + EventStartBlockKey = "startBlock" + EventEndBlockKey = "endBlock" + EventStartingPotKey = "startingPot" + EventWinnerKey = "winner" + EventJackpotKey = "jackpot" + + // Player keys + EventBuyerKey = "buyer" + EventNumKeysKey = "numKeys" + EventPriceKey = "price" + EventJackpotShareKey = "jackpotShare" + EventDividendShareKey = "dividendShare" + EventClaimerKey = "claimer" + + // Admin keys + EventOwnerKey = "owner" + EventPreviousOwnerKey = "previousOwner" + EventNewOwnerKey = "newOwner" +) + +func emitGameStarted(round, startBlock, endBlock, startingPot int64) { + std.Emit( + GameStartedEvent, + EventRoundKey, ufmt.Sprintf("%d", round), + EventStartBlockKey, ufmt.Sprintf("%d", startBlock), + EventEndBlockKey, ufmt.Sprintf("%d", endBlock), + EventStartingPotKey, ufmt.Sprintf("%d", startingPot), + ) +} + +func emitGameEnded(round int64, winner std.Address, jackpot int64) { + std.Emit( + GameEndedEvent, + EventRoundKey, ufmt.Sprintf("%d", round), + EventWinnerKey, winner.String(), + EventJackpotKey, ufmt.Sprintf("%d", jackpot), + ) +} + +func emitKeysPurchased(buyer std.Address, numKeys, price, jackpotShare, dividendShare int64) { + std.Emit( + KeysPurchasedEvent, + EventBuyerKey, buyer.String(), + EventNumKeysKey, ufmt.Sprintf("%d", numKeys), + EventPriceKey, ufmt.Sprintf("%d", price), + EventJackpotShareKey, ufmt.Sprintf("%d", jackpotShare), + EventDividendShareKey, ufmt.Sprintf("%d", dividendShare), + ) +} + +func emitDividendsClaimed(claimer std.Address, amount int64) { + std.Emit( + DividendsClaimedEvent, + EventClaimerKey, claimer.String(), + EventAmountKey, ufmt.Sprintf("%d", amount), + ) +} + +func emitOwnerFeeClaimed(owner std.Address, amount int64) { + std.Emit( + OwnerFeeClaimedEvent, + EventOwnerKey, owner.String(), + EventAmountKey, ufmt.Sprintf("%d", amount), + ) +} diff --git a/examples/gno.land/r/stefann/fomo3d/fomo3d.gno b/examples/gno.land/r/stefann/fomo3d/fomo3d.gno new file mode 100644 index 00000000000..b2384ba07f4 --- /dev/null +++ b/examples/gno.land/r/stefann/fomo3d/fomo3d.gno @@ -0,0 +1,358 @@ +package fomo3d + +import ( + "std" + "strings" + + "gno.land/p/demo/avl" + "gno.land/p/demo/ownable" + "gno.land/p/demo/ufmt" + + "gno.land/r/demo/users" + "gno.land/r/leon/hof" +) + +// FOMO3D (Fear Of Missing Out 3D) is a blockchain-based game that combines elements +// of a lottery and investment mechanics. Players purchase keys using GNOT tokens, +// where each key purchase: +// - Extends the game timer +// - Increases the key price by 1% +// - Makes the buyer the potential winner of the jackpot +// - Distributes dividends to all key holders +// +// Game Mechanics: +// - The last person to buy a key before the timer expires wins the jackpot (47% of all purchases) +// - Key holders earn dividends from each purchase (28% of all purchases) +// - 20% of purchases go to the next round's starting pot +// - 5% goes to development fee +// - Game ends when the timer expires +// +// Inspired by the original Ethereum FOMO3D game but implemented in Gno. + +const ( + MIN_KEY_PRICE int64 = 100000 // minimum key price in ugnot + TIME_EXTENSION int64 = 86400 // time extension in blocks when new key is bought (~24 hours @ 1s blocks) + + // Distribution percentages (total 100%) + JACKPOT_PERCENT int64 = 47 // 47% goes to jackpot + DIVIDENDS_PERCENT int64 = 28 // 28% distributed to key holders + NEXT_ROUND_POT int64 = 20 // 20% goes to next round's starting pot + OWNER_FEE_PERCENT int64 = 5 // 5% goes to contract owner +) + +type PlayerInfo struct { + Keys int64 // number of keys owned + Dividends int64 // unclaimed dividends in ugnot +} + +// GameState represents the current state of the FOMO3D game +type GameState struct { // TODO: Separate GameState and RoundState and save round history tree in GameState + StartBlock int64 // Block when the game started + EndBlock int64 // Block when the game will end + LastKeyBlock int64 // Block of last key purchase + LastBuyer std.Address // Address of last key buyer + Jackpot int64 // Current jackpot in ugnot + KeyPrice int64 // Current price of keys in ugnot + TotalKeys int64 // Total number of keys in circulation + Ended bool // Whether the game has ended + CurrentRound int64 // Current round number + NextPot int64 // Next round's starting pot + OwnerFee int64 // Accumulated owner fees + BuyKeysLink string // Link to BuyKeys function + ClaimDividendsLink string // Link to ClaimDividends function + StartGameLink string // Link to StartGame function +} + +var ( + gameState GameState + players *avl.Tree // maps address -> PlayerInfo + Ownable *ownable.Ownable +) + +func init() { + Ownable = ownable.New() + players = avl.NewTree() + gameState.Ended = true + hof.Register() +} + +// StartGame starts a new game round +func StartGame() { + if !gameState.Ended && gameState.StartBlock != 0 { + panic(ErrGameInProgress.Error()) + } + + gameState.CurrentRound++ + gameState.StartBlock = std.GetHeight() + gameState.EndBlock = gameState.StartBlock + TIME_EXTENSION // Initial 24h window + gameState.LastKeyBlock = gameState.StartBlock + gameState.Jackpot = gameState.NextPot + gameState.NextPot = 0 + gameState.Ended = false + gameState.KeyPrice = MIN_KEY_PRICE + gameState.TotalKeys = 0 + + // Clear previous round's player data + players = avl.NewTree() + + emitGameStarted( + gameState.CurrentRound, + gameState.StartBlock, + gameState.EndBlock, + gameState.Jackpot, + ) +} + +// BuyKeys allows players to purchase keys +func BuyKeys() { + if gameState.Ended { + panic(ErrGameEnded.Error()) + } + + currentBlock := std.GetHeight() + if currentBlock > gameState.EndBlock { + panic(ErrGameTimeExpired.Error()) + } + + // Get sent coins + sent := std.GetOrigSend() + if len(sent) != 1 || sent[0].Denom != "ugnot" { + panic(ErrInvalidPayment.Error()) + } + + payment := sent.AmountOf("ugnot") + if payment < gameState.KeyPrice { + panic(ErrInsufficientPayment.Error()) + } + + // Calculate number of keys that can be bought and actual cost + numKeys := payment / gameState.KeyPrice + actualCost := numKeys * gameState.KeyPrice + excess := payment - actualCost + + // Update buyer's info + buyer := std.PrevRealm().Addr() + var buyerInfo PlayerInfo + if info, exists := players.Get(buyer.String()); exists { + buyerInfo = info.(PlayerInfo) + } + + buyerInfo.Keys += numKeys + gameState.TotalKeys += numKeys + + // Distribute actual cost + jackpotShare := actualCost * JACKPOT_PERCENT / 100 + dividendShare := actualCost * DIVIDENDS_PERCENT / 100 + nextPotShare := actualCost * NEXT_ROUND_POT / 100 + ownerShare := actualCost * OWNER_FEE_PERCENT / 100 + + // Update pools + gameState.Jackpot += jackpotShare + gameState.NextPot += nextPotShare + gameState.OwnerFee += ownerShare + + // Return excess payment to buyer if any + if excess > 0 { + banker := std.GetBanker(std.BankerTypeOrigSend) + banker.SendCoins( + std.CurrentRealm().Addr(), + buyer, + std.NewCoins(std.NewCoin("ugnot", excess)), + ) + } + + // Distribute dividends to all key holders + if players.Size() > 0 && gameState.TotalKeys > 0 { + dividendPerKey := dividendShare / gameState.TotalKeys + players.Iterate("", "", func(key string, value interface{}) bool { + playerInfo := value.(PlayerInfo) + playerInfo.Dividends += playerInfo.Keys * dividendPerKey + players.Set(key, playerInfo) + return false + }) + } + + // Update game state + gameState.LastBuyer = buyer + gameState.LastKeyBlock = currentBlock + gameState.EndBlock = currentBlock + TIME_EXTENSION // Always extend 24h from current block + gameState.KeyPrice += (gameState.KeyPrice * numKeys) / 100 + + // Save buyer's updated info + players.Set(buyer.String(), buyerInfo) + + emitKeysPurchased( + buyer, + numKeys, + gameState.KeyPrice, + jackpotShare, + dividendShare, + ) +} + +// ClaimDividends allows players to withdraw their earned dividends +func ClaimDividends() { + caller := std.PrevRealm().Addr() + + info, exists := players.Get(caller.String()) + if !exists { + panic(ErrNoDividendsToClaim.Error()) + } + + playerInfo := info.(PlayerInfo) + if playerInfo.Dividends == 0 { + panic(ErrNoDividendsToClaim.Error()) + } + + // Reset dividends and send coins + amount := playerInfo.Dividends + playerInfo.Dividends = 0 + players.Set(caller.String(), playerInfo) + + banker := std.GetBanker(std.BankerTypeRealmSend) + banker.SendCoins( + std.CurrentRealm().Addr(), + caller, + std.NewCoins(std.NewCoin("ugnot", amount)), + ) + + emitDividendsClaimed(caller, amount) +} + +// ClaimOwnerFee allows the owner to withdraw accumulated fees +func ClaimOwnerFee() { + Ownable.AssertCallerIsOwner() + + if gameState.OwnerFee == 0 { + panic(ErrNoFeesToClaim.Error()) + } + + amount := gameState.OwnerFee + gameState.OwnerFee = 0 + + banker := std.GetBanker(std.BankerTypeRealmSend) + banker.SendCoins( + std.CurrentRealm().Addr(), + Ownable.Owner(), + std.NewCoins(std.NewCoin("ugnot", amount)), + ) + + emitOwnerFeeClaimed(Ownable.Owner(), amount) +} + +// EndGame ends the current round and distributes the jackpot +func EndGame() { + if gameState.Ended { + panic(ErrGameEnded.Error()) + } + + currentBlock := std.GetHeight() + if currentBlock <= gameState.EndBlock { + panic(ErrGameNotInProgress.Error()) + } + + if gameState.LastBuyer == "" { + panic(ErrNoKeysPurchased.Error()) + } + + gameState.Ended = true + + // Send jackpot to winner + banker := std.GetBanker(std.BankerTypeRealmSend) + banker.SendCoins( + std.CurrentRealm().Addr(), + gameState.LastBuyer, + std.NewCoins(std.NewCoin("ugnot", gameState.Jackpot)), + ) + + emitGameEnded( + gameState.CurrentRound, + gameState.LastBuyer, + gameState.Jackpot, + ) + + // Mint NFT for the winner + if err := mintRoundWinnerNFT(gameState.LastBuyer, gameState.CurrentRound); err != nil { + panic(err.Error()) + } +} + +// GetGameState returns current game state +func GetGameState() (int64, int64, int64, std.Address, int64, int64, int64, bool, int64, int64) { + return gameState.StartBlock, + gameState.EndBlock, + gameState.LastKeyBlock, + gameState.LastBuyer, + gameState.Jackpot, + gameState.KeyPrice, + gameState.TotalKeys, + gameState.Ended, + gameState.NextPot, + gameState.CurrentRound +} + +// GetOwnerInfo returns the owner address and unclaimed fees +func GetOwnerInfo() (std.Address, int64) { + return Ownable.Owner(), gameState.OwnerFee +} + +// Helper to convert string (address or username) to address +func stringToAddress(input string) std.Address { + // Check if input is valid address + addr := std.Address(input) + if addr.IsValid() { + return addr + } + + // Not an address, try to find namespace + if user := users.GetUserByName(input); user != nil { + return user.Address + } + + return "" +} + +func isPlayerInGame(addr std.Address) bool { + _, exists := players.Get(addr.String()) + return exists +} + +// GetPlayerInfo returns a player's keys and dividends +func GetPlayerInfo(addrOrName string) (int64, int64) { + addr := stringToAddress(addrOrName) + + if addr == "" { + panic(ErrInvalidAddressOrName.Error()) + } + + if !isPlayerInGame(addr) { + panic(ErrPlayerNotInGame.Error()) + } + + info, _ := players.Get(addr.String()) + playerInfo := info.(PlayerInfo) + return playerInfo.Keys, playerInfo.Dividends +} + +// Render handles the rendering of game state +func Render(path string) string { + parts := strings.Split(path, "/") + c := len(parts) + + switch { + case path == "": + return RenderHome() + case c == 2 && parts[0] == "player": + if gameState.Ended { + return ufmt.Sprintf("🔴 Game has not started yet.\n\n Call [`StartGame()`](%s) to start a new round.\n\n", gameState.StartGameLink) + } + addr := stringToAddress(parts[1]) + if addr == "" || !isPlayerInGame(addr) { + return "Address not found in game. You need to buy keys first to view your stats.\n\n" + } + keys, dividends := GetPlayerInfo(parts[1]) + return RenderPlayer(addr, keys, dividends) + default: + return "404: Invalid path\n\n" + } +} diff --git a/examples/gno.land/r/stefann/fomo3d/fomo3d_test.gno b/examples/gno.land/r/stefann/fomo3d/fomo3d_test.gno new file mode 100644 index 00000000000..29f2a9b07a9 --- /dev/null +++ b/examples/gno.land/r/stefann/fomo3d/fomo3d_test.gno @@ -0,0 +1,294 @@ +package fomo3d + +import ( + "std" + "testing" + + "gno.land/p/demo/avl" + "gno.land/p/demo/grc/grc721" + "gno.land/p/demo/ownable" + "gno.land/p/demo/testutils" + "gno.land/p/demo/urequire" +) + +// Reset game state +func setupTestGame(t *testing.T) { + gameState = GameState{ + StartBlock: 0, + EndBlock: 0, + LastKeyBlock: 0, + LastBuyer: "", + Jackpot: 0, + KeyPrice: MIN_KEY_PRICE, + TotalKeys: 0, + Ended: true, + CurrentRound: 0, + NextPot: 0, + OwnerFee: 0, + } + players = avl.NewTree() + Ownable = ownable.New() +} + +// Test ownership functionality +func TestOwnership(t *testing.T) { + owner := testutils.TestAddress("owner") + nonOwner := testutils.TestAddress("nonOwner") + + // Set up initial owner + std.TestSetOrigCaller(owner) + std.TestSetOrigPkgAddr(owner) + setupTestGame(t) + + // Transfer ownership to nonOwner first to test ownership functions + std.TestSetOrigCaller(owner) + urequire.NotPanics(t, func() { + Ownable.TransferOwnership(nonOwner) + }) + + // Test fee accumulation + StartGame() + payment := MIN_KEY_PRICE * 10 + std.TestSetOrigCaller(owner) + std.TestSetOrigSend(std.Coins{{"ugnot", payment}}, nil) + std.TestIssueCoins(owner, std.Coins{{"ugnot", payment}}) + std.TestIssueCoins(std.GetOrigPkgAddr(), std.Coins{{"ugnot", payment}}) + BuyKeys() + + // Verify fee accumulation + _, fees := GetOwnerInfo() + expectedFees := payment * OWNER_FEE_PERCENT / 100 + urequire.Equal(t, expectedFees, fees) + + // Test unauthorized fee claim (using old owner) + std.TestSetOrigCaller(owner) + urequire.PanicsWithMessage(t, "ownable: caller is not owner", ClaimOwnerFee) + + // Test authorized fee claim (using new owner) + std.TestSetOrigCaller(nonOwner) + initialBalance := std.GetBanker(std.BankerTypeRealmSend).GetCoins(nonOwner) + std.TestIssueCoins(std.CurrentRealm().Addr(), std.Coins{{"ugnot", expectedFees}}) + urequire.NotPanics(t, ClaimOwnerFee) + + // Verify fees were claimed + _, feesAfter := GetOwnerInfo() + urequire.Equal(t, int64(0), feesAfter) + + finalBalance := std.GetBanker(std.BankerTypeRealmSend).GetCoins(nonOwner) + urequire.Equal(t, initialBalance.AmountOf("ugnot")+expectedFees, finalBalance.AmountOf("ugnot")) +} + +// Test full game flow +func TestFullGameFlow(t *testing.T) { + setupTestGame(t) + + player1 := testutils.TestAddress("player1") + player2 := testutils.TestAddress("player2") + player3 := testutils.TestAddress("player3") + + // Test initial state + urequire.Equal(t, int64(0), gameState.CurrentRound) + urequire.Equal(t, MIN_KEY_PRICE, gameState.KeyPrice) + urequire.Equal(t, true, gameState.Ended) + + // Start game + urequire.NotPanics(t, StartGame) + urequire.Equal(t, false, gameState.Ended) + urequire.Equal(t, std.GetHeight(), gameState.StartBlock) + urequire.Equal(t, int64(1), gameState.CurrentRound) + + t.Run("buying keys", func(t *testing.T) { + // Test insufficient payment + std.TestSetOrigCaller(player1) + std.TestIssueCoins(player1, std.Coins{{"ugnot", MIN_KEY_PRICE - 1}}) + std.TestSetOrigSend(std.Coins{{"ugnot", MIN_KEY_PRICE - 1}}, nil) + std.TestIssueCoins(std.GetOrigPkgAddr(), std.Coins{{"ugnot", MIN_KEY_PRICE - 1}}) + urequire.PanicsWithMessage(t, ErrInsufficientPayment.Error(), BuyKeys) + + // Test successful key purchase + payment := MIN_KEY_PRICE * 3 + std.TestSetOrigSend(std.Coins{{"ugnot", payment}}, nil) + std.TestIssueCoins(std.GetOrigPkgAddr(), std.Coins{{"ugnot", payment}}) + + currentBlock := std.GetHeight() + urequire.NotPanics(t, BuyKeys) + + // Verify time extension + _, endBlock, _, _, _, _, _, _, _, _ := GetGameState() + urequire.Equal(t, currentBlock+TIME_EXTENSION, endBlock) + + // Verify player state + keys, dividends := GetPlayerInfo(player1.String()) + + urequire.Equal(t, int64(3), keys) + urequire.Equal(t, int64(0), dividends) + urequire.Equal(t, player1, gameState.LastBuyer) + + // Verify game state + _, endBlock, _, buyer, pot, price, keys, isEnded, nextPot, round := GetGameState() + urequire.Equal(t, player1, buyer) + urequire.Equal(t, int64(3), keys) + urequire.Equal(t, false, isEnded) + + urequire.Equal(t, payment*JACKPOT_PERCENT/100, pot) + + // Verify owner fee + _, ownerFees := GetOwnerInfo() + urequire.Equal(t, payment*OWNER_FEE_PERCENT/100, ownerFees) + }) + + t.Run("dividend distribution and claiming", func(t *testing.T) { + // Player 2 buys keys + std.TestSetOrigCaller(player2) + payment := gameState.KeyPrice * 2 // Buy 2 keys using current keyPrice + std.TestSetOrigSend(std.Coins{{"ugnot", payment}}, nil) + std.TestIssueCoins(std.GetOrigPkgAddr(), std.Coins{{"ugnot", payment}}) + urequire.NotPanics(t, BuyKeys) + + // Check player1 received dividends + keys1, dividends1 := GetPlayerInfo(player1.String()) + + urequire.Equal(t, int64(3), keys1) + expectedDividends := payment * DIVIDENDS_PERCENT / 100 * 3 / gameState.TotalKeys + urequire.Equal(t, expectedDividends, dividends1) + + // Test claiming dividends + { + // Player1 claims dividends + std.TestSetOrigCaller(player1) + initialBalance := std.GetBanker(std.BankerTypeRealmSend).GetCoins(player1) + urequire.NotPanics(t, ClaimDividends) + + // Verify dividends were claimed + _, dividendsAfter := GetPlayerInfo(player1.String()) + urequire.Equal(t, int64(0), dividendsAfter) + + lastBuyerBalance := std.GetBanker(std.BankerTypeRealmSend).GetCoins(player1) + urequire.Equal(t, initialBalance.AmountOf("ugnot")+expectedDividends, lastBuyerBalance.AmountOf("ugnot")) + } + }) + + t.Run("game ending", func(t *testing.T) { + // Try ending too early + urequire.PanicsWithMessage(t, ErrGameNotInProgress.Error(), EndGame) + + // Skip to end of current time window + currentEndBlock := gameState.EndBlock + std.TestSkipHeights(currentEndBlock - std.GetHeight() + 1) + + // End game successfully + urequire.NotPanics(t, EndGame) + urequire.Equal(t, true, gameState.Ended) + urequire.Equal(t, int64(1), gameState.CurrentRound) + + // Verify winner received jackpot + lastBuyerBalance := std.GetBanker(std.BankerTypeRealmSend).GetCoins(gameState.LastBuyer) + urequire.Equal(t, gameState.Jackpot, lastBuyerBalance.AmountOf("ugnot")) + + // Verify NFT was minted to winner + balance, err := BalanceOf(gameState.LastBuyer) + urequire.NoError(t, err) + urequire.Equal(t, uint64(1), balance) + + // Check NFT metadata + tokenID := grc721.TokenID("1") + metadata, err := TokenMetadata(tokenID) + + urequire.NoError(t, err) + urequire.Equal(t, "Fomo3D Winner - Round #1", metadata.Name) + }) + + // Test new round + t.Run("new round", func(t *testing.T) { + // Calculate expected next pot from previous round + payment1 := MIN_KEY_PRICE * 3 + // After buying 3 keys, price increased by 3% (1% per key) + secondKeyPrice := MIN_KEY_PRICE + (MIN_KEY_PRICE * 3 / 100) + payment2 := secondKeyPrice * 2 + expectedNextPot := (payment1 * NEXT_ROUND_POT / 100) + (payment2 * NEXT_ROUND_POT / 100) + + // Start new round + urequire.NotPanics(t, StartGame) + urequire.Equal(t, false, gameState.Ended) + urequire.Equal(t, int64(2), gameState.CurrentRound) + + start, end, last, buyer, pot, price, keys, isEnded, nextPot, round := GetGameState() + urequire.Equal(t, int64(2), round) + urequire.Equal(t, expectedNextPot, pot) + urequire.Equal(t, int64(0), nextPot) + }) +} + +// Test individual components +func TestStartGame(t *testing.T) { + setupTestGame(t) + + // Test starting first game + urequire.NotPanics(t, StartGame) + urequire.Equal(t, false, gameState.Ended) + urequire.Equal(t, std.GetHeight(), gameState.StartBlock) + + // Test cannot start while game in progress + urequire.PanicsWithMessage(t, ErrGameInProgress.Error(), StartGame) +} + +func TestBuyKeys(t *testing.T) { + setupTestGame(t) + StartGame() + + player := testutils.TestAddress("player") + std.TestSetOrigCaller(player) + + // Test invalid coin denomination + std.TestIssueCoins(player, std.Coins{{"invalid", MIN_KEY_PRICE}}) + std.TestSetOrigSend(std.Coins{{"invalid", MIN_KEY_PRICE}}, nil) + std.TestIssueCoins(std.GetOrigPkgAddr(), std.Coins{{"invalid", MIN_KEY_PRICE}}) + urequire.PanicsWithMessage(t, ErrInvalidPayment.Error(), BuyKeys) + + // Test multiple coin types + std.TestIssueCoins(player, std.Coins{{"ugnot", MIN_KEY_PRICE}, {"other", 100}}) + std.TestSetOrigSend(std.Coins{{"ugnot", MIN_KEY_PRICE}, {"other", 100}}, nil) + std.TestIssueCoins(std.GetOrigPkgAddr(), std.Coins{{"ugnot", MIN_KEY_PRICE}, {"other", 100}}) + urequire.PanicsWithMessage(t, ErrInvalidPayment.Error(), BuyKeys) + + // Test insufficient payment + std.TestIssueCoins(player, std.Coins{{"ugnot", MIN_KEY_PRICE - 1}}) + std.TestSetOrigSend(std.Coins{{"ugnot", MIN_KEY_PRICE - 1}}, nil) + std.TestIssueCoins(std.GetOrigPkgAddr(), std.Coins{{"ugnot", MIN_KEY_PRICE - 1}}) + urequire.PanicsWithMessage(t, ErrInsufficientPayment.Error(), BuyKeys) + + // Test successful purchase + std.TestIssueCoins(player, std.Coins{{"ugnot", MIN_KEY_PRICE * 2}}) + std.TestSetOrigSend(std.Coins{{"ugnot", MIN_KEY_PRICE * 2}}, nil) + std.TestIssueCoins(std.GetOrigPkgAddr(), std.Coins{{"ugnot", MIN_KEY_PRICE * 2}}) + urequire.NotPanics(t, BuyKeys) +} + +func TestClaimDividends(t *testing.T) { + setupTestGame(t) + StartGame() + + player := testutils.TestAddress("player") + std.TestSetOrigCaller(player) + + // Test claiming with no dividends + urequire.PanicsWithMessage(t, ErrNoDividendsToClaim.Error(), ClaimDividends) + + // Setup player with dividends + std.TestIssueCoins(player, std.Coins{{"ugnot", MIN_KEY_PRICE}}) + std.TestSetOrigSend(std.Coins{{"ugnot", MIN_KEY_PRICE}}, nil) + std.TestIssueCoins(std.GetOrigPkgAddr(), std.Coins{{"ugnot", MIN_KEY_PRICE}}) + BuyKeys() + + // Have another player buy to generate dividends + player2 := testutils.TestAddress("player2") + std.TestSetOrigCaller(player2) + std.TestIssueCoins(player2, std.Coins{{"ugnot", MIN_KEY_PRICE * 2}}) + std.TestSetOrigSend(std.Coins{{"ugnot", MIN_KEY_PRICE * 2}}, nil) + std.TestIssueCoins(std.GetOrigPkgAddr(), std.Coins{{"ugnot", MIN_KEY_PRICE * 2}}) + BuyKeys() + + // Test successful claim + std.TestSetOrigCaller(player) + urequire.NotPanics(t, ClaimDividends) +} diff --git a/examples/gno.land/r/stefann/fomo3d/gno.mod b/examples/gno.land/r/stefann/fomo3d/gno.mod new file mode 100644 index 00000000000..1b4e630a285 --- /dev/null +++ b/examples/gno.land/r/stefann/fomo3d/gno.mod @@ -0,0 +1 @@ +module gno.land/r/stefann/fomo3d diff --git a/examples/gno.land/r/stefann/fomo3d/nft.gno b/examples/gno.land/r/stefann/fomo3d/nft.gno new file mode 100644 index 00000000000..adea2fee795 --- /dev/null +++ b/examples/gno.land/r/stefann/fomo3d/nft.gno @@ -0,0 +1,88 @@ +package fomo3d + +import ( + "std" + "strconv" + + "gno.land/p/demo/grc/grc721" +) + +var ( + fomo3dNFT = grc721.NewNFTWithMetadata("Fomo3D Winner", "FOMO") +) + +// Public getters + +func Name() string { + return fomo3dNFT.Name() +} + +func Symbol() string { + return fomo3dNFT.Symbol() +} + +func BalanceOf(owner std.Address) (uint64, error) { + return fomo3dNFT.BalanceOf(owner) +} + +func OwnerOf(tokenID grc721.TokenID) (std.Address, error) { + return fomo3dNFT.OwnerOf(tokenID) +} + +func TokenMetadata(tokenID grc721.TokenID) (grc721.Metadata, error) { + return fomo3dNFT.TokenMetadata(tokenID) +} + +// Transfer and approval methods + +func TransferFrom(from, to std.Address, tokenID grc721.TokenID) error { + return fomo3dNFT.TransferFrom(from, to, tokenID) +} + +func SafeTransferFrom(from, to std.Address, tokenID grc721.TokenID) error { + return fomo3dNFT.SafeTransferFrom(from, to, tokenID) +} + +func Approve(approved std.Address, tokenID grc721.TokenID) error { + return fomo3dNFT.Approve(approved, tokenID) +} + +func GetApproved(tokenID grc721.TokenID) (std.Address, error) { + return fomo3dNFT.GetApproved(tokenID) +} + +func SetApprovalForAll(operator std.Address, approved bool) error { + return fomo3dNFT.SetApprovalForAll(operator, approved) +} + +func IsApprovedForAll(owner, operator std.Address) bool { + return fomo3dNFT.IsApprovedForAll(owner, operator) +} + +// Mints a new NFT for the round winner +func mintRoundWinnerNFT(winner std.Address, roundNumber int64) error { + if winner == "" { + return ErrZeroAddress + } + + roundStr := strconv.FormatInt(roundNumber, 10) + tokenID := grc721.TokenID(roundStr) + + // Create metadata + metadata := grc721.Metadata{ + Name: "Fomo3D Winner - Round #" + roundStr, + Description: "Winner of Fomo3D round #" + roundStr, + Image: "https://ipfs.io/ipfs/bafybeidayyli6bpewkhgtwqpgubmo77kmgjn4r5zq2i7usoyadcmvynhhq", + ExternalURL: "https://gno.land/r/stefann/fomo3d:round/" + roundStr, // TODO: Add this render in main realm that shows details of specific round + Attributes: []grc721.Trait{}, + BackgroundColor: "2D2D2D", // Dark theme background + } + + if err := fomo3dNFT.Mint(winner, tokenID); err != nil { + return err + } + + fomo3dNFT.SetTokenMetadata(tokenID, metadata) + + return nil +} diff --git a/examples/gno.land/r/stefann/fomo3d/render.gno b/examples/gno.land/r/stefann/fomo3d/render.gno new file mode 100644 index 00000000000..ba0c7b8f147 --- /dev/null +++ b/examples/gno.land/r/stefann/fomo3d/render.gno @@ -0,0 +1,138 @@ +package fomo3d + +import ( + "std" + "strconv" + "strings" + + "gno.land/p/demo/grc/grc721" + "gno.land/p/demo/ufmt" + + "gno.land/r/demo/users" +) + +// RenderHome renders the main game state +func RenderHome() string { + var builder strings.Builder + builder.WriteString("# FOMO3D - The Ultimate Game of Greed\n\n") + + // About section + builder.WriteString("## About the Game\n\n") + builder.WriteString("FOMO3D is a game that combines elements of lottery and investment mechanics. ") + builder.WriteString("Players purchase keys using GNOT tokens, where each key purchase:\n\n") + builder.WriteString("* Extends the game timer\n") + builder.WriteString("* Increases the key price by 1%\n") + builder.WriteString("* Makes you the potential winner of the jackpot\n") + builder.WriteString("* Distributes dividends to all key holders\n\n") + builder.WriteString("## How to Win\n\n") + builder.WriteString("* Be the last person to buy a key before the timer expires!\n\n") + builder.WriteString("**Rewards Distribution:**\n") + builder.WriteString("* 47% goes to the jackpot (for the winner)\n") + builder.WriteString("* 28% distributed as dividends to all key holders\n") + builder.WriteString("* 20% goes to next round's starting pot\n") + builder.WriteString("* 5% development fee for continuous improvement\n\n") + + // Play Game section + builder.WriteString("## How to Play\n\n") + builder.WriteString(ufmt.Sprintf("1. **Buy Keys** - Send GNOT to this realm with function [`BuyKeys()`](%s)\n", gameState.BuyKeysLink)) + builder.WriteString(ufmt.Sprintf("2. **Collect Dividends** - Call [`ClaimDividends()`](%s) to collect your earnings\n", gameState.ClaimDividendsLink)) + builder.WriteString("3. **Check Your Stats** - Append `:player/` followed by your address or namespace to the current URL to view your keys and dividends\n") + if gameState.Ended { + builder.WriteString(ufmt.Sprintf("4. **Start New Round** - Call [`StartGame()`](%s) to begin a new round\n", gameState.StartGameLink)) + } + builder.WriteString("\n") + + // Game Status section + builder.WriteString("## Game Status\n\n") + if gameState.StartBlock == 0 { + builder.WriteString("🔴 Game has not started yet.\n\n") + } else { + if gameState.Ended { + builder.WriteString("🔴 **Game Status:** Ended\n") + builder.WriteString(ufmt.Sprintf("🏆 **Winner:** %s\n\n", gameState.LastBuyer)) + } else { + builder.WriteString("🟢 **Game Status:** Active\n\n") + builder.WriteString(ufmt.Sprintf("🔄 **Round:** %d\n\n", gameState.CurrentRound)) + builder.WriteString(ufmt.Sprintf("⏱️ **Time Remaining:** %d blocks\n\n", gameState.EndBlock-std.GetHeight())) + } + builder.WriteString(ufmt.Sprintf("💰 **Jackpot:** %d ugnot\n\n", gameState.Jackpot)) + builder.WriteString(ufmt.Sprintf("🔑 **Key Price:** %d ugnot\n\n", gameState.KeyPrice)) + builder.WriteString(ufmt.Sprintf("📊 **Total Keys:** %d\n\n", gameState.TotalKeys)) + builder.WriteString(ufmt.Sprintf("👤 **Last Buyer:** %s\n\n", getDisplayName(gameState.LastBuyer))) + builder.WriteString(ufmt.Sprintf("🎮 **Next Round Pot:** %d ugnot\n\n", gameState.NextPot)) + } + + // Separator before less important sections + builder.WriteString("---\n\n") + + // Vote For Me section + builder.WriteString("### Vote For Us! 🗳️\n\n") + builder.WriteString("If you enjoy playing FOMO3D, please consider upvoting this game in the [Hall of Realms](https://gno.land/r/leon/hof)!\n\n") + builder.WriteString("Your support helps more players discover the game and grow our community! 🚀\n\n") + + // Report Bug section + builder.WriteString("### Report a Bug 🪲\n\n") + builder.WriteString("Something unusual happened? Help us improve the game by reporting bugs!\n") + builder.WriteString("[Visit our GitHub repository](https://github.com/gnolang/gno/issues)\n\n") + builder.WriteString("Please include:\n") + builder.WriteString("* Detailed description of what happened\n") + builder.WriteString("* Transaction hash (if applicable)\n") + builder.WriteString("* Your address\n") + builder.WriteString("* Current round number\n") + + return builder.String() +} + +// RenderPlayer renders specific player information +func RenderPlayer(addr std.Address, keys int64, dividends int64) string { + var builder strings.Builder + displayName := getDisplayName(addr) + builder.WriteString(ufmt.Sprintf("# Player Stats: %s\n\n", displayName)) + builder.WriteString("## Your Holdings\n\n") + builder.WriteString(ufmt.Sprintf("🔑 **Keys Owned:** %d\n\n", keys)) + builder.WriteString(ufmt.Sprintf("💰 **Unclaimed Dividends:** %d ugnot\n\n", dividends)) + + // Check if player has any NFTs + nftBalance, err := BalanceOf(addr) + if err == nil && nftBalance > 0 { + builder.WriteString("## Your Victory NFTs 🏆\n\n") + + // Iterate through all rounds up to current round to find player's NFTs + for i := int64(1); i <= gameState.CurrentRound; i++ { + tokenID := grc721.TokenID(strconv.FormatInt(i, 10)) + owner, err := OwnerOf(tokenID) + if err == nil && owner == addr { + metadata, err := TokenMetadata(tokenID) + if err == nil { + builder.WriteString(ufmt.Sprintf("### Round #%d Winner\n", i)) + builder.WriteString(ufmt.Sprintf("![NFT](%s)\n\n", metadata.Image)) + builder.WriteString("---\n\n") + } + } + } + } + + builder.WriteString("## Actions\n\n") + builder.WriteString(ufmt.Sprintf("* To buy more keys, send GNOT to this realm with [`BuyKeys()`](%s)\n", gameState.BuyKeysLink)) + if dividends > 0 { + builder.WriteString("* You have unclaimed dividends! Call `ClaimDividends()` to collect them\n") + } + + return builder.String() +} + +// Helper to get display name - just returns namespace if exists, otherwise address +func getDisplayName(addr std.Address) string { + if user := users.GetUserByAddress(addr); user != nil { + return user.Name + } + return addr.String() +} + +// UpdateFunctionLinks updates the links for game functions +func UpdateFunctionLinks(buyKeysLink string, claimDividendsLink string, startGameLink string) { + Ownable.AssertCallerIsOwner() + gameState.BuyKeysLink = buyKeysLink + gameState.ClaimDividendsLink = claimDividendsLink + gameState.StartGameLink = startGameLink +} From 80c68ea4fd77531b984dbe4a0029588601eaabb8 Mon Sep 17 00:00:00 2001 From: Norman Date: Mon, 3 Feb 2025 17:38:04 +0100 Subject: [PATCH 101/143] fix: hardcoded dev path in test Signed-off-by: Norman --- gnovm/cmd/gno/testdata/lint/bad_import.txtar | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/gnovm/cmd/gno/testdata/lint/bad_import.txtar b/gnovm/cmd/gno/testdata/lint/bad_import.txtar index 836374999bd..e3dccf063a5 100644 --- a/gnovm/cmd/gno/testdata/lint/bad_import.txtar +++ b/gnovm/cmd/gno/testdata/lint/bad_import.txtar @@ -3,7 +3,7 @@ ! gno tool lint ./bad_file.gno cmp stdout stdout.golden -cmp stderr stderr.golden +stderr 'bad_file\.gno:3:8: package python is not in std \(.+\) \(code=5\)' -- bad_file.gno -- package main @@ -17,6 +17,4 @@ func main() { -- gno.mod -- module gno.land/p/test --- stdout.golden -- --- stderr.golden -- -bad_file.gno:3:8: package python is not in std (/Users/norman/Code/gno/gnovm/stdlibs/python) (code=5) +-- stdout.golden -- \ No newline at end of file From 19e7f5fc89f9dfc598ff069146d2133b656088a1 Mon Sep 17 00:00:00 2001 From: Norman Date: Mon, 3 Feb 2025 20:20:34 +0100 Subject: [PATCH 102/143] fix: mod graph Signed-off-by: Norman --- gnovm/cmd/gno/mod.go | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/gnovm/cmd/gno/mod.go b/gnovm/cmd/gno/mod.go index 058f6c121fa..28cb6f05323 100644 --- a/gnovm/cmd/gno/mod.go +++ b/gnovm/cmd/gno/mod.go @@ -172,7 +172,7 @@ func (c *modGraphCfg) RegisterFlags(fs *flag.FlagSet) { func execModGraph(cfg *modGraphCfg, args []string, io commands.IO) error { // default to current directory if no args provided if len(args) == 0 { - args = []string{"./..."} + args = []string{"."} } if len(args) > 1 { return flag.ErrHelp @@ -180,16 +180,18 @@ func execModGraph(cfg *modGraphCfg, args []string, io commands.IO) error { stdout := io.Out() - pkgs, err := packages.Load(&packages.LoadConfig{}, args...) + pkgs, err := packages.Load(&packages.LoadConfig{Fetcher: testPackageFetcher}, args...) if err != nil { return err } - for _, pkg := range pkgs { - for _, imp := range pkg.Imports[packages.FileKindPackageSource] { - fmt.Fprintf(stdout, "%s %s\n", pkg.ImportPath, imp) + + return pkgs.Explore(pkgs.Matches().PkgPaths(), func(p *packages.Package) ([]string, error) { + imports := p.Imports[packages.FileKindPackageSource] + for _, imp := range imports { + fmt.Fprintf(stdout, "%s %s\n", p.ImportPath, imp) } - } - return nil + return imports, nil + }) } func execModDownload(cfg *modDownloadCfg, args []string, io commands.IO) error { From babff8fc1f4018b83dc5dc00862701adf1f819bd Mon Sep 17 00:00:00 2001 From: Norman Date: Mon, 3 Feb 2025 20:20:54 +0100 Subject: [PATCH 103/143] chore: miss push Signed-off-by: Norman --- gnovm/pkg/packages/pkglist.go | 87 ++++++++++++++++++++++++++++++++++- 1 file changed, 86 insertions(+), 1 deletion(-) diff --git a/gnovm/pkg/packages/pkglist.go b/gnovm/pkg/packages/pkglist.go index 5258401241a..ce9c9ebf01b 100644 --- a/gnovm/pkg/packages/pkglist.go +++ b/gnovm/pkg/packages/pkglist.go @@ -1,8 +1,10 @@ package packages import ( + "errors" "fmt" "os" + "slices" "github.com/davecgh/go-spew/spew" "github.com/gnolang/gno/gnovm" @@ -25,6 +27,89 @@ func (pl PkgList) Get(pkgPath string) *Package { return nil } +func (pl PkgList) PkgPaths() []string { + res := make([]string, 0, len(pl)) + for _, pkg := range pl { + res = append(res, pkg.ImportPath) + } + return res +} + +func (pl PkgList) Traverse(roots []string, filekinds []FileKind, cb func(p *Package) error) error { + visited := []string{} + + for _, root := range roots { + if err := pl.traverseVisit(&visited, root, filekinds, cb); err != nil { + return err + } + } + + return nil +} + +func (pl PkgList) traverseVisit(visited *[]string, pkgPath string, filekinds []FileKind, cb func(p *Package) error) error { + if slices.Contains(*visited, pkgPath) { + return nil + } + *visited = append(*visited, pkgPath) + + pkg := pl.Get(pkgPath) + if pkg == nil { + return fmt.Errorf("%s: %w", pkgPath, ErrPackageNotFound) + } + + if err := cb(pkg); err != nil { + return err + } + + for _, imp := range pkg.ImportsSpecs.Merge(filekinds...) { + if err := pl.traverseVisit(visited, imp.PkgPath, filekinds, cb); err != nil { + return err + } + } + + return nil +} + +func (pl PkgList) Explore(roots []string, cb func(p *Package) ([]string, error)) error { + visited := []string{} + + for _, root := range roots { + if err := pl.exploreVisit(&visited, root, cb); err != nil { + return err + } + } + + return nil +} + +func (pl PkgList) exploreVisit(visited *[]string, pkgPath string, cb func(p *Package) ([]string, error)) error { + if slices.Contains(*visited, pkgPath) { + return nil + } + *visited = append(*visited, pkgPath) + + pkg := pl.Get(pkgPath) + if pkg == nil { + return fmt.Errorf("%s: %w", pkgPath, ErrPackageNotFound) + } + + next, err := cb(pkg) + if err != nil { + return err + } + + for _, imp := range next { + if err := pl.exploreVisit(visited, imp, cb); err != nil { + return err + } + } + + return nil +} + +var ErrPackageNotFound = errors.New("package not found") + func (pl PkgList) GetByDir(dir string) *Package { for _, p := range pl { if p.Dir == dir { @@ -34,7 +119,7 @@ func (pl PkgList) GetByDir(dir string) *Package { return nil } -func (pl PkgList) Roots() PkgList { +func (pl PkgList) Matches() PkgList { res := PkgList{} for _, p := range pl { if len(p.Match) == 0 { From 4686c7ccff86feba739172845126cc959e6e2351 Mon Sep 17 00:00:00 2001 From: Norman Date: Mon, 3 Feb 2025 20:42:55 +0100 Subject: [PATCH 104/143] fix: allow to ignore stdlibs in sort and sort mempkg files Signed-off-by: Norman --- gno.land/pkg/gnoland/genesis.go | 2 +- gno.land/pkg/integration/pkgloader.go | 2 +- gnovm/cmd/gno/list.go | 2 +- gnovm/cmd/gno/mod.go | 2 +- gnovm/pkg/packages/load.go | 4 ++++ gnovm/pkg/packages/load_test.go | 4 ++-- gnovm/pkg/packages/pkglist.go | 12 ++++++++---- 7 files changed, 18 insertions(+), 10 deletions(-) diff --git a/gno.land/pkg/gnoland/genesis.go b/gno.land/pkg/gnoland/genesis.go index 2b283f5a17f..009005143c7 100644 --- a/gno.land/pkg/gnoland/genesis.go +++ b/gno.land/pkg/gnoland/genesis.go @@ -142,7 +142,7 @@ func LoadPackagesFromDir(cfg *packages.LoadConfig, dir string, creator bft.Addre } // Sort packages by dependencies. - sortedPkgs, err := pkgs.Sort() + sortedPkgs, err := pkgs.Sort(false) if err != nil { return nil, fmt.Errorf("sorting packages: %w", err) } diff --git a/gno.land/pkg/integration/pkgloader.go b/gno.land/pkg/integration/pkgloader.go index ff2d5dcafde..a79901dfb3c 100644 --- a/gno.land/pkg/integration/pkgloader.go +++ b/gno.land/pkg/integration/pkgloader.go @@ -42,7 +42,7 @@ func (pl *PkgsLoader) SetPatch(replace, with string) { } func (pl *PkgsLoader) LoadPackages(creatorKey crypto.PrivKey, fee std.Fee, deposit std.Coins) ([]gnoland.TxWithMetadata, error) { - pkgslist, err := pl.List().Sort() // sorts packages by their dependencies. + pkgslist, err := pl.List().Sort(true) // sorts packages by their dependencies. if err != nil { return nil, fmt.Errorf("unable to sort packages: %w", err) } diff --git a/gnovm/cmd/gno/list.go b/gnovm/cmd/gno/list.go index dcf8591d9f7..6ca32b3eb4e 100644 --- a/gnovm/cmd/gno/list.go +++ b/gnovm/cmd/gno/list.go @@ -64,7 +64,7 @@ func execList(cfg *listCfg, args []string, io commands.IO) error { } // try sort - sorted, err := pkgs.Sort() + sorted, err := pkgs.Sort(false) if err == nil { pkgs = packages.PkgList(sorted) } diff --git a/gnovm/cmd/gno/mod.go b/gnovm/cmd/gno/mod.go index 28cb6f05323..8d9a8a6ec95 100644 --- a/gnovm/cmd/gno/mod.go +++ b/gnovm/cmd/gno/mod.go @@ -180,7 +180,7 @@ func execModGraph(cfg *modGraphCfg, args []string, io commands.IO) error { stdout := io.Out() - pkgs, err := packages.Load(&packages.LoadConfig{Fetcher: testPackageFetcher}, args...) + pkgs, err := packages.Load(&packages.LoadConfig{Fetcher: testPackageFetcher, Deps: true}, args...) if err != nil { return err } diff --git a/gnovm/pkg/packages/load.go b/gnovm/pkg/packages/load.go index 525ff6acfb6..a535346deb1 100644 --- a/gnovm/pkg/packages/load.go +++ b/gnovm/pkg/packages/load.go @@ -7,6 +7,7 @@ import ( "os" "path/filepath" "slices" + "sort" "strings" "github.com/gnolang/gno/gnovm" @@ -240,6 +241,9 @@ func (p *Package) MemPkg() (*gnovm.MemPackage, error) { }) } } + sort.Slice(files, func(i int, j int) bool { + return files[i].Name < files[j].Name + }) return &gnovm.MemPackage{ Name: p.Name, Path: p.ImportPath, diff --git a/gnovm/pkg/packages/load_test.go b/gnovm/pkg/packages/load_test.go index 27b19dea03a..32134ae4af9 100644 --- a/gnovm/pkg/packages/load_test.go +++ b/gnovm/pkg/packages/load_test.go @@ -84,7 +84,7 @@ func TestListAndNonDraftPkgs(t *testing.T) { } // Sort packages - sorted, err := pkgs.Sort() + sorted, err := pkgs.Sort(false) require.NoError(t, err) // Non draft packages @@ -153,7 +153,7 @@ func TestSortPkgs(t *testing.T) { }, } { t.Run(tc.desc, func(t *testing.T) { - sorted, err := tc.in.Sort() + sorted, err := tc.in.Sort(false) if tc.shouldErr { assert.Error(t, err) } else { diff --git a/gnovm/pkg/packages/pkglist.go b/gnovm/pkg/packages/pkglist.go index ce9c9ebf01b..e3c2368b40b 100644 --- a/gnovm/pkg/packages/pkglist.go +++ b/gnovm/pkg/packages/pkglist.go @@ -144,14 +144,14 @@ func (pl PkgList) GetMemPackage(pkgPath string) *gnovm.MemPackage { } // sortPkgs sorts the given packages by their dependencies. -func (pl PkgList) Sort() (SortedPkgList, error) { +func (pl PkgList) Sort(ignoreStdlibs bool) (SortedPkgList, error) { visited := make(map[string]bool) onStack := make(map[string]bool) sortedPkgs := make([]*Package, 0, len(pl)) // Visit all packages for _, p := range pl { - if err := visitPackage(p, pl, visited, onStack, &sortedPkgs); err != nil { + if err := visitPackage(ignoreStdlibs, p, pl, visited, onStack, &sortedPkgs); err != nil { return nil, err } } @@ -160,7 +160,7 @@ 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 *Package, pkgs []*Package, visited, onStack map[string]bool, sortedPkgs *[]*Package) error { +func visitPackage(ignoreStdlibs bool, pkg *Package, pkgs []*Package, visited, onStack map[string]bool, sortedPkgs *[]*Package) error { if onStack[pkg.ImportPath] { return fmt.Errorf("cycle detected: %s", pkg.ImportPath) } @@ -173,12 +173,16 @@ func visitPackage(pkg *Package, pkgs []*Package, visited, onStack map[string]boo // Visit package's dependencies for _, imp := range pkg.Imports[FileKindPackageSource] { + if gnolang.IsStdlib(imp) && ignoreStdlibs { + continue + } + found := false for _, p := range pkgs { if p.ImportPath != imp { continue } - if err := visitPackage(p, pkgs, visited, onStack, sortedPkgs); err != nil { + if err := visitPackage(ignoreStdlibs, p, pkgs, visited, onStack, sortedPkgs); err != nil { return err } found = true From f7d3de88ac6168aaa5d31aa74d9cc2b6ccd57185 Mon Sep 17 00:00:00 2001 From: Norman Date: Mon, 3 Feb 2025 20:49:33 +0100 Subject: [PATCH 105/143] fix: ignore stdlibs in genesis gen Signed-off-by: Norman --- gno.land/pkg/gnoland/genesis.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gno.land/pkg/gnoland/genesis.go b/gno.land/pkg/gnoland/genesis.go index 009005143c7..5215d4988e1 100644 --- a/gno.land/pkg/gnoland/genesis.go +++ b/gno.land/pkg/gnoland/genesis.go @@ -142,7 +142,7 @@ func LoadPackagesFromDir(cfg *packages.LoadConfig, dir string, creator bft.Addre } // Sort packages by dependencies. - sortedPkgs, err := pkgs.Sort(false) + sortedPkgs, err := pkgs.Sort(true) if err != nil { return nil, fmt.Errorf("sorting packages: %w", err) } From 49235597d5ebb76e6524840d982bfdba6e208859 Mon Sep 17 00:00:00 2001 From: Norman Date: Mon, 3 Feb 2025 20:52:03 +0100 Subject: [PATCH 106/143] chore: add todo Signed-off-by: Norman --- gnovm/pkg/packages/load.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/gnovm/pkg/packages/load.go b/gnovm/pkg/packages/load.go index a535346deb1..ab3568778d9 100644 --- a/gnovm/pkg/packages/load.go +++ b/gnovm/pkg/packages/load.go @@ -20,6 +20,8 @@ import ( "golang.org/x/mod/module" ) +// FIXME: should not include pkgs imported in test except for matches and only when Test flag is set + type LoadConfig struct { IO commands.IO Fetcher pkgdownload.PackageFetcher From 98ccddda7f5e4b55de52a9bf1c734520882b728a Mon Sep 17 00:00:00 2001 From: Norman Date: Mon, 3 Feb 2025 21:18:13 +0100 Subject: [PATCH 107/143] fix: include other files in pkg Signed-off-by: Norman --- gnovm/pkg/packages/filekind.go | 7 ++++++- gnovm/pkg/packages/imports.go | 2 +- gnovm/pkg/packages/types.go | 2 +- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/gnovm/pkg/packages/filekind.go b/gnovm/pkg/packages/filekind.go index 88a8ca72e96..bd01cfa1fae 100644 --- a/gnovm/pkg/packages/filekind.go +++ b/gnovm/pkg/packages/filekind.go @@ -24,14 +24,19 @@ const ( FileKindTest = "Test" FileKindXTest = "XTest" FileKindFiletest = "Filetest" + FileKindOther = "Other" ) -func AllFileKinds() []FileKind { +func GnoFileKinds() []FileKind { return []FileKind{FileKindPackageSource, FileKindTest, FileKindXTest, FileKindFiletest} } // GetFileKind analyzes a file's name and body to get it's [FileKind], fset is optional func GetFileKind(filename string, body string, fset *token.FileSet) (FileKind, error) { + if filename == "LICENSE" || filename == "README.md" { + return FileKindOther, nil + } + if !strings.HasSuffix(filename, ".gno") { return FileKindUnknown, fmt.Errorf("%s:1:1: not a gno file", filename) } diff --git a/gnovm/pkg/packages/imports.go b/gnovm/pkg/packages/imports.go index 431990d9355..8168b2aad88 100644 --- a/gnovm/pkg/packages/imports.go +++ b/gnovm/pkg/packages/imports.go @@ -97,7 +97,7 @@ func (imap ImportsMap) ToStrings() map[FileKind][]string { // Merge merges imports, it removes duplicates and sorts the result func (imap ImportsMap) Merge(kinds ...FileKind) []*FileImport { if len(kinds) == 0 { - kinds = AllFileKinds() + kinds = GnoFileKinds() } res := make([]*FileImport, 0, 16) diff --git a/gnovm/pkg/packages/types.go b/gnovm/pkg/packages/types.go index b9b08bc01de..7c54c1ebd0c 100644 --- a/gnovm/pkg/packages/types.go +++ b/gnovm/pkg/packages/types.go @@ -97,7 +97,7 @@ type FilesMap map[FileKind][]string func (fm FilesMap) Size() int { total := 0 - for _, kind := range AllFileKinds() { + for _, kind := range GnoFileKinds() { total += len(fm[kind]) } return total From 52bcc4375feb6a0278e40206d51ddc002cb17e95 Mon Sep 17 00:00:00 2001 From: Norman Date: Mon, 3 Feb 2025 21:34:20 +0100 Subject: [PATCH 108/143] fix: include other files in mempkg Signed-off-by: Norman --- gnovm/pkg/packages/analyze_packages.go | 2 +- gnovm/pkg/packages/load.go | 6 ++---- gnovm/pkg/packages/pkglist.go | 3 --- gnovm/pkg/packages/types.go | 8 ++++---- 4 files changed, 7 insertions(+), 12 deletions(-) diff --git a/gnovm/pkg/packages/analyze_packages.go b/gnovm/pkg/packages/analyze_packages.go index c31bd064971..0964ce0cba3 100644 --- a/gnovm/pkg/packages/analyze_packages.go +++ b/gnovm/pkg/packages/analyze_packages.go @@ -107,7 +107,7 @@ func readPkgDir(pkgDir string, importPath string, fset *token.FileSet) *Package base := entry.Name() - if !strings.HasSuffix(base, ".gno") { + if !strings.HasSuffix(base, ".gno") && base != "LICENSE" && base != "README.md" { continue } diff --git a/gnovm/pkg/packages/load.go b/gnovm/pkg/packages/load.go index ab3568778d9..ec21475fb7b 100644 --- a/gnovm/pkg/packages/load.go +++ b/gnovm/pkg/packages/load.go @@ -8,7 +8,6 @@ import ( "path/filepath" "slices" "sort" - "strings" "github.com/gnolang/gno/gnovm" "github.com/gnolang/gno/gnovm/pkg/gnoenv" @@ -227,12 +226,11 @@ func listDepsRecursive(rootTarget *Package, target *Package, pkgs PackagesMap, d } func (p *Package) MemPkg() (*gnovm.MemPackage, error) { + // XXX: use gnolang.ReadMemPackageFromList + files := []*gnovm.MemFile{} for _, cat := range p.Files { for _, f := range cat { - if !strings.HasSuffix(f, ".gno") { - continue - } body, err := os.ReadFile(filepath.Join(p.Dir, f)) if err != nil { return nil, err diff --git a/gnovm/pkg/packages/pkglist.go b/gnovm/pkg/packages/pkglist.go index e3c2368b40b..087608af4c7 100644 --- a/gnovm/pkg/packages/pkglist.go +++ b/gnovm/pkg/packages/pkglist.go @@ -3,10 +3,8 @@ package packages import ( "errors" "fmt" - "os" "slices" - "github.com/davecgh/go-spew/spew" "github.com/gnolang/gno/gnovm" "github.com/gnolang/gno/gnovm/pkg/gnolang" ) @@ -137,7 +135,6 @@ func (pl PkgList) GetMemPackage(pkgPath string) *gnovm.MemPackage { } memPkg, err := pkg.MemPkg() if err != nil { - spew.Fdump(os.Stderr, "get err", err) panic(err) } return memPkg diff --git a/gnovm/pkg/packages/types.go b/gnovm/pkg/packages/types.go index 7c54c1ebd0c..076a9a004c9 100644 --- a/gnovm/pkg/packages/types.go +++ b/gnovm/pkg/packages/types.go @@ -18,10 +18,10 @@ type Package struct { Name string `json:",omitempty"` // package name Root string `json:",omitempty"` // Gno root, Gno path dir, or module root dir containing this package ModPath string - Match []string `json:",omitempty"` // command-line patterns matching this package - Errors []*Error `json:",omitempty"` // error loading this package (not dependencies) - Draft bool - Files FilesMap + Match []string `json:",omitempty"` // command-line patterns matching this package + Errors []*Error `json:",omitempty"` // error loading this package (not dependencies) + Draft bool `json:",omitempty"` + Files FilesMap `json:",omitempty"` Imports map[FileKind][]string `json:",omitempty"` // import paths used by this package Deps []string `json:",omitempty"` // all (recursively) imported dependencies From 31b92be32dbdd445bd4de44b73ff63bad7c9c253 Mon Sep 17 00:00:00 2001 From: Norman Date: Mon, 3 Feb 2025 21:47:28 +0100 Subject: [PATCH 109/143] =?UTF-8?q?fix:=20gnodev=C3=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Norman --- contribs/gnodev/pkg/dev/packages.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contribs/gnodev/pkg/dev/packages.go b/contribs/gnodev/pkg/dev/packages.go index 41ba499458d..ca9d4690bf5 100644 --- a/contribs/gnodev/pkg/dev/packages.go +++ b/contribs/gnodev/pkg/dev/packages.go @@ -123,7 +123,7 @@ func (pm PackagesMap) toList() packages.PkgList { func (pm PackagesMap) Load(fee std.Fee, start time.Time) ([]gnoland.TxWithMetadata, error) { pkgs := pm.toList() - sorted, err := pkgs.Sort() + sorted, err := pkgs.Sort(true) if err != nil { return nil, fmt.Errorf("unable to sort pkgs: %w", err) } @@ -142,7 +142,7 @@ func (pm PackagesMap) Load(fee std.Fee, start time.Time) ([]gnoland.TxWithMetada } // Open files in directory as MemPackage. - memPkg := gnolang.MustReadMemPackage(modPkg.Dir, modPkg.ImportPath) + memPkg := gnolang.MustReadMemPackage(modPkg.Dir, modPkg.ImportPath, nil) if err := memPkg.Validate(); err != nil { return nil, fmt.Errorf("invalid package: %w", err) } From df14762147e9673381f629b3f5fcc79be4e70a58 Mon Sep 17 00:00:00 2001 From: Blake <104744707+r3v4s@users.noreply.github.com> Date: Tue, 4 Feb 2025 19:03:58 +0900 Subject: [PATCH 110/143] feat: bump max-gas same as current genesis (#3681) ## Description Default gas for genesis has been bumped in #3384 However we still have certain config uses old value. > env(or config) between actual and testing should be same as much as possible to avoid something like... this works with real gnoland but fail with in memory node. --- gno.land/pkg/gnoland/node_inmemory.go | 8 ++++---- gno.land/pkg/integration/node_testing.go | 8 ++++---- tm2/pkg/bft/types/params.go | 2 +- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/gno.land/pkg/gnoland/node_inmemory.go b/gno.land/pkg/gnoland/node_inmemory.go index cc9e74a78d8..3fd3063f8b9 100644 --- a/gno.land/pkg/gnoland/node_inmemory.go +++ b/gno.land/pkg/gnoland/node_inmemory.go @@ -60,10 +60,10 @@ func NewDefaultGenesisConfig(chainid, chaindomain string) *bft.GenesisDoc { func defaultBlockParams() *abci.BlockParams { return &abci.BlockParams{ - MaxTxBytes: 1_000_000, // 1MB, - MaxDataBytes: 2_000_000, // 2MB, - MaxGas: 100_000_000, // 100M gas - TimeIotaMS: 100, // 100ms + MaxTxBytes: 1_000_000, // 1MB, + MaxDataBytes: 2_000_000, // 2MB, + MaxGas: 3_000_000_000, // 3B gas + TimeIotaMS: 100, // 100ms } } diff --git a/gno.land/pkg/integration/node_testing.go b/gno.land/pkg/integration/node_testing.go index edcf53de5d3..c613048ebd7 100644 --- a/gno.land/pkg/integration/node_testing.go +++ b/gno.land/pkg/integration/node_testing.go @@ -103,10 +103,10 @@ func DefaultTestingGenesisConfig(gnoroot string, self crypto.PubKey, tmconfig *t ChainID: tmconfig.ChainID(), ConsensusParams: abci.ConsensusParams{ Block: &abci.BlockParams{ - MaxTxBytes: 1_000_000, // 1MB, - MaxDataBytes: 2_000_000, // 2MB, - MaxGas: 100_000_000, // 100M gas - TimeIotaMS: 100, // 100ms + MaxTxBytes: 1_000_000, // 1MB, + MaxDataBytes: 2_000_000, // 2MB, + MaxGas: 3_000_000_000, // 3B gas + TimeIotaMS: 100, // 100ms }, }, Validators: []bft.GenesisValidator{ diff --git a/tm2/pkg/bft/types/params.go b/tm2/pkg/bft/types/params.go index c2e8f304698..323e12c25cd 100644 --- a/tm2/pkg/bft/types/params.go +++ b/tm2/pkg/bft/types/params.go @@ -24,7 +24,7 @@ const ( MaxBlockDataBytes int64 = 2000000 // 2MB // MaxBlockMaxGas is the max gas limit for the block - MaxBlockMaxGas int64 = 100000000 // 100M gas + MaxBlockMaxGas int64 = 3000000000 // 3B gas // BlockTimeIotaMS is the block time iota (in ms) BlockTimeIotaMS int64 = 100 // ms From f8470906b4b57c552546049f100a2d90d7d348da Mon Sep 17 00:00:00 2001 From: Manfred Touron <94029+moul@users.noreply.github.com> Date: Tue, 4 Feb 2025 13:07:45 +0100 Subject: [PATCH 111/143] chore(examples): improve p/moul/txlink (#3682) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Escape args properly. Related with #3668 --------- Signed-off-by: moul <94029+moul@users.noreply.github.com> Co-authored-by: Jerónimo Albi --- .../p/moul/helplink/helplink_test.gno | 8 ++--- examples/gno.land/p/moul/txlink/txlink.gno | 33 ++++++++++--------- .../gno.land/p/moul/txlink/txlink_test.gno | 11 ++++--- .../gno.land/r/demo/boards/z_0_filetest.gno | 4 +-- .../r/demo/boards/z_10_c_filetest.gno | 6 ++-- .../gno.land/r/demo/boards/z_10_filetest.gno | 2 +- .../r/demo/boards/z_11_d_filetest.gno | 8 ++--- .../gno.land/r/demo/boards/z_11_filetest.gno | 4 +-- .../gno.land/r/demo/boards/z_12_filetest.gno | 2 +- .../gno.land/r/demo/boards/z_2_filetest.gno | 4 +-- .../gno.land/r/demo/boards/z_3_filetest.gno | 4 +-- .../gno.land/r/demo/boards/z_4_filetest.gno | 6 ++-- .../gno.land/r/demo/boards/z_5_c_filetest.gno | 4 +-- .../gno.land/r/demo/boards/z_5_filetest.gno | 6 ++-- .../gno.land/r/demo/boards/z_6_filetest.gno | 8 ++--- .../gno.land/r/demo/boards/z_7_filetest.gno | 2 +- .../gno.land/r/demo/boards/z_8_filetest.gno | 4 +-- .../gno.land/r/demo/boards/z_9_filetest.gno | 2 +- .../gno.land/r/docs/buttons/buttons_test.gno | 2 +- .../gno.land/r/leon/hof/datasource_test.gno | 4 +-- 20 files changed, 65 insertions(+), 59 deletions(-) diff --git a/examples/gno.land/p/moul/helplink/helplink_test.gno b/examples/gno.land/p/moul/helplink/helplink_test.gno index 29cfd02eb67..440001b94ce 100644 --- a/examples/gno.land/p/moul/helplink/helplink_test.gno +++ b/examples/gno.land/p/moul/helplink/helplink_test.gno @@ -18,7 +18,7 @@ func TestFunc(t *testing.T) { {"Realm Example", "foo", []string{"bar", "1", "baz", "2"}, "[Realm Example](/r/lorem/ipsum$help&func=foo&bar=1&baz=2)", "gno.land/r/lorem/ipsum"}, {"Single Arg", "testFunc", []string{"key", "value"}, "[Single Arg]($help&func=testFunc&key=value)", ""}, {"No Args", "noArgsFunc", []string{}, "[No Args]($help&func=noArgsFunc)", ""}, - {"Odd Args", "oddArgsFunc", []string{"key"}, "[Odd Args]($help&func=oddArgsFunc)", ""}, + {"Odd Args", "oddArgsFunc", []string{"key"}, "[Odd Args]($help&func=oddArgsFunc&error=odd+number+of+arguments)", ""}, } for _, tt := range tests { @@ -39,15 +39,15 @@ func TestFuncURL(t *testing.T) { {"foo", []string{"bar", "1", "baz", "2"}, "$help&func=foo&bar=1&baz=2", ""}, {"testFunc", []string{"key", "value"}, "$help&func=testFunc&key=value", ""}, {"noArgsFunc", []string{}, "$help&func=noArgsFunc", ""}, - {"oddArgsFunc", []string{"key"}, "$help&func=oddArgsFunc", ""}, + {"oddArgsFunc", []string{"key"}, "$help&func=oddArgsFunc&error=odd+number+of+arguments", ""}, {"foo", []string{"bar", "1", "baz", "2"}, "/r/lorem/ipsum$help&func=foo&bar=1&baz=2", "gno.land/r/lorem/ipsum"}, {"testFunc", []string{"key", "value"}, "/r/lorem/ipsum$help&func=testFunc&key=value", "gno.land/r/lorem/ipsum"}, {"noArgsFunc", []string{}, "/r/lorem/ipsum$help&func=noArgsFunc", "gno.land/r/lorem/ipsum"}, - {"oddArgsFunc", []string{"key"}, "/r/lorem/ipsum$help&func=oddArgsFunc", "gno.land/r/lorem/ipsum"}, + {"oddArgsFunc", []string{"key"}, "/r/lorem/ipsum$help&func=oddArgsFunc&error=odd+number+of+arguments", "gno.land/r/lorem/ipsum"}, {"foo", []string{"bar", "1", "baz", "2"}, "https://gno.world/r/lorem/ipsum$help&func=foo&bar=1&baz=2", "gno.world/r/lorem/ipsum"}, {"testFunc", []string{"key", "value"}, "https://gno.world/r/lorem/ipsum$help&func=testFunc&key=value", "gno.world/r/lorem/ipsum"}, {"noArgsFunc", []string{}, "https://gno.world/r/lorem/ipsum$help&func=noArgsFunc", "gno.world/r/lorem/ipsum"}, - {"oddArgsFunc", []string{"key"}, "https://gno.world/r/lorem/ipsum$help&func=oddArgsFunc", "gno.world/r/lorem/ipsum"}, + {"oddArgsFunc", []string{"key"}, "https://gno.world/r/lorem/ipsum$help&func=oddArgsFunc&error=odd+number+of+arguments", "gno.world/r/lorem/ipsum"}, } for _, tt := range tests { diff --git a/examples/gno.land/p/moul/txlink/txlink.gno b/examples/gno.land/p/moul/txlink/txlink.gno index 65edda6911e..8f753b4546d 100644 --- a/examples/gno.land/p/moul/txlink/txlink.gno +++ b/examples/gno.land/p/moul/txlink/txlink.gno @@ -15,6 +15,7 @@ package txlink import ( + "net/url" "std" "strings" ) @@ -51,24 +52,26 @@ func (r Realm) prefix() string { // Call returns a URL for the specified function with optional key-value // arguments. func (r Realm) Call(fn string, args ...string) string { - // Start with the base query - url := r.prefix() + "$help&func=" + fn + if len(args) == 0 { + return r.prefix() + "$help&func=" + fn + } + + // Create url.Values to properly encode parameters. + // But manage &func=fn as a special case to keep it as the first argument. + values := url.Values{} // Check if args length is even if len(args)%2 != 0 { - // If not even, we can choose to handle the error here. - // For example, we can just return the URL without appending - // more args. - return url - } - - // Append key-value pairs to the URL - for i := 0; i < len(args); i += 2 { - key := args[i] - value := args[i+1] - // XXX: escape keys and args - url += "&" + key + "=" + value + values.Add("error", "odd number of arguments") + } else { + // Add key-value pairs to values + for i := 0; i < len(args); i += 2 { + key := args[i] + value := args[i+1] + values.Add(key, value) + } } - return url + // Build the base URL and append encoded query parameters + return r.prefix() + "$help&func=" + fn + "&" + values.Encode() } diff --git a/examples/gno.land/p/moul/txlink/txlink_test.gno b/examples/gno.land/p/moul/txlink/txlink_test.gno index 61b532270d4..1da396b27a3 100644 --- a/examples/gno.land/p/moul/txlink/txlink_test.gno +++ b/examples/gno.land/p/moul/txlink/txlink_test.gno @@ -16,19 +16,22 @@ func TestCall(t *testing.T) { {"foo", []string{"bar", "1", "baz", "2"}, "$help&func=foo&bar=1&baz=2", ""}, {"testFunc", []string{"key", "value"}, "$help&func=testFunc&key=value", ""}, {"noArgsFunc", []string{}, "$help&func=noArgsFunc", ""}, - {"oddArgsFunc", []string{"key"}, "$help&func=oddArgsFunc", ""}, + {"oddArgsFunc", []string{"key"}, "$help&func=oddArgsFunc&error=odd+number+of+arguments", ""}, {"foo", []string{"bar", "1", "baz", "2"}, "/r/lorem/ipsum$help&func=foo&bar=1&baz=2", "gno.land/r/lorem/ipsum"}, {"testFunc", []string{"key", "value"}, "/r/lorem/ipsum$help&func=testFunc&key=value", "gno.land/r/lorem/ipsum"}, {"noArgsFunc", []string{}, "/r/lorem/ipsum$help&func=noArgsFunc", "gno.land/r/lorem/ipsum"}, - {"oddArgsFunc", []string{"key"}, "/r/lorem/ipsum$help&func=oddArgsFunc", "gno.land/r/lorem/ipsum"}, + {"oddArgsFunc", []string{"key"}, "/r/lorem/ipsum$help&func=oddArgsFunc&error=odd+number+of+arguments", "gno.land/r/lorem/ipsum"}, {"foo", []string{"bar", "1", "baz", "2"}, "https://gno.world/r/lorem/ipsum$help&func=foo&bar=1&baz=2", "gno.world/r/lorem/ipsum"}, {"testFunc", []string{"key", "value"}, "https://gno.world/r/lorem/ipsum$help&func=testFunc&key=value", "gno.world/r/lorem/ipsum"}, {"noArgsFunc", []string{}, "https://gno.world/r/lorem/ipsum$help&func=noArgsFunc", "gno.world/r/lorem/ipsum"}, - {"oddArgsFunc", []string{"key"}, "https://gno.world/r/lorem/ipsum$help&func=oddArgsFunc", "gno.world/r/lorem/ipsum"}, + {"oddArgsFunc", []string{"key"}, "https://gno.world/r/lorem/ipsum$help&func=oddArgsFunc&error=odd+number+of+arguments", "gno.world/r/lorem/ipsum"}, + {"test", []string{"key", "hello world"}, "$help&func=test&key=hello+world", ""}, + {"test", []string{"key", "a&b=c"}, "$help&func=test&key=a%26b%3Dc", ""}, + {"test", []string{"key", ""}, "$help&func=test&key=", ""}, } for _, tt := range tests { - title := tt.fn + title := string(tt.realm) + "_" + tt.fn t.Run(title, func(t *testing.T) { got := tt.realm.Call(tt.fn, tt.args...) urequire.Equal(t, tt.want, got) diff --git a/examples/gno.land/r/demo/boards/z_0_filetest.gno b/examples/gno.land/r/demo/boards/z_0_filetest.gno index a649895cb01..f56f6495b17 100644 --- a/examples/gno.land/r/demo/boards/z_0_filetest.gno +++ b/examples/gno.land/r/demo/boards/z_0_filetest.gno @@ -30,12 +30,12 @@ func main() { // ## [First Post (title)](/r/demo/boards:test_board/1) // // Body of the first post. (body) -// \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm UTC](/r/demo/boards:test_board/1) \[[x](/r/demo/boards$help&func=DeletePost&bid=1&threadid=1&postid=1)] (0 replies) (0 reposts) +// \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm UTC](/r/demo/boards:test_board/1) \[[x](/r/demo/boards$help&func=DeletePost&bid=1&postid=1&threadid=1)] (0 replies) (0 reposts) // // ---------------------------------------- // ## [Second Post (title)](/r/demo/boards:test_board/2) // // Body of the second post. (body) -// \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm UTC](/r/demo/boards:test_board/2) \[[x](/r/demo/boards$help&func=DeletePost&bid=1&threadid=2&postid=2)] (1 replies) (0 reposts) +// \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm UTC](/r/demo/boards:test_board/2) \[[x](/r/demo/boards$help&func=DeletePost&bid=1&postid=2&threadid=2)] (1 replies) (0 reposts) // // diff --git a/examples/gno.land/r/demo/boards/z_10_c_filetest.gno b/examples/gno.land/r/demo/boards/z_10_c_filetest.gno index 7dd460500d6..3fdd915a389 100644 --- a/examples/gno.land/r/demo/boards/z_10_c_filetest.gno +++ b/examples/gno.land/r/demo/boards/z_10_c_filetest.gno @@ -35,15 +35,15 @@ func main() { // # First Post in (title) // // Body of the first post. (body) -// \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1) \[[reply](/r/demo/boards$help&func=CreateReply&bid=1&threadid=1&postid=1)] \[[repost](/r/demo/boards$help&func=CreateRepost&bid=1&postid=1)] \[[x](/r/demo/boards$help&func=DeletePost&bid=1&threadid=1&postid=1)] +// \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1) \[[reply](/r/demo/boards$help&func=CreateReply&bid=1&postid=1&threadid=1)] \[[repost](/r/demo/boards$help&func=CreateRepost&bid=1&postid=1)] \[[x](/r/demo/boards$help&func=DeletePost&bid=1&postid=1&threadid=1)] // // > First reply of the First post // > -// > \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1/2) \[[reply](/r/demo/boards$help&func=CreateReply&bid=1&threadid=1&postid=2)] \[[x](/r/demo/boards$help&func=DeletePost&bid=1&threadid=1&postid=2)] +// > \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1/2) \[[reply](/r/demo/boards$help&func=CreateReply&bid=1&postid=2&threadid=1)] \[[x](/r/demo/boards$help&func=DeletePost&bid=1&postid=2&threadid=1)] // // ---------------------------------------------------- // # First Post in (title) // // Body of the first post. (body) -// \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1) \[[reply](/r/demo/boards$help&func=CreateReply&bid=1&threadid=1&postid=1)] \[[repost](/r/demo/boards$help&func=CreateRepost&bid=1&postid=1)] \[[x](/r/demo/boards$help&func=DeletePost&bid=1&threadid=1&postid=1)] +// \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1) \[[reply](/r/demo/boards$help&func=CreateReply&bid=1&postid=1&threadid=1)] \[[repost](/r/demo/boards$help&func=CreateRepost&bid=1&postid=1)] \[[x](/r/demo/boards$help&func=DeletePost&bid=1&postid=1&threadid=1)] // diff --git a/examples/gno.land/r/demo/boards/z_10_filetest.gno b/examples/gno.land/r/demo/boards/z_10_filetest.gno index 8a6d11c79cf..80254592d5f 100644 --- a/examples/gno.land/r/demo/boards/z_10_filetest.gno +++ b/examples/gno.land/r/demo/boards/z_10_filetest.gno @@ -33,7 +33,7 @@ func main() { // # First Post in (title) // // Body of the first post. (body) -// \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1) \[[reply](/r/demo/boards$help&func=CreateReply&bid=1&threadid=1&postid=1)] \[[repost](/r/demo/boards$help&func=CreateRepost&bid=1&postid=1)] \[[x](/r/demo/boards$help&func=DeletePost&bid=1&threadid=1&postid=1)] +// \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1) \[[reply](/r/demo/boards$help&func=CreateReply&bid=1&postid=1&threadid=1)] \[[repost](/r/demo/boards$help&func=CreateRepost&bid=1&postid=1)] \[[x](/r/demo/boards$help&func=DeletePost&bid=1&postid=1&threadid=1)] // // ---------------------------------------------------- // thread does not exist with id: 1 diff --git a/examples/gno.land/r/demo/boards/z_11_d_filetest.gno b/examples/gno.land/r/demo/boards/z_11_d_filetest.gno index f64b4c84bba..0a5c1886d24 100644 --- a/examples/gno.land/r/demo/boards/z_11_d_filetest.gno +++ b/examples/gno.land/r/demo/boards/z_11_d_filetest.gno @@ -35,19 +35,19 @@ func main() { // # First Post in (title) // // Body of the first post. (body) -// \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1) \[[reply](/r/demo/boards$help&func=CreateReply&bid=1&threadid=1&postid=1)] \[[repost](/r/demo/boards$help&func=CreateRepost&bid=1&postid=1)] \[[x](/r/demo/boards$help&func=DeletePost&bid=1&threadid=1&postid=1)] +// \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1) \[[reply](/r/demo/boards$help&func=CreateReply&bid=1&postid=1&threadid=1)] \[[repost](/r/demo/boards$help&func=CreateRepost&bid=1&postid=1)] \[[x](/r/demo/boards$help&func=DeletePost&bid=1&postid=1&threadid=1)] // // > First reply of the First post // > -// > \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1/2) \[[reply](/r/demo/boards$help&func=CreateReply&bid=1&threadid=1&postid=2)] \[[x](/r/demo/boards$help&func=DeletePost&bid=1&threadid=1&postid=2)] +// > \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1/2) \[[reply](/r/demo/boards$help&func=CreateReply&bid=1&postid=2&threadid=1)] \[[x](/r/demo/boards$help&func=DeletePost&bid=1&postid=2&threadid=1)] // // ---------------------------------------------------- // # First Post in (title) // // Body of the first post. (body) -// \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1) \[[reply](/r/demo/boards$help&func=CreateReply&bid=1&threadid=1&postid=1)] \[[repost](/r/demo/boards$help&func=CreateRepost&bid=1&postid=1)] \[[x](/r/demo/boards$help&func=DeletePost&bid=1&threadid=1&postid=1)] +// \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1) \[[reply](/r/demo/boards$help&func=CreateReply&bid=1&postid=1&threadid=1)] \[[repost](/r/demo/boards$help&func=CreateRepost&bid=1&postid=1)] \[[x](/r/demo/boards$help&func=DeletePost&bid=1&postid=1&threadid=1)] // // > Edited: First reply of the First post // > -// > \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1/2) \[[reply](/r/demo/boards$help&func=CreateReply&bid=1&threadid=1&postid=2)] \[[x](/r/demo/boards$help&func=DeletePost&bid=1&threadid=1&postid=2)] +// > \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1/2) \[[reply](/r/demo/boards$help&func=CreateReply&bid=1&postid=2&threadid=1)] \[[x](/r/demo/boards$help&func=DeletePost&bid=1&postid=2&threadid=1)] // diff --git a/examples/gno.land/r/demo/boards/z_11_filetest.gno b/examples/gno.land/r/demo/boards/z_11_filetest.gno index 3f56293b3bd..1f04d7b686d 100644 --- a/examples/gno.land/r/demo/boards/z_11_filetest.gno +++ b/examples/gno.land/r/demo/boards/z_11_filetest.gno @@ -33,11 +33,11 @@ func main() { // # First Post in (title) // // Body of the first post. (body) -// \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1) \[[reply](/r/demo/boards$help&func=CreateReply&bid=1&threadid=1&postid=1)] \[[repost](/r/demo/boards$help&func=CreateRepost&bid=1&postid=1)] \[[x](/r/demo/boards$help&func=DeletePost&bid=1&threadid=1&postid=1)] +// \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1) \[[reply](/r/demo/boards$help&func=CreateReply&bid=1&postid=1&threadid=1)] \[[repost](/r/demo/boards$help&func=CreateRepost&bid=1&postid=1)] \[[x](/r/demo/boards$help&func=DeletePost&bid=1&postid=1&threadid=1)] // // ---------------------------------------------------- // # Edited: First Post in (title) // // Edited: Body of the first post. (body) -// \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1) \[[reply](/r/demo/boards$help&func=CreateReply&bid=1&threadid=1&postid=1)] \[[repost](/r/demo/boards$help&func=CreateRepost&bid=1&postid=1)] \[[x](/r/demo/boards$help&func=DeletePost&bid=1&threadid=1&postid=1)] +// \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1) \[[reply](/r/demo/boards$help&func=CreateReply&bid=1&postid=1&threadid=1)] \[[repost](/r/demo/boards$help&func=CreateRepost&bid=1&postid=1)] \[[x](/r/demo/boards$help&func=DeletePost&bid=1&postid=1&threadid=1)] // diff --git a/examples/gno.land/r/demo/boards/z_12_filetest.gno b/examples/gno.land/r/demo/boards/z_12_filetest.gno index ac4adf6ee7b..a362ea59823 100644 --- a/examples/gno.land/r/demo/boards/z_12_filetest.gno +++ b/examples/gno.land/r/demo/boards/z_12_filetest.gno @@ -37,6 +37,6 @@ func main() { // ## [First Post (title)](/r/demo/boards:test_board1/1) // // Body of the first post. (body) -// \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm UTC](/r/demo/boards:test_board1/1) \[[x](/r/demo/boards$help&func=DeletePost&bid=1&threadid=1&postid=1)] (0 replies) (1 reposts) +// \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm UTC](/r/demo/boards:test_board1/1) \[[x](/r/demo/boards$help&func=DeletePost&bid=1&postid=1&threadid=1)] (0 replies) (1 reposts) // // diff --git a/examples/gno.land/r/demo/boards/z_2_filetest.gno b/examples/gno.land/r/demo/boards/z_2_filetest.gno index 31b39644b24..55f94e10f1a 100644 --- a/examples/gno.land/r/demo/boards/z_2_filetest.gno +++ b/examples/gno.land/r/demo/boards/z_2_filetest.gno @@ -32,8 +32,8 @@ func main() { // # Second Post (title) // // Body of the second post. (body) -// \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2) \[[reply](/r/demo/boards$help&func=CreateReply&bid=1&threadid=2&postid=2)] \[[repost](/r/demo/boards$help&func=CreateRepost&bid=1&postid=2)] \[[x](/r/demo/boards$help&func=DeletePost&bid=1&threadid=2&postid=2)] +// \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2) \[[reply](/r/demo/boards$help&func=CreateReply&bid=1&postid=2&threadid=2)] \[[repost](/r/demo/boards$help&func=CreateRepost&bid=1&postid=2)] \[[x](/r/demo/boards$help&func=DeletePost&bid=1&postid=2&threadid=2)] // // > Reply of the second post -// > \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/3) \[[reply](/r/demo/boards$help&func=CreateReply&bid=1&threadid=2&postid=3)] \[[x](/r/demo/boards$help&func=DeletePost&bid=1&threadid=2&postid=3)] +// > \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/3) \[[reply](/r/demo/boards$help&func=CreateReply&bid=1&postid=3&threadid=2)] \[[x](/r/demo/boards$help&func=DeletePost&bid=1&postid=3&threadid=2)] // diff --git a/examples/gno.land/r/demo/boards/z_3_filetest.gno b/examples/gno.land/r/demo/boards/z_3_filetest.gno index 0b2a2df2f91..c2aa264b1d2 100644 --- a/examples/gno.land/r/demo/boards/z_3_filetest.gno +++ b/examples/gno.land/r/demo/boards/z_3_filetest.gno @@ -34,8 +34,8 @@ func main() { // # Second Post (title) // // Body of the second post. (body) -// \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2) \[[reply](/r/demo/boards$help&func=CreateReply&bid=1&threadid=2&postid=2)] \[[repost](/r/demo/boards$help&func=CreateRepost&bid=1&postid=2)] \[[x](/r/demo/boards$help&func=DeletePost&bid=1&threadid=2&postid=2)] +// \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2) \[[reply](/r/demo/boards$help&func=CreateReply&bid=1&postid=2&threadid=2)] \[[repost](/r/demo/boards$help&func=CreateRepost&bid=1&postid=2)] \[[x](/r/demo/boards$help&func=DeletePost&bid=1&postid=2&threadid=2)] // // > Reply of the second post -// > \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/3) \[[reply](/r/demo/boards$help&func=CreateReply&bid=1&threadid=2&postid=3)] \[[x](/r/demo/boards$help&func=DeletePost&bid=1&threadid=2&postid=3)] +// > \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/3) \[[reply](/r/demo/boards$help&func=CreateReply&bid=1&postid=3&threadid=2)] \[[x](/r/demo/boards$help&func=DeletePost&bid=1&postid=3&threadid=2)] // diff --git a/examples/gno.land/r/demo/boards/z_4_filetest.gno b/examples/gno.land/r/demo/boards/z_4_filetest.gno index b781e94e4db..aede35077f9 100644 --- a/examples/gno.land/r/demo/boards/z_4_filetest.gno +++ b/examples/gno.land/r/demo/boards/z_4_filetest.gno @@ -37,13 +37,13 @@ func main() { // # Second Post (title) // // Body of the second post. (body) -// \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2) \[[reply](/r/demo/boards$help&func=CreateReply&bid=1&threadid=2&postid=2)] \[[repost](/r/demo/boards$help&func=CreateRepost&bid=1&postid=2)] \[[x](/r/demo/boards$help&func=DeletePost&bid=1&threadid=2&postid=2)] +// \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2) \[[reply](/r/demo/boards$help&func=CreateReply&bid=1&postid=2&threadid=2)] \[[repost](/r/demo/boards$help&func=CreateRepost&bid=1&postid=2)] \[[x](/r/demo/boards$help&func=DeletePost&bid=1&postid=2&threadid=2)] // // > Reply of the second post -// > \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/3) \[[reply](/r/demo/boards$help&func=CreateReply&bid=1&threadid=2&postid=3)] \[[x](/r/demo/boards$help&func=DeletePost&bid=1&threadid=2&postid=3)] +// > \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/3) \[[reply](/r/demo/boards$help&func=CreateReply&bid=1&postid=3&threadid=2)] \[[x](/r/demo/boards$help&func=DeletePost&bid=1&postid=3&threadid=2)] // // > Second reply of the second post -// > \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/4) \[[reply](/r/demo/boards$help&func=CreateReply&bid=1&threadid=2&postid=4)] \[[x](/r/demo/boards$help&func=DeletePost&bid=1&threadid=2&postid=4)] +// > \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/4) \[[reply](/r/demo/boards$help&func=CreateReply&bid=1&postid=4&threadid=2)] \[[x](/r/demo/boards$help&func=DeletePost&bid=1&postid=4&threadid=2)] // // Realm: diff --git a/examples/gno.land/r/demo/boards/z_5_c_filetest.gno b/examples/gno.land/r/demo/boards/z_5_c_filetest.gno index 723e6a10204..176c11da73c 100644 --- a/examples/gno.land/r/demo/boards/z_5_c_filetest.gno +++ b/examples/gno.land/r/demo/boards/z_5_c_filetest.gno @@ -33,8 +33,8 @@ func main() { // # First Post (title) // // Body of the first post. (body) -// \- [g1w3jhxapjta047h6lta047h6lta047h6laqcyu4](/r/demo/users:g1w3jhxapjta047h6lta047h6lta047h6laqcyu4), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1) \[[reply](/r/demo/boards$help&func=CreateReply&bid=1&threadid=1&postid=1)] \[[repost](/r/demo/boards$help&func=CreateRepost&bid=1&postid=1)] \[[x](/r/demo/boards$help&func=DeletePost&bid=1&threadid=1&postid=1)] +// \- [g1w3jhxapjta047h6lta047h6lta047h6laqcyu4](/r/demo/users:g1w3jhxapjta047h6lta047h6lta047h6laqcyu4), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1) \[[reply](/r/demo/boards$help&func=CreateReply&bid=1&postid=1&threadid=1)] \[[repost](/r/demo/boards$help&func=CreateRepost&bid=1&postid=1)] \[[x](/r/demo/boards$help&func=DeletePost&bid=1&postid=1&threadid=1)] // // > Reply of the first post -// > \- [g1w3jhxapjta047h6lta047h6lta047h6laqcyu4](/r/demo/users:g1w3jhxapjta047h6lta047h6lta047h6laqcyu4), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1/2) \[[reply](/r/demo/boards$help&func=CreateReply&bid=1&threadid=1&postid=2)] \[[x](/r/demo/boards$help&func=DeletePost&bid=1&threadid=1&postid=2)] +// > \- [g1w3jhxapjta047h6lta047h6lta047h6laqcyu4](/r/demo/users:g1w3jhxapjta047h6lta047h6lta047h6laqcyu4), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1/2) \[[reply](/r/demo/boards$help&func=CreateReply&bid=1&postid=2&threadid=1)] \[[x](/r/demo/boards$help&func=DeletePost&bid=1&postid=2&threadid=1)] // diff --git a/examples/gno.land/r/demo/boards/z_5_filetest.gno b/examples/gno.land/r/demo/boards/z_5_filetest.gno index 712af483891..4f6f5cb9b75 100644 --- a/examples/gno.land/r/demo/boards/z_5_filetest.gno +++ b/examples/gno.land/r/demo/boards/z_5_filetest.gno @@ -33,12 +33,12 @@ func main() { // # Second Post (title) // // Body of the second post. (body) -// \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2) \[[reply](/r/demo/boards$help&func=CreateReply&bid=1&threadid=2&postid=2)] \[[repost](/r/demo/boards$help&func=CreateRepost&bid=1&postid=2)] \[[x](/r/demo/boards$help&func=DeletePost&bid=1&threadid=2&postid=2)] +// \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2) \[[reply](/r/demo/boards$help&func=CreateReply&bid=1&postid=2&threadid=2)] \[[repost](/r/demo/boards$help&func=CreateRepost&bid=1&postid=2)] \[[x](/r/demo/boards$help&func=DeletePost&bid=1&postid=2&threadid=2)] // // > Reply of the second post -// > \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/3) \[[reply](/r/demo/boards$help&func=CreateReply&bid=1&threadid=2&postid=3)] \[[x](/r/demo/boards$help&func=DeletePost&bid=1&threadid=2&postid=3)] +// > \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/3) \[[reply](/r/demo/boards$help&func=CreateReply&bid=1&postid=3&threadid=2)] \[[x](/r/demo/boards$help&func=DeletePost&bid=1&postid=3&threadid=2)] // // > Second reply of the second post // > -// > \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/4) \[[reply](/r/demo/boards$help&func=CreateReply&bid=1&threadid=2&postid=4)] \[[x](/r/demo/boards$help&func=DeletePost&bid=1&threadid=2&postid=4)] +// > \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/4) \[[reply](/r/demo/boards$help&func=CreateReply&bid=1&postid=4&threadid=2)] \[[x](/r/demo/boards$help&func=DeletePost&bid=1&postid=4&threadid=2)] // diff --git a/examples/gno.land/r/demo/boards/z_6_filetest.gno b/examples/gno.land/r/demo/boards/z_6_filetest.gno index ec40cf5f8e9..39791606aae 100644 --- a/examples/gno.land/r/demo/boards/z_6_filetest.gno +++ b/examples/gno.land/r/demo/boards/z_6_filetest.gno @@ -35,16 +35,16 @@ func main() { // # Second Post (title) // // Body of the second post. (body) -// \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2) \[[reply](/r/demo/boards$help&func=CreateReply&bid=1&threadid=2&postid=2)] \[[repost](/r/demo/boards$help&func=CreateRepost&bid=1&postid=2)] \[[x](/r/demo/boards$help&func=DeletePost&bid=1&threadid=2&postid=2)] +// \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2) \[[reply](/r/demo/boards$help&func=CreateReply&bid=1&postid=2&threadid=2)] \[[repost](/r/demo/boards$help&func=CreateRepost&bid=1&postid=2)] \[[x](/r/demo/boards$help&func=DeletePost&bid=1&postid=2&threadid=2)] // // > Reply of the second post -// > \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/3) \[[reply](/r/demo/boards$help&func=CreateReply&bid=1&threadid=2&postid=3)] \[[x](/r/demo/boards$help&func=DeletePost&bid=1&threadid=2&postid=3)] +// > \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/3) \[[reply](/r/demo/boards$help&func=CreateReply&bid=1&postid=3&threadid=2)] \[[x](/r/demo/boards$help&func=DeletePost&bid=1&postid=3&threadid=2)] // > // > > First reply of the first reply // > > -// > > \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/5) \[[reply](/r/demo/boards$help&func=CreateReply&bid=1&threadid=2&postid=5)] \[[x](/r/demo/boards$help&func=DeletePost&bid=1&threadid=2&postid=5)] +// > > \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/5) \[[reply](/r/demo/boards$help&func=CreateReply&bid=1&postid=5&threadid=2)] \[[x](/r/demo/boards$help&func=DeletePost&bid=1&postid=5&threadid=2)] // // > Second reply of the second post // > -// > \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/4) \[[reply](/r/demo/boards$help&func=CreateReply&bid=1&threadid=2&postid=4)] \[[x](/r/demo/boards$help&func=DeletePost&bid=1&threadid=2&postid=4)] +// > \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/4) \[[reply](/r/demo/boards$help&func=CreateReply&bid=1&postid=4&threadid=2)] \[[x](/r/demo/boards$help&func=DeletePost&bid=1&postid=4&threadid=2)] // diff --git a/examples/gno.land/r/demo/boards/z_7_filetest.gno b/examples/gno.land/r/demo/boards/z_7_filetest.gno index 353b84f6d87..9ff95f7492d 100644 --- a/examples/gno.land/r/demo/boards/z_7_filetest.gno +++ b/examples/gno.land/r/demo/boards/z_7_filetest.gno @@ -28,6 +28,6 @@ func main() { // ## [First Post (title)](/r/demo/boards:test_board/1) // // Body of the first post. (body) -// \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm UTC](/r/demo/boards:test_board/1) \[[x](/r/demo/boards$help&func=DeletePost&bid=1&threadid=1&postid=1)] (0 replies) (0 reposts) +// \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm UTC](/r/demo/boards:test_board/1) \[[x](/r/demo/boards$help&func=DeletePost&bid=1&postid=1&threadid=1)] (0 replies) (0 reposts) // // diff --git a/examples/gno.land/r/demo/boards/z_8_filetest.gno b/examples/gno.land/r/demo/boards/z_8_filetest.gno index 4896dfcfccf..47d84737698 100644 --- a/examples/gno.land/r/demo/boards/z_8_filetest.gno +++ b/examples/gno.land/r/demo/boards/z_8_filetest.gno @@ -35,11 +35,11 @@ func main() { // _[see thread](/r/demo/boards:test_board/2)_ // // Reply of the second post -// \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/3) \[[reply](/r/demo/boards$help&func=CreateReply&bid=1&threadid=2&postid=3)] \[[x](/r/demo/boards$help&func=DeletePost&bid=1&threadid=2&postid=3)] +// \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/3) \[[reply](/r/demo/boards$help&func=CreateReply&bid=1&postid=3&threadid=2)] \[[x](/r/demo/boards$help&func=DeletePost&bid=1&postid=3&threadid=2)] // // _[see all 1 replies](/r/demo/boards:test_board/2/3)_ // // > First reply of the first reply // > -// > \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/5) \[[reply](/r/demo/boards$help&func=CreateReply&bid=1&threadid=2&postid=5)] \[[x](/r/demo/boards$help&func=DeletePost&bid=1&threadid=2&postid=5)] +// > \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/5) \[[reply](/r/demo/boards$help&func=CreateReply&bid=1&postid=5&threadid=2)] \[[x](/r/demo/boards$help&func=DeletePost&bid=1&postid=5&threadid=2)] // diff --git a/examples/gno.land/r/demo/boards/z_9_filetest.gno b/examples/gno.land/r/demo/boards/z_9_filetest.gno index ca37e306bda..96af90263d5 100644 --- a/examples/gno.land/r/demo/boards/z_9_filetest.gno +++ b/examples/gno.land/r/demo/boards/z_9_filetest.gno @@ -34,5 +34,5 @@ func main() { // # First Post in (title) // // Body of the first post. (body) -// \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:second_board/1/1) \[[reply](/r/demo/boards$help&func=CreateReply&bid=2&threadid=1&postid=1)] \[[x](/r/demo/boards$help&func=DeletePost&bid=2&threadid=1&postid=1)] +// \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:second_board/1/1) \[[reply](/r/demo/boards$help&func=CreateReply&bid=2&postid=1&threadid=1)] \[[x](/r/demo/boards$help&func=DeletePost&bid=2&postid=1&threadid=1)] // diff --git a/examples/gno.land/r/docs/buttons/buttons_test.gno b/examples/gno.land/r/docs/buttons/buttons_test.gno index 2903fa1a858..c6164f3c687 100644 --- a/examples/gno.land/r/docs/buttons/buttons_test.gno +++ b/examples/gno.land/r/docs/buttons/buttons_test.gno @@ -7,7 +7,7 @@ import ( func TestRenderMotdLink(t *testing.T) { res := Render("motd") - const wantLink = "/r/docs/buttons$help&func=UpdateMOTD&newmotd=Message!" + const wantLink = "/r/docs/buttons$help&func=UpdateMOTD&newmotd=Message%21" if !strings.Contains(res, wantLink) { t.Fatalf("%s\ndoes not contain correct help page link: %s", res, wantLink) } diff --git a/examples/gno.land/r/leon/hof/datasource_test.gno b/examples/gno.land/r/leon/hof/datasource_test.gno index 376f981875f..fb67f20e7e7 100644 --- a/examples/gno.land/r/leon/hof/datasource_test.gno +++ b/examples/gno.land/r/leon/hof/datasource_test.gno @@ -151,7 +151,7 @@ func TestItemRecord(t *testing.T) { content, _ := r.Content() wantContent := "# Submission #1\n\n\n```\ngno.land/r/demo/test\n```\n\nby demo\n\n" + "[View realm](/r/demo/test)\n\nSubmitted at Block #42\n\n" + - "#### [2👍](/r/leon/hof$help&func=Upvote&pkgpath=gno.land/r/demo/test) - " + - "[1👎](/r/leon/hof$help&func=Downvote&pkgpath=gno.land/r/demo/test)\n\n" + "#### [2👍](/r/leon/hof$help&func=Upvote&pkgpath=gno.land%2Fr%2Fdemo%2Ftest) - " + + "[1👎](/r/leon/hof$help&func=Downvote&pkgpath=gno.land%2Fr%2Fdemo%2Ftest)\n\n" uassert.Equal(t, wantContent, content) } From 85a8740bdafb790c556ef5ce485e08357bec54e5 Mon Sep 17 00:00:00 2001 From: 6h057 <15034695+omarsy@users.noreply.github.com> Date: Tue, 4 Feb 2025 13:16:00 +0100 Subject: [PATCH 112/143] feat(stdlibs/std)!: replace `IsOriginCall` with `PrevRealm().IsUser()` for EOA checks (#3399) Linked to https://github.com/gnolang/gno/issues/1475 --------- Co-authored-by: Morgan Bazalgette --- docs/reference/stdlibs/std/chain.md | 12 ------- .../gno.land/r/demo/banktest/z_3_filetest.gno | 1 - examples/gno.land/r/demo/boards/public.gno | 12 +++---- .../gno.land/r/demo/boards/z_0_a_filetest.gno | 5 +++ .../gno.land/r/demo/boards/z_0_b_filetest.gno | 5 +++ .../gno.land/r/demo/boards/z_0_c_filetest.gno | 5 +++ .../gno.land/r/demo/boards/z_0_d_filetest.gno | 5 +++ .../gno.land/r/demo/boards/z_0_e_filetest.gno | 5 +++ .../gno.land/r/demo/boards/z_0_filetest.gno | 5 +++ .../r/demo/boards/z_10_a_filetest.gno | 6 ++++ .../r/demo/boards/z_10_b_filetest.gno | 6 ++++ .../r/demo/boards/z_10_c_filetest.gno | 6 ++++ .../gno.land/r/demo/boards/z_10_filetest.gno | 6 ++++ .../r/demo/boards/z_11_a_filetest.gno | 6 ++++ .../r/demo/boards/z_11_b_filetest.gno | 6 ++++ .../r/demo/boards/z_11_c_filetest.gno | 6 ++++ .../r/demo/boards/z_11_d_filetest.gno | 6 ++++ .../gno.land/r/demo/boards/z_11_filetest.gno | 6 ++++ .../r/demo/boards/z_12_a_filetest.gno | 2 ++ .../r/demo/boards/z_12_b_filetest.gno | 5 +++ .../r/demo/boards/z_12_c_filetest.gno | 5 +++ .../r/demo/boards/z_12_d_filetest.gno | 5 +++ .../gno.land/r/demo/boards/z_12_filetest.gno | 7 +++++ .../gno.land/r/demo/boards/z_1_filetest.gno | 5 +++ .../gno.land/r/demo/boards/z_2_filetest.gno | 4 +++ .../gno.land/r/demo/boards/z_3_filetest.gno | 6 ++++ .../gno.land/r/demo/boards/z_4_filetest.gno | 6 ++++ .../gno.land/r/demo/boards/z_5_b_filetest.gno | 2 ++ .../gno.land/r/demo/boards/z_5_c_filetest.gno | 2 ++ .../gno.land/r/demo/boards/z_5_d_filetest.gno | 2 ++ .../gno.land/r/demo/boards/z_5_filetest.gno | 6 ++++ .../gno.land/r/demo/boards/z_6_filetest.gno | 6 ++++ .../gno.land/r/demo/boards/z_7_filetest.gno | 5 +++ .../gno.land/r/demo/boards/z_8_filetest.gno | 6 ++++ .../gno.land/r/demo/boards/z_9_a_filetest.gno | 5 +++ .../gno.land/r/demo/boards/z_9_b_filetest.gno | 5 +++ .../gno.land/r/demo/boards/z_9_filetest.gno | 4 +++ .../r/demo/tests/subtests/subtests.gno | 2 +- examples/gno.land/r/demo/tests/tests.gno | 2 +- examples/gno.land/r/demo/tests/tests_test.gno | 31 ++++++++++++------- .../gno.land/r/demo/users/z_11_filetest.gno | 2 +- .../gno.land/r/demo/users/z_11b_filetest.gno | 2 +- .../gno.land/r/demo/users/z_5_filetest.gno | 3 ++ .../gno.land/r/demo/users/z_6_filetest.gno | 2 +- .../transpile/valid_transpile_file.txtar | 4 +-- gnovm/pkg/transpiler/transpiler_test.go | 14 ++++----- gnovm/stdlibs/generated.go | 20 ------------ gnovm/stdlibs/std/native.gno | 12 +++---- gnovm/stdlibs/std/native.go | 4 +-- gnovm/stdlibs/std/native_test.go | 2 +- gnovm/tests/files/std5.gno | 4 +-- gnovm/tests/files/std8.gno | 4 +-- gnovm/tests/stdlibs/generated.go | 20 ------------ gnovm/tests/stdlibs/std/std.gno | 1 - gnovm/tests/stdlibs/std/std.go | 4 +-- 55 files changed, 227 insertions(+), 103 deletions(-) diff --git a/docs/reference/stdlibs/std/chain.md b/docs/reference/stdlibs/std/chain.md index 6a1da6483fd..f3b2c6be0b8 100644 --- a/docs/reference/stdlibs/std/chain.md +++ b/docs/reference/stdlibs/std/chain.md @@ -4,18 +4,6 @@ id: chain # Chain-related -## IsOriginCall -```go -func IsOriginCall() bool -``` -Checks if the caller of the function is an EOA. Returns **true** if caller is an EOA, **false** otherwise. - -#### Usage -```go -if !std.IsOriginCall() {...} -``` ---- - ## AssertOriginCall ```go func AssertOriginCall() diff --git a/examples/gno.land/r/demo/banktest/z_3_filetest.gno b/examples/gno.land/r/demo/banktest/z_3_filetest.gno index 7b6758c3e4f..7bf2aea4f38 100644 --- a/examples/gno.land/r/demo/banktest/z_3_filetest.gno +++ b/examples/gno.land/r/demo/banktest/z_3_filetest.gno @@ -18,7 +18,6 @@ func main() { banker := std.GetBanker(std.BankerTypeRealmSend) send := std.Coins{{"ugnot", 123}} banker.SendCoins(banktestAddr, mainaddr, send) - } // Error: diff --git a/examples/gno.land/r/demo/boards/public.gno b/examples/gno.land/r/demo/boards/public.gno index 1d26126fcb2..db545446641 100644 --- a/examples/gno.land/r/demo/boards/public.gno +++ b/examples/gno.land/r/demo/boards/public.gno @@ -17,7 +17,7 @@ func GetBoardIDFromName(name string) (BoardID, bool) { } func CreateBoard(name string) BoardID { - if !(std.IsOriginCall() || std.PrevRealm().IsUser()) { + if !std.PrevRealm().IsUser() { panic("invalid non-user call") } bid := incGetBoardID() @@ -43,7 +43,7 @@ func checkAnonFee() bool { } func CreateThread(bid BoardID, title string, body string) PostID { - if !(std.IsOriginCall() || std.PrevRealm().IsUser()) { + if !std.PrevRealm().IsUser() { panic("invalid non-user call") } caller := std.GetOrigCaller() @@ -61,7 +61,7 @@ func CreateThread(bid BoardID, title string, body string) PostID { } func CreateReply(bid BoardID, threadid, postid PostID, body string) PostID { - if !(std.IsOriginCall() || std.PrevRealm().IsUser()) { + if !std.PrevRealm().IsUser() { panic("invalid non-user call") } caller := std.GetOrigCaller() @@ -91,7 +91,7 @@ func CreateReply(bid BoardID, threadid, postid PostID, body string) PostID { // If dstBoard is private, does not ping back. // If board specified by bid is private, panics. func CreateRepost(bid BoardID, postid PostID, title string, body string, dstBoardID BoardID) PostID { - if !(std.IsOriginCall() || std.PrevRealm().IsUser()) { + if !std.PrevRealm().IsUser() { panic("invalid non-user call") } caller := std.GetOrigCaller() @@ -121,7 +121,7 @@ func CreateRepost(bid BoardID, postid PostID, title string, body string, dstBoar } func DeletePost(bid BoardID, threadid, postid PostID, reason string) { - if !(std.IsOriginCall() || std.PrevRealm().IsUser()) { + if !std.PrevRealm().IsUser() { panic("invalid non-user call") } caller := std.GetOrigCaller() @@ -153,7 +153,7 @@ func DeletePost(bid BoardID, threadid, postid PostID, reason string) { } func EditPost(bid BoardID, threadid, postid PostID, title, body string) { - if !(std.IsOriginCall() || std.PrevRealm().IsUser()) { + if !std.PrevRealm().IsUser() { panic("invalid non-user call") } caller := std.GetOrigCaller() diff --git a/examples/gno.land/r/demo/boards/z_0_a_filetest.gno b/examples/gno.land/r/demo/boards/z_0_a_filetest.gno index 5e8ff520a54..297231970a5 100644 --- a/examples/gno.land/r/demo/boards/z_0_a_filetest.gno +++ b/examples/gno.land/r/demo/boards/z_0_a_filetest.gno @@ -2,12 +2,17 @@ package boards_test import ( + "std" + + "gno.land/p/demo/testutils" "gno.land/r/demo/boards" ) var bid boards.BoardID func init() { + caller := testutils.TestAddress("caller") + std.TestSetRealm(std.NewUserRealm(caller)) bid = boards.CreateBoard("test_board") boards.CreateThread(bid, "First Post (title)", "Body of the first post. (body)") pid := boards.CreateThread(bid, "Second Post (title)", "Body of the second post. (body)") diff --git a/examples/gno.land/r/demo/boards/z_0_b_filetest.gno b/examples/gno.land/r/demo/boards/z_0_b_filetest.gno index 9bcbe9ffafa..4830161ac71 100644 --- a/examples/gno.land/r/demo/boards/z_0_b_filetest.gno +++ b/examples/gno.land/r/demo/boards/z_0_b_filetest.gno @@ -4,6 +4,9 @@ package boards_test // SEND: 19900000ugnot import ( + "std" + + "gno.land/p/demo/testutils" "gno.land/r/demo/boards" "gno.land/r/demo/users" ) @@ -11,6 +14,8 @@ import ( var bid boards.BoardID func init() { + caller := testutils.TestAddress("caller") + std.TestSetRealm(std.NewUserRealm(caller)) users.Register("", "gnouser", "my profile") bid = boards.CreateBoard("test_board") } diff --git a/examples/gno.land/r/demo/boards/z_0_c_filetest.gno b/examples/gno.land/r/demo/boards/z_0_c_filetest.gno index 99fd339aed8..d0b0930240d 100644 --- a/examples/gno.land/r/demo/boards/z_0_c_filetest.gno +++ b/examples/gno.land/r/demo/boards/z_0_c_filetest.gno @@ -4,6 +4,9 @@ package boards_test // SEND: 200000000ugnot import ( + "std" + + "gno.land/p/demo/testutils" "gno.land/r/demo/boards" "gno.land/r/demo/users" ) @@ -11,6 +14,8 @@ import ( var bid boards.BoardID func init() { + caller := testutils.TestAddress("caller") + std.TestSetRealm(std.NewUserRealm(caller)) users.Register("", "gnouser", "my profile") boards.CreateThread(1, "First Post (title)", "Body of the first post. (body)") } diff --git a/examples/gno.land/r/demo/boards/z_0_d_filetest.gno b/examples/gno.land/r/demo/boards/z_0_d_filetest.gno index c77e60e3f3a..7e21f83febd 100644 --- a/examples/gno.land/r/demo/boards/z_0_d_filetest.gno +++ b/examples/gno.land/r/demo/boards/z_0_d_filetest.gno @@ -4,6 +4,9 @@ package boards_test // SEND: 200000000ugnot import ( + "std" + + "gno.land/p/demo/testutils" "gno.land/r/demo/boards" "gno.land/r/demo/users" ) @@ -11,6 +14,8 @@ import ( var bid boards.BoardID func init() { + caller := testutils.TestAddress("caller") + std.TestSetRealm(std.NewUserRealm(caller)) users.Register("", "gnouser", "my profile") bid = boards.CreateBoard("test_board") boards.CreateReply(bid, 0, 0, "Reply of the second post") diff --git a/examples/gno.land/r/demo/boards/z_0_e_filetest.gno b/examples/gno.land/r/demo/boards/z_0_e_filetest.gno index 6db036e87ba..bdf6d63727b 100644 --- a/examples/gno.land/r/demo/boards/z_0_e_filetest.gno +++ b/examples/gno.land/r/demo/boards/z_0_e_filetest.gno @@ -4,6 +4,9 @@ package boards_test // SEND: 200000000ugnot import ( + "std" + + "gno.land/p/demo/testutils" "gno.land/r/demo/boards" "gno.land/r/demo/users" ) @@ -11,6 +14,8 @@ import ( var bid boards.BoardID func init() { + caller := testutils.TestAddress("caller") + std.TestSetRealm(std.NewUserRealm(caller)) users.Register("", "gnouser", "my profile") boards.CreateReply(bid, 0, 0, "Reply of the second post") } diff --git a/examples/gno.land/r/demo/boards/z_0_filetest.gno b/examples/gno.land/r/demo/boards/z_0_filetest.gno index f56f6495b17..dc881ce46ad 100644 --- a/examples/gno.land/r/demo/boards/z_0_filetest.gno +++ b/examples/gno.land/r/demo/boards/z_0_filetest.gno @@ -4,6 +4,9 @@ package boards_test // SEND: 20000000ugnot import ( + "std" + + "gno.land/p/demo/testutils" "gno.land/r/demo/boards" "gno.land/r/demo/users" ) @@ -11,6 +14,8 @@ import ( var bid boards.BoardID func init() { + caller := testutils.TestAddress("caller") + std.TestSetRealm(std.NewUserRealm(caller)) users.Register("", "gnouser", "my profile") bid = boards.CreateBoard("test_board") diff --git a/examples/gno.land/r/demo/boards/z_10_a_filetest.gno b/examples/gno.land/r/demo/boards/z_10_a_filetest.gno index ad57283bfcf..fc04555bf39 100644 --- a/examples/gno.land/r/demo/boards/z_10_a_filetest.gno +++ b/examples/gno.land/r/demo/boards/z_10_a_filetest.gno @@ -4,8 +4,10 @@ package boards_test // SEND: 200000000ugnot import ( + "std" "strconv" + "gno.land/p/demo/testutils" "gno.land/r/demo/boards" "gno.land/r/demo/users" ) @@ -16,6 +18,8 @@ var ( ) func init() { + caller := testutils.TestAddress("caller") + std.TestSetRealm(std.NewUserRealm(caller)) users.Register("", "gnouser", "my profile") bid = boards.CreateBoard("test_board") @@ -23,6 +27,8 @@ func init() { } func main() { + caller := testutils.TestAddress("caller") + std.TestSetRealm(std.NewUserRealm(caller)) println(boards.Render("test_board/" + strconv.Itoa(int(pid)))) // boardId 2 not exist boards.DeletePost(2, pid, pid, "") diff --git a/examples/gno.land/r/demo/boards/z_10_b_filetest.gno b/examples/gno.land/r/demo/boards/z_10_b_filetest.gno index cf8a332174f..2353268ef54 100644 --- a/examples/gno.land/r/demo/boards/z_10_b_filetest.gno +++ b/examples/gno.land/r/demo/boards/z_10_b_filetest.gno @@ -4,8 +4,10 @@ package boards_test // SEND: 200000000ugnot import ( + "std" "strconv" + "gno.land/p/demo/testutils" "gno.land/r/demo/boards" "gno.land/r/demo/users" ) @@ -16,6 +18,8 @@ var ( ) func init() { + caller := testutils.TestAddress("caller") + std.TestSetRealm(std.NewUserRealm(caller)) users.Register("", "gnouser", "my profile") bid = boards.CreateBoard("test_board") @@ -25,6 +29,8 @@ func init() { func main() { println(boards.Render("test_board/" + strconv.Itoa(int(pid)))) // pid of 2 not exist + caller := testutils.TestAddress("caller") + std.TestSetRealm(std.NewUserRealm(caller)) boards.DeletePost(bid, 2, 2, "") println("----------------------------------------------------") println(boards.Render("test_board/" + strconv.Itoa(int(pid)))) diff --git a/examples/gno.land/r/demo/boards/z_10_c_filetest.gno b/examples/gno.land/r/demo/boards/z_10_c_filetest.gno index 3fdd915a389..8f85cf63ecb 100644 --- a/examples/gno.land/r/demo/boards/z_10_c_filetest.gno +++ b/examples/gno.land/r/demo/boards/z_10_c_filetest.gno @@ -4,8 +4,10 @@ package boards_test // SEND: 200000000ugnot import ( + "std" "strconv" + "gno.land/p/demo/testutils" "gno.land/r/demo/boards" "gno.land/r/demo/users" ) @@ -17,6 +19,8 @@ var ( ) func init() { + caller := testutils.TestAddress("caller") + std.TestSetRealm(std.NewUserRealm(caller)) users.Register("", "gnouser", "my profile") bid = boards.CreateBoard("test_board") @@ -26,6 +30,8 @@ func init() { func main() { println(boards.Render("test_board/" + strconv.Itoa(int(pid)))) + caller := testutils.TestAddress("caller") + std.TestSetRealm(std.NewUserRealm(caller)) boards.DeletePost(bid, pid, rid, "") println("----------------------------------------------------") println(boards.Render("test_board/" + strconv.Itoa(int(pid)))) diff --git a/examples/gno.land/r/demo/boards/z_10_filetest.gno b/examples/gno.land/r/demo/boards/z_10_filetest.gno index 80254592d5f..01e0f9439c7 100644 --- a/examples/gno.land/r/demo/boards/z_10_filetest.gno +++ b/examples/gno.land/r/demo/boards/z_10_filetest.gno @@ -4,8 +4,10 @@ package boards_test // SEND: 200000000ugnot import ( + "std" "strconv" + "gno.land/p/demo/testutils" "gno.land/r/demo/boards" "gno.land/r/demo/users" ) @@ -16,6 +18,8 @@ var ( ) func init() { + caller := testutils.TestAddress("caller") + std.TestSetRealm(std.NewUserRealm(caller)) users.Register("", "gnouser", "my profile") bid = boards.CreateBoard("test_board") @@ -23,6 +27,8 @@ func init() { } func main() { + caller := testutils.TestAddress("caller") + std.TestSetRealm(std.NewUserRealm(caller)) println(boards.Render("test_board/" + strconv.Itoa(int(pid)))) boards.DeletePost(bid, pid, pid, "") println("----------------------------------------------------") diff --git a/examples/gno.land/r/demo/boards/z_11_a_filetest.gno b/examples/gno.land/r/demo/boards/z_11_a_filetest.gno index d7dc7b90782..b891e395fe6 100644 --- a/examples/gno.land/r/demo/boards/z_11_a_filetest.gno +++ b/examples/gno.land/r/demo/boards/z_11_a_filetest.gno @@ -4,8 +4,10 @@ package boards_test // SEND: 200000000ugnot import ( + "std" "strconv" + "gno.land/p/demo/testutils" "gno.land/r/demo/boards" "gno.land/r/demo/users" ) @@ -16,6 +18,8 @@ var ( ) func init() { + caller := testutils.TestAddress("caller") + std.TestSetRealm(std.NewUserRealm(caller)) users.Register("", "gnouser", "my profile") bid = boards.CreateBoard("test_board") @@ -25,6 +29,8 @@ func init() { func main() { println(boards.Render("test_board/" + strconv.Itoa(int(pid)))) // board 2 not exist + caller := testutils.TestAddress("caller") + std.TestSetRealm(std.NewUserRealm(caller)) boards.EditPost(2, pid, pid, "Edited: First Post in (title)", "Edited: Body of the first post. (body)") println("----------------------------------------------------") println(boards.Render("test_board/" + strconv.Itoa(int(pid)))) diff --git a/examples/gno.land/r/demo/boards/z_11_b_filetest.gno b/examples/gno.land/r/demo/boards/z_11_b_filetest.gno index 3aa28095502..9322ac191c5 100644 --- a/examples/gno.land/r/demo/boards/z_11_b_filetest.gno +++ b/examples/gno.land/r/demo/boards/z_11_b_filetest.gno @@ -4,8 +4,10 @@ package boards_test // SEND: 200000000ugnot import ( + "std" "strconv" + "gno.land/p/demo/testutils" "gno.land/r/demo/boards" "gno.land/r/demo/users" ) @@ -16,6 +18,8 @@ var ( ) func init() { + caller := testutils.TestAddress("caller") + std.TestSetRealm(std.NewUserRealm(caller)) users.Register("", "gnouser", "my profile") bid = boards.CreateBoard("test_board") @@ -25,6 +29,8 @@ func init() { func main() { println(boards.Render("test_board/" + strconv.Itoa(int(pid)))) // thread 2 not exist + caller := testutils.TestAddress("caller") + std.TestSetRealm(std.NewUserRealm(caller)) boards.EditPost(bid, 2, pid, "Edited: First Post in (title)", "Edited: Body of the first post. (body)") println("----------------------------------------------------") println(boards.Render("test_board/" + strconv.Itoa(int(pid)))) diff --git a/examples/gno.land/r/demo/boards/z_11_c_filetest.gno b/examples/gno.land/r/demo/boards/z_11_c_filetest.gno index df764303562..de4f828c1ca 100644 --- a/examples/gno.land/r/demo/boards/z_11_c_filetest.gno +++ b/examples/gno.land/r/demo/boards/z_11_c_filetest.gno @@ -4,8 +4,10 @@ package boards_test // SEND: 200000000ugnot import ( + "std" "strconv" + "gno.land/p/demo/testutils" "gno.land/r/demo/boards" "gno.land/r/demo/users" ) @@ -16,6 +18,8 @@ var ( ) func init() { + caller := testutils.TestAddress("caller") + std.TestSetRealm(std.NewUserRealm(caller)) users.Register("", "gnouser", "my profile") bid = boards.CreateBoard("test_board") @@ -25,6 +29,8 @@ func init() { func main() { println(boards.Render("test_board/" + strconv.Itoa(int(pid)))) // post 2 not exist + caller := testutils.TestAddress("caller") + std.TestSetRealm(std.NewUserRealm(caller)) boards.EditPost(bid, pid, 2, "Edited: First Post in (title)", "Edited: Body of the first post. (body)") println("----------------------------------------------------") println(boards.Render("test_board/" + strconv.Itoa(int(pid)))) diff --git a/examples/gno.land/r/demo/boards/z_11_d_filetest.gno b/examples/gno.land/r/demo/boards/z_11_d_filetest.gno index 0a5c1886d24..344583de7d4 100644 --- a/examples/gno.land/r/demo/boards/z_11_d_filetest.gno +++ b/examples/gno.land/r/demo/boards/z_11_d_filetest.gno @@ -4,8 +4,10 @@ package boards_test // SEND: 200000000ugnot import ( + "std" "strconv" + "gno.land/p/demo/testutils" "gno.land/r/demo/boards" "gno.land/r/demo/users" ) @@ -17,6 +19,8 @@ var ( ) func init() { + caller := testutils.TestAddress("caller") + std.TestSetRealm(std.NewUserRealm(caller)) users.Register("", "gnouser", "my profile") bid = boards.CreateBoard("test_board") @@ -26,6 +30,8 @@ func init() { func main() { println(boards.Render("test_board/" + strconv.Itoa(int(pid)))) + caller := testutils.TestAddress("caller") + std.TestSetRealm(std.NewUserRealm(caller)) boards.EditPost(bid, pid, rid, "", "Edited: First reply of the First post\n") println("----------------------------------------------------") println(boards.Render("test_board/" + strconv.Itoa(int(pid)))) diff --git a/examples/gno.land/r/demo/boards/z_11_filetest.gno b/examples/gno.land/r/demo/boards/z_11_filetest.gno index 1f04d7b686d..4cea63126c5 100644 --- a/examples/gno.land/r/demo/boards/z_11_filetest.gno +++ b/examples/gno.land/r/demo/boards/z_11_filetest.gno @@ -4,8 +4,10 @@ package boards_test // SEND: 200000000ugnot import ( + "std" "strconv" + "gno.land/p/demo/testutils" "gno.land/r/demo/boards" "gno.land/r/demo/users" ) @@ -16,6 +18,8 @@ var ( ) func init() { + caller := testutils.TestAddress("caller") + std.TestSetRealm(std.NewUserRealm(caller)) users.Register("", "gnouser", "my profile") bid = boards.CreateBoard("test_board") @@ -24,6 +28,8 @@ func init() { func main() { println(boards.Render("test_board/" + strconv.Itoa(int(pid)))) + caller := testutils.TestAddress("caller") + std.TestSetRealm(std.NewUserRealm(caller)) boards.EditPost(bid, pid, pid, "Edited: First Post in (title)", "Edited: Body of the first post. (body)") println("----------------------------------------------------") println(boards.Render("test_board/" + strconv.Itoa(int(pid)))) diff --git a/examples/gno.land/r/demo/boards/z_12_a_filetest.gno b/examples/gno.land/r/demo/boards/z_12_a_filetest.gno index 909be880efa..380e9bc09e0 100644 --- a/examples/gno.land/r/demo/boards/z_12_a_filetest.gno +++ b/examples/gno.land/r/demo/boards/z_12_a_filetest.gno @@ -12,6 +12,8 @@ import ( ) func main() { + caller := testutils.TestAddress("caller") + std.TestSetRealm(std.NewUserRealm(caller)) users.Register("", "gnouser", "my profile") // create a post via registered user bid1 := boards.CreateBoard("test_board1") diff --git a/examples/gno.land/r/demo/boards/z_12_b_filetest.gno b/examples/gno.land/r/demo/boards/z_12_b_filetest.gno index 6b2166895c0..553108df9b3 100644 --- a/examples/gno.land/r/demo/boards/z_12_b_filetest.gno +++ b/examples/gno.land/r/demo/boards/z_12_b_filetest.gno @@ -4,11 +4,16 @@ package boards_test // SEND: 200000000ugnot import ( + "std" + + "gno.land/p/demo/testutils" "gno.land/r/demo/boards" "gno.land/r/demo/users" ) func main() { + caller := testutils.TestAddress("caller") + std.TestSetRealm(std.NewUserRealm(caller)) users.Register("", "gnouser", "my profile") bid1 := boards.CreateBoard("test_board1") pid := boards.CreateThread(bid1, "First Post (title)", "Body of the first post. (body)") diff --git a/examples/gno.land/r/demo/boards/z_12_c_filetest.gno b/examples/gno.land/r/demo/boards/z_12_c_filetest.gno index 7397c487d7d..3b2e7ec04c0 100644 --- a/examples/gno.land/r/demo/boards/z_12_c_filetest.gno +++ b/examples/gno.land/r/demo/boards/z_12_c_filetest.gno @@ -4,11 +4,16 @@ package boards_test // SEND: 200000000ugnot import ( + "std" + + "gno.land/p/demo/testutils" "gno.land/r/demo/boards" "gno.land/r/demo/users" ) func main() { + caller := testutils.TestAddress("caller") + std.TestSetRealm(std.NewUserRealm(caller)) users.Register("", "gnouser", "my profile") bid1 := boards.CreateBoard("test_board1") boards.CreateThread(bid1, "First Post (title)", "Body of the first post. (body)") diff --git a/examples/gno.land/r/demo/boards/z_12_d_filetest.gno b/examples/gno.land/r/demo/boards/z_12_d_filetest.gno index 37b6473f7ac..20818b05c0f 100644 --- a/examples/gno.land/r/demo/boards/z_12_d_filetest.gno +++ b/examples/gno.land/r/demo/boards/z_12_d_filetest.gno @@ -4,11 +4,16 @@ package boards_test // SEND: 200000000ugnot import ( + "std" + + "gno.land/p/demo/testutils" "gno.land/r/demo/boards" "gno.land/r/demo/users" ) func main() { + caller := testutils.TestAddress("caller") + std.TestSetRealm(std.NewUserRealm(caller)) users.Register("", "gnouser", "my profile") bid1 := boards.CreateBoard("test_board1") pid := boards.CreateThread(bid1, "First Post (title)", "Body of the first post. (body)") diff --git a/examples/gno.land/r/demo/boards/z_12_filetest.gno b/examples/gno.land/r/demo/boards/z_12_filetest.gno index a362ea59823..cc4439c5934 100644 --- a/examples/gno.land/r/demo/boards/z_12_filetest.gno +++ b/examples/gno.land/r/demo/boards/z_12_filetest.gno @@ -4,6 +4,9 @@ package boards_test // SEND: 200000000ugnot import ( + "std" + + "gno.land/p/demo/testutils" "gno.land/r/demo/boards" "gno.land/r/demo/users" ) @@ -15,6 +18,8 @@ var ( ) func init() { + caller := testutils.TestAddress("caller") + std.TestSetRealm(std.NewUserRealm(caller)) users.Register("", "gnouser", "my profile") bid1 = boards.CreateBoard("test_board1") @@ -23,6 +28,8 @@ func init() { } func main() { + caller := testutils.TestAddress("caller") + std.TestSetRealm(std.NewUserRealm(caller)) rid := boards.CreateRepost(bid1, pid, "", "Check this out", bid2) println(rid) println(boards.Render("test_board2")) diff --git a/examples/gno.land/r/demo/boards/z_1_filetest.gno b/examples/gno.land/r/demo/boards/z_1_filetest.gno index 4d46c81b83d..6db254c661d 100644 --- a/examples/gno.land/r/demo/boards/z_1_filetest.gno +++ b/examples/gno.land/r/demo/boards/z_1_filetest.gno @@ -4,6 +4,9 @@ package boards_test // SEND: 200000000ugnot import ( + "std" + + "gno.land/p/demo/testutils" "gno.land/r/demo/boards" "gno.land/r/demo/users" ) @@ -11,6 +14,8 @@ import ( var board *boards.Board func init() { + caller := testutils.TestAddress("caller") + std.TestSetRealm(std.NewUserRealm(caller)) users.Register("", "gnouser", "my profile") _ = boards.CreateBoard("test_board_1") diff --git a/examples/gno.land/r/demo/boards/z_2_filetest.gno b/examples/gno.land/r/demo/boards/z_2_filetest.gno index 55f94e10f1a..bf8b643913c 100644 --- a/examples/gno.land/r/demo/boards/z_2_filetest.gno +++ b/examples/gno.land/r/demo/boards/z_2_filetest.gno @@ -4,8 +4,10 @@ package boards_test // SEND: 200000000ugnot import ( + "std" "strconv" + "gno.land/p/demo/testutils" "gno.land/r/demo/boards" "gno.land/r/demo/users" ) @@ -16,6 +18,8 @@ var ( ) func init() { + caller := testutils.TestAddress("caller") + std.TestSetRealm(std.NewUserRealm(caller)) users.Register("", "gnouser", "my profile") bid = boards.CreateBoard("test_board") diff --git a/examples/gno.land/r/demo/boards/z_3_filetest.gno b/examples/gno.land/r/demo/boards/z_3_filetest.gno index c2aa264b1d2..4717bfd3958 100644 --- a/examples/gno.land/r/demo/boards/z_3_filetest.gno +++ b/examples/gno.land/r/demo/boards/z_3_filetest.gno @@ -4,8 +4,10 @@ package boards_test // SEND: 200000000ugnot import ( + "std" "strconv" + "gno.land/p/demo/testutils" "gno.land/r/demo/boards" "gno.land/r/demo/users" ) @@ -16,6 +18,8 @@ var ( ) func init() { + caller := testutils.TestAddress("caller") + std.TestSetRealm(std.NewUserRealm(caller)) users.Register("", "gnouser", "my profile") bid = boards.CreateBoard("test_board") @@ -24,6 +28,8 @@ func init() { } func main() { + caller := testutils.TestAddress("caller") + std.TestSetRealm(std.NewUserRealm(caller)) rid := boards.CreateReply(bid, pid, pid, "Reply of the second post") println(rid) println(boards.Render("test_board/" + strconv.Itoa(int(pid)))) diff --git a/examples/gno.land/r/demo/boards/z_4_filetest.gno b/examples/gno.land/r/demo/boards/z_4_filetest.gno index aede35077f9..e519e6babfb 100644 --- a/examples/gno.land/r/demo/boards/z_4_filetest.gno +++ b/examples/gno.land/r/demo/boards/z_4_filetest.gno @@ -4,8 +4,10 @@ package boards_test // SEND: 200000000ugnot import ( + "std" "strconv" + "gno.land/p/demo/testutils" "gno.land/r/demo/boards" "gno.land/r/demo/users" ) @@ -16,6 +18,8 @@ var ( ) func init() { + caller := testutils.TestAddress("caller") + std.TestSetRealm(std.NewUserRealm(caller)) users.Register("", "gnouser", "my profile") bid = boards.CreateBoard("test_board") @@ -26,6 +30,8 @@ func init() { } func main() { + caller := testutils.TestAddress("caller") + std.TestSetRealm(std.NewUserRealm(caller)) rid2 := boards.CreateReply(bid, pid, pid, "Second reply of the second post") println(rid2) println(boards.Render("test_board/" + strconv.Itoa(int(pid)))) diff --git a/examples/gno.land/r/demo/boards/z_5_b_filetest.gno b/examples/gno.land/r/demo/boards/z_5_b_filetest.gno index e79da5c3677..0ad15ca2600 100644 --- a/examples/gno.land/r/demo/boards/z_5_b_filetest.gno +++ b/examples/gno.land/r/demo/boards/z_5_b_filetest.gno @@ -14,6 +14,8 @@ import ( const admin = std.Address("g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj") func main() { + caller := testutils.TestAddress("caller") + std.TestSetRealm(std.NewUserRealm(caller)) users.Register("", "gnouser", "my profile") // create board via registered user bid := boards.CreateBoard("test_board") diff --git a/examples/gno.land/r/demo/boards/z_5_c_filetest.gno b/examples/gno.land/r/demo/boards/z_5_c_filetest.gno index 176c11da73c..abe8f1cf0bd 100644 --- a/examples/gno.land/r/demo/boards/z_5_c_filetest.gno +++ b/examples/gno.land/r/demo/boards/z_5_c_filetest.gno @@ -14,6 +14,8 @@ import ( const admin = std.Address("g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj") func main() { + caller := testutils.TestAddress("caller") + std.TestSetRealm(std.NewUserRealm(caller)) users.Register("", "gnouser", "my profile") // create board via registered user bid := boards.CreateBoard("test_board") diff --git a/examples/gno.land/r/demo/boards/z_5_d_filetest.gno b/examples/gno.land/r/demo/boards/z_5_d_filetest.gno index 54cfe49eec6..33175efd4f2 100644 --- a/examples/gno.land/r/demo/boards/z_5_d_filetest.gno +++ b/examples/gno.land/r/demo/boards/z_5_d_filetest.gno @@ -14,6 +14,8 @@ import ( const admin = std.Address("g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj") func main() { + caller := testutils.TestAddress("caller") + std.TestSetRealm(std.NewUserRealm(caller)) users.Register("", "gnouser", "my profile") // create board via registered user bid := boards.CreateBoard("test_board") diff --git a/examples/gno.land/r/demo/boards/z_5_filetest.gno b/examples/gno.land/r/demo/boards/z_5_filetest.gno index 4f6f5cb9b75..04ef39e8938 100644 --- a/examples/gno.land/r/demo/boards/z_5_filetest.gno +++ b/examples/gno.land/r/demo/boards/z_5_filetest.gno @@ -4,8 +4,10 @@ package boards_test // SEND: 200000000ugnot import ( + "std" "strconv" + "gno.land/p/demo/testutils" "gno.land/r/demo/boards" "gno.land/r/demo/users" ) @@ -16,6 +18,8 @@ var ( ) func init() { + caller := testutils.TestAddress("caller") + std.TestSetRealm(std.NewUserRealm(caller)) users.Register("", "gnouser", "my profile") bid = boards.CreateBoard("test_board") @@ -25,6 +29,8 @@ func init() { } func main() { + caller := testutils.TestAddress("caller") + std.TestSetRealm(std.NewUserRealm(caller)) rid2 := boards.CreateReply(bid, pid, pid, "Second reply of the second post\n") println(boards.Render("test_board/" + strconv.Itoa(int(pid)))) } diff --git a/examples/gno.land/r/demo/boards/z_6_filetest.gno b/examples/gno.land/r/demo/boards/z_6_filetest.gno index 39791606aae..8847c46130a 100644 --- a/examples/gno.land/r/demo/boards/z_6_filetest.gno +++ b/examples/gno.land/r/demo/boards/z_6_filetest.gno @@ -4,8 +4,10 @@ package boards_test // SEND: 200000000ugnot import ( + "std" "strconv" + "gno.land/p/demo/testutils" "gno.land/r/demo/boards" "gno.land/r/demo/users" ) @@ -17,6 +19,8 @@ var ( ) func init() { + caller := testutils.TestAddress("caller") + std.TestSetRealm(std.NewUserRealm(caller)) users.Register("", "gnouser", "my profile") bid = boards.CreateBoard("test_board") @@ -26,6 +30,8 @@ func init() { } func main() { + caller := testutils.TestAddress("caller") + std.TestSetRealm(std.NewUserRealm(caller)) boards.CreateReply(bid, pid, pid, "Second reply of the second post\n") boards.CreateReply(bid, pid, rid, "First reply of the first reply\n") println(boards.Render("test_board/" + strconv.Itoa(int(pid)))) diff --git a/examples/gno.land/r/demo/boards/z_7_filetest.gno b/examples/gno.land/r/demo/boards/z_7_filetest.gno index 9ff95f7492d..30f39351815 100644 --- a/examples/gno.land/r/demo/boards/z_7_filetest.gno +++ b/examples/gno.land/r/demo/boards/z_7_filetest.gno @@ -4,11 +4,16 @@ package boards_test // SEND: 200000000ugnot import ( + "std" + + "gno.land/p/demo/testutils" "gno.land/r/demo/boards" "gno.land/r/demo/users" ) func init() { + caller := testutils.TestAddress("caller") + std.TestSetRealm(std.NewUserRealm(caller)) // register users.Register("", "gnouser", "my profile") diff --git a/examples/gno.land/r/demo/boards/z_8_filetest.gno b/examples/gno.land/r/demo/boards/z_8_filetest.gno index 47d84737698..5824dbc8ad6 100644 --- a/examples/gno.land/r/demo/boards/z_8_filetest.gno +++ b/examples/gno.land/r/demo/boards/z_8_filetest.gno @@ -4,8 +4,10 @@ package boards_test // SEND: 200000000ugnot import ( + "std" "strconv" + "gno.land/p/demo/testutils" "gno.land/r/demo/boards" "gno.land/r/demo/users" ) @@ -17,6 +19,8 @@ var ( ) func init() { + caller := testutils.TestAddress("caller") + std.TestSetRealm(std.NewUserRealm(caller)) users.Register("", "gnouser", "my profile") bid = boards.CreateBoard("test_board") @@ -26,6 +30,8 @@ func init() { } func main() { + caller := testutils.TestAddress("caller") + std.TestSetRealm(std.NewUserRealm(caller)) boards.CreateReply(bid, pid, pid, "Second reply of the second post\n") rid2 := boards.CreateReply(bid, pid, rid, "First reply of the first reply\n") println(boards.Render("test_board/" + strconv.Itoa(int(pid)) + "/" + strconv.Itoa(int(rid2)))) diff --git a/examples/gno.land/r/demo/boards/z_9_a_filetest.gno b/examples/gno.land/r/demo/boards/z_9_a_filetest.gno index 8d07ba0e710..79f68f20200 100644 --- a/examples/gno.land/r/demo/boards/z_9_a_filetest.gno +++ b/examples/gno.land/r/demo/boards/z_9_a_filetest.gno @@ -4,6 +4,9 @@ package boards_test // SEND: 200000000ugnot import ( + "std" + + "gno.land/p/demo/testutils" "gno.land/r/demo/boards" "gno.land/r/demo/users" ) @@ -11,6 +14,8 @@ import ( var dstBoard boards.BoardID func init() { + caller := testutils.TestAddress("caller") + std.TestSetRealm(std.NewUserRealm(caller)) users.Register("", "gnouser", "my profile") dstBoard = boards.CreateBoard("dst_board") diff --git a/examples/gno.land/r/demo/boards/z_9_b_filetest.gno b/examples/gno.land/r/demo/boards/z_9_b_filetest.gno index 68daf770b4f..703557cf476 100644 --- a/examples/gno.land/r/demo/boards/z_9_b_filetest.gno +++ b/examples/gno.land/r/demo/boards/z_9_b_filetest.gno @@ -4,6 +4,9 @@ package boards_test // SEND: 200000000ugnot import ( + "std" + + "gno.land/p/demo/testutils" "gno.land/r/demo/boards" "gno.land/r/demo/users" ) @@ -14,6 +17,8 @@ var ( ) func init() { + caller := testutils.TestAddress("caller") + std.TestSetRealm(std.NewUserRealm(caller)) users.Register("", "gnouser", "my profile") srcBoard = boards.CreateBoard("first_board") diff --git a/examples/gno.land/r/demo/boards/z_9_filetest.gno b/examples/gno.land/r/demo/boards/z_9_filetest.gno index 96af90263d5..9e23258553e 100644 --- a/examples/gno.land/r/demo/boards/z_9_filetest.gno +++ b/examples/gno.land/r/demo/boards/z_9_filetest.gno @@ -4,8 +4,10 @@ package boards_test // SEND: 200000000ugnot import ( + "std" "strconv" + "gno.land/p/demo/testutils" "gno.land/r/demo/boards" "gno.land/r/demo/users" ) @@ -17,6 +19,8 @@ var ( ) func init() { + caller := testutils.TestAddress("caller") + std.TestSetRealm(std.NewUserRealm(caller)) users.Register("", "gnouser", "my profile") firstBoard = boards.CreateBoard("first_board") diff --git a/examples/gno.land/r/demo/tests/subtests/subtests.gno b/examples/gno.land/r/demo/tests/subtests/subtests.gno index 6bf43cba5eb..5043c704017 100644 --- a/examples/gno.land/r/demo/tests/subtests/subtests.gno +++ b/examples/gno.land/r/demo/tests/subtests/subtests.gno @@ -21,5 +21,5 @@ func CallAssertOriginCall() { } func CallIsOriginCall() bool { - return std.IsOriginCall() + return std.PrevRealm().IsUser() } diff --git a/examples/gno.land/r/demo/tests/tests.gno b/examples/gno.land/r/demo/tests/tests.gno index e7fde94ea08..cdeea62de66 100644 --- a/examples/gno.land/r/demo/tests/tests.gno +++ b/examples/gno.land/r/demo/tests/tests.gno @@ -32,7 +32,7 @@ func CallAssertOriginCall() { } func CallIsOriginCall() bool { - return std.IsOriginCall() + return std.PrevRealm().IsUser() } func CallSubtestsAssertOriginCall() { diff --git a/examples/gno.land/r/demo/tests/tests_test.gno b/examples/gno.land/r/demo/tests/tests_test.gno index ccbc6b91265..fa3872744c8 100644 --- a/examples/gno.land/r/demo/tests/tests_test.gno +++ b/examples/gno.land/r/demo/tests/tests_test.gno @@ -1,17 +1,23 @@ -package tests +package tests_test import ( "std" "testing" + + "gno.land/p/demo/testutils" + "gno.land/r/demo/tests" ) func TestAssertOriginCall(t *testing.T) { // CallAssertOriginCall(): no panic - CallAssertOriginCall() - if !CallIsOriginCall() { + caller := testutils.TestAddress("caller") + std.TestSetRealm(std.NewUserRealm(caller)) + tests.CallAssertOriginCall() + if !tests.CallIsOriginCall() { t.Errorf("expected IsOriginCall=true but got false") } + std.TestSetRealm(std.NewCodeRealm("gno.land/r/demo/tests")) // CallAssertOriginCall() from a block: panic expectedReason := "invalid non-origin call" func() { @@ -23,10 +29,10 @@ func TestAssertOriginCall(t *testing.T) { }() // if called inside a function literal, this is no longer an origin call // because there's one additional frame (the function literal block). - if CallIsOriginCall() { + if tests.CallIsOriginCall() { t.Errorf("expected IsOriginCall=false but got true") } - CallAssertOriginCall() + tests.CallAssertOriginCall() }() // CallSubtestsAssertOriginCall(): panic @@ -36,23 +42,24 @@ func TestAssertOriginCall(t *testing.T) { t.Errorf("expected panic with '%v', got '%v'", expectedReason, r) } }() - if CallSubtestsIsOriginCall() { + if tests.CallSubtestsIsOriginCall() { t.Errorf("expected IsOriginCall=false but got true") } - CallSubtestsAssertOriginCall() + tests.CallSubtestsAssertOriginCall() } func TestPrevRealm(t *testing.T) { var ( - user1Addr = std.DerivePkgAddr("user1.gno") + firstRealm = std.DerivePkgAddr("gno.land/r/demo/tests_test") rTestsAddr = std.DerivePkgAddr("gno.land/r/demo/tests") ) - // When a single realm in the frames, PrevRealm returns the user - if addr := GetPrevRealm().Addr(); addr != user1Addr { - t.Errorf("want GetPrevRealm().Addr==%s, got %s", user1Addr, addr) + // When only one realm in the frames, PrevRealm returns the same realm + if addr := tests.GetPrevRealm().Addr(); addr != firstRealm { + println(tests.GetPrevRealm()) + t.Errorf("want GetPrevRealm().Addr==%s, got %s", firstRealm, addr) } // When 2 or more realms in the frames, PrevRealm returns the second to last - if addr := GetRSubtestsPrevRealm().Addr(); addr != rTestsAddr { + if addr := tests.GetRSubtestsPrevRealm().Addr(); addr != rTestsAddr { t.Errorf("want GetRSubtestsPrevRealm().Addr==%s, got %s", rTestsAddr, addr) } } diff --git a/examples/gno.land/r/demo/users/z_11_filetest.gno b/examples/gno.land/r/demo/users/z_11_filetest.gno index 27c7e9813da..212dc169007 100644 --- a/examples/gno.land/r/demo/users/z_11_filetest.gno +++ b/examples/gno.land/r/demo/users/z_11_filetest.gno @@ -11,8 +11,8 @@ import ( const admin = std.Address("g1manfred47kzduec920z88wfr64ylksmdcedlf5") func main() { - caller := std.GetOrigCaller() // main std.TestSetOrigCaller(admin) + caller := std.GetOrigCaller() // main users.AdminAddRestrictedName("superrestricted") // test restricted name diff --git a/examples/gno.land/r/demo/users/z_11b_filetest.gno b/examples/gno.land/r/demo/users/z_11b_filetest.gno index be508963911..6041f4b7113 100644 --- a/examples/gno.land/r/demo/users/z_11b_filetest.gno +++ b/examples/gno.land/r/demo/users/z_11b_filetest.gno @@ -11,8 +11,8 @@ import ( const admin = std.Address("g1manfred47kzduec920z88wfr64ylksmdcedlf5") func main() { - caller := std.GetOrigCaller() // main std.TestSetOrigCaller(admin) + caller := std.GetOrigCaller() // main // add restricted name users.AdminAddRestrictedName("superrestricted") // grant invite to caller diff --git a/examples/gno.land/r/demo/users/z_5_filetest.gno b/examples/gno.land/r/demo/users/z_5_filetest.gno index 6465cc9c378..9080b509b8e 100644 --- a/examples/gno.land/r/demo/users/z_5_filetest.gno +++ b/examples/gno.land/r/demo/users/z_5_filetest.gno @@ -16,14 +16,17 @@ func main() { users.Register("", "gnouser", "my profile") // as admin, grant invites to gnouser std.TestSetOrigCaller(admin) + std.TestSetRealm(std.NewUserRealm(admin)) users.GrantInvites(caller.String() + ":1") // switch back to caller std.TestSetOrigCaller(caller) + std.TestSetRealm(std.NewUserRealm(caller)) // invite another addr test1 := testutils.TestAddress("test1") users.Invite(test1.String()) // switch to test1 std.TestSetOrigCaller(test1) + std.TestSetRealm(std.NewUserRealm(test1)) std.TestSetOrigSend(std.Coins{{"dontcare", 1}}, nil) users.Register(caller, "satoshi", "my other profile") println(users.Render("")) diff --git a/examples/gno.land/r/demo/users/z_6_filetest.gno b/examples/gno.land/r/demo/users/z_6_filetest.gno index 919088088a2..e6a63d83358 100644 --- a/examples/gno.land/r/demo/users/z_6_filetest.gno +++ b/examples/gno.land/r/demo/users/z_6_filetest.gno @@ -9,7 +9,7 @@ import ( const admin = std.Address("g1manfred47kzduec920z88wfr64ylksmdcedlf5") func main() { - caller := std.GetOrigCaller() + caller := std.GetOrigCaller() // main // as admin, grant invites to unregistered user. std.TestSetOrigCaller(admin) users.GrantInvites(caller.String() + ":1") diff --git a/gnovm/cmd/gno/testdata/transpile/valid_transpile_file.txtar b/gnovm/cmd/gno/testdata/transpile/valid_transpile_file.txtar index 906c43cc41b..5e1f1a5ff0f 100644 --- a/gnovm/cmd/gno/testdata/transpile/valid_transpile_file.txtar +++ b/gnovm/cmd/gno/testdata/transpile/valid_transpile_file.txtar @@ -36,7 +36,7 @@ package main import "std" func hello() { - std.AssertOriginCall() + std.GetChainID() } -- main.gno.gen.go.golden -- @@ -61,5 +61,5 @@ package main import "github.com/gnolang/gno/gnovm/stdlibs/std" func hello() { - std.AssertOriginCall(nil) + std.GetChainID(nil) } diff --git a/gnovm/pkg/transpiler/transpiler_test.go b/gnovm/pkg/transpiler/transpiler_test.go index 2a0707f7f79..63b77e49446 100644 --- a/gnovm/pkg/transpiler/transpiler_test.go +++ b/gnovm/pkg/transpiler/transpiler_test.go @@ -344,13 +344,13 @@ func Float32bits(i float32) uint32 func testfunc() { println(Float32bits(3.14159)) - std.AssertOriginCall() + std.GetChainID() } func otherFunc() { std := 1 // This is (incorrectly) changed for now. - std.AssertOriginCall() + std.GetChainID() } `, expectedOutput: ` @@ -363,13 +363,13 @@ import "github.com/gnolang/gno/gnovm/stdlibs/std" func testfunc() { println(Float32bits(3.14159)) - std.AssertOriginCall(nil) + std.GetChainID(nil) } func otherFunc() { std := 1 // This is (incorrectly) changed for now. - std.AssertOriginCall(nil) + std.GetChainID(nil) } `, expectedImports: []*ast.ImportSpec{ @@ -388,11 +388,11 @@ func otherFunc() { source: ` package std -func AssertOriginCall() +func GetChainID() func origCaller() string func testfunc() { - AssertOriginCall() + GetChainID() println(origCaller()) } `, @@ -403,7 +403,7 @@ func testfunc() { package std func testfunc() { - AssertOriginCall(nil) + GetChainID(nil) println(X_origCaller(nil)) } `, diff --git a/gnovm/stdlibs/generated.go b/gnovm/stdlibs/generated.go index 6bd45de3589..a349ddf092e 100644 --- a/gnovm/stdlibs/generated.go +++ b/gnovm/stdlibs/generated.go @@ -428,26 +428,6 @@ var nativeFuncs = [...]NativeFunc{ ) }, }, - { - "std", - "IsOriginCall", - []gno.FieldTypeExpr{}, - []gno.FieldTypeExpr{ - {Name: gno.N("r0"), Type: gno.X("bool")}, - }, - true, - func(m *gno.Machine) { - r0 := libs_std.IsOriginCall( - m, - ) - - m.PushValue(gno.Go2GnoValue( - m.Alloc, - m.Store, - reflect.ValueOf(&r0).Elem(), - )) - }, - }, { "std", "GetChainID", diff --git a/gnovm/stdlibs/std/native.gno b/gnovm/stdlibs/std/native.gno index 9cf8808a07e..2baa1f92f48 100644 --- a/gnovm/stdlibs/std/native.gno +++ b/gnovm/stdlibs/std/native.gno @@ -1,15 +1,11 @@ package std -// AssertOriginCall panics if [IsOriginCall] returns false. -func AssertOriginCall() // injected - -// IsOriginCall returns true only if the calling method is invoked via a direct -// MsgCall. It returns false for all other cases, like if the calling method +// AssertOriginCall panics if the calling method is not invoked via a direct +// MsgCall. It panics for for other cases, like if the calling method // is invoked by another method (even from the same realm or package). -// It also returns false every time when the transaction is broadcasted via +// It also panic every time when the transaction is broadcasted via // MsgRun. -func IsOriginCall() bool // injected - +func AssertOriginCall() // injected func GetChainID() string // injected func GetChainDomain() string // injected func GetHeight() int64 // injected diff --git a/gnovm/stdlibs/std/native.go b/gnovm/stdlibs/std/native.go index 9e398e907a2..68f4542f689 100644 --- a/gnovm/stdlibs/std/native.go +++ b/gnovm/stdlibs/std/native.go @@ -7,12 +7,12 @@ import ( ) func AssertOriginCall(m *gno.Machine) { - if !IsOriginCall(m) { + if !isOriginCall(m) { m.Panic(typedString("invalid non-origin call")) } } -func IsOriginCall(m *gno.Machine) bool { +func isOriginCall(m *gno.Machine) bool { n := m.NumFrames() if n == 0 { return false diff --git a/gnovm/stdlibs/std/native_test.go b/gnovm/stdlibs/std/native_test.go index 851785575d7..acbd22055d6 100644 --- a/gnovm/stdlibs/std/native_test.go +++ b/gnovm/stdlibs/std/native_test.go @@ -184,7 +184,7 @@ func TestPrevRealmIsOrigin(t *testing.T) { assert := assert.New(t) addr, pkgPath := X_getRealm(tt.machine, 1) - isOrigin := IsOriginCall(tt.machine) + isOrigin := isOriginCall(tt.machine) assert.Equal(string(tt.expectedAddr), addr) assert.Equal(tt.expectedPkgPath, pkgPath) diff --git a/gnovm/tests/files/std5.gno b/gnovm/tests/files/std5.gno index 2f9e98bb4ec..1f1d013c3df 100644 --- a/gnovm/tests/files/std5.gno +++ b/gnovm/tests/files/std5.gno @@ -13,10 +13,10 @@ func main() { // Stacktrace: // panic: frame not found -// callerAt(n) +// callerAt(n) // gonative:std.callerAt // std.GetCallerAt(2) -// std/native.gno:45 +// std/native.gno:41 // main() // main/files/std5.gno:10 diff --git a/gnovm/tests/files/std8.gno b/gnovm/tests/files/std8.gno index dfc2b8ca5fd..3d0e4a7085e 100644 --- a/gnovm/tests/files/std8.gno +++ b/gnovm/tests/files/std8.gno @@ -23,10 +23,10 @@ func main() { // Stacktrace: // panic: frame not found -// callerAt(n) +// callerAt(n) // gonative:std.callerAt // std.GetCallerAt(4) -// std/native.gno:45 +// std/native.gno:41 // fn() // main/files/std8.gno:16 // testutils.WrapCall(inner) diff --git a/gnovm/tests/stdlibs/generated.go b/gnovm/tests/stdlibs/generated.go index 4445d2467e8..4690f47d82f 100644 --- a/gnovm/tests/stdlibs/generated.go +++ b/gnovm/tests/stdlibs/generated.go @@ -43,26 +43,6 @@ var nativeFuncs = [...]NativeFunc{ ) }, }, - { - "std", - "IsOriginCall", - []gno.FieldTypeExpr{}, - []gno.FieldTypeExpr{ - {Name: gno.N("r0"), Type: gno.X("bool")}, - }, - true, - func(m *gno.Machine) { - r0 := testlibs_std.IsOriginCall( - m, - ) - - m.PushValue(gno.Go2GnoValue( - m.Alloc, - m.Store, - reflect.ValueOf(&r0).Elem(), - )) - }, - }, { "std", "TestSkipHeights", diff --git a/gnovm/tests/stdlibs/std/std.gno b/gnovm/tests/stdlibs/std/std.gno index dcb5a64dbb3..c30071313fe 100644 --- a/gnovm/tests/stdlibs/std/std.gno +++ b/gnovm/tests/stdlibs/std/std.gno @@ -1,7 +1,6 @@ package std func AssertOriginCall() // injected -func IsOriginCall() bool // injected func TestSkipHeights(count int64) // injected func TestSetOrigCaller(addr Address) { testSetOrigCaller(string(addr)) } diff --git a/gnovm/tests/stdlibs/std/std.go b/gnovm/tests/stdlibs/std/std.go index 675194b252f..eac51c5fb0e 100644 --- a/gnovm/tests/stdlibs/std/std.go +++ b/gnovm/tests/stdlibs/std/std.go @@ -26,7 +26,7 @@ type RealmOverride struct { } func AssertOriginCall(m *gno.Machine) { - if !IsOriginCall(m) { + if !isOriginCall(m) { m.Panic(typedString("invalid non-origin call")) } } @@ -37,7 +37,7 @@ func typedString(s gno.StringValue) gno.TypedValue { return tv } -func IsOriginCall(m *gno.Machine) bool { +func isOriginCall(m *gno.Machine) bool { tname := m.Frames[0].Func.Name switch tname { case "main": // test is a _filetest From 479b314fd60e5952c75ad4953e1ea298546fd0b3 Mon Sep 17 00:00:00 2001 From: 6h057 <15034695+omarsy@users.noreply.github.com> Date: Tue, 4 Feb 2025 22:13:45 +0100 Subject: [PATCH 113/143] feat(gnovm): support constant evaluation of len and cap on arrays (#3600) Closes: #3201 This update introduces the ability to evaluate len and cap for arrays at preprocess-time, allowing these values to be treated as constants. While the array itself is not constant, the values of len and cap can be determined during the preprocess. This change eliminates the need for machine.EvalStatic that causes a vm crash. --------- Co-authored-by: Lee ByeongJun Co-authored-by: Petar Dambovaliev --- gnovm/pkg/gnolang/preprocess.go | 42 ++++++++++++++++++++++++++------- gnovm/pkg/gnolang/type_check.go | 4 ++-- gnovm/pkg/gnolang/types.go | 7 ++++++ gnovm/tests/files/const51.gno | 20 ++++++++++++++++ gnovm/tests/files/const52.gno | 11 +++++++++ 5 files changed, 73 insertions(+), 11 deletions(-) create mode 100644 gnovm/tests/files/const51.gno create mode 100644 gnovm/tests/files/const52.gno diff --git a/gnovm/pkg/gnolang/preprocess.go b/gnovm/pkg/gnolang/preprocess.go index ca5834aa44e..0b86449b235 100644 --- a/gnovm/pkg/gnolang/preprocess.go +++ b/gnovm/pkg/gnolang/preprocess.go @@ -3366,18 +3366,42 @@ func getResultTypedValues(cx *CallExpr) []TypedValue { // NOTE: Generally, conversion happens in a separate step while leaving // composite exprs/nodes that contain constant expression nodes (e.g. const // exprs in the rhs of AssignStmts). +// +// Array-related expressions like `len` and `cap` are manually evaluated +// as constants, even if the array itself is not a constant. This evaluation +// is handled independently of the rest of the constant evaluation process, +// bypassing machine.EvalStatic. func evalConst(store Store, last BlockNode, x Expr) *ConstExpr { // TODO: some check or verification for ensuring x - // is constant? From the machine? - m := NewMachine(".dontcare", store) - m.PreprocessorMode = true + var cx *ConstExpr + if clx, ok := x.(*CallExpr); ok { + t := evalStaticTypeOf(store, last, clx.Args[0]) + if ar, ok := unwrapPointerType(baseOf(t)).(*ArrayType); ok { + fv := clx.Func.(*ConstExpr).V.(*FuncValue) + switch fv.Name { + case "cap", "len": + tv := TypedValue{T: IntType} + tv.SetInt(ar.Len) + cx = &ConstExpr{ + Source: x, + TypedValue: tv, + } + default: + panic(fmt.Sprintf("unexpected const func %s", fv.Name)) + } + } + } - cv := m.EvalStatic(last, x) - m.PreprocessorMode = false - m.Release() - cx := &ConstExpr{ - Source: x, - TypedValue: cv, + if cx == nil { + // is constant? From the machine? + m := NewMachine(".dontcare", store) + cv := m.EvalStatic(last, x) + m.PreprocessorMode = false + m.Release() + cx = &ConstExpr{ + Source: x, + TypedValue: cv, + } } cx.SetLine(x.GetLine()) cx.SetAttribute(ATTR_PREPROCESSED, true) diff --git a/gnovm/pkg/gnolang/type_check.go b/gnovm/pkg/gnolang/type_check.go index f96cb71e4b6..a79e9c43ecc 100644 --- a/gnovm/pkg/gnolang/type_check.go +++ b/gnovm/pkg/gnolang/type_check.go @@ -270,7 +270,7 @@ Main: switch { case fv.Name == "len": at := evalStaticTypeOf(store, last, currExpr.Args[0]) - if _, ok := baseOf(at).(*ArrayType); ok { + if _, ok := unwrapPointerType(baseOf(at)).(*ArrayType); ok { // ok break Main } @@ -278,7 +278,7 @@ Main: break Main case fv.Name == "cap": at := evalStaticTypeOf(store, last, currExpr.Args[0]) - if _, ok := baseOf(at).(*ArrayType); ok { + if _, ok := unwrapPointerType(baseOf(at)).(*ArrayType); ok { // ok break Main } diff --git a/gnovm/pkg/gnolang/types.go b/gnovm/pkg/gnolang/types.go index 374ac6d9150..8ac07162f10 100644 --- a/gnovm/pkg/gnolang/types.go +++ b/gnovm/pkg/gnolang/types.go @@ -1469,6 +1469,13 @@ func baseOf(t Type) Type { } } +func unwrapPointerType(t Type) Type { + if pt, ok := t.(*PointerType); ok { + return pt.Elem() + } + return t +} + // NOTE: it may be faster to switch on baseOf(). func (dt *DeclaredType) Kind() Kind { return dt.Base.Kind() diff --git a/gnovm/tests/files/const51.gno b/gnovm/tests/files/const51.gno new file mode 100644 index 00000000000..b00748b0ec7 --- /dev/null +++ b/gnovm/tests/files/const51.gno @@ -0,0 +1,20 @@ +package main + +type T1 struct { + x [2]string +} + +type T2 struct { + x *[2]string +} + +func main() { + t1 := T1{x: [2]string{"a", "b"}} + t2 := T2{x: &[2]string{"a", "b"}} + const c1 = len(t1.x) + const c2 = len(t2.x) + println(c1, c2) +} + +// Output: +// 2 2 diff --git a/gnovm/tests/files/const52.gno b/gnovm/tests/files/const52.gno new file mode 100644 index 00000000000..c213faeb12b --- /dev/null +++ b/gnovm/tests/files/const52.gno @@ -0,0 +1,11 @@ +package main + +func main() { + s := make([][2]string, 1) // Slice with length 1 + s[0] = [2]string{"a", "b"} // Assign value to s[0] + const r = len(s[0]) + println(r) // Prints: 2 +} + +// Output: +// 2 From 3614b605cb79a29bfdeabde2f9732f3b901d1418 Mon Sep 17 00:00:00 2001 From: Morgan Date: Tue, 4 Feb 2025 22:15:29 +0100 Subject: [PATCH 114/143] fix(github-bot): require most rules to activate when base == `master` (#3592) Co-authored-by: Antoine Eddi <5222525+aeddi@users.noreply.github.com> --- contribs/github-bot/internal/config/config.go | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/contribs/github-bot/internal/config/config.go b/contribs/github-bot/internal/config/config.go index 43a505ad15a..5db91a73413 100644 --- a/contribs/github-bot/internal/config/config.go +++ b/contribs/github-bot/internal/config/config.go @@ -33,12 +33,18 @@ func Config(gh *client.GitHub) ([]AutomaticCheck, []ManualCheck) { auto := []AutomaticCheck{ { Description: "Maintainers must be able to edit this pull request ([more info](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/allowing-changes-to-a-pull-request-branch-created-from-a-fork))", - If: c.CreatedFromFork(), - Then: r.MaintainerCanModify(), + If: c.And( + c.BaseBranch("^master$"), + c.CreatedFromFork(), + ), + Then: r.MaintainerCanModify(), }, { Description: "Changes to 'docs' folder must be reviewed/authored by at least one devrel and one tech-staff", - If: c.FileChanged(gh, "^docs/"), + If: c.And( + c.BaseBranch("^master$"), + c.FileChanged(gh, "^docs/"), + ), Then: r.And( r.Or( r.AuthorInTeam(gh, "tech-staff"), @@ -57,7 +63,10 @@ func Config(gh *client.GitHub) ([]AutomaticCheck, []ManualCheck) { }, { Description: "Pending initial approval by a review team member, or review from tech-staff", - If: c.Not(c.AuthorInTeam(gh, "tech-staff")), + If: c.And( + c.BaseBranch("^master$"), + c.Not(c.AuthorInTeam(gh, "tech-staff")), + ), Then: r. If(r.Or( r.ReviewByOrgMembers(gh).WithDesiredState(utils.ReviewStateApproved), @@ -91,7 +100,7 @@ func Config(gh *client.GitHub) ([]AutomaticCheck, []ManualCheck) { { Description: "Determine if infra needs to be updated before merging", If: c.And( - c.BaseBranch("master"), + c.BaseBranch("^master$"), c.Or( c.FileChanged(gh, `Dockerfile`), c.FileChanged(gh, `^misc/deployments`), From 8410060e33725bf99b7417088b140895eea43485 Mon Sep 17 00:00:00 2001 From: Manfred Touron <94029+moul@users.noreply.github.com> Date: Tue, 4 Feb 2025 22:23:45 +0100 Subject: [PATCH 115/143] feat: add r/moul/present (#3668) This realm is a good example of reusing and integrating some of my recently added libraries: - `p/moul/md` #2819 - `p/moul/mdtable` #3100 - `p/moul/realmpath` #3257 - `p/moul/txlink` #3289 - `p/moul/collection` #3321 - `p/demo/avl/pager` #2584 It helped me identify that `txlink` was not escaping the arguments, which resulted in invalid links. (fixed in #3682) Additionally, it provided me with a better understanding of: - The shortcomings of the `p/moul/md` API, particularly regarding `"\n"` handling - The need for improved management of the pager for `p/moul/collection` - What kind of UI improvements we could need on gnoweb. #3355 Demo: https://github.com/user-attachments/assets/4b20cee8-b8d7-4eba-90a8-5b87a3c19521 I also suggest you to look at the `filetest.gno` file. I believe we should proceed with the merge, to inspire others to create similar composed realms. However, I have a few improvement ideas: 1. Extract most of the generic logic into a `p/moul/present`. 2. Consider either making r/moul/present importable by `r/coreteam/present` to create a hub for presentations from all teammates, or the opposite: make `r/coreteam/present` the content source and allow `r/moul/present` to display a subset where `author="moul"` What are your thoughts? 3. Clean up the code using an improved `p/moul/md` and possibly new `p/` generic utilities. Depenes on #3682 --------- Signed-off-by: moul <94029+moul@users.noreply.github.com> --- examples/gno.land/r/moul/present/admin.gno | 96 ----- examples/gno.land/r/moul/present/present.gno | 353 ++++++++++++++++++ .../r/moul/present/present_filetest.gno | 233 ++++++++++++ .../gno.land/r/moul/present/present_init.gno | 25 ++ .../r/moul/present/present_miami23.gno | 42 --- .../moul/present/present_miami23_filetest.gno | 11 - .../gno.land/r/moul/present/presentations.gno | 17 - 7 files changed, 611 insertions(+), 166 deletions(-) delete mode 100644 examples/gno.land/r/moul/present/admin.gno create mode 100644 examples/gno.land/r/moul/present/present.gno create mode 100644 examples/gno.land/r/moul/present/present_filetest.gno create mode 100644 examples/gno.land/r/moul/present/present_init.gno delete mode 100644 examples/gno.land/r/moul/present/present_miami23.gno delete mode 100644 examples/gno.land/r/moul/present/present_miami23_filetest.gno delete mode 100644 examples/gno.land/r/moul/present/presentations.gno diff --git a/examples/gno.land/r/moul/present/admin.gno b/examples/gno.land/r/moul/present/admin.gno deleted file mode 100644 index ab99b1725c5..00000000000 --- a/examples/gno.land/r/moul/present/admin.gno +++ /dev/null @@ -1,96 +0,0 @@ -package present - -import ( - "std" - "strings" - - "gno.land/p/demo/avl" -) - -var ( - adminAddr std.Address - moderatorList avl.Tree - inPause bool -) - -func init() { - // adminAddr = std.GetOrigCaller() // FIXME: find a way to use this from the main's genesis. - adminAddr = "g1manfred47kzduec920z88wfr64ylksmdcedlf5" -} - -func AdminSetAdminAddr(addr std.Address) { - assertIsAdmin() - adminAddr = addr -} - -func AdminSetInPause(state bool) { - assertIsAdmin() - inPause = state -} - -func AdminAddModerator(addr std.Address) { - assertIsAdmin() - moderatorList.Set(addr.String(), true) -} - -func AdminRemoveModerator(addr std.Address) { - assertIsAdmin() - moderatorList.Set(addr.String(), false) // XXX: delete instead? -} - -func ModAddPost(slug, title, body, publicationDate, authors, tags string) { - assertIsModerator() - - caller := std.GetOrigCaller() - tagList := strings.Split(tags, ",") - authorList := strings.Split(authors, ",") - - err := b.NewPost(caller, slug, title, body, publicationDate, authorList, tagList) - checkErr(err) -} - -func ModEditPost(slug, title, body, publicationDate, authors, tags string) { - assertIsModerator() - - tagList := strings.Split(tags, ",") - authorList := strings.Split(authors, ",") - - err := b.GetPost(slug).Update(title, body, publicationDate, authorList, tagList) - checkErr(err) -} - -func isAdmin(addr std.Address) bool { - return addr == adminAddr -} - -func isModerator(addr std.Address) bool { - _, found := moderatorList.Get(addr.String()) - return found -} - -func assertIsAdmin() { - caller := std.GetOrigCaller() - if !isAdmin(caller) { - panic("access restricted.") - } -} - -func assertIsModerator() { - caller := std.GetOrigCaller() - if isAdmin(caller) || isModerator(caller) { - return - } - panic("access restricted") -} - -func assertNotInPause() { - if inPause { - panic("access restricted (pause)") - } -} - -func checkErr(err error) { - if err != nil { - panic(err) - } -} diff --git a/examples/gno.land/r/moul/present/present.gno b/examples/gno.land/r/moul/present/present.gno new file mode 100644 index 00000000000..b4f880318bf --- /dev/null +++ b/examples/gno.land/r/moul/present/present.gno @@ -0,0 +1,353 @@ +package present + +import ( + "net/url" + "std" + "strconv" + "strings" + "time" + + "gno.land/p/demo/avl/pager" + "gno.land/p/demo/ownable" + "gno.land/p/demo/seqid" + "gno.land/p/demo/ufmt" + "gno.land/p/moul/collection" + "gno.land/p/moul/md" + "gno.land/p/moul/mdtable" + "gno.land/p/moul/realmpath" + "gno.land/p/moul/txlink" +) + +type Presentation struct { + Slug string + Title string + Event string + Author string + Uploader std.Address + Date time.Time + Content string + EditDate time.Time + NumSlides int +} + +var ( + presentations *collection.Collection + Ownable *ownable.Ownable +) + +func init() { + presentations = collection.New() + // for /view and /slides + presentations.AddIndex("slug", func(v interface{}) string { + return v.(*Presentation).Slug + }, collection.UniqueIndex) + + // for table sorting + presentations.AddIndex("date", func(v interface{}) string { + return v.(*Presentation).Date.String() + }, collection.DefaultIndex) + presentations.AddIndex("author", func(v interface{}) string { + return v.(*Presentation).Author + }, collection.DefaultIndex) + presentations.AddIndex("title", func(v interface{}) string { + return v.(*Presentation).Title + }, collection.DefaultIndex) + + Ownable = ownable.New() +} + +// Render handles the realm's rendering logic +func Render(path string) string { + req := realmpath.Parse(path) + + // Get slug from path + slug := req.PathPart(0) + + // List view (home) + if slug == "" { + return renderList(req) + } + + // Slides view + if req.PathPart(1) == "slides" { + page := 1 + if pageStr := req.Query.Get("page"); pageStr != "" { + var err error + page, err = strconv.Atoi(pageStr) + if err != nil { + return "400: invalid page number" + } + } + return renderSlides(slug, page) + } + + // Regular view + return renderView(slug) +} + +// Set adds or updates a presentation +func Set(slug, title, event, author, date, content string) string { + Ownable.AssertCallerIsOwner() + + parsedDate, err := time.Parse("2006-01-02", date) + if err != nil { + return "400: invalid date format (expected: YYYY-MM-DD)" + } + + numSlides := 1 // Count intro slide + for _, line := range strings.Split(content, "\n") { + if strings.HasPrefix(line, "## ") { + numSlides++ + } + } + numSlides++ // Count thank you slide + + p := &Presentation{ + Slug: slug, + Title: title, + Event: event, + Author: author, + Uploader: std.PrevRealm().Addr(), + Date: parsedDate, + Content: content, + EditDate: time.Now(), + NumSlides: numSlides, + } + + presentations.Set(p) + return "presentation saved successfully" +} + +// Delete removes a presentation +func Delete(slug string) string { + Ownable.AssertCallerIsOwner() + + entry := presentations.GetFirst("slug", slug) + if entry == nil { + return "404: presentation not found" + } + + // XXX: consider this: + // if entry.Obj.(*Presentation).Uploader != std.PrevRealm().Addr() { + // return "401: unauthorized - only the uploader can delete their presentations" + // } + + // Convert the entry's ID from string to uint64 and delete + numericID, err := seqid.FromString(entry.ID) + if err != nil { + return "500: invalid entry ID format" + } + + presentations.Delete(uint64(numericID)) + return "presentation deleted successfully" +} + +func renderList(req *realmpath.Request) string { + var out strings.Builder + out.WriteString(md.H1("Presentations")) + + // Setup pager + index := presentations.GetIndex(getSortField(req)) + pgr := pager.NewPager(index, 10, isSortReversed(req)) + + // Get current page + page := pgr.MustGetPageByPath(req.String()) + + // Create table + dateColumn := renderSortLink(req, "date", "Date") + titleColumn := renderSortLink(req, "title", "Title") + authorColumn := renderSortLink(req, "author", "Author") + table := mdtable.Table{ + Headers: []string{dateColumn, titleColumn, "Event", authorColumn, "Slides"}, + } + + // Add rows from current page + for _, item := range page.Items { + // Get the actual presentation using the ID from the index + // XXX: improve p/moul/collection to make this more convenient. + // - no need to make per-id lookup. + // - transparently support multi-values. + // - integrate a sortable pager? + var ids []string + if ids_, ok := item.Value.([]string); ok { + ids = ids_ + } else if id, ok := item.Value.(string); ok { + ids = []string{id} + } + + for _, id := range ids { + entry := presentations.GetFirst(collection.IDIndex, id) + if entry == nil { + continue + } + p := entry.Obj.(*Presentation) + + table.Append([]string{ + p.Date.Format("2006-01-02"), + md.Link(p.Title, localPath(p.Slug, nil)), + p.Event, + p.Author, + ufmt.Sprintf("%d", p.NumSlides), + }) + } + } + + out.WriteString(table.String()) + out.WriteString(page.Picker()) // XXX: picker is not preserving the previous flags, should take "req" as argument. + return out.String() +} + +func (p *Presentation) FirstSlide() string { + var out strings.Builder + out.WriteString(md.H1(p.Title)) + out.WriteString(md.Paragraph(md.Bold(p.Event) + ", " + p.Date.Format("2 Jan 2006"))) + out.WriteString(md.Paragraph("by " + md.Bold(p.Author))) // XXX: link to u/? + return out.String() +} + +func (p *Presentation) LastSlide() string { + var out strings.Builder + out.WriteString(md.H1(p.Title)) + out.WriteString(md.H2("Thank You!")) + out.WriteString(md.Paragraph(p.Author)) + fullPath := "https://" + std.GetChainDomain() + localPath(p.Slug, nil) + out.WriteString(md.Paragraph("🔗 " + md.Link(fullPath, fullPath))) + // XXX: QRCode + return out.String() +} + +func renderView(slug string) string { + if slug == "" { + return "400: missing presentation slug" + } + + entry := presentations.GetFirst("slug", slug) + if entry == nil { + return "404: presentation not found" + } + + p := entry.Obj.(*Presentation) + var out strings.Builder + + // Header using FirstSlide helper + out.WriteString(p.FirstSlide()) + + // Slide mode link + out.WriteString(md.Link("View as slides", localPath(p.Slug+"/slides", nil)) + "\n\n") + out.WriteString(md.HorizontalRule()) + out.WriteString(md.Paragraph(p.Content)) + + // Metadata footer + out.WriteString(md.HorizontalRule()) + out.WriteString(ufmt.Sprintf("Last edited: %s\n\n", p.EditDate.Format("2006-01-02 15:04:05"))) + out.WriteString(ufmt.Sprintf("Uploader: `%s`\n\n", p.Uploader)) + out.WriteString(ufmt.Sprintf("Number of slides: %d\n\n", p.NumSlides)) + + // Admin actions + // XXX: consider a dynamic toggle for admin actions + editLink := txlink.Call("Set", + "slug", p.Slug, + "title", p.Title, + "author", p.Author, + "event", p.Event, + "date", p.Date.Format("2006-01-02"), + ) + deleteLink := txlink.Call("Delete", "slug", p.Slug) + out.WriteString(md.Paragraph(md.Link("Edit", editLink) + " | " + md.Link("Delete", deleteLink))) + + return out.String() +} + +// renderSlidesNavigation returns the navigation bar for slides +func renderSlidesNavigation(slug string, currentPage, totalSlides int) string { + var out strings.Builder + if currentPage > 1 { + prevLink := localPath(slug+"/slides", url.Values{"page": {ufmt.Sprintf("%d", currentPage-1)}}) + out.WriteString(md.Link("← Prev", prevLink) + " ") + } + out.WriteString(ufmt.Sprintf("| %d/%d |", currentPage, totalSlides)) + if currentPage < totalSlides { + nextLink := localPath(slug+"/slides", url.Values{"page": {ufmt.Sprintf("%d", currentPage+1)}}) + out.WriteString(" " + md.Link("Next →", nextLink)) + } + return md.Paragraph(out.String()) +} + +func renderSlides(slug string, currentPage int) string { + if slug == "" { + return "400: missing presentation ID" + } + + entry := presentations.GetFirst("slug", slug) + if entry == nil { + return "404: presentation not found" + } + + p := entry.Obj.(*Presentation) + slides := strings.Split("\n"+p.Content, "\n## ") + if currentPage < 1 || currentPage > p.NumSlides { + return "404: invalid slide number" + } + + var out strings.Builder + + // Display current slide + if currentPage == 1 { + out.WriteString(p.FirstSlide()) + } else if currentPage == p.NumSlides { + out.WriteString(p.LastSlide()) + } else { + out.WriteString(md.H1(p.Title)) + out.WriteString("## " + slides[currentPage-1] + "\n\n") + } + + out.WriteString(renderSlidesNavigation(slug, currentPage, p.NumSlides)) + return out.String() +} + +// Helper functions for sorting and pagination +func getSortField(req *realmpath.Request) string { + field := req.Query.Get("sort") + switch field { + case "date", "slug", "author", "title": + return field + } + return "date" +} + +func isSortReversed(req *realmpath.Request) bool { + return req.Query.Get("order") != "asc" +} + +func renderSortLink(req *realmpath.Request, field, label string) string { + currentField := getSortField(req) + currentOrder := req.Query.Get("order") + + newOrder := "desc" + if field == currentField && currentOrder != "asc" { + newOrder = "asc" + } + + query := req.Query + query.Set("sort", field) + query.Set("order", newOrder) + + if field == currentField { + if newOrder == "asc" { + label += " ↑" + } else { + label += " ↓" + } + } + + return md.Link(label, "?"+query.Encode()) +} + +// helper to create local realm links +func localPath(path string, query url.Values) string { + req := &realmpath.Request{ + Path: path, + Query: query, + } + return req.String() +} diff --git a/examples/gno.land/r/moul/present/present_filetest.gno b/examples/gno.land/r/moul/present/present_filetest.gno new file mode 100644 index 00000000000..7e9385454b9 --- /dev/null +++ b/examples/gno.land/r/moul/present/present_filetest.gno @@ -0,0 +1,233 @@ +package main + +import ( + "gno.land/r/moul/present" +) + +func main() { + // Cleanup initial state + ret := present.Delete("demo") + if ret != "presentation deleted successfully" { + panic("internal error") + } + + // Create presentations with IDs from 10-20 + presentations := []struct { + id string + title string + event string + author string + date string + content string + }{ + {"s10", "title10", "event3", "author1", "2024-01-01", "## s10.0\n## s10.1"}, + {"s11", "title11", "event1", "author2", "2024-01-15", "## s11.0\n## s11.1"}, + {"s12", "title12", "event2", "author1", "2024-02-01", "## s12.0\n## s12.1"}, + {"s13", "title13", "event1", "author3", "2024-01-20", "## s13.0\n## s13.1"}, + {"s14", "title14", "event3", "author2", "2024-03-01", "## s14.0\n## s14.1"}, + {"s15", "title15", "event2", "author1", "2024-02-15", "## s15.0\n## s15.1\n## s15.2"}, + {"s16", "title16", "event1", "author4", "2024-03-15", "## s16.0\n## s16.1"}, + {"s17", "title17", "event3", "author2", "2024-01-10", "## s17.0\n## s17.1"}, + {"s18", "title18", "event2", "author3", "2024-02-20", "## s18.0\n## s18.1"}, + {"s19", "title19", "event1", "author1", "2024-03-10", "## s19.0\n## s19.1"}, + {"s20", "title20", "event3", "author4", "2024-01-05", "## s20.0\n## s20.1"}, + } + + for _, p := range presentations { + result := present.Set(p.id, p.title, p.event, p.author, p.date, p.content) + if result != "presentation saved successfully" { + panic("failed to add presentation: " + result) + } + } + + // Test different sorting scenarios + printRender("") // default + printRender("?order=asc&sort=date") // by date ascending + printRender("?order=asc&sort=title") // by title ascending + printRender("?order=asc&sort=author") // by author ascending (multiple entries per author) + + // Test pagination + printRender("?order=asc&sort=title&page=2") // second page + + // Test view + printRender("s15") // view by slug + + // Test slides + printRender("s15/slides") // slides by slug + printRender("s15/slides?page=2") // slides by slug, second page + printRender("s15/slides?page=3") // slides by slug, third page + printRender("s15/slides?page=4") // slides by slug, fourth page + printRender("s15/slides?page=5") // slides by slug, fifth page +} + +// Helper function to print path and render result +func printRender(path string) { + println("+-------------------------------") + println("| PATH:", path) + println("| RESULT:\n" + present.Render(path) + "\n") +} + +// Output: +// +------------------------------- +// | PATH: +// | RESULT: +// # Presentations +// | [Date ↑](?order=asc&sort=date) | [Title](?order=desc&sort=title) | Event | [Author](?order=desc&sort=author) | Slides | +// | --- | --- | --- | --- | --- | +// | 2024-03-15 | [title16](/r/moul/present:s16) | event1 | author4 | 4 | +// | 2024-03-10 | [title19](/r/moul/present:s19) | event1 | author1 | 4 | +// | 2024-03-01 | [title14](/r/moul/present:s14) | event3 | author2 | 4 | +// | 2024-02-20 | [title18](/r/moul/present:s18) | event2 | author3 | 4 | +// | 2024-02-15 | [title15](/r/moul/present:s15) | event2 | author1 | 5 | +// | 2024-02-01 | [title12](/r/moul/present:s12) | event2 | author1 | 4 | +// | 2024-01-20 | [title13](/r/moul/present:s13) | event1 | author3 | 4 | +// | 2024-01-15 | [title11](/r/moul/present:s11) | event1 | author2 | 4 | +// | 2024-01-10 | [title17](/r/moul/present:s17) | event3 | author2 | 4 | +// | 2024-01-05 | [title20](/r/moul/present:s20) | event3 | author4 | 4 | +// **1** | [2](?page=2) +// +// +------------------------------- +// | PATH: ?order=asc&sort=date +// | RESULT: +// # Presentations +// | [Date ↓](?order=desc&sort=date) | [Title](?order=desc&sort=title) | Event | [Author](?order=desc&sort=author) | Slides | +// | --- | --- | --- | --- | --- | +// | 2024-01-01 | [title10](/r/moul/present:s10) | event3 | author1 | 4 | +// | 2024-01-05 | [title20](/r/moul/present:s20) | event3 | author4 | 4 | +// | 2024-01-10 | [title17](/r/moul/present:s17) | event3 | author2 | 4 | +// | 2024-01-15 | [title11](/r/moul/present:s11) | event1 | author2 | 4 | +// | 2024-01-20 | [title13](/r/moul/present:s13) | event1 | author3 | 4 | +// | 2024-02-01 | [title12](/r/moul/present:s12) | event2 | author1 | 4 | +// | 2024-02-15 | [title15](/r/moul/present:s15) | event2 | author1 | 5 | +// | 2024-02-20 | [title18](/r/moul/present:s18) | event2 | author3 | 4 | +// | 2024-03-01 | [title14](/r/moul/present:s14) | event3 | author2 | 4 | +// | 2024-03-10 | [title19](/r/moul/present:s19) | event1 | author1 | 4 | +// **1** | [2](?page=2) +// +// +------------------------------- +// | PATH: ?order=asc&sort=title +// | RESULT: +// # Presentations +// | [Date](?order=desc&sort=date) | [Title](?order=desc&sort=title) | Event | [Author](?order=desc&sort=author) | Slides | +// | --- | --- | --- | --- | --- | +// | 2024-01-01 | [title10](/r/moul/present:s10) | event3 | author1 | 4 | +// | 2024-01-15 | [title11](/r/moul/present:s11) | event1 | author2 | 4 | +// | 2024-02-01 | [title12](/r/moul/present:s12) | event2 | author1 | 4 | +// | 2024-01-20 | [title13](/r/moul/present:s13) | event1 | author3 | 4 | +// | 2024-03-01 | [title14](/r/moul/present:s14) | event3 | author2 | 4 | +// | 2024-02-15 | [title15](/r/moul/present:s15) | event2 | author1 | 5 | +// | 2024-03-15 | [title16](/r/moul/present:s16) | event1 | author4 | 4 | +// | 2024-01-10 | [title17](/r/moul/present:s17) | event3 | author2 | 4 | +// | 2024-02-20 | [title18](/r/moul/present:s18) | event2 | author3 | 4 | +// | 2024-03-10 | [title19](/r/moul/present:s19) | event1 | author1 | 4 | +// **1** | [2](?page=2) +// +// +------------------------------- +// | PATH: ?order=asc&sort=author +// | RESULT: +// # Presentations +// | [Date](?order=desc&sort=date) | [Title](?order=desc&sort=title) | Event | [Author](?order=desc&sort=author) | Slides | +// | --- | --- | --- | --- | --- | +// | 2024-01-01 | [title10](/r/moul/present:s10) | event3 | author1 | 4 | +// | 2024-02-01 | [title12](/r/moul/present:s12) | event2 | author1 | 4 | +// | 2024-02-15 | [title15](/r/moul/present:s15) | event2 | author1 | 5 | +// | 2024-03-10 | [title19](/r/moul/present:s19) | event1 | author1 | 4 | +// | 2024-01-15 | [title11](/r/moul/present:s11) | event1 | author2 | 4 | +// | 2024-03-01 | [title14](/r/moul/present:s14) | event3 | author2 | 4 | +// | 2024-01-10 | [title17](/r/moul/present:s17) | event3 | author2 | 4 | +// | 2024-01-20 | [title13](/r/moul/present:s13) | event1 | author3 | 4 | +// | 2024-02-20 | [title18](/r/moul/present:s18) | event2 | author3 | 4 | +// | 2024-03-15 | [title16](/r/moul/present:s16) | event1 | author4 | 4 | +// | 2024-01-05 | [title20](/r/moul/present:s20) | event3 | author4 | 4 | +// +// +// +------------------------------- +// | PATH: ?order=asc&sort=title&page=2 +// | RESULT: +// # Presentations +// | [Date](?order=desc&page=2&sort=date) | [Title](?order=desc&page=2&sort=title) | Event | [Author](?order=desc&page=2&sort=author) | Slides | +// | --- | --- | --- | --- | --- | +// | 2024-01-05 | [title20](/r/moul/present:s20) | event3 | author4 | 4 | +// [1](?page=1) | **2** +// +// +------------------------------- +// | PATH: s15 +// | RESULT: +// # title15 +// **event2**, 15 Feb 2024 +// +// by **author1** +// +// [View as slides](/r/moul/present:s15/slides) +// +// --- +// ## s15.0 +// ## s15.1 +// ## s15.2 +// +// --- +// Last edited: 2009-02-13 23:31:30 +// +// Uploader: `g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm` +// +// Number of slides: 5 +// +// [Edit](/r/moul/present$help&func=Set&author=author1&date=2024-02-15&event=event2&slug=s15&title=title15) | [Delete](/r/moul/present$help&func=Delete&slug=s15) +// +// +// +// +------------------------------- +// | PATH: s15/slides +// | RESULT: +// # title15 +// **event2**, 15 Feb 2024 +// +// by **author1** +// +// | 1/5 | [Next →](/r/moul/present:s15/slides?page=2) +// +// +// +// +------------------------------- +// | PATH: s15/slides?page=2 +// | RESULT: +// # title15 +// ## s15.0 +// +// [← Prev](/r/moul/present:s15/slides?page=1) | 2/5 | [Next →](/r/moul/present:s15/slides?page=3) +// +// +// +// +------------------------------- +// | PATH: s15/slides?page=3 +// | RESULT: +// # title15 +// ## s15.1 +// +// [← Prev](/r/moul/present:s15/slides?page=2) | 3/5 | [Next →](/r/moul/present:s15/slides?page=4) +// +// +// +// +------------------------------- +// | PATH: s15/slides?page=4 +// | RESULT: +// # title15 +// ## s15.2 +// +// [← Prev](/r/moul/present:s15/slides?page=3) | 4/5 | [Next →](/r/moul/present:s15/slides?page=5) +// +// +// +// +------------------------------- +// | PATH: s15/slides?page=5 +// | RESULT: +// # title15 +// ## Thank You! +// author1 +// +// 🔗 [https://tests\.gno\.land/r/moul/present:s15](https://tests.gno.land/r/moul/present:s15) +// +// [← Prev](/r/moul/present:s15/slides?page=4) | 5/5 | +// +// +// diff --git a/examples/gno.land/r/moul/present/present_init.gno b/examples/gno.land/r/moul/present/present_init.gno new file mode 100644 index 00000000000..b103bdf8cd6 --- /dev/null +++ b/examples/gno.land/r/moul/present/present_init.gno @@ -0,0 +1,25 @@ +package present + +func init() { + _ = Set( + "demo", // id + "Demo Slides", // title + "Demo Event", // event + "@demo", // author + "2025-02-02", // date + `## Slide One +- Point A +- Point B +- Point C + +## Slide Two +- Feature 1 +- Feature 2 +- Feature 3 + +## Slide Three +- Next step +- Another step +- Final step`, + ) +} diff --git a/examples/gno.land/r/moul/present/present_miami23.gno b/examples/gno.land/r/moul/present/present_miami23.gno deleted file mode 100644 index ca2160de3a9..00000000000 --- a/examples/gno.land/r/moul/present/present_miami23.gno +++ /dev/null @@ -1,42 +0,0 @@ -package present - -func init() { - path := "miami23" - title := "Portal Loop Demo (Miami 2023)" - body := ` -Rendered by Gno. - -[Source (WIP)](https://github.com/gnolang/gno/pull/1176) - -## Portal Loop - -- DONE: Dynamic homepage, key pages, aliases, and redirects. -- TODO: Deploy with history, complete worxdao v0. -- Will replace the static gno.land site. -- Enhances local development. - -[GitHub Issue](https://github.com/gnolang/gno/issues/1108) - -## Roadmap - -- Crafting the roadmap this week, open to collaboration. -- Combining onchain (portal loop) and offchain (GitHub). -- Next week: Unveiling the official v0 roadmap. - -## Teams, DAOs, Projects - -- Developing worxDAO contracts for directories of projects and teams. -- GitHub teams and projects align with this structure. -- CODEOWNER file updates coming. -- Initial teams announced next week. - -## Tech Team Retreat Plan - -- Continue Portal Loop. -- Consider dApp development. -- Explore new topics [here](https://github.com/orgs/gnolang/projects/15/). -- Engage in workshops. -- Connect and have fun with colleagues. -` - _ = b.NewPost(adminAddr, path, title, body, "2023-10-15T13:17:24Z", []string{"moul"}, []string{"demo", "portal-loop", "miami"}) -} diff --git a/examples/gno.land/r/moul/present/present_miami23_filetest.gno b/examples/gno.land/r/moul/present/present_miami23_filetest.gno deleted file mode 100644 index 09d332ec6e4..00000000000 --- a/examples/gno.land/r/moul/present/present_miami23_filetest.gno +++ /dev/null @@ -1,11 +0,0 @@ -package main - -import ( - "gno.land/r/moul/present" -) - -func main() { - println(present.Render("")) - println("------------------------------------") - println(present.Render("p/miami23")) -} diff --git a/examples/gno.land/r/moul/present/presentations.gno b/examples/gno.land/r/moul/present/presentations.gno deleted file mode 100644 index c5529804751..00000000000 --- a/examples/gno.land/r/moul/present/presentations.gno +++ /dev/null @@ -1,17 +0,0 @@ -package present - -import ( - "gno.land/p/demo/blog" -) - -// TODO: switch from p/blog to p/present - -var b = &blog.Blog{ - Title: "Manfred's Presentations", - Prefix: "/r/moul/present:", - NoBreadcrumb: true, -} - -func Render(path string) string { - return b.Render(path) -} From 6fe3b319ff45f97ac4912ddecc530addaecb58c7 Mon Sep 17 00:00:00 2001 From: Miguel Victoria Villaquiran Date: Tue, 4 Feb 2025 23:03:49 +0100 Subject: [PATCH 116/143] chore(pipeline): run build of github pages on pullRequests (#3607) Co-authored-by: Morgan Bazalgette --- .github/workflows/gh-pages.yml | 23 ++++++++++++++++++----- misc/stdlib_diff/Makefile | 4 +++- misc/stdlib_diff/README.md | 4 ++-- 3 files changed, 23 insertions(+), 8 deletions(-) diff --git a/.github/workflows/gh-pages.yml b/.github/workflows/gh-pages.yml index a293469bb5d..2b27a2537e1 100644 --- a/.github/workflows/gh-pages.yml +++ b/.github/workflows/gh-pages.yml @@ -1,11 +1,14 @@ # generate Go docs and publish on gh-pages branch # Live at: https://gnolang.github.io/gno -name: Go Reference Docs Deployment +name: GitHub pages (godoc & stdlib_diff) build and deploy on: push: branches: - master + pull_request: + branches: + - master workflow_dispatch: permissions: @@ -19,29 +22,39 @@ concurrency: jobs: build: - if: ${{ github.repository == 'gnolang/gno' }} # Alternatively, validate based on provided tokens and permissions. + if: github.repository == 'gnolang/gno' # Alternatively, validate based on provided tokens and permissions. runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-go@v5 with: go-version-file: go.mod - - run: echo "GOROOT=$(go env GOROOT)" >> $GITHUB_ENV - - run: echo $GOROOT + # Use the goroot at the top of the project to compare with the GnoVM + # stdlib, rather than the one in stdlib_diff (which may have a go.mod with + # a different toolchain version). + - run: echo "GOROOT_SAVE=$(go env GOROOT)" >> $GITHUB_ENV - run: "cd misc/stdlib_diff && make gen" - run: "cd misc/gendocs && make install gen" - run: "mkdir -p pages_output/stdlib_diff" - run: | cp -r misc/gendocs/godoc/* pages_output/ cp -r misc/stdlib_diff/stdlib_diff/* pages_output/stdlib_diff/ + + # These two last steps will be skipped on pull requests - uses: actions/configure-pages@v5 id: pages + if: github.event_name != 'pull_request' + - uses: actions/upload-pages-artifact@v3 + if: github.event_name != 'pull_request' with: path: ./pages_output deploy: - if: ${{ github.repository == 'gnolang/gno' }} # Alternatively, validate based on provided tokens and permissions. + if: > + github.repository == 'gnolang/gno' && + github.ref == 'refs/heads/master' && + github.event_name == 'push' runs-on: ubuntu-latest environment: name: github-pages diff --git a/misc/stdlib_diff/Makefile b/misc/stdlib_diff/Makefile index 439af22c586..32dcf95a2ec 100644 --- a/misc/stdlib_diff/Makefile +++ b/misc/stdlib_diff/Makefile @@ -1,7 +1,9 @@ all: clean gen +GOROOT_SAVE ?= $(shell go env GOROOT) + gen: - go run . -src $(GOROOT)/src -dst ../../gnovm/stdlibs -out ./stdlib_diff + go run . -src $(GOROOT_SAVE)/src -dst ../../gnovm/stdlibs -out ./stdlib_diff clean: rm -rf stdlib_diff diff --git a/misc/stdlib_diff/README.md b/misc/stdlib_diff/README.md index 32c3cbcd93d..47d05a0373b 100644 --- a/misc/stdlib_diff/README.md +++ b/misc/stdlib_diff/README.md @@ -1,6 +1,6 @@ # stdlibs_diff -stdlibs_diff is a tool that generates an html report indicating differences between gno standard libraries and go standrad libraries +stdlibs_diff is a tool that generates an html report indicating differences between gno standard libraries and go standard libraries. ## Usage @@ -27,4 +27,4 @@ Compare the `gno` standard libraries the `go` standard libraries ## Tips -An index.html is generated at the root of the report location. Utilize it to navigate easily through the report. \ No newline at end of file +An index.html is generated at the root of the report location. Utilize it to navigate easily through the report. From d75f1a2fe060c64f0ed51182f5dda965043ace9f Mon Sep 17 00:00:00 2001 From: Nemanja Matic <106317308+Nemanya8@users.noreply.github.com> Date: Tue, 4 Feb 2025 17:38:43 -0500 Subject: [PATCH 117/143] fix(docs/gnokey): addpkg subcommand documentation (#3635) --- docs/gno-tooling/cli/gnokey/state-changing-calls.md | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/docs/gno-tooling/cli/gnokey/state-changing-calls.md b/docs/gno-tooling/cli/gnokey/state-changing-calls.md index 79a777cca51..b301e99be56 100644 --- a/docs/gno-tooling/cli/gnokey/state-changing-calls.md +++ b/docs/gno-tooling/cli/gnokey/state-changing-calls.md @@ -99,12 +99,11 @@ Next, let's configure the `addpkg` subcommand to publish this package to the the `example/p/` folder, the command will look like this: ```bash -gnokey maketx addpkg \ +gnokey maketx addpkg \ -pkgpath "gno.land/p//hello_world" \ -pkgdir "." \ --send "" \ -gas-fee 10000000ugnot \ --gas-wanted 8000000 \ +-gas-wanted 200000 \ -broadcast \ -chainid portal-loop \ -remote "https://rpc.gno.land:443" @@ -114,15 +113,14 @@ Once we have added a desired [namespace](../../../concepts/namespaces.md) to upl a keypair name to use to execute the transaction: ```bash -gnokey maketx addpkg \ +gnokey maketx addpkg \ -pkgpath "gno.land/p/examplenamespace/hello_world" \ -pkgdir "." \ --send "" \ -gas-fee 10000000ugnot \ -gas-wanted 200000 \ -broadcast \ -chainid portal-loop \ --remote "https://rpc.gno.land:443" +-remote "https://rpc.gno.land:443" \ mykey ``` From d7bfee2a5946446dccd8f00812b29daa4ca7a437 Mon Sep 17 00:00:00 2001 From: Mustapha <102119509+mous1985@users.noreply.github.com> Date: Wed, 5 Feb 2025 12:28:26 +0100 Subject: [PATCH 118/143] feat(mux): add wildcard (*) for more flexible route matching (#3631) This pull request is for add some TODOs in `p/demo/mux` : - Add handling for NotFoundHandler - Add wildcard detection in route Basic example: ```go router.HandleFunc("r/user/*", func(rw *ResponseWriter, req *Request)) // match: // "r/user/profile" // "r/user/posts" // "r/user/home/avatar" --- examples/gno.land/p/demo/mux/handler.gno | 6 ++-- examples/gno.land/p/demo/mux/request.gno | 27 ++++++++------ examples/gno.land/p/demo/mux/request_test.gno | 33 ++++++++++------- examples/gno.land/p/demo/mux/router.gno | 35 ++++++++++++++++--- examples/gno.land/p/demo/mux/router_test.gno | 28 ++++++++++++++- 5 files changed, 98 insertions(+), 31 deletions(-) diff --git a/examples/gno.land/p/demo/mux/handler.gno b/examples/gno.land/p/demo/mux/handler.gno index 835d050a52c..4d937dbacab 100644 --- a/examples/gno.land/p/demo/mux/handler.gno +++ b/examples/gno.land/p/demo/mux/handler.gno @@ -7,6 +7,8 @@ type Handler struct { type HandlerFunc func(*ResponseWriter, *Request) -// TODO: type ErrHandlerFunc func(*ResponseWriter, *Request) error -// TODO: NotFoundHandler +type ErrHandlerFunc func(*ResponseWriter, *Request) error + +type NotFoundHandler func(*ResponseWriter, *Request) + // TODO: AutomaticIndex diff --git a/examples/gno.land/p/demo/mux/request.gno b/examples/gno.land/p/demo/mux/request.gno index 7b5b74da91b..eaa2f287069 100644 --- a/examples/gno.land/p/demo/mux/request.gno +++ b/examples/gno.land/p/demo/mux/request.gno @@ -18,24 +18,29 @@ type Request struct { // GetVar retrieves a variable from the path based on routing rules. func (r *Request) GetVar(key string) string { - var ( - handlerParts = strings.Split(r.HandlerPath, "/") - reqParts = strings.Split(r.Path, "/") - ) - - for i := 0; i < len(handlerParts); i++ { - handlerPart := handlerParts[i] + handlerParts := strings.Split(r.HandlerPath, "/") + reqParts := strings.Split(r.Path, "/") + reqIndex := 0 + for handlerIndex := 0; handlerIndex < len(handlerParts); handlerIndex++ { + handlerPart := handlerParts[handlerIndex] switch { case handlerPart == "*": - // XXX: implement a/b/*/d/e - panic("not implemented") + // If a wildcard "*" is found, consume all remaining segments + wildcardParts := reqParts[reqIndex:] + reqIndex = len(reqParts) // Consume all remaining segments + return strings.Join(wildcardParts, "/") // Return all remaining segments as a string case strings.HasPrefix(handlerPart, "{") && strings.HasSuffix(handlerPart, "}"): + // If a variable of the form {param} is found we compare it with the key parameter := handlerPart[1 : len(handlerPart)-1] if parameter == key { - return reqParts[i] + return reqParts[reqIndex] } + reqIndex++ default: - // continue + if reqIndex >= len(reqParts) || handlerPart != reqParts[reqIndex] { + return "" + } + reqIndex++ } } diff --git a/examples/gno.land/p/demo/mux/request_test.gno b/examples/gno.land/p/demo/mux/request_test.gno index 5f8088b4964..24c611c1f9d 100644 --- a/examples/gno.land/p/demo/mux/request_test.gno +++ b/examples/gno.land/p/demo/mux/request_test.gno @@ -1,8 +1,10 @@ package mux import ( - "fmt" "testing" + + "gno.land/p/demo/uassert" + "gno.land/p/demo/ufmt" ) func TestRequest_GetVar(t *testing.T) { @@ -12,28 +14,35 @@ func TestRequest_GetVar(t *testing.T) { getVarKey string expectedOutput string }{ + {"users/{id}", "users/123", "id", "123"}, {"users/123", "users/123", "id", ""}, {"users/{id}", "users/123", "nonexistent", ""}, - {"a/{b}/c/{d}", "a/42/c/1337", "b", "42"}, - {"a/{b}/c/{d}", "a/42/c/1337", "d", "1337"}, - {"{a}", "foo", "a", "foo"}, - // TODO: wildcards: a/*/c - // TODO: multiple patterns per slashes: a/{b}-{c}/d - } + {"users/{userId}/posts/{postId}", "users/123/posts/456", "userId", "123"}, + {"users/{userId}/posts/{postId}", "users/123/posts/456", "postId", "456"}, + + // Wildcards + {"*", "users/123", "*", "users/123"}, + {"*", "users/123/posts/456", "*", "users/123/posts/456"}, + {"*", "users/123/posts/456/comments/789", "*", "users/123/posts/456/comments/789"}, + {"users/*", "users/john/posts", "*", "john/posts"}, + {"users/*/comments", "users/jane/comments", "*", "jane/comments"}, + {"api/*/posts/*", "api/v1/posts/123", "*", "v1/posts/123"}, + // wildcards and parameters + {"api/{version}/*", "api/v1/user/settings", "version", "v1"}, + } for _, tt := range cases { - name := fmt.Sprintf("%s-%s", tt.handlerPath, tt.reqPath) + name := ufmt.Sprintf("%s-%s", tt.handlerPath, tt.reqPath) t.Run(name, func(t *testing.T) { req := &Request{ HandlerPath: tt.handlerPath, Path: tt.reqPath, } - output := req.GetVar(tt.getVarKey) - if output != tt.expectedOutput { - t.Errorf("Expected '%q, but got %q", tt.expectedOutput, output) - } + uassert.Equal(t, tt.expectedOutput, output, + "handler: %q, path: %q, key: %q", + tt.handlerPath, tt.reqPath, tt.getVarKey) }) } } diff --git a/examples/gno.land/p/demo/mux/router.gno b/examples/gno.land/p/demo/mux/router.gno index fe6bf70abdf..4fca43a0378 100644 --- a/examples/gno.land/p/demo/mux/router.gno +++ b/examples/gno.land/p/demo/mux/router.gno @@ -5,7 +5,7 @@ import "strings" // Router handles the routing and rendering logic. type Router struct { routes []Handler - NotFoundHandler HandlerFunc + NotFoundHandler NotFoundHandler } // NewRouter creates a new Router instance. @@ -23,8 +23,14 @@ func (r *Router) Render(reqPath string) string { for _, route := range r.routes { patParts := strings.Split(route.Pattern, "/") - - if len(patParts) != len(reqParts) { + wildcard := false + for _, part := range patParts { + if part == "*" { + wildcard = true + break + } + } + if !wildcard && len(patParts) != len(reqParts) { continue } @@ -34,7 +40,7 @@ func (r *Router) Render(reqPath string) string { reqPart := reqParts[i] if patPart == "*" { - continue + break } if strings.HasPrefix(patPart, "{") && strings.HasSuffix(patPart, "}") { continue @@ -63,12 +69,31 @@ func (r *Router) Render(reqPath string) string { return res.Output() } -// Handle registers a route and its handler function. +// HandleFunc registers a route and its handler function. func (r *Router) HandleFunc(pattern string, fn HandlerFunc) { route := Handler{Pattern: pattern, Fn: fn} r.routes = append(r.routes, route) } +// HandleErrFunc registers a route and its error handler function. +func (r *Router) HandleErrFunc(pattern string, fn ErrHandlerFunc) { + + // Convert ErrHandlerFunc to regular HandlerFunc + handler := func(res *ResponseWriter, req *Request) { + if err := fn(res, req); err != nil { + res.Write("Error: " + err.Error()) + } + } + + r.HandleFunc(pattern, handler) +} + +// SetNotFoundHandler sets custom message for 404 defaultNotFoundHandler. +func (r *Router) SetNotFoundHandler(handler NotFoundHandler) { + r.NotFoundHandler = handler +} + +// stripQueryString removes query string from the request path. func stripQueryString(reqPath string) string { i := strings.Index(reqPath, "?") if i == -1 { diff --git a/examples/gno.land/p/demo/mux/router_test.gno b/examples/gno.land/p/demo/mux/router_test.gno index cc6aad62146..c1c5d218165 100644 --- a/examples/gno.land/p/demo/mux/router_test.gno +++ b/examples/gno.land/p/demo/mux/router_test.gno @@ -72,7 +72,33 @@ func TestRouter_Render(t *testing.T) { }) }, }, - + { + label: "wildcard in route", + path: "hello/Alice/Bob", + expectedOutput: "Matched: Alice/Bob", + setupHandler: func(t *testing.T, r *Router) { + r.HandleFunc("hello/*", func(rw *ResponseWriter, req *Request) { + path := req.GetVar("*") + uassert.Equal(t, "Alice/Bob", path) + uassert.Equal(t, "hello/Alice/Bob", req.Path) + rw.Write("Matched: " + path) + }) + }, + }, + { + label: "wildcard in route with query string", + path: "hello/Alice/Bob?foo=bar", + expectedOutput: "Matched: Alice/Bob", + setupHandler: func(t *testing.T, r *Router) { + r.HandleFunc("hello/*", func(rw *ResponseWriter, req *Request) { + path := req.GetVar("*") + uassert.Equal(t, "Alice/Bob", path) + uassert.Equal(t, "hello/Alice/Bob?foo=bar", req.RawPath) + uassert.Equal(t, "hello/Alice/Bob", req.Path) + rw.Write("Matched: " + path) + }) + }, + }, // TODO: {"hello", "Hello, world!"}, // TODO: hello/, /hello, hello//Alice, hello/Alice/, hello/Alice/Bob, etc } From e12b3f1a352556259dc49ee585cc78cfbddf2bed Mon Sep 17 00:00:00 2001 From: JJOptimist <86833563+JJOptimist@users.noreply.github.com> Date: Wed, 5 Feb 2025 12:32:00 +0100 Subject: [PATCH 119/143] feat: add JJOptimist's Homepage realm to examples (#3405) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This pull request adds a new realm example to the Gno examples repository—JJOptimist's Homepage. The realm includes: - Personal introduction and contact information - Configuration management system - Owner controls - Tests for ownership and rendering --------- Co-authored-by: Leon Hudak <33522493+leohhhn@users.noreply.github.com> --- .../gno.land/r/jjoptimist/home/config.gno | 32 ++++++++ examples/gno.land/r/jjoptimist/home/gno.mod | 1 + examples/gno.land/r/jjoptimist/home/home.gno | 82 +++++++++++++++++++ .../gno.land/r/jjoptimist/home/home_test.gno | 60 ++++++++++++++ 4 files changed, 175 insertions(+) create mode 100644 examples/gno.land/r/jjoptimist/home/config.gno create mode 100644 examples/gno.land/r/jjoptimist/home/gno.mod create mode 100644 examples/gno.land/r/jjoptimist/home/home.gno create mode 100644 examples/gno.land/r/jjoptimist/home/home_test.gno diff --git a/examples/gno.land/r/jjoptimist/home/config.gno b/examples/gno.land/r/jjoptimist/home/config.gno new file mode 100644 index 00000000000..7f6ad955806 --- /dev/null +++ b/examples/gno.land/r/jjoptimist/home/config.gno @@ -0,0 +1,32 @@ +package home + +import ( + "std" + + "gno.land/p/demo/ownable" +) + +type Config struct { + Title string + Description string + Github string +} + +var config = Config{ + Title: "JJOptimist's Home Realm 🏠", + Description: "Exploring Gno and building on-chain", + Github: "jjoptimist", +} + +var Ownable = ownable.NewWithAddress(std.Address("g16vfw3r7zuz43fhky3xfsuc2hdv9tnhvlkyn0nj")) + +func GetConfig() Config { + return config +} + +func UpdateConfig(newTitle, newDescription, newGithub string) { + Ownable.AssertCallerIsOwner() + config.Title = newTitle + config.Description = newDescription + config.Github = newGithub +} diff --git a/examples/gno.land/r/jjoptimist/home/gno.mod b/examples/gno.land/r/jjoptimist/home/gno.mod new file mode 100644 index 00000000000..b4b591f6ab7 --- /dev/null +++ b/examples/gno.land/r/jjoptimist/home/gno.mod @@ -0,0 +1 @@ +module gno.land/r/jjoptimist/home diff --git a/examples/gno.land/r/jjoptimist/home/home.gno b/examples/gno.land/r/jjoptimist/home/home.gno new file mode 100644 index 00000000000..91a23670271 --- /dev/null +++ b/examples/gno.land/r/jjoptimist/home/home.gno @@ -0,0 +1,82 @@ +package home + +import ( + "std" + "strconv" + "time" + + "gno.land/r/leon/hof" +) + +const ( + gnomeArt1 = ` /\ + / \ + ,,,,, +(o.o) +(\_/) +-"-"-` + + gnomeArt2 = ` /\ + / \ + ,,,,, +(^.^) +(\_/) + -"-` + + gnomeArt3 = ` /\ + / \ + ,,,,, +(*.*) +(\_/) +"-"-"` + + gnomeArt4 = ` /\ + / \ + ,,,,, +(o.~) +(\_/) + -"-` +) + +var creation time.Time + +func getGnomeArt(height int64) string { + var art string + switch { + case height%7 == 0: + art = gnomeArt4 // winking gnome + case height%5 == 0: + art = gnomeArt3 // starry-eyed gnome + case height%3 == 0: + art = gnomeArt2 // happy gnome + default: + art = gnomeArt1 // regular gnome + } + return "```\n" + art + "\n```\n" +} + +func init() { + creation = time.Now() + hof.Register() +} + +func Render(path string) string { + height := std.GetHeight() + + output := "# " + config.Title + "\n\n" + + output += "## About Me\n" + output += "- 👋 Hi, I'm JJOptimist\n" + output += getGnomeArt(height) + output += "- 🌱 " + config.Description + "\n" + + output += "## Contact\n" + output += "- 📫 GitHub: [" + config.Github + "](https://github.com/" + config.Github + ")\n" + + output += "\n---\n" + output += "_Realm created: " + creation.Format("2006-01-02 15:04:05 UTC") + "_\n" + output += "_Owner: " + Ownable.Owner().String() + "_\n" + output += "_Current Block Height: " + strconv.Itoa(int(height)) + "_" + + return output +} diff --git a/examples/gno.land/r/jjoptimist/home/home_test.gno b/examples/gno.land/r/jjoptimist/home/home_test.gno new file mode 100644 index 00000000000..742204cca71 --- /dev/null +++ b/examples/gno.land/r/jjoptimist/home/home_test.gno @@ -0,0 +1,60 @@ +package home + +import ( + "strings" + "testing" +) + +func TestConfig(t *testing.T) { + cfg := GetConfig() + + if cfg.Title != "JJOptimist's Home Realm 🏠" { + t.Errorf("Expected title to be 'JJOptimist's Home Realm 🏠', got %s", cfg.Title) + } + if cfg.Description != "Exploring Gno and building on-chain" { + t.Errorf("Expected description to be 'Exploring Gno and building on-chain', got %s", cfg.Description) + } + if cfg.Github != "jjoptimist" { + t.Errorf("Expected github to be 'jjoptimist', got %s", cfg.Github) + } +} + +func TestRender(t *testing.T) { + output := Render("") + + // Test that required sections are present + if !strings.Contains(output, "# "+config.Title) { + t.Error("Rendered output missing title") + } + if !strings.Contains(output, "## About Me") { + t.Error("Rendered output missing About Me section") + } + if !strings.Contains(output, "## Contact") { + t.Error("Rendered output missing Contact section") + } + if !strings.Contains(output, config.Description) { + t.Error("Rendered output missing description") + } + if !strings.Contains(output, config.Github) { + t.Error("Rendered output missing github link") + } +} + +func TestGetGnomeArt(t *testing.T) { + tests := []struct { + height int64 + expected string + }{ + {7, gnomeArt4}, // height divisible by 7 + {5, gnomeArt3}, // height divisible by 5 + {3, gnomeArt2}, // height divisible by 3 + {2, gnomeArt1}, // default case + } + + for _, tt := range tests { + art := getGnomeArt(tt.height) + if !strings.Contains(art, tt.expected) { + t.Errorf("For height %d, expected art containing %s, got %s", tt.height, tt.expected, art) + } + } +} From 9e6a67bc0709a6a4a3186b11bb1b9bffc6200a04 Mon Sep 17 00:00:00 2001 From: Guilhem Fanton <8671905+gfanton@users.noreply.github.com> Date: Wed, 5 Feb 2025 15:21:35 +0100 Subject: [PATCH 120/143] fix(p2p): Avoid inifinty loop during transport/listener `Accept` (#3662) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This PR aims to fix two potential infinite loops during `Accept` in the `p2p` package. - First, it removes the `ErrTransportInactive` error. Based on how `Accept` is used, I believe the inactive error does not make much sense here. `Accept` should always block, even in an inactive state. Having `Accept` in two states—blocking and non-blocking—complicates the logic unnecessarily. Additionally, returning an inactive error creates an infinite loop, as the handler will keep looping over `Accept` until a blocking state is reached. - If the underlying transport or listener is closed, the context should also be canceled, as this is not a recoverable error. Currently, if the transport or listener is closed, it will loop indefinitely until the context is properly closed. While this situation should rarely occur, since the context should be canceled when closing the transport, I believe it is safe to force the cancellation of the context to avoid any potential deadloop. **EDIT:** I've removed the context select from the loop to simplify the logic. The logic remains unchanged, but it avoids having two sources of truth. This PR also reduces unnecessary error and warning noise when the listener or transport is closed. --------- Signed-off-by: gfanton <8671905+gfanton@users.noreply.github.com> --- tm2/pkg/p2p/switch.go | 73 ++++++++++++------------ tm2/pkg/p2p/switch_test.go | 31 +++++++++++ tm2/pkg/p2p/transport.go | 102 +++++++++++++++------------------- tm2/pkg/p2p/transport_test.go | 10 ++-- tm2/pkg/service/service.go | 2 +- 5 files changed, 117 insertions(+), 101 deletions(-) diff --git a/tm2/pkg/p2p/switch.go b/tm2/pkg/p2p/switch.go index 7d9e768dd4b..7784c1f3989 100644 --- a/tm2/pkg/p2p/switch.go +++ b/tm2/pkg/p2p/switch.go @@ -5,6 +5,7 @@ import ( "context" "crypto/rand" "encoding/binary" + "errors" "fmt" "math" "sync" @@ -622,50 +623,50 @@ func (sw *MultiplexSwitch) isPrivatePeer(id types.ID) bool { // and persisting them func (sw *MultiplexSwitch) runAcceptLoop(ctx context.Context) { for { - select { - case <-ctx.Done(): - sw.Logger.Debug("switch context close received") + p, err := sw.transport.Accept(ctx, sw.peerBehavior) - return + switch { + case err == nil: // ok + case errors.Is(err, context.Canceled), errors.Is(err, context.DeadlineExceeded): + // Upper context as been canceled/timeout + sw.Logger.Debug("switch context close received") + return // exit + case errors.As(err, &errTransportClosed): + // Underlaying transport as been closed + sw.Logger.Warn("cannot accept connection on closed transport, exiting") + return // exit default: - p, err := sw.transport.Accept(ctx, sw.peerBehavior) - if err != nil { - sw.Logger.Error( - "error encountered during peer connection accept", - "err", err, - ) + // An error occurred during accept, report and continue + sw.Logger.Error("error encountered during peer connection accept", "err", err) + continue + } - continue - } + // Ignore connection if we already have enough peers. + if in := sw.Peers().NumInbound(); in >= sw.maxInboundPeers { + sw.Logger.Info( + "Ignoring inbound connection: already have enough inbound peers", + "address", p.SocketAddr(), + "have", in, + "max", sw.maxInboundPeers, + ) - // Ignore connection if we already have enough peers. - if in := sw.Peers().NumInbound(); in >= sw.maxInboundPeers { - sw.Logger.Info( - "Ignoring inbound connection: already have enough inbound peers", - "address", p.SocketAddr(), - "have", in, - "max", sw.maxInboundPeers, - ) + sw.transport.Remove(p) + continue + } - sw.transport.Remove(p) + // There are open peer slots, add peers + if err := sw.addPeer(p); err != nil { + sw.transport.Remove(p) - continue + if p.IsRunning() { + _ = p.Stop() } - // There are open peer slots, add peers - if err := sw.addPeer(p); err != nil { - sw.transport.Remove(p) - - if p.IsRunning() { - _ = p.Stop() - } - - sw.Logger.Info( - "Ignoring inbound connection: error while adding peer", - "err", err, - "id", p.ID(), - ) - } + sw.Logger.Info( + "Ignoring inbound connection: error while adding peer", + "err", err, + "id", p.ID(), + ) } } } diff --git a/tm2/pkg/p2p/switch_test.go b/tm2/pkg/p2p/switch_test.go index cf0a0c41bb5..b10ab3faba5 100644 --- a/tm2/pkg/p2p/switch_test.go +++ b/tm2/pkg/p2p/switch_test.go @@ -890,3 +890,34 @@ func TestCalculateBackoff(t *testing.T) { } }) } + +func TestSwitchAcceptLoopTransportClosed(t *testing.T) { + t.Parallel() + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + var transportClosed bool + mockTransport := &mockTransport{ + acceptFn: func(context.Context, PeerBehavior) (PeerConn, error) { + transportClosed = true + return nil, errTransportClosed + }, + } + + sw := NewMultiplexSwitch(mockTransport) + + // Run the accept loop + done := make(chan struct{}) + go func() { + sw.runAcceptLoop(ctx) + close(done) // signal that accept loop as ended + }() + + select { + case <-time.After(time.Second * 2): + require.FailNow(t, "timeout while waiting for running loop to stop") + case <-done: + assert.True(t, transportClosed) + } +} diff --git a/tm2/pkg/p2p/transport.go b/tm2/pkg/p2p/transport.go index 150072ad5eb..255fa60152b 100644 --- a/tm2/pkg/p2p/transport.go +++ b/tm2/pkg/p2p/transport.go @@ -2,6 +2,7 @@ package p2p import ( "context" + goerrors "errors" "fmt" "io" "log/slog" @@ -22,7 +23,6 @@ const defaultHandshakeTimeout = 3 * time.Second var ( errTransportClosed = errors.New("transport is closed") - errTransportInactive = errors.New("transport is inactive") errDuplicateConnection = errors.New("duplicate peer connection") errPeerIDNodeInfoMismatch = errors.New("connection ID does not match node info ID") errPeerIDDialMismatch = errors.New("connection ID does not match dialed ID") @@ -75,7 +75,10 @@ func NewMultiplexTransport( mConfig conn.MConnConfig, logger *slog.Logger, ) *MultiplexTransport { + ctx, cancel := context.WithCancel(context.Background()) return &MultiplexTransport{ + ctx: ctx, + cancelFn: cancel, peerCh: make(chan peerInfo, 1), mConfig: mConfig, nodeInfo: nodeInfo, @@ -92,12 +95,6 @@ func (mt *MultiplexTransport) NetAddress() types.NetAddress { // Accept waits for a verified inbound Peer to connect, and returns it [BLOCKING] func (mt *MultiplexTransport) Accept(ctx context.Context, behavior PeerBehavior) (PeerConn, error) { - // Sanity check, no need to wait - // on an inactive transport - if mt.listener == nil { - return nil, errTransportInactive - } - select { case <-ctx.Done(): return nil, ctx.Err() @@ -142,44 +139,35 @@ func (mt *MultiplexTransport) Close() error { } mt.cancelFn() - return mt.listener.Close() } // Listen starts an active process of listening for incoming connections [NON-BLOCKING] -func (mt *MultiplexTransport) Listen(addr types.NetAddress) (rerr error) { +func (mt *MultiplexTransport) Listen(addr types.NetAddress) error { // Reserve a port, and start listening ln, err := net.Listen("tcp", addr.DialString()) if err != nil { return fmt.Errorf("unable to listen on address, %w", err) } - defer func() { - if rerr != nil { - ln.Close() - } - }() - if addr.Port == 0 { // net.Listen on port 0 means the kernel will auto-allocate a port // - find out which one has been given to us. tcpAddr, ok := ln.Addr().(*net.TCPAddr) if !ok { + ln.Close() return fmt.Errorf("error finding port (after listening on port 0): %w", err) } addr.Port = uint16(tcpAddr.Port) } - // Set up the context - mt.ctx, mt.cancelFn = context.WithCancel(context.Background()) - mt.netAddr = addr mt.listener = ln // Run the routine for accepting // incoming peer connections - go mt.runAcceptLoop() + go mt.runAcceptLoop(mt.ctx) return nil } @@ -189,60 +177,58 @@ func (mt *MultiplexTransport) Listen(addr types.NetAddress) (rerr error) { // 1. accepted by the transport // 2. filtered // 3. upgraded (handshaked + verified) -func (mt *MultiplexTransport) runAcceptLoop() { +func (mt *MultiplexTransport) runAcceptLoop(ctx context.Context) { var wg sync.WaitGroup - defer func() { wg.Wait() // Wait for all process routines - close(mt.peerCh) }() - for { - select { - case <-mt.ctx.Done(): - mt.logger.Debug("transport accept context closed") + ctx, cancel := context.WithCancel(ctx) + defer cancel() // cancel sub-connection process - return + for { + // Accept an incoming peer connection + c, err := mt.listener.Accept() + + switch { + case err == nil: // ok + case goerrors.Is(err, net.ErrClosed): + // Listener has been closed, this is not recoverable. + mt.logger.Debug("listener has been closed") + return // exit default: - // Accept an incoming peer connection - c, err := mt.listener.Accept() + // An error occurred during accept, report and continue + mt.logger.Warn("accept p2p connection error", "err", err) + continue + } + + // Process the new connection asynchronously + wg.Add(1) + + go func(c net.Conn) { + defer wg.Done() + + info, err := mt.processConn(c, "") if err != nil { mt.logger.Error( - "unable to accept p2p connection", + "unable to process p2p connection", "err", err, ) - continue - } - - // Process the new connection asynchronously - wg.Add(1) + // Close the connection + _ = c.Close() - go func(c net.Conn) { - defer wg.Done() - - info, err := mt.processConn(c, "") - if err != nil { - mt.logger.Error( - "unable to process p2p connection", - "err", err, - ) - - // Close the connection - _ = c.Close() - - return - } + return + } - select { - case mt.peerCh <- info: - case <-mt.ctx.Done(): - // Give up if the transport was closed. - _ = c.Close() - } - }(c) - } + select { + case mt.peerCh <- info: + case <-ctx.Done(): + // Give up if the transport was closed. + _ = c.Close() + } + }(c) } } diff --git a/tm2/pkg/p2p/transport_test.go b/tm2/pkg/p2p/transport_test.go index 3eb3264ec2b..5ec4efda1ad 100644 --- a/tm2/pkg/p2p/transport_test.go +++ b/tm2/pkg/p2p/transport_test.go @@ -122,14 +122,12 @@ func TestMultiplexTransport_Accept(t *testing.T) { transport := NewMultiplexTransport(ni, nk, mCfg, logger) - p, err := transport.Accept(context.Background(), nil) + ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond) + defer cancel() + p, err := transport.Accept(ctx, nil) assert.Nil(t, p) - assert.ErrorIs( - t, - err, - errTransportInactive, - ) + assert.ErrorIs(t, err, context.DeadlineExceeded) }) t.Run("transport closed", func(t *testing.T) { diff --git a/tm2/pkg/service/service.go b/tm2/pkg/service/service.go index 05f7a4f4ae6..c93eb06b298 100644 --- a/tm2/pkg/service/service.go +++ b/tm2/pkg/service/service.go @@ -159,7 +159,7 @@ func (bs *BaseService) OnStart() error { return nil } func (bs *BaseService) Stop() error { if atomic.CompareAndSwapUint32(&bs.stopped, 0, 1) { if atomic.LoadUint32(&bs.started) == 0 { - bs.Logger.Error(fmt.Sprintf("Not stopping %v -- have not been started yet", bs.name), "impl", bs.impl) + bs.Logger.Warn(fmt.Sprintf("Not stopping %v -- have not been started yet", bs.name), "impl", bs.impl) // revert flag atomic.StoreUint32(&bs.stopped, 0) return ErrNotStarted From 0b76b0b0dfe9ae6d0f6546f8142113310f51453b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kaza=C3=AF?= <149690535+kazai777@users.noreply.github.com> Date: Wed, 5 Feb 2025 20:02:35 +0100 Subject: [PATCH 121/143] feat(cmd/gno): add gno version command (#3002) I've added the `version` command to gno to get the version of gno that is installed
Contributors' checklist... - [ ] Added new tests, or not needed, or not feasible - [ ] Provided an example (e.g. screenshot) to aid review or the PR is self-explanatory - [ ] Updated the official documentation or not needed - [ ] No breaking changes were made, or a `BREAKING CHANGE: xxx` message was included in the description - [ ] Added references to related issues and PRs - [ ] Provided any useful hints for running manual tests
--------- Co-authored-by: Morgan --- gnovm/Makefile | 5 ++++- gnovm/cmd/gno/main.go | 1 + gnovm/cmd/gno/version.go | 24 ++++++++++++++++++++++++ gnovm/cmd/gno/version_test.go | 32 ++++++++++++++++++++++++++++++++ gnovm/pkg/version/version.go | 3 +++ 5 files changed, 64 insertions(+), 1 deletion(-) create mode 100644 gnovm/cmd/gno/version.go create mode 100644 gnovm/cmd/gno/version_test.go create mode 100644 gnovm/pkg/version/version.go diff --git a/gnovm/Makefile b/gnovm/Makefile index ce745e44aae..2206fa2c8c8 100644 --- a/gnovm/Makefile +++ b/gnovm/Makefile @@ -27,11 +27,14 @@ GOTEST_FLAGS ?= -v -p 1 -timeout=30m GNOROOT_DIR ?= $(abspath $(lastword $(MAKEFILE_LIST))/../../) # We can't use '-trimpath' yet as amino use absolute path from call stack # to find some directory: see #1236 -GOBUILD_FLAGS ?= -ldflags "-X github.com/gnolang/gno/gnovm/pkg/gnoenv._GNOROOT=$(GNOROOT_DIR)" +GOBUILD_FLAGS ?= -ldflags "-X github.com/gnolang/gno/gnovm/pkg/version.Version=$(VERSION) -X github.com/gnolang/gno/gnovm/pkg/gnoenv._GNOROOT=$(GNOROOT_DIR)" # file where to place cover profile; used for coverage commands which are # more complex than adding -coverprofile, like test.cmd.coverage. GOTEST_COVER_PROFILE ?= cmd-profile.out +# user for gno version [branch].[N]+[hash] +VERSION ?= $(shell git describe --tags --exact-match 2>/dev/null || echo "$(shell git rev-parse --abbrev-ref HEAD).$(shell git rev-list --count HEAD)+$(shell git rev-parse --short HEAD)") + ######################################## # Dev tools .PHONY: build diff --git a/gnovm/cmd/gno/main.go b/gnovm/cmd/gno/main.go index 5f8bb7b522e..b18e610d535 100644 --- a/gnovm/cmd/gno/main.go +++ b/gnovm/cmd/gno/main.go @@ -41,6 +41,7 @@ func newGnocliCmd(io commands.IO) *commands.Command { newTestCmd(io), newToolCmd(io), // version -- show cmd/gno, golang versions + newGnoVersionCmd(io), // vet ) diff --git a/gnovm/cmd/gno/version.go b/gnovm/cmd/gno/version.go new file mode 100644 index 00000000000..f9b967d1c40 --- /dev/null +++ b/gnovm/cmd/gno/version.go @@ -0,0 +1,24 @@ +package main + +import ( + "context" + + "github.com/gnolang/gno/gnovm/pkg/version" + "github.com/gnolang/gno/tm2/pkg/commands" +) + +// newVersionCmd creates a new version command +func newGnoVersionCmd(io commands.IO) *commands.Command { + return commands.NewCommand( + commands.Metadata{ + Name: "version", + ShortUsage: "version", + ShortHelp: "display installed gno version", + }, + nil, + func(_ context.Context, args []string) error { + io.Println("gno version:", version.Version) + return nil + }, + ) +} diff --git a/gnovm/cmd/gno/version_test.go b/gnovm/cmd/gno/version_test.go new file mode 100644 index 00000000000..fab47319297 --- /dev/null +++ b/gnovm/cmd/gno/version_test.go @@ -0,0 +1,32 @@ +package main + +import ( + "testing" + + "github.com/gnolang/gno/gnovm/pkg/version" +) + +func TestVersionApp(t *testing.T) { + originalVersion := version.Version + + t.Cleanup(func() { + version.Version = originalVersion + }) + + versionValues := []string{"chain/test4.2", "develop", "master"} + + testCases := make([]testMainCase, len(versionValues)) + for i, v := range versionValues { + testCases[i] = testMainCase{ + args: []string{"version"}, + stdoutShouldContain: "gno version: " + v, + } + } + + for i, testCase := range testCases { + t.Run(versionValues[i], func(t *testing.T) { + version.Version = versionValues[i] + testMainCaseRun(t, []testMainCase{testCase}) + }) + } +} diff --git a/gnovm/pkg/version/version.go b/gnovm/pkg/version/version.go new file mode 100644 index 00000000000..933d4fac3e5 --- /dev/null +++ b/gnovm/pkg/version/version.go @@ -0,0 +1,3 @@ +package version + +var Version = "develop" From 0bc4423841959df6dd3ef0db66ed2925f1acce26 Mon Sep 17 00:00:00 2001 From: Alexis Colin Date: Thu, 6 Feb 2025 22:44:39 +0900 Subject: [PATCH 122/143] fix(gnoweb): escape bash chars in help args (#3672) This PR ensures that special characters like ! and ? in user inputs are properly escaped when generating commands in the docs page ($help). Previously, entering ! or any other special char could cause the command string to break by omitting a closing ", making it invalid. This fix applies proper escaping to prevent such issues, ensuring that generated commands remain valid and executable. The fix introduces an escaping function that handles shell-sensitive characters before inserting them into the generated command strings. This approach ensures the commands remain intact without affecting their output when executed. Thus, the escape char is also removed from the cmd when the shell-sensitive char is removed from the arg input. cf: [issue 3355](https://github.com/gnolang/gno/issues/3355#issuecomment-2598841346) --- gno.land/pkg/gnoweb/components/layouts/header.html | 2 +- gno.land/pkg/gnoweb/frontend/js/realmhelp.ts | 9 +++++---- gno.land/pkg/gnoweb/frontend/js/utils.ts | 4 ++++ gno.land/pkg/gnoweb/public/js/realmhelp.js | 2 +- gno.land/pkg/gnoweb/public/js/utils.js | 2 +- 5 files changed, 12 insertions(+), 7 deletions(-) diff --git a/gno.land/pkg/gnoweb/components/layouts/header.html b/gno.land/pkg/gnoweb/components/layouts/header.html index 8a1433ccd1c..5743d0a82b6 100644 --- a/gno.land/pkg/gnoweb/components/layouts/header.html +++ b/gno.land/pkg/gnoweb/components/layouts/header.html @@ -21,4 +21,4 @@
{{ range .Links }} {{ template "ui/header_link" . }} {{ end }}
-{{ end }} +{{ end }} diff --git a/gno.land/pkg/gnoweb/frontend/js/realmhelp.ts b/gno.land/pkg/gnoweb/frontend/js/realmhelp.ts index 3177e034257..950c85cdbe3 100644 --- a/gno.land/pkg/gnoweb/frontend/js/realmhelp.ts +++ b/gno.land/pkg/gnoweb/frontend/js/realmhelp.ts @@ -1,4 +1,4 @@ -import { debounce } from "./utils"; +import { debounce, escapeShellSpecialChars } from "./utils"; class Help { private DOM: { @@ -67,7 +67,7 @@ class Help { localStorage.setItem("helpAddressInput", address); this.funcList.forEach((func) => func.updateAddr(address)); - }); + }, 50); addressInput?.addEventListener("input", () => debouncedUpdate(addressInput)); cmdModeSelect?.addEventListener("change", (e) => { @@ -124,7 +124,7 @@ class HelpFunc { private bindEvents(): void { const debouncedUpdate = debounce((paramName: string, paramValue: string) => { if (paramName) this.updateArg(paramName, paramValue); - }); + }, 50); this.DOM.el.addEventListener("input", (e) => { const target = e.target as HTMLInputElement; @@ -143,10 +143,11 @@ class HelpFunc { } public updateArg(paramName: string, paramValue: string): void { + const escapedValue = escapeShellSpecialChars(paramValue); this.DOM.args .filter((arg) => arg.dataset.arg === paramName) .forEach((arg) => { - arg.textContent = paramValue || ""; + arg.textContent = escapedValue || ""; }); } diff --git a/gno.land/pkg/gnoweb/frontend/js/utils.ts b/gno.land/pkg/gnoweb/frontend/js/utils.ts index 83de509efa5..d975b4516f3 100644 --- a/gno.land/pkg/gnoweb/frontend/js/utils.ts +++ b/gno.land/pkg/gnoweb/frontend/js/utils.ts @@ -10,3 +10,7 @@ export function debounce void>(func: T, delay: num }, delay); }; } + +export function escapeShellSpecialChars(arg: string): string { + return arg.replace(/([$`"\\!|&;<>*?{}()])/g, "\\$1"); +} diff --git a/gno.land/pkg/gnoweb/public/js/realmhelp.js b/gno.land/pkg/gnoweb/public/js/realmhelp.js index 68bcafbb75f..7008a54514e 100644 --- a/gno.land/pkg/gnoweb/public/js/realmhelp.js +++ b/gno.land/pkg/gnoweb/public/js/realmhelp.js @@ -1 +1 @@ -function d(a,e=250){let t;return function(...s){t!==void 0&&clearTimeout(t),t=setTimeout(()=>{a.apply(this,s)},e)}}var l=class a{DOM;funcList;static SELECTORS={container:".js-help-view",func:"[data-func]",addressInput:"[data-role='help-input-addr']",cmdModeSelect:"[data-role='help-select-mode']"};constructor(){this.DOM={el:document.querySelector(a.SELECTORS.container),funcs:[],addressInput:null,cmdModeSelect:null},this.funcList=[],this.DOM.el?this.init():console.warn("Help: Main container not found.")}init(){let{el:e}=this.DOM;e&&(this.DOM.funcs=Array.from(e.querySelectorAll(a.SELECTORS.func)),this.DOM.addressInput=e.querySelector(a.SELECTORS.addressInput),this.DOM.cmdModeSelect=e.querySelector(a.SELECTORS.cmdModeSelect),this.funcList=this.DOM.funcs.map(t=>new o(t)),this.restoreAddress(),this.bindEvents())}restoreAddress(){let{addressInput:e}=this.DOM;if(e){let t=localStorage.getItem("helpAddressInput");t&&(e.value=t,this.funcList.forEach(s=>s.updateAddr(t)))}}bindEvents(){let{addressInput:e,cmdModeSelect:t}=this.DOM,s=d(r=>{let n=r.value;localStorage.setItem("helpAddressInput",n),this.funcList.forEach(i=>i.updateAddr(n))});e?.addEventListener("input",()=>s(e)),t?.addEventListener("change",r=>{let n=r.target;this.funcList.forEach(i=>i.updateMode(n.value))})}},o=class a{DOM;funcName;static SELECTORS={address:"[data-role='help-code-address']",args:"[data-role='help-code-args']",mode:"[data-code-mode]",paramInput:"[data-role='help-param-input']"};constructor(e){this.DOM={el:e,addrs:Array.from(e.querySelectorAll(a.SELECTORS.address)),args:Array.from(e.querySelectorAll(a.SELECTORS.args)),modes:Array.from(e.querySelectorAll(a.SELECTORS.mode)),paramInputs:Array.from(e.querySelectorAll(a.SELECTORS.paramInput))},this.funcName=e.dataset.func||null,this.initializeArgs(),this.bindEvents()}static sanitizeArgsInput(e){let t=e.dataset.param||"",s=e.value.trim();return t||console.warn("sanitizeArgsInput: param is missing in arg input dataset."),{paramName:t,paramValue:s}}bindEvents(){let e=d((t,s)=>{t&&this.updateArg(t,s)});this.DOM.el.addEventListener("input",t=>{let s=t.target;if(s.dataset.role==="help-param-input"){let{paramName:r,paramValue:n}=a.sanitizeArgsInput(s);e(r,n)}})}initializeArgs(){this.DOM.paramInputs.forEach(e=>{let{paramName:t,paramValue:s}=a.sanitizeArgsInput(e);t&&this.updateArg(t,s)})}updateArg(e,t){this.DOM.args.filter(s=>s.dataset.arg===e).forEach(s=>{s.textContent=t||""})}updateAddr(e){this.DOM.addrs.forEach(t=>{t.textContent=e.trim()||"ADDRESS"})}updateMode(e){this.DOM.modes.forEach(t=>{let s=t.dataset.codeMode===e;t.classList.toggle("inline",s),t.classList.toggle("hidden",!s),t.dataset.copyContent=s?`help-cmd-${this.funcName}`:""})}},p=()=>new l;export{p as default}; +function d(s,e=250){let t;return function(...a){t!==void 0&&clearTimeout(t),t=setTimeout(()=>{s.apply(this,a)},e)}}function u(s){return s.replace(/([$`"\\!|&;<>*?{}()])/g,"\\$1")}var l=class s{DOM;funcList;static SELECTORS={container:".js-help-view",func:"[data-func]",addressInput:"[data-role='help-input-addr']",cmdModeSelect:"[data-role='help-select-mode']"};constructor(){this.DOM={el:document.querySelector(s.SELECTORS.container),funcs:[],addressInput:null,cmdModeSelect:null},this.funcList=[],this.DOM.el?this.init():console.warn("Help: Main container not found.")}init(){let{el:e}=this.DOM;e&&(this.DOM.funcs=Array.from(e.querySelectorAll(s.SELECTORS.func)),this.DOM.addressInput=e.querySelector(s.SELECTORS.addressInput),this.DOM.cmdModeSelect=e.querySelector(s.SELECTORS.cmdModeSelect),this.funcList=this.DOM.funcs.map(t=>new o(t)),this.restoreAddress(),this.bindEvents())}restoreAddress(){let{addressInput:e}=this.DOM;if(e){let t=localStorage.getItem("helpAddressInput");t&&(e.value=t,this.funcList.forEach(a=>a.updateAddr(t)))}}bindEvents(){let{addressInput:e,cmdModeSelect:t}=this.DOM,a=d(n=>{let r=n.value;localStorage.setItem("helpAddressInput",r),this.funcList.forEach(i=>i.updateAddr(r))},50);e?.addEventListener("input",()=>a(e)),t?.addEventListener("change",n=>{let r=n.target;this.funcList.forEach(i=>i.updateMode(r.value))})}},o=class s{DOM;funcName;static SELECTORS={address:"[data-role='help-code-address']",args:"[data-role='help-code-args']",mode:"[data-code-mode]",paramInput:"[data-role='help-param-input']"};constructor(e){this.DOM={el:e,addrs:Array.from(e.querySelectorAll(s.SELECTORS.address)),args:Array.from(e.querySelectorAll(s.SELECTORS.args)),modes:Array.from(e.querySelectorAll(s.SELECTORS.mode)),paramInputs:Array.from(e.querySelectorAll(s.SELECTORS.paramInput))},this.funcName=e.dataset.func||null,this.initializeArgs(),this.bindEvents()}static sanitizeArgsInput(e){let t=e.dataset.param||"",a=e.value.trim();return t||console.warn("sanitizeArgsInput: param is missing in arg input dataset."),{paramName:t,paramValue:a}}bindEvents(){let e=d((t,a)=>{t&&this.updateArg(t,a)},50);this.DOM.el.addEventListener("input",t=>{let a=t.target;if(a.dataset.role==="help-param-input"){let{paramName:n,paramValue:r}=s.sanitizeArgsInput(a);e(n,r)}})}initializeArgs(){this.DOM.paramInputs.forEach(e=>{let{paramName:t,paramValue:a}=s.sanitizeArgsInput(e);t&&this.updateArg(t,a)})}updateArg(e,t){let a=u(t);this.DOM.args.filter(n=>n.dataset.arg===e).forEach(n=>{n.textContent=a||""})}updateAddr(e){this.DOM.addrs.forEach(t=>{t.textContent=e.trim()||"ADDRESS"})}updateMode(e){this.DOM.modes.forEach(t=>{let a=t.dataset.codeMode===e;t.classList.toggle("inline",a),t.classList.toggle("hidden",!a),t.dataset.copyContent=a?`help-cmd-${this.funcName}`:""})}},m=()=>new l;export{m as default}; diff --git a/gno.land/pkg/gnoweb/public/js/utils.js b/gno.land/pkg/gnoweb/public/js/utils.js index e27fb93bc1c..ce96def444a 100644 --- a/gno.land/pkg/gnoweb/public/js/utils.js +++ b/gno.land/pkg/gnoweb/public/js/utils.js @@ -1 +1 @@ -function r(t,n=250){let e;return function(...i){e!==void 0&&clearTimeout(e),e=setTimeout(()=>{t.apply(this,i)},n)}}export{r as debounce}; +function i(e,n=250){let t;return function(...r){t!==void 0&&clearTimeout(t),t=setTimeout(()=>{e.apply(this,r)},n)}}function a(e){return e.replace(/([$`"\\!|&;<>*?{}()])/g,"\\$1")}export{i as debounce,a as escapeShellSpecialChars}; From 7e1f5b5ade984f5a9b4083d9cffb5a0000ea7f89 Mon Sep 17 00:00:00 2001 From: Morgan Date: Thu, 6 Feb 2025 14:59:51 +0100 Subject: [PATCH 123/143] fix(cmd/gno): only perform preprocessing in lint (#3597) fixes #3547 cc @leohhhn --- gnovm/cmd/gno/run.go | 2 +- gnovm/cmd/gno/tool_lint.go | 20 ++++++------ gnovm/cmd/gno/tool_lint_test.go | 42 ++++++++++++++++-------- gnovm/pkg/gnolang/debugger_test.go | 2 +- gnovm/pkg/gnolang/files_test.go | 6 ++-- gnovm/pkg/gnolang/machine.go | 45 ++++++++++++++++++++++++++ gnovm/pkg/repl/repl.go | 2 +- gnovm/pkg/test/imports.go | 52 ++++++++++++++++++++++++++---- gnovm/pkg/test/test.go | 5 +-- gnovm/tests/integ/init/gno.mod | 1 + gnovm/tests/integ/init/main.gno | 10 ++++++ 11 files changed, 145 insertions(+), 42 deletions(-) create mode 100644 gnovm/tests/integ/init/gno.mod create mode 100644 gnovm/tests/integ/init/main.gno diff --git a/gnovm/cmd/gno/run.go b/gnovm/cmd/gno/run.go index 489016aa3d4..34bf818e8f5 100644 --- a/gnovm/cmd/gno/run.go +++ b/gnovm/cmd/gno/run.go @@ -93,7 +93,7 @@ func execRun(cfg *runCfg, args []string, io commands.IO) error { // init store and machine _, testStore := test.Store( - cfg.rootDir, false, + cfg.rootDir, stdin, stdout, stderr) if cfg.verbose { testStore.SetLogStoreOps(true) diff --git a/gnovm/cmd/gno/tool_lint.go b/gnovm/cmd/gno/tool_lint.go index ce3465b484e..6983175cea0 100644 --- a/gnovm/cmd/gno/tool_lint.go +++ b/gnovm/cmd/gno/tool_lint.go @@ -97,9 +97,9 @@ func execLint(cfg *lintCfg, args []string, io commands.IO) error { hasError := false - bs, ts := test.Store( - rootDir, false, - nopReader{}, goio.Discard, goio.Discard, + bs, ts := test.StoreWithOptions( + rootDir, nopReader{}, goio.Discard, goio.Discard, + test.StoreOptions{PreprocessOnly: true}, ) for _, pkgPath := range pkgPaths { @@ -162,13 +162,10 @@ func execLint(cfg *lintCfg, args []string, io commands.IO) error { tm := test.Machine(gs, goio.Discard, memPkg.Path) defer tm.Release() - // Check package - tm.RunMemPackage(memPkg, true) - // Check test files - testFiles := lintTestFiles(memPkg) + packageFiles := sourceAndTestFileset(memPkg) - tm.RunFiles(testFiles.Files...) + tm.PreprocessFiles(memPkg.Name, memPkg.Path, packageFiles, false, false) }) if hasRuntimeErr { hasError = true @@ -221,20 +218,21 @@ func lintTypeCheck(io commands.IO, memPkg *gnovm.MemPackage, testStore gno.Store return true, nil } -func lintTestFiles(memPkg *gnovm.MemPackage) *gno.FileSet { +func sourceAndTestFileset(memPkg *gnovm.MemPackage) *gno.FileSet { testfiles := &gno.FileSet{} for _, mfile := range memPkg.Files { if !strings.HasSuffix(mfile.Name, ".gno") { continue // Skip non-GNO files } - n, _ := gno.ParseFile(mfile.Name, mfile.Body) + n := gno.MustParseFile(mfile.Name, mfile.Body) if n == nil { continue // Skip empty files } // XXX: package ending with `_test` is not supported yet - if strings.HasSuffix(mfile.Name, "_test.gno") && !strings.HasSuffix(string(n.PkgName), "_test") { + if !strings.HasSuffix(mfile.Name, "_filetest.gno") && + !strings.HasSuffix(string(n.PkgName), "_test") { // Keep only test files testfiles.AddFiles(n) } diff --git a/gnovm/cmd/gno/tool_lint_test.go b/gnovm/cmd/gno/tool_lint_test.go index 85b625fa367..3f9e5cd59ba 100644 --- a/gnovm/cmd/gno/tool_lint_test.go +++ b/gnovm/cmd/gno/tool_lint_test.go @@ -10,49 +10,63 @@ func TestLintApp(t *testing.T) { { args: []string{"tool", "lint"}, errShouldBe: "flag: help requested", - }, { + }, + { args: []string{"tool", "lint", "../../tests/integ/run_main/"}, stderrShouldContain: "./../../tests/integ/run_main: gno.mod file not found in current or any parent directory (code=1)", errShouldBe: "exit code: 1", - }, { + }, + { args: []string{"tool", "lint", "../../tests/integ/undefined_variable_test/undefined_variables_test.gno"}, stderrShouldContain: "undefined_variables_test.gno:6:28: name toto not declared (code=2)", errShouldBe: "exit code: 1", - }, { + }, + { args: []string{"tool", "lint", "../../tests/integ/package_not_declared/main.gno"}, stderrShouldContain: "main.gno:4:2: name fmt not declared (code=2)", errShouldBe: "exit code: 1", - }, { + }, + { args: []string{"tool", "lint", "../../tests/integ/several-lint-errors/main.gno"}, - stderrShouldContain: "../../tests/integ/several-lint-errors/main.gno:5:5: expected ';', found example (code=2)\n../../tests/integ/several-lint-errors/main.gno:6", + stderrShouldContain: "../../tests/integ/several-lint-errors/main.gno:5:5: expected ';', found example (code=3)\n../../tests/integ/several-lint-errors/main.gno:6", errShouldBe: "exit code: 1", - }, { + }, + { args: []string{"tool", "lint", "../../tests/integ/several-files-multiple-errors/main.gno"}, stderrShouldContain: func() string { lines := []string{ - "../../tests/integ/several-files-multiple-errors/file2.gno:3:5: expected 'IDENT', found '{' (code=2)", - "../../tests/integ/several-files-multiple-errors/file2.gno:5:1: expected type, found '}' (code=2)", - "../../tests/integ/several-files-multiple-errors/main.gno:5:5: expected ';', found example (code=2)", - "../../tests/integ/several-files-multiple-errors/main.gno:6:2: expected '}', found 'EOF' (code=2)", + "../../tests/integ/several-files-multiple-errors/file2.gno:3:5: expected 'IDENT', found '{' (code=3)", + "../../tests/integ/several-files-multiple-errors/file2.gno:5:1: expected type, found '}' (code=3)", + "../../tests/integ/several-files-multiple-errors/main.gno:5:5: expected ';', found example (code=3)", + "../../tests/integ/several-files-multiple-errors/main.gno:6:2: expected '}', found 'EOF' (code=3)", } return strings.Join(lines, "\n") + "\n" }(), errShouldBe: "exit code: 1", - }, { + }, + { args: []string{"tool", "lint", "../../tests/integ/minimalist_gnomod/"}, // TODO: raise an error because there is a gno.mod, but no .gno files - }, { + }, + { args: []string{"tool", "lint", "../../tests/integ/invalid_module_name/"}, // TODO: raise an error because gno.mod is invalid - }, { + }, + { args: []string{"tool", "lint", "../../tests/integ/invalid_gno_file/"}, stderrShouldContain: "../../tests/integ/invalid_gno_file/invalid.gno:1:1: expected 'package', found packag (code=2)", errShouldBe: "exit code: 1", - }, { + }, + { args: []string{"tool", "lint", "../../tests/integ/typecheck_missing_return/"}, stderrShouldContain: "../../tests/integ/typecheck_missing_return/main.gno:5:1: missing return (code=4)", errShouldBe: "exit code: 1", }, + { + args: []string{"tool", "lint", "../../tests/integ/init/"}, + // stderr / stdout should be empty; the init function and statements + // should not be executed + }, // TODO: 'gno mod' is valid? // TODO: are dependencies valid? diff --git a/gnovm/pkg/gnolang/debugger_test.go b/gnovm/pkg/gnolang/debugger_test.go index 926ff0595e6..a9e0a4834d5 100644 --- a/gnovm/pkg/gnolang/debugger_test.go +++ b/gnovm/pkg/gnolang/debugger_test.go @@ -39,7 +39,7 @@ func evalTest(debugAddr, in, file string) (out, err string) { err = strings.TrimSpace(strings.ReplaceAll(err, "../../tests/files/", "files/")) }() - _, testStore := test.Store(gnoenv.RootDir(), false, stdin, stdout, stderr) + _, testStore := test.Store(gnoenv.RootDir(), stdin, stdout, stderr) f := gnolang.MustReadFile(file) diff --git a/gnovm/pkg/gnolang/files_test.go b/gnovm/pkg/gnolang/files_test.go index 2c82f6d8f29..31f04087855 100644 --- a/gnovm/pkg/gnolang/files_test.go +++ b/gnovm/pkg/gnolang/files_test.go @@ -45,9 +45,9 @@ func TestFiles(t *testing.T) { Error: io.Discard, Sync: *withSync, } - o.BaseStore, o.TestStore = test.Store( - rootDir, true, - nopReader{}, o.WriterForStore(), io.Discard, + o.BaseStore, o.TestStore = test.StoreWithOptions( + rootDir, nopReader{}, o.WriterForStore(), io.Discard, + test.StoreOptions{WithExtern: true}, ) return o } diff --git a/gnovm/pkg/gnolang/machine.go b/gnovm/pkg/gnolang/machine.go index 75d12ac5402..f7d2cf10f2c 100644 --- a/gnovm/pkg/gnolang/machine.go +++ b/gnovm/pkg/gnolang/machine.go @@ -454,6 +454,51 @@ func (m *Machine) RunFiles(fns ...*FileNode) { m.runInitFromUpdates(pv, updates) } +// PreprocessFiles runs Preprocess on the given files. It is used to detect +// compile-time errors in the package. +func (m *Machine) PreprocessFiles(pkgName, pkgPath string, fset *FileSet, save, withOverrides bool) (*PackageNode, *PackageValue) { + if !withOverrides { + if err := checkDuplicates(fset); err != nil { + panic(fmt.Errorf("running package %q: %w", pkgName, err)) + } + } + pn := NewPackageNode(Name(pkgName), pkgPath, fset) + pv := pn.NewPackage() + pb := pv.GetBlock(m.Store) + m.SetActivePackage(pv) + m.Store.SetBlockNode(pn) + PredefineFileSet(m.Store, pn, fset) + for _, fn := range fset.Files { + fn = Preprocess(m.Store, pn, fn).(*FileNode) + // After preprocessing, save blocknodes to store. + SaveBlockNodes(m.Store, fn) + // Make block for fn. + // Each file for each *PackageValue gets its own file *Block, + // with values copied over from each file's + // *FileNode.StaticBlock. + fb := m.Alloc.NewBlock(fn, pb) + fb.Values = make([]TypedValue, len(fn.StaticBlock.Values)) + copy(fb.Values, fn.StaticBlock.Values) + pv.AddFileBlock(fn.Name, fb) + } + // Get new values across all files in package. + pn.PrepareNewValues(pv) + // save package value. + var throwaway *Realm + if save { + // store new package values and types + throwaway = m.saveNewPackageValuesAndTypes() + if throwaway != nil { + m.Realm = throwaway + } + m.resavePackageValues(throwaway) + if throwaway != nil { + m.Realm = nil + } + } + return pn, pv +} + // Add files to the package's *FileSet and run decls in them. // This will also run each init function encountered. // Returns the updated typed values of package. diff --git a/gnovm/pkg/repl/repl.go b/gnovm/pkg/repl/repl.go index b0944d21646..fff80d672dc 100644 --- a/gnovm/pkg/repl/repl.go +++ b/gnovm/pkg/repl/repl.go @@ -125,7 +125,7 @@ func NewRepl(opts ...ReplOption) *Repl { r.stderr = &b r.storeFunc = func() gno.Store { - _, st := test.Store(gnoenv.RootDir(), false, r.stdin, r.stdout, r.stderr) + _, st := test.Store(gnoenv.RootDir(), r.stdin, r.stdout, r.stderr) return st } diff --git a/gnovm/pkg/test/imports.go b/gnovm/pkg/test/imports.go index a8dd709e501..95302ecffb0 100644 --- a/gnovm/pkg/test/imports.go +++ b/gnovm/pkg/test/imports.go @@ -25,22 +25,56 @@ import ( storetypes "github.com/gnolang/gno/tm2/pkg/store/types" ) +type StoreOptions struct { + // WithExtern interprets imports of packages under "github.com/gnolang/gno/_test/" + // as imports under the directory in gnovm/tests/files/extern. + // This should only be used for GnoVM internal filetests (gnovm/tests/files). + WithExtern bool + + // PreprocessOnly instructs the PackageGetter to run the imported files using + // [gno.Machine.PreprocessFiles]. It avoids executing code for contexts + // which only intend to perform a type check, ie. `gno lint`. + PreprocessOnly bool +} + // NOTE: this isn't safe, should only be used for testing. func Store( rootDir string, - withExtern bool, stdin io.Reader, stdout, stderr io.Writer, ) ( baseStore storetypes.CommitStore, resStore gno.Store, ) { + return StoreWithOptions(rootDir, stdin, stdout, stderr, StoreOptions{}) +} + +// StoreWithOptions is a variant of [Store] which additionally accepts a +// [StoreOptions] argument. +func StoreWithOptions( + rootDir string, + stdin io.Reader, + stdout, stderr io.Writer, + opts StoreOptions, +) ( + baseStore storetypes.CommitStore, + resStore gno.Store, +) { + processMemPackage := func(m *gno.Machine, memPkg *gnovm.MemPackage, save bool) (*gno.PackageNode, *gno.PackageValue) { + return m.RunMemPackage(memPkg, save) + } + if opts.PreprocessOnly { + processMemPackage = func(m *gno.Machine, memPkg *gnovm.MemPackage, save bool) (*gno.PackageNode, *gno.PackageValue) { + m.Store.AddMemPackage(memPkg) + return m.PreprocessFiles(memPkg.Name, memPkg.Path, gno.ParseMemPackage(memPkg), save, false) + } + } getPackage := func(pkgPath string, store gno.Store) (pn *gno.PackageNode, pv *gno.PackageValue) { if pkgPath == "" { panic(fmt.Sprintf("invalid zero package path in testStore().pkgGetter")) } - if withExtern { + if opts.WithExtern { // if _test package... const testPath = "github.com/gnolang/gno/_test/" if strings.HasPrefix(pkgPath, testPath) { @@ -54,7 +88,7 @@ func Store( Store: store, Context: ctx, }) - return m2.RunMemPackage(memPkg, true) + return processMemPackage(m2, memPkg, true) } } @@ -129,7 +163,7 @@ func Store( } // Load normal stdlib. - pn, pv = loadStdlib(rootDir, pkgPath, store, stdout) + pn, pv = loadStdlib(rootDir, pkgPath, store, stdout, opts.PreprocessOnly) if pn != nil { return } @@ -150,8 +184,7 @@ func Store( Store: store, Context: ctx, }) - pn, pv = m2.RunMemPackage(memPkg, true) - return + return processMemPackage(m2, memPkg, true) } return nil, nil } @@ -164,7 +197,7 @@ func Store( return } -func loadStdlib(rootDir, pkgPath string, store gno.Store, stdout io.Writer) (*gno.PackageNode, *gno.PackageValue) { +func loadStdlib(rootDir, pkgPath string, store gno.Store, stdout io.Writer, preprocessOnly bool) (*gno.PackageNode, *gno.PackageValue) { dirs := [...]string{ // Normal stdlib path. filepath.Join(rootDir, "gnovm", "stdlibs", pkgPath), @@ -202,6 +235,11 @@ func loadStdlib(rootDir, pkgPath string, store gno.Store, stdout io.Writer) (*gn Output: stdout, Store: store, }) + if preprocessOnly { + m2.Store.AddMemPackage(memPkg) + return m2.PreprocessFiles(memPkg.Name, memPkg.Path, gno.ParseMemPackage(memPkg), true, true) + } + // TODO: make this work when using gno lint. return m2.RunMemPackageWithOverrides(memPkg, true) } diff --git a/gnovm/pkg/test/test.go b/gnovm/pkg/test/test.go index d06540761d7..92a867e1886 100644 --- a/gnovm/pkg/test/test.go +++ b/gnovm/pkg/test/test.go @@ -139,10 +139,7 @@ func NewTestOptions(rootDir string, stdin io.Reader, stdout, stderr io.Writer) * Output: stdout, Error: stderr, } - opts.BaseStore, opts.TestStore = Store( - rootDir, false, - stdin, opts.WriterForStore(), stderr, - ) + opts.BaseStore, opts.TestStore = Store(rootDir, stdin, opts.WriterForStore(), stderr) return opts } diff --git a/gnovm/tests/integ/init/gno.mod b/gnovm/tests/integ/init/gno.mod new file mode 100644 index 00000000000..28c7e51b750 --- /dev/null +++ b/gnovm/tests/integ/init/gno.mod @@ -0,0 +1 @@ +module gno.land/r/demo/init diff --git a/gnovm/tests/integ/init/main.gno b/gnovm/tests/integ/init/main.gno new file mode 100644 index 00000000000..88cfafb9f24 --- /dev/null +++ b/gnovm/tests/integ/init/main.gno @@ -0,0 +1,10 @@ +package main + +var _ = func() int { + println("HELLO HELLO!!") + return 1 +}() + +func init() { + println("HELLO WORLD!") +} From dd0360dc3c209585e6a7f5fac2f8f9341bdaf46d Mon Sep 17 00:00:00 2001 From: Leon Hudak <33522493+leohhhn@users.noreply.github.com> Date: Thu, 6 Feb 2025 15:08:11 +0100 Subject: [PATCH 124/143] chore: remove govdao dependency in `r/gov/dao/bridge` (#3523) ## Description Cherry-picked from: #3166 This PR removes the `r/gov/dao/v2` import from the `r/gov/dao/bridge` realm. Previously, cyclic imports were easily created if a realm imported the bridge realm (to expose a executor constructor func), and GovDAO imported that realm. In my case: - `r/sys/users` imported `r/gov/dao/bridge` to create executor constructors - `r/gov/dao/bridge` imported `r/gov/dao/v2` to have access to the implementation - `r/gov/dao/v2` imported `r/sys/users` to have access to user data. - -> creating a cyclic dependency which is not allowed. This is fixed by modifying the `r/gov/dao/v2` contract to expose a safe-object, which in turn exposes all top-level functions as methods. Then, the bridge uses a one-time init package which will load v2 into the bridge in genesis, as per @moul's comment. --------- Co-authored-by: Manfred Touron <94029+moul@users.noreply.github.com> --- examples/gno.land/r/gov/dao/bridge/bridge.gno | 46 +++++++++++++++---- .../gno.land/r/gov/dao/bridge/bridge_test.gno | 45 ++++++++++++++++-- examples/gno.land/r/gov/dao/bridge/v2.gno | 42 ----------------- examples/gno.land/r/gov/dao/init/gno.mod | 1 + examples/gno.land/r/gov/dao/init/init.gno | 13 ++++++ examples/gno.land/r/gov/dao/v2/dao.gno | 19 ++++++-- examples/gno.land/r/gov/dao/v2/poc.gno | 10 ++-- .../gno.land/r/gov/dao/v2/prop1_filetest.gno | 8 ++-- .../gno.land/r/gov/dao/v2/prop2_filetest.gno | 7 +-- .../gno.land/r/gov/dao/v2/prop3_filetest.gno | 11 +++-- .../gno.land/r/gov/dao/v2/prop4_filetest.gno | 1 + .../gno.land/r/sys/params/params_test.gno | 6 ++- 12 files changed, 130 insertions(+), 79 deletions(-) delete mode 100644 examples/gno.land/r/gov/dao/bridge/v2.gno create mode 100644 examples/gno.land/r/gov/dao/init/gno.mod create mode 100644 examples/gno.land/r/gov/dao/init/init.gno diff --git a/examples/gno.land/r/gov/dao/bridge/bridge.gno b/examples/gno.land/r/gov/dao/bridge/bridge.gno index ba47978f33f..87a12d94e54 100644 --- a/examples/gno.land/r/gov/dao/bridge/bridge.gno +++ b/examples/gno.land/r/gov/dao/bridge/bridge.gno @@ -3,34 +3,60 @@ package bridge import ( "std" + "gno.land/p/demo/dao" "gno.land/p/demo/ownable" ) -const initialOwner = std.Address("g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq") // @moul +const ( + initialOwner = std.Address("g1manfred47kzduec920z88wfr64ylksmdcedlf5") // @moul + loader = "gno.land/r/gov/dao/init" +) -var b *Bridge +var ( + b *Bridge + Ownable = ownable.NewWithAddress(initialOwner) +) // Bridge is the active GovDAO // implementation bridge type Bridge struct { - *ownable.Ownable - dao DAO } // init constructs the initial GovDAO implementation func init() { b = &Bridge{ - Ownable: ownable.NewWithAddress(initialOwner), - dao: &govdaoV2{}, + dao: nil, // initially set via r/gov/dao/init + } +} + +// LoadGovDAO loads the initial version of GovDAO into the bridge +// All changes to b.dao need to be done via GovDAO proposals after +func LoadGovDAO(d DAO) { + if std.PrevRealm().PkgPath() != loader { + panic("unauthorized") } + + b.dao = d } -// SetDAO sets the currently active GovDAO implementation -func SetDAO(dao DAO) { - b.AssertCallerIsOwner() +// NewGovDAOImplChangeExecutor allows creating a GovDAO proposal +// Which will upgrade the GovDAO version inside the bridge +func NewGovDAOImplChangeExecutor(newImpl DAO) dao.Executor { + callback := func() error { + b.dao = newImpl + return nil + } + + return b.dao.NewGovDAOExecutor(callback) +} - b.dao = dao +// SetGovDAO allows the admin to set the GovDAO version manually +// This functionality can be fully disabled by Ownable.DropOwnership(), +// making this realm fully managed by GovDAO. +func SetGovDAO(d DAO) { + Ownable.AssertCallerIsOwner() + b.dao = d } // GovDAO returns the current GovDAO implementation diff --git a/examples/gno.land/r/gov/dao/bridge/bridge_test.gno b/examples/gno.land/r/gov/dao/bridge/bridge_test.gno index 38b5d4be257..da06db293ab 100644 --- a/examples/gno.land/r/gov/dao/bridge/bridge_test.gno +++ b/examples/gno.land/r/gov/dao/bridge/bridge_test.gno @@ -1,9 +1,8 @@ package bridge import ( - "testing" - "std" + "testing" "gno.land/p/demo/dao" "gno.land/p/demo/ownable" @@ -27,11 +26,47 @@ func TestBridge_DAO(t *testing.T) { uassert.Equal(t, proposalID, GovDAO().Propose(dao.ProposalRequest{})) } +func TestBridge_LoadGovDAO(t *testing.T) { + t.Run("invalid initializer path", func(t *testing.T) { + std.TestSetRealm(std.NewCodeRealm("gno.land/r/demo/init")) // invalid loader + + // Attempt to set a new DAO implementation + uassert.PanicsWithMessage(t, "unauthorized", func() { + LoadGovDAO(&mockDAO{}) + }) + }) + + t.Run("valid loader", func(t *testing.T) { + var ( + initializer = "gno.land/r/gov/dao/init" + proposalID = uint64(10) + mockDAO = &mockDAO{ + proposeFn: func(_ dao.ProposalRequest) uint64 { + return proposalID + }, + } + ) + + std.TestSetRealm(std.NewCodeRealm(initializer)) + + // Attempt to set a new DAO implementation + uassert.NotPanics(t, func() { + LoadGovDAO(mockDAO) + }) + + uassert.Equal( + t, + mockDAO.Propose(dao.ProposalRequest{}), + GovDAO().Propose(dao.ProposalRequest{}), + ) + }) +} + func TestBridge_SetDAO(t *testing.T) { t.Run("invalid owner", func(t *testing.T) { // Attempt to set a new DAO implementation uassert.PanicsWithMessage(t, ownable.ErrUnauthorized.Error(), func() { - SetDAO(&mockDAO{}) + SetGovDAO(&mockDAO{}) }) }) @@ -49,10 +84,10 @@ func TestBridge_SetDAO(t *testing.T) { std.TestSetOrigCaller(addr) - b.Ownable = ownable.NewWithAddress(addr) + Ownable = ownable.NewWithAddress(addr) urequire.NotPanics(t, func() { - SetDAO(mockDAO) + SetGovDAO(mockDAO) }) uassert.Equal( diff --git a/examples/gno.land/r/gov/dao/bridge/v2.gno b/examples/gno.land/r/gov/dao/bridge/v2.gno deleted file mode 100644 index 216419cf31d..00000000000 --- a/examples/gno.land/r/gov/dao/bridge/v2.gno +++ /dev/null @@ -1,42 +0,0 @@ -package bridge - -import ( - "gno.land/p/demo/dao" - "gno.land/p/demo/membstore" - govdao "gno.land/r/gov/dao/v2" -) - -// govdaoV2 is a wrapper for interacting with the /r/gov/dao/v2 Realm -type govdaoV2 struct{} - -func (g *govdaoV2) Propose(request dao.ProposalRequest) uint64 { - return govdao.Propose(request) -} - -func (g *govdaoV2) VoteOnProposal(id uint64, option dao.VoteOption) { - govdao.VoteOnProposal(id, option) -} - -func (g *govdaoV2) ExecuteProposal(id uint64) { - govdao.ExecuteProposal(id) -} - -func (g *govdaoV2) GetPropStore() dao.PropStore { - return govdao.GetPropStore() -} - -func (g *govdaoV2) GetMembStore() membstore.MemberStore { - return govdao.GetMembStore() -} - -func (g *govdaoV2) NewGovDAOExecutor(cb func() error) dao.Executor { - return govdao.NewGovDAOExecutor(cb) -} - -func (g *govdaoV2) NewMemberPropExecutor(cb func() []membstore.Member) dao.Executor { - return govdao.NewMemberPropExecutor(cb) -} - -func (g *govdaoV2) NewMembStoreImplExecutor(cb func() membstore.MemberStore) dao.Executor { - return govdao.NewMembStoreImplExecutor(cb) -} diff --git a/examples/gno.land/r/gov/dao/init/gno.mod b/examples/gno.land/r/gov/dao/init/gno.mod new file mode 100644 index 00000000000..40541f4f152 --- /dev/null +++ b/examples/gno.land/r/gov/dao/init/gno.mod @@ -0,0 +1 @@ +module gno.land/r/gov/dao/init diff --git a/examples/gno.land/r/gov/dao/init/init.gno b/examples/gno.land/r/gov/dao/init/init.gno new file mode 100644 index 00000000000..39bdbedba83 --- /dev/null +++ b/examples/gno.land/r/gov/dao/init/init.gno @@ -0,0 +1,13 @@ +// Package init's only task is to load the initial GovDAO version into the bridge. +// This is done to avoid gov/dao/v2 as a bridge dependency, +// As this can often lead to cyclic dependency errors. +package init + +import ( + "gno.land/r/gov/dao/bridge" + govdao "gno.land/r/gov/dao/v2" +) + +func init() { + bridge.LoadGovDAO(govdao.GovDAO) +} diff --git a/examples/gno.land/r/gov/dao/v2/dao.gno b/examples/gno.land/r/gov/dao/v2/dao.gno index 5ee8e63236a..d69f9901301 100644 --- a/examples/gno.land/r/gov/dao/v2/dao.gno +++ b/examples/gno.land/r/gov/dao/v2/dao.gno @@ -11,8 +11,17 @@ import ( var ( d *simpledao.SimpleDAO // the current active DAO implementation members membstore.MemberStore // the member store + + // GovDAO exposes all functions of this contract as methods + GovDAO = &DAO{} ) +// DAO is an empty struct that allows all +// functions of this realm to be methods instead of functions +// This allows a registry, such as r/gov/dao/bridge +// to take this object and match it to a required interface +type DAO struct{} + const daoPkgPath = "gno.land/r/gov/dao/v2" func init() { @@ -33,7 +42,7 @@ func init() { // Propose is designed to be called by another contract or with // `maketx run`, not by a `maketx call`. -func Propose(request dao.ProposalRequest) uint64 { +func (_ DAO) Propose(request dao.ProposalRequest) uint64 { idx, err := d.Propose(request) if err != nil { panic(err) @@ -43,25 +52,25 @@ func Propose(request dao.ProposalRequest) uint64 { } // VoteOnProposal casts a vote for the given proposal -func VoteOnProposal(id uint64, option dao.VoteOption) { +func (_ DAO) VoteOnProposal(id uint64, option dao.VoteOption) { if err := d.VoteOnProposal(id, option); err != nil { panic(err) } } // ExecuteProposal executes the proposal -func ExecuteProposal(id uint64) { +func (_ DAO) ExecuteProposal(id uint64) { if err := d.ExecuteProposal(id); err != nil { panic(err) } } // GetPropStore returns the active proposal store -func GetPropStore() dao.PropStore { +func (_ DAO) GetPropStore() dao.PropStore { return d } // GetMembStore returns the active member store -func GetMembStore() membstore.MemberStore { +func (_ DAO) GetMembStore() membstore.MemberStore { return members } diff --git a/examples/gno.land/r/gov/dao/v2/poc.gno b/examples/gno.land/r/gov/dao/v2/poc.gno index 30d8a403f6e..81bdc7c9b12 100644 --- a/examples/gno.land/r/gov/dao/v2/poc.gno +++ b/examples/gno.land/r/gov/dao/v2/poc.gno @@ -13,7 +13,7 @@ import ( var errNoChangesProposed = errors.New("no set changes proposed") // NewGovDAOExecutor creates the govdao wrapped callback executor -func NewGovDAOExecutor(cb func() error) dao.Executor { +func (_ DAO) NewGovDAOExecutor(cb func() error) dao.Executor { if cb == nil { panic(errNoChangesProposed) } @@ -25,7 +25,7 @@ func NewGovDAOExecutor(cb func() error) dao.Executor { } // NewMemberPropExecutor returns the GOVDAO member change executor -func NewMemberPropExecutor(changesFn func() []membstore.Member) dao.Executor { +func (_ DAO) NewMemberPropExecutor(changesFn func() []membstore.Member) dao.Executor { if changesFn == nil { panic(errNoChangesProposed) } @@ -65,10 +65,10 @@ func NewMemberPropExecutor(changesFn func() []membstore.Member) dao.Executor { return errs } - return NewGovDAOExecutor(callback) + return GovDAO.NewGovDAOExecutor(callback) } -func NewMembStoreImplExecutor(changeFn func() membstore.MemberStore) dao.Executor { +func (_ DAO) NewMembStoreImplExecutor(changeFn func() membstore.MemberStore) dao.Executor { if changeFn == nil { panic(errNoChangesProposed) } @@ -79,7 +79,7 @@ func NewMembStoreImplExecutor(changeFn func() membstore.MemberStore) dao.Executo return nil } - return NewGovDAOExecutor(callback) + return GovDAO.NewGovDAOExecutor(callback) } // setMembStoreImpl sets a new dao.MembStore implementation diff --git a/examples/gno.land/r/gov/dao/v2/prop1_filetest.gno b/examples/gno.land/r/gov/dao/v2/prop1_filetest.gno index 7d8975e1fe8..c8ea983cc73 100644 --- a/examples/gno.land/r/gov/dao/v2/prop1_filetest.gno +++ b/examples/gno.land/r/gov/dao/v2/prop1_filetest.gno @@ -12,11 +12,13 @@ import ( "gno.land/p/demo/dao" pVals "gno.land/p/sys/validators" + _ "gno.land/r/gov/dao/init" // so that the govdao initializer is executed govdao "gno.land/r/gov/dao/v2" validators "gno.land/r/sys/validators/v2" ) func init() { + changesFn := func() []pVals.Validator { return []pVals.Validator{ { @@ -51,7 +53,7 @@ func init() { Executor: executor, } - govdao.Propose(prop) + govdao.GovDAO.Propose(prop) } func main() { @@ -60,13 +62,13 @@ func main() { println("--") println(govdao.Render("0")) println("--") - govdao.VoteOnProposal(0, dao.YesVote) + govdao.GovDAO.VoteOnProposal(0, dao.YesVote) println("--") println(govdao.Render("0")) println("--") println(validators.Render("")) println("--") - govdao.ExecuteProposal(0) + govdao.GovDAO.ExecuteProposal(0) println("--") println(govdao.Render("0")) println("--") diff --git a/examples/gno.land/r/gov/dao/v2/prop2_filetest.gno b/examples/gno.land/r/gov/dao/v2/prop2_filetest.gno index 84a64bc4ee2..f85373a471c 100644 --- a/examples/gno.land/r/gov/dao/v2/prop2_filetest.gno +++ b/examples/gno.land/r/gov/dao/v2/prop2_filetest.gno @@ -5,6 +5,7 @@ import ( "gno.land/p/demo/dao" gnoblog "gno.land/r/gnoland/blog" + _ "gno.land/r/gov/dao/init" // so that the govdao initializer is executed govdao "gno.land/r/gov/dao/v2" ) @@ -28,7 +29,7 @@ func init() { Executor: ex, } - govdao.Propose(prop) + govdao.GovDAO.Propose(prop) } func main() { @@ -37,13 +38,13 @@ func main() { println("--") println(govdao.Render("0")) println("--") - govdao.VoteOnProposal(0, "YES") + govdao.GovDAO.VoteOnProposal(0, "YES") println("--") println(govdao.Render("0")) println("--") println(gnoblog.Render("")) println("--") - govdao.ExecuteProposal(0) + govdao.GovDAO.ExecuteProposal(0) println("--") println(govdao.Render("0")) println("--") diff --git a/examples/gno.land/r/gov/dao/v2/prop3_filetest.gno b/examples/gno.land/r/gov/dao/v2/prop3_filetest.gno index 068f520e7e2..4032ba41d55 100644 --- a/examples/gno.land/r/gov/dao/v2/prop3_filetest.gno +++ b/examples/gno.land/r/gov/dao/v2/prop3_filetest.gno @@ -6,6 +6,7 @@ import ( "gno.land/p/demo/dao" "gno.land/p/demo/membstore" "gno.land/r/gov/dao/bridge" + _ "gno.land/r/gov/dao/init" // so that the govdao initializer is executed govdao "gno.land/r/gov/dao/v2" ) @@ -34,7 +35,7 @@ func init() { prop := dao.ProposalRequest{ Title: title, Description: description, - Executor: govdao.NewMemberPropExecutor(memberFn), + Executor: govdao.GovDAO.NewMemberPropExecutor(memberFn), } bridge.GovDAO().Propose(prop) @@ -42,25 +43,25 @@ func init() { func main() { println("--") - println(govdao.GetMembStore().Size()) + println(govdao.GovDAO.GetMembStore().Size()) println("--") println(govdao.Render("")) println("--") println(govdao.Render("0")) println("--") - govdao.VoteOnProposal(0, "YES") + govdao.GovDAO.VoteOnProposal(0, "YES") println("--") println(govdao.Render("0")) println("--") println(govdao.Render("")) println("--") - govdao.ExecuteProposal(0) + govdao.GovDAO.ExecuteProposal(0) println("--") println(govdao.Render("0")) println("--") println(govdao.Render("")) println("--") - println(govdao.GetMembStore().Size()) + println(govdao.GovDAO.GetMembStore().Size()) } // Output: diff --git a/examples/gno.land/r/gov/dao/v2/prop4_filetest.gno b/examples/gno.land/r/gov/dao/v2/prop4_filetest.gno index 13ca572c512..49326495dac 100644 --- a/examples/gno.land/r/gov/dao/v2/prop4_filetest.gno +++ b/examples/gno.land/r/gov/dao/v2/prop4_filetest.gno @@ -3,6 +3,7 @@ package main import ( "gno.land/p/demo/dao" "gno.land/r/gov/dao/bridge" + _ "gno.land/r/gov/dao/init" // so that the govdao initializer is executed govdaov2 "gno.land/r/gov/dao/v2" "gno.land/r/sys/params" ) diff --git a/examples/gno.land/r/sys/params/params_test.gno b/examples/gno.land/r/sys/params/params_test.gno index eaa1ad039d3..a15da1e7499 100644 --- a/examples/gno.land/r/sys/params/params_test.gno +++ b/examples/gno.land/r/sys/params/params_test.gno @@ -1,6 +1,10 @@ package params -import "testing" +import ( + "testing" + + _ "gno.land/r/gov/dao/init" // so that loader.init is executed +) // Testing this package is limited because it only contains an `std.Set` method // without a corresponding `std.Get` method. For comprehensive testing, refer to From 37ab807315fa751531e69d28d533524b49ffb8b6 Mon Sep 17 00:00:00 2001 From: Morgan Date: Thu, 6 Feb 2025 15:40:06 +0100 Subject: [PATCH 125/143] test(cmd/gnoland): add test to ensure to not import tests/stdlibs (#3589) Fixes #3585 --- gno.land/cmd/gnoland/imports_test.go | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 gno.land/cmd/gnoland/imports_test.go diff --git a/gno.land/cmd/gnoland/imports_test.go b/gno.land/cmd/gnoland/imports_test.go new file mode 100644 index 00000000000..c5ae81599b4 --- /dev/null +++ b/gno.land/cmd/gnoland/imports_test.go @@ -0,0 +1,20 @@ +package main + +import ( + "os/exec" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestNoTestingStdlibImport(t *testing.T) { + // See: https://github.com/gnolang/gno/issues/3585 + // The gno.land binary should not import testing stdlibs, which contain unsafe + // code in the respective native bindings. + + res, err := exec.Command("go", "list", "-f", `{{ join .Deps "\n" }}`, ".").CombinedOutput() + require.NoError(t, err) + assert.Contains(t, string(res), "github.com/gnolang/gno/gnovm/stdlibs\n", "should contain normal stdlibs") + assert.NotContains(t, string(res), "github.com/gnolang/gno/gnovm/tests/stdlibs\n", "should not contain test stdlibs") +} From a01a030af45dcba60f16f571d1f7232e139b9bf5 Mon Sep 17 00:00:00 2001 From: Manfred Touron <94029+moul@users.noreply.github.com> Date: Thu, 6 Feb 2025 16:15:36 +0100 Subject: [PATCH 126/143] chore: make codecov only care about newlines (#3277) ![CleanShot 2024-12-05 at 17 01 41](https://github.com/user-attachments/assets/454f769b-9b87-418a-9499-4cf28ab763ad) RFC Co-authored-by: Nathan Toups <612924+n2p5@users.noreply.github.com> Co-authored-by: Morgan --- .github/codecov.yml | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/.github/codecov.yml b/.github/codecov.yml index f0cb9583cf2..d973bcce859 100644 --- a/.github/codecov.yml +++ b/.github/codecov.yml @@ -10,19 +10,10 @@ coverage: round: down precision: 2 status: - project: + patch: # new lines default: - target: auto - threshold: 5 # Let's decrease this later. - base: parent - if_no_uploads: error - if_not_found: success - if_ci_failed: error - only_pulls: false - patch: - default: - target: auto - threshold: 5 # Let's decrease this later. + target: 80 + threshold: 10 base: auto if_no_uploads: error if_not_found: success From 08d29a50d7ff9762417da205e1bab48b70c081ab Mon Sep 17 00:00:00 2001 From: Alexis Colin Date: Fri, 7 Feb 2025 01:04:03 +0900 Subject: [PATCH 127/143] feat(gnoweb): display network info (#3679) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This PR introduces a Network Info section in the gnoweb interface, displaying details such as `RPC` and `ChainID`. - It adds a new icon in the search bar, which triggers a modal (purely CSS-based, no JavaScript) to display these network details. - The modal is fully interactive, allowing users to close it by clicking either on the close button or anywhere outside the modal. This enhancement improves visibility and accessibility of network info from gnoweb without relying on JavaScript. Capture d’écran 2025-02-05 à 18 32 41 Capture d’écran 2025-02-05 à 18 32 50 --------- Co-authored-by: Morgan --- .../pkg/gnoweb/components/layout_header.go | 2 + .../pkg/gnoweb/components/layouts/header.html | 69 ++++++++++++++++--- gno.land/pkg/gnoweb/components/ui/icons.html | 45 ++++++++++++ gno.land/pkg/gnoweb/frontend/css/tx.config.js | 1 + gno.land/pkg/gnoweb/handler.go | 2 + gno.land/pkg/gnoweb/public/styles.css | 2 +- 6 files changed, 111 insertions(+), 10 deletions(-) diff --git a/gno.land/pkg/gnoweb/components/layout_header.go b/gno.land/pkg/gnoweb/components/layout_header.go index b85efde5f85..d446212c6e0 100644 --- a/gno.land/pkg/gnoweb/components/layout_header.go +++ b/gno.land/pkg/gnoweb/components/layout_header.go @@ -16,6 +16,8 @@ type HeaderData struct { Breadcrumb BreadcrumbData WebQuery url.Values Links []HeaderLink + ChainId string + Remote string } func StaticHeaderLinks(realmPath string, webQuery url.Values) []HeaderLink { diff --git a/gno.land/pkg/gnoweb/components/layouts/header.html b/gno.land/pkg/gnoweb/components/layouts/header.html index 5743d0a82b6..851833b1dc0 100644 --- a/gno.land/pkg/gnoweb/components/layouts/header.html +++ b/gno.land/pkg/gnoweb/components/layouts/header.html @@ -6,16 +6,67 @@ Gno username profile pic -
{{ range .Links }} {{ template "ui/header_link" . }} {{ end }}
diff --git a/gno.land/pkg/gnoweb/components/ui/icons.html b/gno.land/pkg/gnoweb/components/ui/icons.html index feef8226be7..f1145d74359 100644 --- a/gno.land/pkg/gnoweb/components/ui/icons.html +++ b/gno.land/pkg/gnoweb/components/ui/icons.html @@ -120,5 +120,50 @@ fill="transparent" /> + + + + + + + + + + + + + + + {{ end }} diff --git a/gno.land/pkg/gnoweb/frontend/css/tx.config.js b/gno.land/pkg/gnoweb/frontend/css/tx.config.js index 451688d7da6..06aa685676a 100644 --- a/gno.land/pkg/gnoweb/frontend/css/tx.config.js +++ b/gno.land/pkg/gnoweb/frontend/css/tx.config.js @@ -26,6 +26,7 @@ export default { borderRadius: { sm: `${pxToRem(4)}rem`, DEFAULT: `${pxToRem(6)}rem`, + full: "9999px", }, colors: { light: "#FFFFFF", diff --git a/gno.land/pkg/gnoweb/handler.go b/gno.land/pkg/gnoweb/handler.go index ac39f4ce0f9..4c1dae31261 100644 --- a/gno.land/pkg/gnoweb/handler.go +++ b/gno.land/pkg/gnoweb/handler.go @@ -123,6 +123,8 @@ func (h *WebHandler) prepareIndexBodyView(r *http.Request, indexData *components RealmPath: gnourl.Encode(EncodePath | EncodeArgs | EncodeQuery | EncodeNoEscape), Breadcrumb: breadcrumb, WebQuery: gnourl.WebQuery, + ChainId: h.Static.ChainId, + Remote: h.Static.RemoteHelp, } switch { diff --git a/gno.land/pkg/gnoweb/public/styles.css b/gno.land/pkg/gnoweb/public/styles.css index ec575bb3735..96e768a313e 100644 --- a/gno.land/pkg/gnoweb/public/styles.css +++ b/gno.land/pkg/gnoweb/public/styles.css @@ -1,3 +1,3 @@ @font-face{font-family:Roboto;font-style:normal;font-weight:900;font-display:swap;src:url(fonts/roboto/roboto-mono-normal.woff2) format("woff2"),url(fonts/roboto/roboto-mono-normal.woff) format("woff")}@font-face{font-family:Inter var;font-weight:100 900;font-display:block;font-style:oblique 0deg 10deg;src:url(fonts/intervar/Intervar.woff2) format("woff2")}*,:after,:before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: } -/*! tailwindcss v3.4.14 | MIT License | https://tailwindcss.com*/*,:after,:before{box-sizing:border-box;border:0 solid #bdbdbd}:after,:before{--tw-content:""}:host,html{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:Roboto,Menlo,Consolas,Ubuntu Mono,Roboto Mono,DejaVu Sans Mono,monospace;;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#7c7c7c}input::placeholder,textarea::placeholder{opacity:1;color:#7c7c7c}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}html{--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity));font-family:Inter var,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji,sans-serif;font-size:1rem;--tw-text-opacity:1;color:rgb(84 89 93/var(--tw-text-opacity));font-feature-settings:"kern" on,"liga" on,"calt" on,"zero" on;-webkit-font-feature-settings:"kern" on,"liga" on,"calt" on,"zero" on;-webkit-text-size-adjust:100%;-moz-text-size-adjust:100%;text-size-adjust:100%;-moz-osx-font-smoothing:grayscale;font-smoothing:antialiased;font-variant-ligatures:contextual common-ligatures;font-kerning:normal;text-rendering:optimizeLegibility}svg{max-height:100%;max-width:100%}form{margin-top:0;margin-bottom:0}.realm-view{overflow-wrap:break-word;padding-top:1.5rem;font-size:1rem}@media (min-width:51.25rem){.realm-view{padding-top:2.5rem}}.realm-view>:first-child{margin-top:0!important}.realm-view a{font-weight:500;--tw-text-opacity:1;color:rgb(34 108 87/var(--tw-text-opacity))}.realm-view a:hover{text-decoration-line:underline}.realm-view h1,.realm-view h2,.realm-view h3,.realm-view h4{margin-top:3rem;line-height:1.25;--tw-text-opacity:1;color:rgb(8 8 9/var(--tw-text-opacity))}.realm-view h2,.realm-view h2 *{font-weight:700}.realm-view h3,.realm-view h3 *,.realm-view h4,.realm-view h4 *{font-weight:600}.realm-view h1+h2,.realm-view h2+h3,.realm-view h3+h4{margin-top:1rem}.realm-view h1{font-size:2.375rem;font-weight:700}.realm-view h2{font-size:1.5rem}.realm-view h3{margin-top:2.5rem;font-size:1.25rem}.realm-view h3,.realm-view h4{--tw-text-opacity:1;color:rgb(84 89 93/var(--tw-text-opacity))}.realm-view h4{margin-top:1.5rem;margin-bottom:1.5rem;font-size:1.125rem;font-weight:500}.realm-view p{margin-top:1.25rem;margin-bottom:1.25rem}.realm-view strong{font-weight:700;--tw-text-opacity:1;color:rgb(8 8 9/var(--tw-text-opacity))}.realm-view strong *{font-weight:700}.realm-view em{font-style:oblique 14deg}.realm-view blockquote{margin-top:1rem;margin-bottom:1rem;border-left-width:4px;--tw-border-opacity:1;border-color:rgb(153 153 153/var(--tw-border-opacity));padding-left:1rem;--tw-text-opacity:1;color:rgb(84 89 93/var(--tw-text-opacity));font-style:oblique 14deg}.realm-view ol,.realm-view ul{margin-top:1.5rem;margin-bottom:1.5rem;padding-left:1rem}.realm-view ol li,.realm-view ul li{margin-bottom:.5rem}.realm-view img{margin-top:2rem;margin-bottom:2rem;max-width:100%}.realm-view figure{margin-top:1.5rem;margin-bottom:1.5rem;text-align:center}.realm-view figcaption{font-size:.875rem;--tw-text-opacity:1;color:rgb(84 89 93/var(--tw-text-opacity))}.realm-view :not(pre)>code{border-radius:.25rem;background-color:rgb(226 226 226/var(--tw-bg-opacity));padding:.125rem .25rem;font-size:.96em}.realm-view :not(pre)>code,.realm-view pre{--tw-bg-opacity:1;font-family:Roboto,Menlo,Consolas,Ubuntu Mono,Roboto Mono,DejaVu Sans Mono,monospace;}.realm-view pre{overflow-x:auto;border-radius:.375rem;background-color:rgb(240 240 240/var(--tw-bg-opacity));padding:1rem}.realm-view hr{margin-top:2.5rem;margin-bottom:2.5rem;border-top-width:1px;--tw-border-opacity:1;border-color:rgb(226 226 226/var(--tw-border-opacity))}.realm-view table{margin-top:2rem;margin-bottom:2rem;display:block;width:100%;max-width:100%;border-collapse:collapse;overflow-x:auto}.realm-view td,.realm-view th{white-space:normal;overflow-wrap:break-word;border-width:1px;padding:.5rem 1rem}.realm-view th{--tw-bg-opacity:1;background-color:rgb(226 226 226/var(--tw-bg-opacity));font-weight:700}.realm-view caption{margin-top:.5rem;text-align:left;font-size:.875rem;--tw-text-opacity:1;color:rgb(84 89 93/var(--tw-text-opacity))}.realm-view q{margin-top:1.5rem;margin-bottom:1.5rem;border-left-width:4px;--tw-border-opacity:1;border-left-color:rgb(204 204 204/var(--tw-border-opacity));padding-left:1rem;--tw-text-opacity:1;color:rgb(85 85 85/var(--tw-text-opacity));font-style:oblique 14deg;quotes:"“" "”" "‘" "’"}.realm-view q:after,.realm-view q:before{margin-right:.25rem;font-size:1.5rem;--tw-text-opacity:1;color:rgb(153 153 153/var(--tw-text-opacity));content:open-quote;vertical-align:-.4rem}.realm-view q:after{content:close-quote}.realm-view q:before{content:open-quote}.realm-view q:after{content:close-quote}.realm-view ol ol,.realm-view ol ul,.realm-view ul ol,.realm-view ul ul{margin-top:.75rem;margin-bottom:.5rem;padding-left:1rem}.realm-view ul{list-style-type:disc}.realm-view ol{list-style-type:decimal}.realm-view abbr[title]{cursor:help;border-bottom-width:1px;border-style:dotted}.realm-view details{margin-top:1.25rem;margin-bottom:1.25rem}.realm-view summary{cursor:pointer;font-weight:700}.realm-view a code{color:inherit}.realm-view video{margin-top:2rem;margin-bottom:2rem;max-width:100%}.realm-view math{font-family:Roboto,Menlo,Consolas,Ubuntu Mono,Roboto Mono,DejaVu Sans Mono,monospace;}.realm-view small{font-size:.875rem}.realm-view del{text-decoration-line:line-through}.realm-view sub{vertical-align:sub;font-size:.75rem}.realm-view sup{vertical-align:super;font-size:.75rem}.realm-view button,.realm-view input{border-width:1px;--tw-border-opacity:1;border-color:rgb(153 153 153/var(--tw-border-opacity));padding:.5rem 1rem}main :is(h1,h2,h3,h4){scroll-margin-top:6rem}::-moz-selection{--tw-bg-opacity:1;background-color:rgb(34 108 87/var(--tw-bg-opacity));--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}::selection{--tw-bg-opacity:1;background-color:rgb(34 108 87/var(--tw-bg-opacity));--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}.sidemenu .peer:checked+label>svg{--tw-text-opacity:1;color:rgb(34 108 87/var(--tw-text-opacity))}.toc-expend-btn:has(#toc-expend:checked)+nav{display:block}.toc-expend-btn:has(#toc-expend:checked) .toc-expend-btn_ico{--tw-rotate:180deg;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.main-header:has(#sidemenu-docs:checked)+main #sidebar #sidebar-docs,.main-header:has(#sidemenu-meta:checked)+main #sidebar #sidebar-meta,.main-header:has(#sidemenu-source:checked)+main #sidebar #sidebar-source,.main-header:has(#sidemenu-summary:checked)+main #sidebar #sidebar-summary{display:block}@media (min-width:40rem){:is(.main-header:has(#sidemenu-source:checked),.main-header:has(#sidemenu-docs:checked),.main-header:has(#sidemenu-meta:checked)) .main-navigation,:is(.main-header:has(#sidemenu-source:checked),.main-header:has(#sidemenu-docs:checked),.main-header:has(#sidemenu-meta:checked))+main .realm-view{grid-column:span 6/span 6}:is(.main-header:has(#sidemenu-source:checked),.main-header:has(#sidemenu-docs:checked),.main-header:has(#sidemenu-meta:checked)) .sidemenu,:is(.main-header:has(#sidemenu-source:checked),.main-header:has(#sidemenu-docs:checked),.main-header:has(#sidemenu-meta:checked))+main #sidebar{grid-column:span 4/span 4}}:is(.main-header:has(#sidemenu-source:checked),.main-header:has(#sidemenu-docs:checked),.main-header:has(#sidemenu-meta:checked))+main #sidebar:before{position:absolute;top:0;left:-1.75rem;z-index:-1;display:block;height:100%;width:50vw;--tw-bg-opacity:1;background-color:rgb(226 226 226/var(--tw-bg-opacity));--tw-content:"";content:var(--tw-content)}main :is(.source-code)>pre{overflow:scroll;border-radius:.375rem;--tw-bg-opacity:1!important;background-color:rgb(255 255 255/var(--tw-bg-opacity))!important;padding:1rem .25rem;font-family:Roboto,Menlo,Consolas,Ubuntu Mono,Roboto Mono,DejaVu Sans Mono,monospace;;font-size:.875rem}@media (min-width:40rem){main :is(.source-code)>pre{padding:2rem .75rem;font-size:1rem}}main .realm-view>pre a:hover{text-decoration-line:none}main :is(.realm-view,.source-code)>pre .chroma-ln:target{background-color:transparent!important}main :is(.realm-view,.source-code)>pre .chroma-line:has(.chroma-ln:target),main :is(.realm-view,.source-code)>pre .chroma-line:has(.chroma-ln:target) .chroma-cl,main :is(.realm-view,.source-code)>pre .chroma-line:has(.chroma-lnlinks:hover),main :is(.realm-view,.source-code)>pre .chroma-line:has(.chroma-lnlinks:hover) .chroma-cl{border-radius:.375rem;--tw-bg-opacity:1!important;background-color:rgb(226 226 226/var(--tw-bg-opacity))!important}main :is(.realm-view,.source-code)>pre .chroma-ln{scroll-margin-top:6rem}.dev-mode .toc-expend-btn{cursor:pointer;border-width:1px;--tw-bg-opacity:1;background-color:rgb(226 226 226/var(--tw-bg-opacity))}.dev-mode .toc-expend-btn:hover{--tw-bg-opacity:1;background-color:rgb(240 240 240/var(--tw-bg-opacity))}@media (min-width:51.25rem){.dev-mode .toc-expend-btn{border-style:none;background-color:transparent}}.dev-mode #sidebar-summary{--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity))}@media (min-width:51.25rem){.dev-mode #sidebar-summary{background-color:transparent}}.dev-mode .toc-nav{font-family:Roboto,Menlo,Consolas,Ubuntu Mono,Roboto Mono,DejaVu Sans Mono,monospace;}.absolute{position:absolute}.relative{position:relative}.sticky{position:sticky}.bottom-1{bottom:.25rem}.left-0{left:0}.right-2{right:.5rem}.right-3{right:.75rem}.top-0{top:0}.top-1\/2{top:50%}.top-14{top:3.5rem}.top-2{top:.5rem}.z-1{z-index:1}.z-max{z-index:9999}.col-span-1{grid-column:span 1/span 1}.col-span-10{grid-column:span 10/span 10}.col-span-3{grid-column:span 3/span 3}.col-span-7{grid-column:span 7/span 7}.row-span-1{grid-row:span 1/span 1}.mx-auto{margin-left:auto;margin-right:auto}.mb-1{margin-bottom:.25rem}.mb-2{margin-bottom:.5rem}.mb-3{margin-bottom:.75rem}.mb-4{margin-bottom:1rem}.mb-8{margin-bottom:2rem}.mr-10{margin-right:2.5rem}.mt-10{margin-top:2.5rem}.mt-2{margin-top:.5rem}.mt-4{margin-top:1rem}.mt-6{margin-top:1.5rem}.line-clamp-2{overflow:hidden;display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2}.block{display:block}.inline-block{display:inline-block}.inline{display:inline}.flex{display:flex}.grid{display:grid}.hidden{display:none}.h-10{height:2.5rem}.h-4{height:1rem}.h-5{height:1.25rem}.h-6{height:1.5rem}.h-full{height:100%}.max-h-screen{max-height:100vh}.min-h-full{min-height:100%}.min-h-screen{min-height:100vh}.w-10{width:2.5rem}.w-4{width:1rem}.w-5{width:1.25rem}.w-full{width:100%}.min-w-2{min-width:.5rem}.min-w-48{min-width:12rem}.max-w-screen-max{max-width:98.75rem}.shrink-0{flex-shrink:0}.grow-\[2\]{flex-grow:2}.-translate-y-1\/2{--tw-translate-y:-50%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.cursor-pointer{cursor:pointer}.list-none{list-style-type:none}.appearance-none{-webkit-appearance:none;-moz-appearance:none;appearance:none}.grid-flow-dense{grid-auto-flow:dense}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-cols-10{grid-template-columns:repeat(10,minmax(0,1fr))}.flex-col{flex-direction:column}.items-start{align-items:flex-start}.items-center{align-items:center}.items-stretch{align-items:stretch}.justify-end{justify-content:flex-end}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-0\.5{gap:.125rem}.gap-1{gap:.25rem}.gap-1\.5{gap:.375rem}.gap-2{gap:.5rem}.gap-3{gap:.75rem}.gap-4{gap:1rem}.gap-8{gap:2rem}.gap-x-20{-moz-column-gap:5rem;column-gap:5rem}.gap-x-3{-moz-column-gap:.75rem;column-gap:.75rem}.gap-y-2{row-gap:.5rem}.space-y-2>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.5rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.5rem*var(--tw-space-y-reverse))}.overflow-hidden{overflow:hidden}.overflow-scroll{overflow:scroll}.whitespace-nowrap{white-space:nowrap}.whitespace-pre-wrap{white-space:pre-wrap}.rounded{border-radius:.375rem}.rounded-sm{border-radius:.25rem}.border{border-width:1px}.border-b{border-bottom-width:1px}.border-l{border-left-width:1px}.border-t{border-top-width:1px}.border-gray-100{--tw-border-opacity:1;border-color:rgb(226 226 226/var(--tw-border-opacity))}.bg-gray-100{--tw-bg-opacity:1;background-color:rgb(226 226 226/var(--tw-bg-opacity))}.bg-gray-300{--tw-bg-opacity:1;background-color:rgb(153 153 153/var(--tw-bg-opacity))}.bg-gray-50{--tw-bg-opacity:1;background-color:rgb(240 240 240/var(--tw-bg-opacity))}.bg-light{--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity))}.bg-transparent{background-color:transparent}.p-1\.5{padding:.375rem}.p-2{padding:.5rem}.p-4{padding:1rem}.px-1{padding-left:.25rem;padding-right:.25rem}.px-10{padding-left:2.5rem;padding-right:2.5rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-4{padding-left:1rem;padding-right:1rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.py-1\.5{padding-top:.375rem;padding-bottom:.375rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.py-px{padding-top:1px;padding-bottom:1px}.pb-24{padding-bottom:6rem}.pb-3{padding-bottom:.75rem}.pb-4{padding-bottom:1rem}.pb-6{padding-bottom:1.5rem}.pb-8{padding-bottom:2rem}.pl-4{padding-left:1rem}.pr-10{padding-right:2.5rem}.pt-0\.5{padding-top:.125rem}.pt-2{padding-top:.5rem}.font-mono{font-family:Roboto,Menlo,Consolas,Ubuntu Mono,Roboto Mono,DejaVu Sans Mono,monospace;}.text-100{font-size:.875rem}.text-200{font-size:1rem}.text-50{font-size:.75rem}.text-600{font-size:1.5rem}.font-bold{font-weight:700}.font-medium{font-weight:500}.font-normal{font-weight:400}.font-semibold{font-weight:600}.capitalize{text-transform:capitalize}.leading-tight{line-height:1.25}.text-gray-300{--tw-text-opacity:1;color:rgb(153 153 153/var(--tw-text-opacity))}.text-gray-400{--tw-text-opacity:1;color:rgb(124 124 124/var(--tw-text-opacity))}.text-gray-600{--tw-text-opacity:1;color:rgb(84 89 93/var(--tw-text-opacity))}.text-gray-800{--tw-text-opacity:1;color:rgb(19 19 19/var(--tw-text-opacity))}.text-gray-900{--tw-text-opacity:1;color:rgb(8 8 9/var(--tw-text-opacity))}.text-green-600{--tw-text-opacity:1;color:rgb(34 108 87/var(--tw-text-opacity))}.text-light{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}.outline-none{outline:2px solid transparent;outline-offset:2px}.text-stroke{-webkit-text-stroke:currentColor;-webkit-text-stroke-width:.6px}.no-scrollbar::-webkit-scrollbar{display:none}.no-scrollbar{-ms-overflow-style:none;scrollbar-width:none}.\*\:pl-0>*{padding-left:0}.before\:px-\[0\.18rem\]:before{content:var(--tw-content);padding-left:.18rem;padding-right:.18rem}.before\:text-gray-300:before{content:var(--tw-content);--tw-text-opacity:1;color:rgb(153 153 153/var(--tw-text-opacity))}.before\:content-\[\'\/\'\]:before{--tw-content:"/";content:var(--tw-content)}.before\:content-\[\'\:\'\]:before{--tw-content:":";content:var(--tw-content)}.before\:content-\[\'open\'\]:before{--tw-content:"open";content:var(--tw-content)}.after\:pointer-events-none:after{content:var(--tw-content);pointer-events:none}.after\:absolute:after{content:var(--tw-content);position:absolute}.after\:bottom-0:after{content:var(--tw-content);bottom:0}.after\:left-0:after{content:var(--tw-content);left:0}.after\:top-0:after{content:var(--tw-content);top:0}.after\:block:after{content:var(--tw-content);display:block}.after\:h-1:after{content:var(--tw-content);height:.25rem}.after\:h-full:after{content:var(--tw-content);height:100%}.after\:w-full:after{content:var(--tw-content);width:100%}.after\:rounded-t-sm:after{content:var(--tw-content);border-top-left-radius:.25rem;border-top-right-radius:.25rem}.after\:bg-gray-100:after{content:var(--tw-content);--tw-bg-opacity:1;background-color:rgb(226 226 226/var(--tw-bg-opacity))}.after\:bg-green-600:after{content:var(--tw-content);--tw-bg-opacity:1;background-color:rgb(34 108 87/var(--tw-bg-opacity))}.first\:mt-8:first-child{margin-top:2rem}.first\:border-t:first-child{border-top-width:1px}.hover\:border-gray-300:hover{--tw-border-opacity:1;border-color:rgb(153 153 153/var(--tw-border-opacity))}.hover\:bg-gray-100:hover{--tw-bg-opacity:1;background-color:rgb(226 226 226/var(--tw-bg-opacity))}.hover\:bg-gray-50:hover{--tw-bg-opacity:1;background-color:rgb(240 240 240/var(--tw-bg-opacity))}.hover\:bg-green-600:hover{--tw-bg-opacity:1;background-color:rgb(34 108 87/var(--tw-bg-opacity))}.hover\:text-gray-600:hover{--tw-text-opacity:1;color:rgb(84 89 93/var(--tw-text-opacity))}.hover\:text-green-600:hover{--tw-text-opacity:1;color:rgb(34 108 87/var(--tw-text-opacity))}.hover\:text-light:hover{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}.hover\:underline:hover{text-decoration-line:underline}.focus\:border-gray-300:focus{--tw-border-opacity:1;border-color:rgb(153 153 153/var(--tw-border-opacity))}.focus\:border-l-gray-300:focus{--tw-border-opacity:1;border-left-color:rgb(153 153 153/var(--tw-border-opacity))}.group:hover .group-hover\:border-gray-300{--tw-border-opacity:1;border-color:rgb(153 153 153/var(--tw-border-opacity))}.group:hover .group-hover\:border-l-gray-300{--tw-border-opacity:1;border-left-color:rgb(153 153 153/var(--tw-border-opacity))}.group.is-active .group-\[\.is-active\]\:text-green-600{--tw-text-opacity:1;color:rgb(34 108 87/var(--tw-text-opacity))}.peer:checked~.peer-checked\:before\:content-\[\'close\'\]:before{--tw-content:"close";content:var(--tw-content)}.peer:focus-within~.peer-focus-within\:hidden{display:none}.has-\[ul\:empty\]\:hidden:has(ul:empty){display:none}.has-\[\:focus-within\]\:border-gray-300:has(:focus-within){--tw-border-opacity:1;border-color:rgb(153 153 153/var(--tw-border-opacity))}.has-\[\:focus\]\:border-gray-300:has(:focus){--tw-border-opacity:1;border-color:rgb(153 153 153/var(--tw-border-opacity))}@media (min-width:30rem){.sm\:gap-6{gap:1.5rem}}@media (min-width:40rem){.md\:col-span-3{grid-column:span 3/span 3}.md\:mb-0{margin-bottom:0}.md\:h-4{height:1rem}.md\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.md\:flex-row{flex-direction:row}.md\:items-center{align-items:center}.md\:gap-x-8{-moz-column-gap:2rem;column-gap:2rem}.md\:px-10{padding-left:2.5rem;padding-right:2.5rem}.md\:pb-0{padding-bottom:0}}@media (min-width:51.25rem){.lg\:order-2{order:2}.lg\:col-span-3{grid-column:span 3/span 3}.lg\:col-span-7{grid-column:span 7/span 7}.lg\:row-span-2{grid-row:span 2/span 2}.lg\:row-start-1{grid-row-start:1}.lg\:mb-4{margin-bottom:1rem}.lg\:mt-0{margin-top:0}.lg\:mt-10{margin-top:2.5rem}.lg\:block{display:block}.lg\:hidden{display:none}.lg\:grid-cols-10{grid-template-columns:repeat(10,minmax(0,1fr))}.lg\:flex-row{flex-direction:row}.lg\:justify-start{justify-content:flex-start}.lg\:justify-between{justify-content:space-between}.lg\:gap-x-20{-moz-column-gap:5rem;column-gap:5rem}.lg\:border-none{border-style:none}.lg\:bg-transparent{background-color:transparent}.lg\:p-0{padding:0}.lg\:px-0{padding-left:0;padding-right:0}.lg\:px-2{padding-left:.5rem;padding-right:.5rem}.lg\:py-1\.5{padding-top:.375rem;padding-bottom:.375rem}.lg\:pb-28{padding-bottom:7rem}.lg\:pt-2{padding-top:.5rem}.lg\:text-200{font-size:1rem}.lg\:font-semibold{font-weight:600}.lg\:first\:mt-0:first-child{margin-top:0}.lg\:hover\:bg-transparent:hover{background-color:transparent}}@media (min-width:63.75rem){.xl\:inline{display:inline}.xl\:hidden{display:none}.xl\:grid-cols-10{grid-template-columns:repeat(10,minmax(0,1fr))}.xl\:flex-row{flex-direction:row}.xl\:items-center{align-items:center}.xl\:gap-20{gap:5rem}.xl\:gap-6{gap:1.5rem}.xl\:pt-0{padding-top:0}}@media (min-width:85.375rem){.xxl\:inline-block{display:inline-block}.xxl\:h-4{height:1rem}.xxl\:w-4{width:1rem}.xxl\:gap-20{gap:5rem}.xxl\:gap-x-32{-moz-column-gap:8rem;column-gap:8rem}.xxl\:pr-1{padding-right:.25rem}} \ No newline at end of file +/*! tailwindcss v3.4.14 | MIT License | https://tailwindcss.com*/*,:after,:before{box-sizing:border-box;border:0 solid #bdbdbd}:after,:before{--tw-content:""}:host,html{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:Roboto,Menlo,Consolas,Ubuntu Mono,Roboto Mono,DejaVu Sans Mono,monospace;;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#7c7c7c}input::placeholder,textarea::placeholder{opacity:1;color:#7c7c7c}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}html{--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity));font-family:Inter var,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji,sans-serif;font-size:1rem;--tw-text-opacity:1;color:rgb(84 89 93/var(--tw-text-opacity));font-feature-settings:"kern" on,"liga" on,"calt" on,"zero" on;-webkit-font-feature-settings:"kern" on,"liga" on,"calt" on,"zero" on;-webkit-text-size-adjust:100%;-moz-text-size-adjust:100%;text-size-adjust:100%;-moz-osx-font-smoothing:grayscale;font-smoothing:antialiased;font-variant-ligatures:contextual common-ligatures;font-kerning:normal;text-rendering:optimizeLegibility}svg{max-height:100%;max-width:100%}form{margin-top:0;margin-bottom:0}.realm-view{overflow-wrap:break-word;padding-top:1.5rem;font-size:1rem}@media (min-width:51.25rem){.realm-view{padding-top:2.5rem}}.realm-view>:first-child{margin-top:0!important}.realm-view a{font-weight:500;--tw-text-opacity:1;color:rgb(34 108 87/var(--tw-text-opacity))}.realm-view a:hover{text-decoration-line:underline}.realm-view h1,.realm-view h2,.realm-view h3,.realm-view h4{margin-top:3rem;line-height:1.25;--tw-text-opacity:1;color:rgb(8 8 9/var(--tw-text-opacity))}.realm-view h2,.realm-view h2 *{font-weight:700}.realm-view h3,.realm-view h3 *,.realm-view h4,.realm-view h4 *{font-weight:600}.realm-view h1+h2,.realm-view h2+h3,.realm-view h3+h4{margin-top:1rem}.realm-view h1{font-size:2.375rem;font-weight:700}.realm-view h2{font-size:1.5rem}.realm-view h3{margin-top:2.5rem;font-size:1.25rem}.realm-view h3,.realm-view h4{--tw-text-opacity:1;color:rgb(84 89 93/var(--tw-text-opacity))}.realm-view h4{margin-top:1.5rem;margin-bottom:1.5rem;font-size:1.125rem;font-weight:500}.realm-view p{margin-top:1.25rem;margin-bottom:1.25rem}.realm-view strong{font-weight:700;--tw-text-opacity:1;color:rgb(8 8 9/var(--tw-text-opacity))}.realm-view strong *{font-weight:700}.realm-view em{font-style:oblique 14deg}.realm-view blockquote{margin-top:1rem;margin-bottom:1rem;border-left-width:4px;--tw-border-opacity:1;border-color:rgb(153 153 153/var(--tw-border-opacity));padding-left:1rem;--tw-text-opacity:1;color:rgb(84 89 93/var(--tw-text-opacity));font-style:oblique 14deg}.realm-view ol,.realm-view ul{margin-top:1.5rem;margin-bottom:1.5rem;padding-left:1rem}.realm-view ol li,.realm-view ul li{margin-bottom:.5rem}.realm-view img{margin-top:2rem;margin-bottom:2rem;max-width:100%}.realm-view figure{margin-top:1.5rem;margin-bottom:1.5rem;text-align:center}.realm-view figcaption{font-size:.875rem;--tw-text-opacity:1;color:rgb(84 89 93/var(--tw-text-opacity))}.realm-view :not(pre)>code{border-radius:.25rem;background-color:rgb(226 226 226/var(--tw-bg-opacity));padding:.125rem .25rem;font-size:.96em}.realm-view :not(pre)>code,.realm-view pre{--tw-bg-opacity:1;font-family:Roboto,Menlo,Consolas,Ubuntu Mono,Roboto Mono,DejaVu Sans Mono,monospace;}.realm-view pre{overflow-x:auto;border-radius:.375rem;background-color:rgb(240 240 240/var(--tw-bg-opacity));padding:1rem}.realm-view hr{margin-top:2.5rem;margin-bottom:2.5rem;border-top-width:1px;--tw-border-opacity:1;border-color:rgb(226 226 226/var(--tw-border-opacity))}.realm-view table{margin-top:2rem;margin-bottom:2rem;display:block;width:100%;max-width:100%;border-collapse:collapse;overflow-x:auto}.realm-view td,.realm-view th{white-space:normal;overflow-wrap:break-word;border-width:1px;padding:.5rem 1rem}.realm-view th{--tw-bg-opacity:1;background-color:rgb(226 226 226/var(--tw-bg-opacity));font-weight:700}.realm-view caption{margin-top:.5rem;text-align:left;font-size:.875rem;--tw-text-opacity:1;color:rgb(84 89 93/var(--tw-text-opacity))}.realm-view q{margin-top:1.5rem;margin-bottom:1.5rem;border-left-width:4px;--tw-border-opacity:1;border-left-color:rgb(204 204 204/var(--tw-border-opacity));padding-left:1rem;--tw-text-opacity:1;color:rgb(85 85 85/var(--tw-text-opacity));font-style:oblique 14deg;quotes:"“" "”" "‘" "’"}.realm-view q:after,.realm-view q:before{margin-right:.25rem;font-size:1.5rem;--tw-text-opacity:1;color:rgb(153 153 153/var(--tw-text-opacity));content:open-quote;vertical-align:-.4rem}.realm-view q:after{content:close-quote}.realm-view q:before{content:open-quote}.realm-view q:after{content:close-quote}.realm-view ol ol,.realm-view ol ul,.realm-view ul ol,.realm-view ul ul{margin-top:.75rem;margin-bottom:.5rem;padding-left:1rem}.realm-view ul{list-style-type:disc}.realm-view ol{list-style-type:decimal}.realm-view abbr[title]{cursor:help;border-bottom-width:1px;border-style:dotted}.realm-view details{margin-top:1.25rem;margin-bottom:1.25rem}.realm-view summary{cursor:pointer;font-weight:700}.realm-view a code{color:inherit}.realm-view video{margin-top:2rem;margin-bottom:2rem;max-width:100%}.realm-view math{font-family:Roboto,Menlo,Consolas,Ubuntu Mono,Roboto Mono,DejaVu Sans Mono,monospace;}.realm-view small{font-size:.875rem}.realm-view del{text-decoration-line:line-through}.realm-view sub{vertical-align:sub;font-size:.75rem}.realm-view sup{vertical-align:super;font-size:.75rem}.realm-view button,.realm-view input{border-width:1px;--tw-border-opacity:1;border-color:rgb(153 153 153/var(--tw-border-opacity));padding:.5rem 1rem}main :is(h1,h2,h3,h4){scroll-margin-top:6rem}::-moz-selection{--tw-bg-opacity:1;background-color:rgb(34 108 87/var(--tw-bg-opacity));--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}::selection{--tw-bg-opacity:1;background-color:rgb(34 108 87/var(--tw-bg-opacity));--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}.sidemenu .peer:checked+label>svg{--tw-text-opacity:1;color:rgb(34 108 87/var(--tw-text-opacity))}.toc-expend-btn:has(#toc-expend:checked)+nav{display:block}.toc-expend-btn:has(#toc-expend:checked) .toc-expend-btn_ico{--tw-rotate:180deg;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.main-header:has(#sidemenu-docs:checked)+main #sidebar #sidebar-docs,.main-header:has(#sidemenu-meta:checked)+main #sidebar #sidebar-meta,.main-header:has(#sidemenu-source:checked)+main #sidebar #sidebar-source,.main-header:has(#sidemenu-summary:checked)+main #sidebar #sidebar-summary{display:block}@media (min-width:40rem){:is(.main-header:has(#sidemenu-source:checked),.main-header:has(#sidemenu-docs:checked),.main-header:has(#sidemenu-meta:checked)) .main-navigation,:is(.main-header:has(#sidemenu-source:checked),.main-header:has(#sidemenu-docs:checked),.main-header:has(#sidemenu-meta:checked))+main .realm-view{grid-column:span 6/span 6}:is(.main-header:has(#sidemenu-source:checked),.main-header:has(#sidemenu-docs:checked),.main-header:has(#sidemenu-meta:checked)) .sidemenu,:is(.main-header:has(#sidemenu-source:checked),.main-header:has(#sidemenu-docs:checked),.main-header:has(#sidemenu-meta:checked))+main #sidebar{grid-column:span 4/span 4}}:is(.main-header:has(#sidemenu-source:checked),.main-header:has(#sidemenu-docs:checked),.main-header:has(#sidemenu-meta:checked))+main #sidebar:before{position:absolute;top:0;left:-1.75rem;z-index:-1;display:block;height:100%;width:50vw;--tw-bg-opacity:1;background-color:rgb(226 226 226/var(--tw-bg-opacity));--tw-content:"";content:var(--tw-content)}main :is(.source-code)>pre{overflow:scroll;border-radius:.375rem;--tw-bg-opacity:1!important;background-color:rgb(255 255 255/var(--tw-bg-opacity))!important;padding:1rem .25rem;font-family:Roboto,Menlo,Consolas,Ubuntu Mono,Roboto Mono,DejaVu Sans Mono,monospace;;font-size:.875rem}@media (min-width:40rem){main :is(.source-code)>pre{padding:2rem .75rem;font-size:1rem}}main .realm-view>pre a:hover{text-decoration-line:none}main :is(.realm-view,.source-code)>pre .chroma-ln:target{background-color:transparent!important}main :is(.realm-view,.source-code)>pre .chroma-line:has(.chroma-ln:target),main :is(.realm-view,.source-code)>pre .chroma-line:has(.chroma-ln:target) .chroma-cl,main :is(.realm-view,.source-code)>pre .chroma-line:has(.chroma-lnlinks:hover),main :is(.realm-view,.source-code)>pre .chroma-line:has(.chroma-lnlinks:hover) .chroma-cl{border-radius:.375rem;--tw-bg-opacity:1!important;background-color:rgb(226 226 226/var(--tw-bg-opacity))!important}main :is(.realm-view,.source-code)>pre .chroma-ln{scroll-margin-top:6rem}.dev-mode .toc-expend-btn{cursor:pointer;border-width:1px;--tw-bg-opacity:1;background-color:rgb(226 226 226/var(--tw-bg-opacity))}.dev-mode .toc-expend-btn:hover{--tw-bg-opacity:1;background-color:rgb(240 240 240/var(--tw-bg-opacity))}@media (min-width:51.25rem){.dev-mode .toc-expend-btn{border-style:none;background-color:transparent}}.dev-mode #sidebar-summary{--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity))}@media (min-width:51.25rem){.dev-mode #sidebar-summary{background-color:transparent}}.dev-mode .toc-nav{font-family:Roboto,Menlo,Consolas,Ubuntu Mono,Roboto Mono,DejaVu Sans Mono,monospace;}.invisible{visibility:hidden}.fixed{position:fixed}.absolute{position:absolute}.relative{position:relative}.sticky{position:sticky}.inset-0{inset:0}.bottom-1{bottom:.25rem}.left-0{left:0}.right-0{right:0}.right-2{right:.5rem}.right-3{right:.75rem}.right-px{right:1px}.top-0{top:0}.top-1\/2{top:50%}.top-14{top:3.5rem}.top-2{top:.5rem}.top-px{top:1px}.z-1{z-index:1}.z-max{z-index:9999}.col-span-1{grid-column:span 1/span 1}.col-span-10{grid-column:span 10/span 10}.col-span-3{grid-column:span 3/span 3}.col-span-7{grid-column:span 7/span 7}.row-span-1{grid-row:span 1/span 1}.mx-auto{margin-left:auto;margin-right:auto}.mb-1{margin-bottom:.25rem}.mb-2{margin-bottom:.5rem}.mb-3{margin-bottom:.75rem}.mb-4{margin-bottom:1rem}.mb-8{margin-bottom:2rem}.mr-10{margin-right:2.5rem}.mt-10{margin-top:2.5rem}.mt-2{margin-top:.5rem}.mt-4{margin-top:1rem}.mt-6{margin-top:1.5rem}.line-clamp-2{overflow:hidden;display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2}.block{display:block}.inline-block{display:inline-block}.inline{display:inline}.flex{display:flex}.grid{display:grid}.hidden{display:none}.h-10{height:2.5rem}.h-3{height:.75rem}.h-4{height:1rem}.h-5{height:1.25rem}.h-6{height:1.5rem}.h-\[calc\(100\%-2px\)\]{height:calc(100% - 2px)}.h-full{height:100%}.max-h-screen{max-height:100vh}.min-h-full{min-height:100%}.min-h-screen{min-height:100vh}.w-10{width:2.5rem}.w-3{width:.75rem}.w-4{width:1rem}.w-5{width:1.25rem}.w-72{width:18rem}.w-full{width:100%}.min-w-2{min-width:.5rem}.min-w-48{min-width:12rem}.max-w-screen-max{max-width:98.75rem}.shrink-0{flex-shrink:0}.grow-\[2\]{flex-grow:2}.-translate-x-full{--tw-translate-x:-100%}.-translate-x-full,.-translate-y-1\/2{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.-translate-y-1\/2{--tw-translate-y:-50%}.cursor-pointer{cursor:pointer}.list-none{list-style-type:none}.appearance-none{-webkit-appearance:none;-moz-appearance:none;appearance:none}.grid-flow-dense{grid-auto-flow:dense}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-cols-10{grid-template-columns:repeat(10,minmax(0,1fr))}.flex-col{flex-direction:column}.items-start{align-items:flex-start}.items-center{align-items:center}.items-stretch{align-items:stretch}.justify-start{justify-content:flex-start}.justify-end{justify-content:flex-end}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-0\.5{gap:.125rem}.gap-1{gap:.25rem}.gap-1\.5{gap:.375rem}.gap-2{gap:.5rem}.gap-3{gap:.75rem}.gap-4{gap:1rem}.gap-8{gap:2rem}.gap-x-20{-moz-column-gap:5rem;column-gap:5rem}.gap-x-3{-moz-column-gap:.75rem;column-gap:.75rem}.gap-y-2{row-gap:.5rem}.space-y-2>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.5rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.5rem*var(--tw-space-y-reverse))}.overflow-hidden{overflow:hidden}.overflow-scroll{overflow:scroll}.whitespace-nowrap{white-space:nowrap}.whitespace-pre-wrap{white-space:pre-wrap}.rounded{border-radius:.375rem}.rounded-sm{border-radius:.25rem}.rounded-r{border-top-right-radius:.375rem;border-bottom-right-radius:.375rem}.border{border-width:1px}.border-b{border-bottom-width:1px}.border-l{border-left-width:1px}.border-t{border-top-width:1px}.border-gray-100{--tw-border-opacity:1;border-color:rgb(226 226 226/var(--tw-border-opacity))}.bg-gray-100{--tw-bg-opacity:1;background-color:rgb(226 226 226/var(--tw-bg-opacity))}.bg-gray-300{--tw-bg-opacity:1;background-color:rgb(153 153 153/var(--tw-bg-opacity))}.bg-gray-50{--tw-bg-opacity:1;background-color:rgb(240 240 240/var(--tw-bg-opacity))}.bg-light{--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity))}.bg-transparent{background-color:transparent}.p-1\.5{padding:.375rem}.p-2{padding:.5rem}.p-4{padding:1rem}.px-1{padding-left:.25rem;padding-right:.25rem}.px-10{padding-left:2.5rem;padding-right:2.5rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-4{padding-left:1rem;padding-right:1rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.py-1\.5{padding-top:.375rem;padding-bottom:.375rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.py-px{padding-top:1px;padding-bottom:1px}.pb-24{padding-bottom:6rem}.pb-3{padding-bottom:.75rem}.pb-4{padding-bottom:1rem}.pb-6{padding-bottom:1.5rem}.pb-8{padding-bottom:2rem}.pl-4{padding-left:1rem}.pr-10{padding-right:2.5rem}.pt-0\.5{padding-top:.125rem}.pt-2{padding-top:.5rem}.font-mono{font-family:Roboto,Menlo,Consolas,Ubuntu Mono,Roboto Mono,DejaVu Sans Mono,monospace;}.text-100{font-size:.875rem}.text-200{font-size:1rem}.text-50{font-size:.75rem}.text-600{font-size:1.5rem}.font-bold{font-weight:700}.font-medium{font-weight:500}.font-normal{font-weight:400}.font-semibold{font-weight:600}.capitalize{text-transform:capitalize}.leading-tight{line-height:1.25}.text-gray-300{--tw-text-opacity:1;color:rgb(153 153 153/var(--tw-text-opacity))}.text-gray-400{--tw-text-opacity:1;color:rgb(124 124 124/var(--tw-text-opacity))}.text-gray-600{--tw-text-opacity:1;color:rgb(84 89 93/var(--tw-text-opacity))}.text-gray-800{--tw-text-opacity:1;color:rgb(19 19 19/var(--tw-text-opacity))}.text-gray-900{--tw-text-opacity:1;color:rgb(8 8 9/var(--tw-text-opacity))}.text-green-600{--tw-text-opacity:1;color:rgb(34 108 87/var(--tw-text-opacity))}.text-light{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}.opacity-0{opacity:0}.outline-none{outline:2px solid transparent;outline-offset:2px}.text-stroke{-webkit-text-stroke:currentColor;-webkit-text-stroke-width:.6px}.no-scrollbar::-webkit-scrollbar{display:none}.no-scrollbar{-ms-overflow-style:none;scrollbar-width:none}.\*\:pl-0>*{padding-left:0}.before\:px-\[0\.18rem\]:before{content:var(--tw-content);padding-left:.18rem;padding-right:.18rem}.before\:text-gray-300:before{content:var(--tw-content);--tw-text-opacity:1;color:rgb(153 153 153/var(--tw-text-opacity))}.before\:content-\[\'\/\'\]:before{--tw-content:"/";content:var(--tw-content)}.before\:content-\[\'\:\'\]:before{--tw-content:":";content:var(--tw-content)}.before\:content-\[\'open\'\]:before{--tw-content:"open";content:var(--tw-content)}.after\:pointer-events-none:after{content:var(--tw-content);pointer-events:none}.after\:absolute:after{content:var(--tw-content);position:absolute}.after\:bottom-0:after{content:var(--tw-content);bottom:0}.after\:left-0:after{content:var(--tw-content);left:0}.after\:top-0:after{content:var(--tw-content);top:0}.after\:block:after{content:var(--tw-content);display:block}.after\:h-1:after{content:var(--tw-content);height:.25rem}.after\:h-full:after{content:var(--tw-content);height:100%}.after\:w-full:after{content:var(--tw-content);width:100%}.after\:rounded-t-sm:after{content:var(--tw-content);border-top-left-radius:.25rem;border-top-right-radius:.25rem}.after\:bg-gray-100:after{content:var(--tw-content);--tw-bg-opacity:1;background-color:rgb(226 226 226/var(--tw-bg-opacity))}.after\:bg-green-600:after{content:var(--tw-content);--tw-bg-opacity:1;background-color:rgb(34 108 87/var(--tw-bg-opacity))}.first\:mt-8:first-child{margin-top:2rem}.first\:border-t:first-child{border-top-width:1px}.hover\:border-gray-300:hover{--tw-border-opacity:1;border-color:rgb(153 153 153/var(--tw-border-opacity))}.hover\:bg-gray-100:hover{--tw-bg-opacity:1;background-color:rgb(226 226 226/var(--tw-bg-opacity))}.hover\:bg-gray-50:hover{--tw-bg-opacity:1;background-color:rgb(240 240 240/var(--tw-bg-opacity))}.hover\:bg-green-600:hover{--tw-bg-opacity:1;background-color:rgb(34 108 87/var(--tw-bg-opacity))}.hover\:text-gray-600:hover{--tw-text-opacity:1;color:rgb(84 89 93/var(--tw-text-opacity))}.hover\:text-gray-900:hover{--tw-text-opacity:1;color:rgb(8 8 9/var(--tw-text-opacity))}.hover\:text-green-600:hover{--tw-text-opacity:1;color:rgb(34 108 87/var(--tw-text-opacity))}.hover\:text-light:hover{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}.hover\:underline:hover{text-decoration-line:underline}.focus\:border-gray-300:focus{--tw-border-opacity:1;border-color:rgb(153 153 153/var(--tw-border-opacity))}.focus\:border-l-gray-300:focus{--tw-border-opacity:1;border-left-color:rgb(153 153 153/var(--tw-border-opacity))}.group:hover .group-hover\:border-gray-300{--tw-border-opacity:1;border-color:rgb(153 153 153/var(--tw-border-opacity))}.group:hover .group-hover\:border-l-gray-300{--tw-border-opacity:1;border-left-color:rgb(153 153 153/var(--tw-border-opacity))}.group.is-active .group-\[\.is-active\]\:text-green-600{--tw-text-opacity:1;color:rgb(34 108 87/var(--tw-text-opacity))}.peer:checked~.peer-checked\:visible{visibility:visible}.peer:checked~.peer-checked\:opacity-100{opacity:1}.peer:checked~.peer-checked\:before\:content-\[\'close\'\]:before{--tw-content:"close";content:var(--tw-content)}.peer:focus-within~.peer-focus-within\:hidden{display:none}.has-\[ul\:empty\]\:hidden:has(ul:empty){display:none}.has-\[\:focus-within\]\:border-gray-300:has(:focus-within){--tw-border-opacity:1;border-color:rgb(153 153 153/var(--tw-border-opacity))}.has-\[\:focus\]\:border-gray-300:has(:focus){--tw-border-opacity:1;border-color:rgb(153 153 153/var(--tw-border-opacity))}@media (min-width:30rem){.sm\:gap-6{gap:1.5rem}}@media (min-width:40rem){.md\:col-span-3{grid-column:span 3/span 3}.md\:mb-0{margin-bottom:0}.md\:flex{display:flex}.md\:h-4{height:1rem}.md\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.md\:flex-row{flex-direction:row}.md\:items-center{align-items:center}.md\:gap-x-8{-moz-column-gap:2rem;column-gap:2rem}.md\:px-10{padding-left:2.5rem;padding-right:2.5rem}.md\:pb-0{padding-bottom:0}.md\:pr-8{padding-right:2rem}}@media (min-width:51.25rem){.lg\:order-2{order:2}.lg\:col-span-3{grid-column:span 3/span 3}.lg\:col-span-7{grid-column:span 7/span 7}.lg\:row-span-2{grid-row:span 2/span 2}.lg\:row-start-1{grid-row-start:1}.lg\:mb-4{margin-bottom:1rem}.lg\:mt-0{margin-top:0}.lg\:mt-10{margin-top:2.5rem}.lg\:block{display:block}.lg\:hidden{display:none}.lg\:grid-cols-10{grid-template-columns:repeat(10,minmax(0,1fr))}.lg\:flex-row{flex-direction:row}.lg\:justify-start{justify-content:flex-start}.lg\:justify-between{justify-content:space-between}.lg\:gap-x-20{-moz-column-gap:5rem;column-gap:5rem}.lg\:border-none{border-style:none}.lg\:bg-transparent{background-color:transparent}.lg\:p-0{padding:0}.lg\:px-0{padding-left:0;padding-right:0}.lg\:px-2{padding-left:.5rem;padding-right:.5rem}.lg\:py-1\.5{padding-top:.375rem;padding-bottom:.375rem}.lg\:pb-28{padding-bottom:7rem}.lg\:pt-2{padding-top:.5rem}.lg\:text-200{font-size:1rem}.lg\:font-semibold{font-weight:600}.lg\:first\:mt-0:first-child{margin-top:0}.lg\:hover\:bg-transparent:hover{background-color:transparent}}@media (min-width:63.75rem){.xl\:inline{display:inline}.xl\:hidden{display:none}.xl\:grid-cols-10{grid-template-columns:repeat(10,minmax(0,1fr))}.xl\:flex-row{flex-direction:row}.xl\:items-center{align-items:center}.xl\:gap-20{gap:5rem}.xl\:gap-6{gap:1.5rem}.xl\:pt-0{padding-top:0}}@media (min-width:85.375rem){.xxl\:inline-block{display:inline-block}.xxl\:h-4{height:1rem}.xxl\:w-4{width:1rem}.xxl\:gap-20{gap:5rem}.xxl\:gap-x-32{-moz-column-gap:8rem;column-gap:8rem}.xxl\:pr-1{padding-right:.25rem}} \ No newline at end of file From 29c3ee60b1d4cacaf19c2693589dc0075bcb0b44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jer=C3=B3nimo=20Albi?= Date: Thu, 6 Feb 2025 17:27:21 +0100 Subject: [PATCH 128/143] chore(examples): change AVL pager to expect a read only tree (#3673) Using a read only tree now that there is an interface for it would make sense sense for the pager --- examples/gno.land/p/demo/avl/pager/pager.gno | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/gno.land/p/demo/avl/pager/pager.gno b/examples/gno.land/p/demo/avl/pager/pager.gno index f5f909a473d..6a77ba3eb6f 100644 --- a/examples/gno.land/p/demo/avl/pager/pager.gno +++ b/examples/gno.land/p/demo/avl/pager/pager.gno @@ -5,13 +5,13 @@ import ( "net/url" "strconv" - "gno.land/p/demo/avl" + "gno.land/p/demo/avl/rotree" "gno.land/p/demo/ufmt" ) // Pager is a struct that holds the AVL tree and pagination parameters. type Pager struct { - Tree avl.ITree + Tree rotree.IReadOnlyTree PageQueryParam string SizeQueryParam string DefaultPageSize int @@ -37,7 +37,7 @@ type Item struct { } // NewPager creates a new Pager with default values. -func NewPager(tree avl.ITree, defaultPageSize int, reversed bool) *Pager { +func NewPager(tree rotree.IReadOnlyTree, defaultPageSize int, reversed bool) *Pager { return &Pager{ Tree: tree, PageQueryParam: "page", From ce6a4aa5e8935b6dd8befc22f40d1f6a3bd628a9 Mon Sep 17 00:00:00 2001 From: ltzmaxwell Date: Fri, 7 Feb 2025 01:20:51 +0800 Subject: [PATCH 129/143] fix(gnovm): correct filetest directive behavior (#3697) The test result is misleading. Without an output directive set, the current logic does not provide a prompt even when actual output occurs. ```go package main func main() { println("ok") } // Error: // panic xxx ``` ## before fix: === RUN TestFiles === PAUSE TestFiles === CONT TestFiles === RUN TestFiles/a111.gno --- PASS: TestFiles (0.14s) --- PASS: TestFiles/a111.gno (0.01s) PASS ok command-line-arguments 1.619s ## after fix: === RUN TestFiles === PAUSE TestFiles === CONT TestFiles === RUN TestFiles/a111.gno files_test.go:92: unexpected output: ok --- FAIL: TestFiles (0.13s) --- FAIL: TestFiles/a111.gno (0.01s) FAIL FAIL command-line-arguments 1.479s FAIL --------- Co-authored-by: Morgan --- gnovm/pkg/test/filetest.go | 5 +++++ gnovm/tests/files/addressable_5.gno | 3 +++ gnovm/tests/files/type_alias.gno | 5 +++-- gnovm/tests/files/types/eql_0f49.gno | 2 ++ 4 files changed, 13 insertions(+), 2 deletions(-) diff --git a/gnovm/pkg/test/filetest.go b/gnovm/pkg/test/filetest.go index 1934f429568..c24c014a9ba 100644 --- a/gnovm/pkg/test/filetest.go +++ b/gnovm/pkg/test/filetest.go @@ -103,6 +103,11 @@ func (opts *TestOptions) runFiletest(filename string, source []byte) (string, er // The Error directive (and many others) will have one trailing newline, // which is not in the output - so add it there. match(errDirective, result.Error+"\n") + } else if result.Output != "" { + outputDirective := dirs.First(DirectiveOutput) + if outputDirective == nil { + return "", fmt.Errorf("unexpected output:\n%s", result.Output) + } } else { err = m.CheckEmpty() if err != nil { diff --git a/gnovm/tests/files/addressable_5.gno b/gnovm/tests/files/addressable_5.gno index 800cc744458..fa39ef42841 100644 --- a/gnovm/tests/files/addressable_5.gno +++ b/gnovm/tests/files/addressable_5.gno @@ -9,3 +9,6 @@ func main() { le := &binary.LittleEndian println(&le.AppendUint16(b, 0)[0]) } + +// Output: +// &(0 uint8) diff --git a/gnovm/tests/files/type_alias.gno b/gnovm/tests/files/type_alias.gno index e95c54126ec..09918f6d591 100644 --- a/gnovm/tests/files/type_alias.gno +++ b/gnovm/tests/files/type_alias.gno @@ -6,7 +6,8 @@ import "gno.land/p/demo/uassert" type TestingT = uassert.TestingT func main() { - println(TestingT) + println("ok") } -// No need for output; not panicking is passing. +// Output: +// ok diff --git a/gnovm/tests/files/types/eql_0f49.gno b/gnovm/tests/files/types/eql_0f49.gno index b5a4bf4ed05..b4d6f7e3972 100644 --- a/gnovm/tests/files/types/eql_0f49.gno +++ b/gnovm/tests/files/types/eql_0f49.gno @@ -14,6 +14,8 @@ func main() { } +// Output: +// true // true // true // true From dcd3834890d3f68543f423614c67f9396017c352 Mon Sep 17 00:00:00 2001 From: SunSpirit <48086732+sunspirit99@users.noreply.github.com> Date: Fri, 7 Feb 2025 00:58:08 +0700 Subject: [PATCH 130/143] feat(examples): Implement markdown package (#2912) From https://github.com/gnolang/gno/issues/2753 I keep this PR open to see if I am on the right approach. If it's suitable, I'll investigate to make further improvements and provide implementation examples in the `Render()` functions of some current demo realms to demonstrate the use of this package ![Screenshot from 2024-10-23 19-28-43](https://github.com/user-attachments/assets/a321f13a-01c7-432f-9f06-b02b5e86951a) cc @moul
Contributors' checklist... - [ ] Added new tests, or not needed, or not feasible - [ ] Provided an example (e.g. screenshot) to aid review or the PR is self-explanatory - [ ] Updated the official documentation or not needed - [ ] No breaking changes were made, or a `BREAKING CHANGE: xxx` message was included in the description - [ ] Added references to related issues and PRs - [ ] Provided any useful hints for running manual tests - [ ] Added new benchmarks to [generated graphs](https://gnoland.github.io/benchmarks), if any. More info [here](https://github.com/gnolang/gno/blob/master/.benchmarks/README.md).
--------- Co-authored-by: Leon Hudak <33522493+leohhhn@users.noreply.github.com> Co-authored-by: leohhhn --- examples/gno.land/p/sunspirit/md/gno.mod | 1 + examples/gno.land/p/sunspirit/md/md.gno | 179 ++++++++++++++++++ examples/gno.land/p/sunspirit/md/md_test.gno | 175 +++++++++++++++++ examples/gno.land/p/sunspirit/table/gno.mod | 1 + examples/gno.land/p/sunspirit/table/table.gno | 106 +++++++++++ .../gno.land/p/sunspirit/table/table_test.gno | 146 ++++++++++++++ examples/gno.land/r/sunspirit/home/gno.mod | 1 + examples/gno.land/r/sunspirit/home/home.gno | 34 ++++ examples/gno.land/r/sunspirit/md/gno.mod | 1 + examples/gno.land/r/sunspirit/md/md.gno | 158 ++++++++++++++++ examples/gno.land/r/sunspirit/md/md_test.gno | 13 ++ 11 files changed, 815 insertions(+) create mode 100644 examples/gno.land/p/sunspirit/md/gno.mod create mode 100644 examples/gno.land/p/sunspirit/md/md.gno create mode 100644 examples/gno.land/p/sunspirit/md/md_test.gno create mode 100644 examples/gno.land/p/sunspirit/table/gno.mod create mode 100644 examples/gno.land/p/sunspirit/table/table.gno create mode 100644 examples/gno.land/p/sunspirit/table/table_test.gno create mode 100644 examples/gno.land/r/sunspirit/home/gno.mod create mode 100644 examples/gno.land/r/sunspirit/home/home.gno create mode 100644 examples/gno.land/r/sunspirit/md/gno.mod create mode 100644 examples/gno.land/r/sunspirit/md/md.gno create mode 100644 examples/gno.land/r/sunspirit/md/md_test.gno diff --git a/examples/gno.land/p/sunspirit/md/gno.mod b/examples/gno.land/p/sunspirit/md/gno.mod new file mode 100644 index 00000000000..caee634f66f --- /dev/null +++ b/examples/gno.land/p/sunspirit/md/gno.mod @@ -0,0 +1 @@ +module gno.land/p/sunspirit/md diff --git a/examples/gno.land/p/sunspirit/md/md.gno b/examples/gno.land/p/sunspirit/md/md.gno new file mode 100644 index 00000000000..965373bee85 --- /dev/null +++ b/examples/gno.land/p/sunspirit/md/md.gno @@ -0,0 +1,179 @@ +package md + +import ( + "strings" + + "gno.land/p/demo/ufmt" +) + +// Builder helps to build a Markdown string from individual elements +type Builder struct { + elements []string +} + +// NewBuilder creates a new Builder instance +func NewBuilder() *Builder { + return &Builder{} +} + +// Add adds a Markdown element to the builder +func (m *Builder) Add(md ...string) *Builder { + m.elements = append(m.elements, md...) + return m +} + +// Render returns the final Markdown string joined with the specified separator +func (m *Builder) Render(separator string) string { + return strings.Join(m.elements, separator) +} + +// Bold returns bold text for markdown +func Bold(text string) string { + return ufmt.Sprintf("**%s**", text) +} + +// Italic returns italicized text for markdown +func Italic(text string) string { + return ufmt.Sprintf("*%s*", text) +} + +// Strikethrough returns strikethrough text for markdown +func Strikethrough(text string) string { + return ufmt.Sprintf("~~%s~~", text) +} + +// H1 returns a level 1 header for markdown +func H1(text string) string { + return ufmt.Sprintf("# %s\n", text) +} + +// H2 returns a level 2 header for markdown +func H2(text string) string { + return ufmt.Sprintf("## %s\n", text) +} + +// H3 returns a level 3 header for markdown +func H3(text string) string { + return ufmt.Sprintf("### %s\n", text) +} + +// H4 returns a level 4 header for markdown +func H4(text string) string { + return ufmt.Sprintf("#### %s\n", text) +} + +// H5 returns a level 5 header for markdown +func H5(text string) string { + return ufmt.Sprintf("##### %s\n", text) +} + +// H6 returns a level 6 header for markdown +func H6(text string) string { + return ufmt.Sprintf("###### %s\n", text) +} + +// BulletList returns an bullet list for markdown +func BulletList(items []string) string { + var sb strings.Builder + for _, item := range items { + sb.WriteString(ufmt.Sprintf("- %s\n", item)) + } + return sb.String() +} + +// OrderedList returns an ordered list for markdown +func OrderedList(items []string) string { + var sb strings.Builder + for i, item := range items { + sb.WriteString(ufmt.Sprintf("%d. %s\n", i+1, item)) + } + return sb.String() +} + +// TodoList returns a list of todo items with checkboxes for markdown +func TodoList(items []string, done []bool) string { + var sb strings.Builder + + for i, item := range items { + checkbox := " " + if done[i] { + checkbox = "x" + } + sb.WriteString(ufmt.Sprintf("- [%s] %s\n", checkbox, item)) + } + return sb.String() +} + +// Blockquote returns a blockquote for markdown +func Blockquote(text string) string { + lines := strings.Split(text, "\n") + var sb strings.Builder + for _, line := range lines { + sb.WriteString(ufmt.Sprintf("> %s\n", line)) + } + + return sb.String() +} + +// InlineCode returns inline code for markdown +func InlineCode(code string) string { + return ufmt.Sprintf("`%s`", code) +} + +// CodeBlock creates a markdown code block +func CodeBlock(content string) string { + return ufmt.Sprintf("```\n%s\n```", content) +} + +// LanguageCodeBlock creates a markdown code block with language-specific syntax highlighting +func LanguageCodeBlock(language, content string) string { + return ufmt.Sprintf("```%s\n%s\n```", language, content) +} + +// LineBreak returns the specified number of line breaks for markdown +func LineBreak(count uint) string { + if count > 0 { + return strings.Repeat("\n", int(count)+1) + } + return "" +} + +// HorizontalRule returns a horizontal rule for markdown +func HorizontalRule() string { + return "---\n" +} + +// Link returns a hyperlink for markdown +func Link(text, url string) string { + return ufmt.Sprintf("[%s](%s)", text, url) +} + +// Image returns an image for markdown +func Image(altText, url string) string { + return ufmt.Sprintf("![%s](%s)", altText, url) +} + +// Footnote returns a footnote for markdown +func Footnote(reference, text string) string { + return ufmt.Sprintf("[%s]: %s", reference, text) +} + +// Paragraph wraps the given text in a Markdown paragraph +func Paragraph(content string) string { + return ufmt.Sprintf("%s\n", content) +} + +// MdTable is an interface for table types that can be converted to Markdown format +type MdTable interface { + String() string +} + +// Table takes any MdTable implementation and returns its markdown representation +func Table(table MdTable) string { + return table.String() +} + +// EscapeMarkdown escapes special markdown characters in a string +func EscapeMarkdown(text string) string { + return ufmt.Sprintf("``%s``", text) +} diff --git a/examples/gno.land/p/sunspirit/md/md_test.gno b/examples/gno.land/p/sunspirit/md/md_test.gno new file mode 100644 index 00000000000..529cc2535bb --- /dev/null +++ b/examples/gno.land/p/sunspirit/md/md_test.gno @@ -0,0 +1,175 @@ +package md + +import ( + "testing" + + "gno.land/p/demo/uassert" + "gno.land/p/sunspirit/table" +) + +func TestNewBuilder(t *testing.T) { + mdBuilder := NewBuilder() + + uassert.Equal(t, len(mdBuilder.elements), 0, "Expected 0 elements") +} + +func TestAdd(t *testing.T) { + mdBuilder := NewBuilder() + + header := H1("Hi") + body := Paragraph("This is a test") + + mdBuilder.Add(header, body) + + uassert.Equal(t, len(mdBuilder.elements), 2, "Expected 2 element") + uassert.Equal(t, mdBuilder.elements[0], header, "Expected element %s, got %s", header, mdBuilder.elements[0]) + uassert.Equal(t, mdBuilder.elements[1], body, "Expected element %s, got %s", body, mdBuilder.elements[1]) +} + +func TestRender(t *testing.T) { + mdBuilder := NewBuilder() + + header := H1("Hello") + body := Paragraph("This is a test") + + seperator := "\n" + expected := header + seperator + body + + output := mdBuilder.Add(header, body).Render(seperator) + + uassert.Equal(t, output, expected, "Expected rendered string %s, got %s", expected, output) +} + +func Test_Bold(t *testing.T) { + uassert.Equal(t, Bold("Hello"), "**Hello**") +} + +func Test_Italic(t *testing.T) { + uassert.Equal(t, Italic("Hello"), "*Hello*") +} + +func Test_Strikethrough(t *testing.T) { + uassert.Equal(t, Strikethrough("Hello"), "~~Hello~~") +} + +func Test_H1(t *testing.T) { + uassert.Equal(t, H1("Header 1"), "# Header 1\n") +} + +func Test_H2(t *testing.T) { + uassert.Equal(t, H2("Header 2"), "## Header 2\n") +} + +func Test_H3(t *testing.T) { + uassert.Equal(t, H3("Header 3"), "### Header 3\n") +} + +func Test_H4(t *testing.T) { + uassert.Equal(t, H4("Header 4"), "#### Header 4\n") +} + +func Test_H5(t *testing.T) { + uassert.Equal(t, H5("Header 5"), "##### Header 5\n") +} + +func Test_H6(t *testing.T) { + uassert.Equal(t, H6("Header 6"), "###### Header 6\n") +} + +func Test_BulletList(t *testing.T) { + items := []string{"Item 1", "Item 2", "Item 3"} + result := BulletList(items) + expected := "- Item 1\n- Item 2\n- Item 3\n" + uassert.Equal(t, result, expected) +} + +func Test_OrderedList(t *testing.T) { + items := []string{"Item 1", "Item 2", "Item 3"} + result := OrderedList(items) + expected := "1. Item 1\n2. Item 2\n3. Item 3\n" + uassert.Equal(t, result, expected) +} + +func Test_TodoList(t *testing.T) { + items := []string{"Task 1", "Task 2"} + done := []bool{true, false} + result := TodoList(items, done) + expected := "- [x] Task 1\n- [ ] Task 2\n" + uassert.Equal(t, result, expected) +} + +func Test_Blockquote(t *testing.T) { + text := "This is a blockquote.\nIt has multiple lines." + result := Blockquote(text) + expected := "> This is a blockquote.\n> It has multiple lines.\n" + uassert.Equal(t, result, expected) +} + +func Test_InlineCode(t *testing.T) { + result := InlineCode("code") + uassert.Equal(t, result, "`code`") +} + +func Test_LanguageCodeBlock(t *testing.T) { + result := LanguageCodeBlock("python", "print('Hello')") + expected := "```python\nprint('Hello')\n```" + uassert.Equal(t, result, expected) +} + +func Test_CodeBlock(t *testing.T) { + result := CodeBlock("print('Hello')") + expected := "```\nprint('Hello')\n```" + uassert.Equal(t, result, expected) +} + +func Test_LineBreak(t *testing.T) { + result := LineBreak(2) + expected := "\n\n\n" + uassert.Equal(t, result, expected) + + result = LineBreak(0) + expected = "" + uassert.Equal(t, result, expected) +} + +func Test_HorizontalRule(t *testing.T) { + result := HorizontalRule() + uassert.Equal(t, result, "---\n") +} + +func Test_Link(t *testing.T) { + result := Link("Google", "http://google.com") + uassert.Equal(t, result, "[Google](http://google.com)") +} + +func Test_Image(t *testing.T) { + result := Image("Alt text", "http://image.url") + uassert.Equal(t, result, "![Alt text](http://image.url)") +} + +func Test_Footnote(t *testing.T) { + result := Footnote("1", "This is a footnote.") + uassert.Equal(t, result, "[1]: This is a footnote.") +} + +func Test_Paragraph(t *testing.T) { + result := Paragraph("This is a paragraph.") + uassert.Equal(t, result, "This is a paragraph.\n") +} + +func Test_Table(t *testing.T) { + tb, err := table.New([]string{"Header1", "Header2"}, [][]string{ + {"Row1Col1", "Row1Col2"}, + {"Row2Col1", "Row2Col2"}, + }) + uassert.NoError(t, err) + + result := Table(tb) + expected := "| Header1 | Header2 |\n| ---|---|\n| Row1Col1 | Row1Col2 |\n| Row2Col1 | Row2Col2 |\n" + uassert.Equal(t, result, expected) +} + +func Test_EscapeMarkdown(t *testing.T) { + result := EscapeMarkdown("- This is `code`") + uassert.Equal(t, result, "``- This is `code```") +} diff --git a/examples/gno.land/p/sunspirit/table/gno.mod b/examples/gno.land/p/sunspirit/table/gno.mod new file mode 100644 index 00000000000..1814c50b25d --- /dev/null +++ b/examples/gno.land/p/sunspirit/table/gno.mod @@ -0,0 +1 @@ +module gno.land/p/sunspirit/table diff --git a/examples/gno.land/p/sunspirit/table/table.gno b/examples/gno.land/p/sunspirit/table/table.gno new file mode 100644 index 00000000000..8c27516c962 --- /dev/null +++ b/examples/gno.land/p/sunspirit/table/table.gno @@ -0,0 +1,106 @@ +package table + +import ( + "strings" + + "gno.land/p/demo/ufmt" +) + +// Table defines the structure for a markdown table +type Table struct { + header []string + rows [][]string +} + +// Validate checks if the number of columns in each row matches the number of columns in the header +func (t *Table) Validate() error { + numCols := len(t.header) + for _, row := range t.rows { + if len(row) != numCols { + return ufmt.Errorf("row %v does not match header length %d", row, numCols) + } + } + return nil +} + +// New creates a new Table instance, ensuring the header and rows match in size +func New(header []string, rows [][]string) (*Table, error) { + t := &Table{ + header: header, + rows: rows, + } + + if err := t.Validate(); err != nil { + return nil, err + } + + return t, nil +} + +// Table returns a markdown string for the given Table +func (t *Table) String() string { + if err := t.Validate(); err != nil { + panic(err) + } + + var sb strings.Builder + + sb.WriteString("| " + strings.Join(t.header, " | ") + " |\n") + sb.WriteString("| " + strings.Repeat("---|", len(t.header)) + "\n") + + for _, row := range t.rows { + sb.WriteString("| " + strings.Join(row, " | ") + " |\n") + } + + return sb.String() +} + +// AddRow adds a new row to the table +func (t *Table) AddRow(row []string) error { + if len(row) != len(t.header) { + return ufmt.Errorf("row %v does not match header length %d", row, len(t.header)) + } + t.rows = append(t.rows, row) + return nil +} + +// AddColumn adds a new column to the table with the specified values +func (t *Table) AddColumn(header string, values []string) error { + if len(values) != len(t.rows) { + return ufmt.Errorf("values length %d does not match the number of rows %d", len(values), len(t.rows)) + } + + // Add the new header + t.header = append(t.header, header) + + // Add the new column values to each row + for i, value := range values { + t.rows[i] = append(t.rows[i], value) + } + return nil +} + +// RemoveRow removes a row from the table by its index +func (t *Table) RemoveRow(index int) error { + if index < 0 || index >= len(t.rows) { + return ufmt.Errorf("index %d is out of range", index) + } + t.rows = append(t.rows[:index], t.rows[index+1:]...) + return nil +} + +// RemoveColumn removes a column from the table by its index +func (t *Table) RemoveColumn(index int) error { + if index < 0 || index >= len(t.header) { + return ufmt.Errorf("index %d is out of range", index) + } + + // Remove the column from the header + t.header = append(t.header[:index], t.header[index+1:]...) + + // Remove the corresponding column from each row + for i := range t.rows { + t.rows[i] = append(t.rows[i][:index], t.rows[i][index+1:]...) + } + return nil +} diff --git a/examples/gno.land/p/sunspirit/table/table_test.gno b/examples/gno.land/p/sunspirit/table/table_test.gno new file mode 100644 index 00000000000..d4cd56ad0a8 --- /dev/null +++ b/examples/gno.land/p/sunspirit/table/table_test.gno @@ -0,0 +1,146 @@ +package table + +import ( + "testing" + + "gno.land/p/demo/uassert" + "gno.land/p/demo/urequire" +) + +func TestNew(t *testing.T) { + header := []string{"Name", "Age", "Country"} + rows := [][]string{ + {"Alice", "30", "USA"}, + {"Bob", "25", "UK"}, + } + + table, err := New(header, rows) + urequire.NoError(t, err) + + uassert.Equal(t, len(header), len(table.header)) + uassert.Equal(t, len(rows), len(table.rows)) +} + +func Test_AddRow(t *testing.T) { + header := []string{"Name", "Age"} + rows := [][]string{ + {"Alice", "30"}, + {"Bob", "25"}, + } + + table, err := New(header, rows) + urequire.NoError(t, err) + + // Add a valid row + err = table.AddRow([]string{"Charlie", "28"}) + urequire.NoError(t, err) + + expectedRows := [][]string{ + {"Alice", "30"}, + {"Bob", "25"}, + {"Charlie", "28"}, + } + uassert.Equal(t, len(expectedRows), len(table.rows)) + + // Attempt to add a row with a different number of columns + err = table.AddRow([]string{"David"}) + uassert.Error(t, err) +} + +func Test_AddColumn(t *testing.T) { + header := []string{"Name", "Age"} + rows := [][]string{ + {"Alice", "30"}, + {"Bob", "25"}, + } + + table, err := New(header, rows) + urequire.NoError(t, err) + + // Add a valid column + err = table.AddColumn("Country", []string{"USA", "UK"}) + urequire.NoError(t, err) + + expectedHeader := []string{"Name", "Age", "Country"} + expectedRows := [][]string{ + {"Alice", "30", "USA"}, + {"Bob", "25", "UK"}, + } + uassert.Equal(t, len(expectedHeader), len(table.header)) + uassert.Equal(t, len(expectedRows), len(table.rows)) + + // Attempt to add a column with a different number of values + err = table.AddColumn("City", []string{"New York"}) + uassert.Error(t, err) +} + +func Test_RemoveRow(t *testing.T) { + header := []string{"Name", "Age", "Country"} + rows := [][]string{ + {"Alice", "30", "USA"}, + {"Bob", "25", "UK"}, + } + + table, err := New(header, rows) + urequire.NoError(t, err) + + // Remove the first row + err = table.RemoveRow(0) + urequire.NoError(t, err) + + expectedRows := [][]string{ + {"Bob", "25", "UK"}, + } + uassert.Equal(t, len(expectedRows), len(table.rows)) + + // Attempt to remove a row out of range + err = table.RemoveRow(5) + uassert.Error(t, err) +} + +func Test_RemoveColumn(t *testing.T) { + header := []string{"Name", "Age", "Country"} + rows := [][]string{ + {"Alice", "30", "USA"}, + {"Bob", "25", "UK"}, + } + + table, err := New(header, rows) + urequire.NoError(t, err) + + // Remove the second column (Age) + err = table.RemoveColumn(1) + urequire.NoError(t, err) + + expectedHeader := []string{"Name", "Country"} + expectedRows := [][]string{ + {"Alice", "USA"}, + {"Bob", "UK"}, + } + uassert.Equal(t, len(expectedHeader), len(table.header)) + uassert.Equal(t, len(expectedRows), len(table.rows)) + + // Attempt to remove a column out of range + err = table.RemoveColumn(5) + uassert.Error(t, err) +} + +func Test_Validate(t *testing.T) { + header := []string{"Name", "Age", "Country"} + rows := [][]string{ + {"Alice", "30", "USA"}, + {"Bob", "25"}, + } + + table, err := New(header, rows[:1]) + urequire.NoError(t, err) + + // Validate should pass + err = table.Validate() + urequire.NoError(t, err) + + // Add an invalid row and validate again + table.rows = append(table.rows, rows[1]) + err = table.Validate() + uassert.Error(t, err) +} diff --git a/examples/gno.land/r/sunspirit/home/gno.mod b/examples/gno.land/r/sunspirit/home/gno.mod new file mode 100644 index 00000000000..2aea0280fff --- /dev/null +++ b/examples/gno.land/r/sunspirit/home/gno.mod @@ -0,0 +1 @@ +module gno.land/r/sunspirit/home diff --git a/examples/gno.land/r/sunspirit/home/home.gno b/examples/gno.land/r/sunspirit/home/home.gno new file mode 100644 index 00000000000..fbf9709e8d4 --- /dev/null +++ b/examples/gno.land/r/sunspirit/home/home.gno @@ -0,0 +1,34 @@ +package home + +import ( + "strings" + + "gno.land/p/demo/ufmt" + "gno.land/p/sunspirit/md" +) + +func Render(path string) string { + var sb strings.Builder + + sb.WriteString(md.H1("Sunspirit's Home") + md.LineBreak(1)) + + sb.WriteString(md.Paragraph(ufmt.Sprintf( + "Welcome to Sunspirit’s home! This is where I’ll bring %s to Gno.land, crafted with my experience and creativity.", + md.Italic(md.Bold("simple, useful dapps")), + )) + md.LineBreak(1)) + + sb.WriteString(md.Paragraph(ufmt.Sprintf( + "📚 I’ve created a Markdown rendering library at %s. Feel free to use it for your own projects!", + md.Link("gno.land/p/sunspirit/md", "/p/sunspirit/md"), + )) + md.LineBreak(1)) + + sb.WriteString(md.Paragraph("💬 I’d love to hear your feedback to help improve this library!") + md.LineBreak(1)) + + sb.WriteString(md.Paragraph(ufmt.Sprintf( + "🌐 You can check out a demo of this package in action at %s.", + md.Link("gno.land/r/sunspirit/md", "/r/sunspirit/md"), + )) + md.LineBreak(1)) + sb.WriteString(md.HorizontalRule()) + + return sb.String() +} diff --git a/examples/gno.land/r/sunspirit/md/gno.mod b/examples/gno.land/r/sunspirit/md/gno.mod new file mode 100644 index 00000000000..ff3a7c54d96 --- /dev/null +++ b/examples/gno.land/r/sunspirit/md/gno.mod @@ -0,0 +1 @@ +module gno.land/r/sunspirit/md diff --git a/examples/gno.land/r/sunspirit/md/md.gno b/examples/gno.land/r/sunspirit/md/md.gno new file mode 100644 index 00000000000..8c21ea0215c --- /dev/null +++ b/examples/gno.land/r/sunspirit/md/md.gno @@ -0,0 +1,158 @@ +package md + +import ( + "gno.land/p/sunspirit/md" + "gno.land/p/sunspirit/table" +) + +func Render(path string) string { + title := "A simple, flexible, and easy-to-use library for creating markdown documents in gno.land" + + mdBuilder := md.NewBuilder(). + Add(md.H1(md.Italic(md.Bold(title)))). + + // Bold Text section + Add( + md.H3(md.Bold("1. Bold Text")), + md.Paragraph("To make text bold, use the `md.Bold()` function:"), + md.Bold("This is bold text"), + ). + + // Italic Text section + Add( + md.H3(md.Bold("2. Italic Text")), + md.Paragraph("To make text italic, use the `md.Italic()` function:"), + md.Italic("This is italic text"), + ). + + // Strikethrough Text section + Add( + md.H3(md.Bold("3. Strikethrough Text")), + md.Paragraph("To add strikethrough, use the `md.Strikethrough()` function:"), + md.Strikethrough("This text is strikethrough"), + ). + + // Headers section + Add( + md.H3(md.Bold("4. Headers (H1 to H6)")), + md.Paragraph("You can create headers (H1 to H6) using the `md.H1()` to `md.H6()` functions:"), + md.H1("This is a level 1 header"), + md.H2("This is a level 2 header"), + md.H3("This is a level 3 header"), + md.H4("This is a level 4 header"), + md.H5("This is a level 5 header"), + md.H6("This is a level 6 header"), + ). + + // Bullet List section + Add( + md.H3(md.Bold("5. Bullet List")), + md.Paragraph("To create bullet lists, use the `md.BulletList()` function:"), + md.BulletList([]string{"Item 1", "Item 2", "Item 3"}), + ). + + // Ordered List section + Add( + md.H3(md.Bold("6. Ordered List")), + md.Paragraph("To create ordered lists, use the `md.OrderedList()` function:"), + md.OrderedList([]string{"First", "Second", "Third"}), + ). + + // Todo List section + Add( + md.H3(md.Bold("7. Todo List")), + md.Paragraph("You can create a todo list using the `md.TodoList()` function, which supports checkboxes:"), + md.TodoList([]string{"Task 1", "Task 2"}, []bool{true, false}), + ). + + // Blockquote section + Add( + md.H3(md.Bold("8. Blockquote")), + md.Paragraph("To create blockquotes, use the `md.Blockquote()` function:"), + md.Blockquote("This is a blockquote.\nIt can span multiple lines."), + ). + + // Inline Code section + Add( + md.H3(md.Bold("9. Inline Code")), + md.Paragraph("To insert inline code, use the `md.InlineCode()` function:"), + md.InlineCode("fmt.Println() // inline code"), + ). + + // Code Block section + Add( + md.H3(md.Bold("10. Code Block")), + md.Paragraph("For multi-line code blocks, use the `md.CodeBlock()` function:"), + md.CodeBlock("package main\n\nfunc main() {\n\t// Your code here\n}"), + ). + + // Horizontal Rule section + Add( + md.H3(md.Bold("11. Horizontal Rule")), + md.Paragraph("To add a horizontal rule (separator), use the `md.HorizontalRule()` function:"), + md.LineBreak(1), + md.HorizontalRule(), + ). + + // Language-specific Code Block section + Add( + md.H3(md.Bold("12. Language-specific Code Block")), + md.Paragraph("To create language-specific code blocks, use the `md.LanguageCodeBlock()` function:"), + md.LanguageCodeBlock("go", "package main\n\nfunc main() {}"), + ). + + // Hyperlink section + Add( + md.H3(md.Bold("13. Hyperlink")), + md.Paragraph("To create a hyperlink, use the `md.Link()` function:"), + md.Link("Gnoland official docs", "https://docs.gno.land"), + ). + + // Image section + Add( + md.H3(md.Bold("14. Image")), + md.Paragraph("To insert an image, use the `md.Image()` function:"), + md.LineBreak(1), + md.Image("Gnoland Logo", "https://gnolang.github.io/blog/2024-05-21_the-gnome/src/banner.png"), + ). + + // Footnote section + Add( + md.H3(md.Bold("15. Footnote")), + md.Paragraph("To create footnotes, use the `md.Footnote()` function:"), + md.LineBreak(1), + md.Footnote("1", "This is a footnote."), + ). + + // Table section + Add( + md.H3(md.Bold("16. Table")), + md.Paragraph("To create a table, use the `md.Table()` function. Here's an example of a table:"), + ) + + // Create a table using the table package + tb, _ := table.New([]string{"Feature", "Description"}, [][]string{ + {"Bold", "Make text bold using " + md.Bold("double asterisks")}, + {"Italic", "Make text italic using " + md.Italic("single asterisks")}, + {"Strikethrough", "Cross out text using " + md.Strikethrough("double tildes")}, + }) + mdBuilder.Add(md.Table(tb)) + + // Escaping Markdown section + mdBuilder.Add( + md.H3(md.Bold("17. Escaping Markdown")), + md.Paragraph("Sometimes, you need to escape special Markdown characters (like *, _, and `). Use the `md.EscapeMarkdown()` function for this:"), + ) + + // Example of escaping markdown + text := "- Escape special chars like *, _, and ` in markdown" + mdBuilder.Add( + md.H4("Text Without Escape:"), + text, + md.LineBreak(1), + md.H4("Text With Escape:"), + md.EscapeMarkdown(text), + ) + + return mdBuilder.Render(md.LineBreak(1)) +} diff --git a/examples/gno.land/r/sunspirit/md/md_test.gno b/examples/gno.land/r/sunspirit/md/md_test.gno new file mode 100644 index 00000000000..2e1ce9b9931 --- /dev/null +++ b/examples/gno.land/r/sunspirit/md/md_test.gno @@ -0,0 +1,13 @@ +package md + +import ( + "strings" + "testing" +) + +func TestRender(t *testing.T) { + output := Render("") + if !strings.Contains(output, "A simple, flexible, and easy-to-use library for creating markdown documents in gno.land") { + t.Errorf("invalid output") + } +} From de4a405487f521e57434c078efa50129170e04e1 Mon Sep 17 00:00:00 2001 From: 6h057 <15034695+omarsy@users.noreply.github.com> Date: Thu, 6 Feb 2025 19:16:42 +0100 Subject: [PATCH 131/143] fix(rpc): always return array reponses for batch requests (#3678) closes: #3676 Currently, when a batch request contains only a single item, our implementation returns a single response object instead of an array. **What This PR Does:** My changes update the logic so that even if the batch request includes only one item, the endpoint will still return an array containing that single response. --------- Co-authored-by: Nathan Toups <612924+n2p5@users.noreply.github.com> --- tm2/pkg/bft/rpc/lib/server/handlers.go | 15 +++++++++++++- tm2/pkg/bft/rpc/lib/server/handlers_test.go | 23 +++++++-------------- tm2/pkg/bft/rpc/lib/server/http_server.go | 20 +++++++----------- 3 files changed, 30 insertions(+), 28 deletions(-) diff --git a/tm2/pkg/bft/rpc/lib/server/handlers.go b/tm2/pkg/bft/rpc/lib/server/handlers.go index 9e10596a975..b91db806342 100644 --- a/tm2/pkg/bft/rpc/lib/server/handlers.go +++ b/tm2/pkg/bft/rpc/lib/server/handlers.go @@ -145,6 +145,11 @@ func makeJSONRPCHandler(funcMap map[string]*RPCFunc, logger *slog.Logger) http.H requests types.RPCRequests responses types.RPCResponses ) + + // isRPCRequestArray is used to determine if the incoming payload is an array of requests. + // This flag helps decide whether to return an array of responses (for batch requests) or a single response. + isRPCRequestArray := true + if err := json.Unmarshal(b, &requests); err != nil { // next, try to unmarshal as a single request var request types.RPCRequest @@ -153,6 +158,7 @@ func makeJSONRPCHandler(funcMap map[string]*RPCFunc, logger *slog.Logger) http.H return } requests = []types.RPCRequest{request} + isRPCRequestArray = false } for _, request := range requests { @@ -191,9 +197,16 @@ func makeJSONRPCHandler(funcMap map[string]*RPCFunc, logger *slog.Logger) http.H } responses = append(responses, types.NewRPCSuccessResponse(request.ID, result)) } - if len(responses) > 0 { + if len(responses) == 0 { + return + } + + if isRPCRequestArray { WriteRPCResponseArrayHTTP(w, responses) + return } + + WriteRPCResponseHTTP(w, responses[0]) } } diff --git a/tm2/pkg/bft/rpc/lib/server/handlers_test.go b/tm2/pkg/bft/rpc/lib/server/handlers_test.go index f6572be7e0a..dde2cf1e327 100644 --- a/tm2/pkg/bft/rpc/lib/server/handlers_test.go +++ b/tm2/pkg/bft/rpc/lib/server/handlers_test.go @@ -171,6 +171,12 @@ func TestRPCNotificationInBatch(t *testing.T) { ]`, 1, }, + { + `[ + {"jsonrpc": "2.0","method":"c","id":"abc","params":["a","10"]} + ]`, + 1, + }, { `[ {"jsonrpc": "2.0","id": ""}, @@ -198,21 +204,8 @@ func TestRPCNotificationInBatch(t *testing.T) { // try to unmarshal an array first err = json.Unmarshal(blob, &responses) if err != nil { - // if we were actually expecting an array, but got an error - if tt.expectCount > 1 { - t.Errorf("#%d: expected an array, couldn't unmarshal it\nblob: %s", i, blob) - continue - } else { - // we were expecting an error here, so let's unmarshal a single response - var response types.RPCResponse - err = json.Unmarshal(blob, &response) - if err != nil { - t.Errorf("#%d: expected successful parsing of an RPCResponse\nblob: %s", i, blob) - continue - } - // have a single-element result - responses = types.RPCResponses{response} - } + t.Errorf("#%d: expected an array, couldn't unmarshal it\nblob: %s", i, blob) + continue } if tt.expectCount != len(responses) { t.Errorf("#%d: expected %d response(s), but got %d\nblob: %s", i, tt.expectCount, len(responses), blob) diff --git a/tm2/pkg/bft/rpc/lib/server/http_server.go b/tm2/pkg/bft/rpc/lib/server/http_server.go index a4e535160b5..a5cec3d5c81 100644 --- a/tm2/pkg/bft/rpc/lib/server/http_server.go +++ b/tm2/pkg/bft/rpc/lib/server/http_server.go @@ -119,18 +119,14 @@ func WriteRPCResponseHTTP(w http.ResponseWriter, res types.RPCResponse) { // can write arrays of responses for batched request/response interactions via // the JSON RPC. func WriteRPCResponseArrayHTTP(w http.ResponseWriter, res types.RPCResponses) { - if len(res) == 1 { - WriteRPCResponseHTTP(w, res[0]) - } else { - jsonBytes, err := json.MarshalIndent(res, "", " ") - if err != nil { - panic(err) - } - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(200) - if _, err := w.Write(jsonBytes); err != nil { - panic(err) - } + jsonBytes, err := json.MarshalIndent(res, "", " ") + if err != nil { + panic(err) + } + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(200) + if _, err := w.Write(jsonBytes); err != nil { + panic(err) } } From 47395b1a95e3edf058cf823061485d8201be2258 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Milo=C5=A1=20=C5=BDivkovi=C4=87?= Date: Fri, 7 Feb 2025 02:43:04 +0100 Subject: [PATCH 132/143] fix: utilize `p2p.ExternalAddress` properly for dialing (#3581) ## Description This PR fixes a bug with the `Transport` bind address, where the bound address was taking in a wrong param, as part of an oversight. It also adds context on the newly introduced checks. The PR also fixes an issue with the redial mechanism for persistent peers, where peers wouldn't be redialed more than once. --- tm2/pkg/bft/node/node.go | 47 +++++++++++++++---- tm2/pkg/internal/p2p/p2p.go | 14 +++--- tm2/pkg/p2p/discovery/discovery.go | 23 ++++++++-- tm2/pkg/p2p/discovery/discovery_test.go | 6 +-- tm2/pkg/p2p/mock/peer.go | 2 +- tm2/pkg/p2p/peer.go | 2 +- tm2/pkg/p2p/peer_test.go | 4 +- tm2/pkg/p2p/switch.go | 61 ++++++++++++++++--------- tm2/pkg/p2p/switch_test.go | 2 +- tm2/pkg/p2p/transport.go | 1 + tm2/pkg/p2p/transport_test.go | 8 ++-- tm2/pkg/p2p/types/node_info.go | 35 ++++++++++---- tm2/pkg/p2p/types/node_info_test.go | 48 +++++++++++++------ 13 files changed, 177 insertions(+), 76 deletions(-) diff --git a/tm2/pkg/bft/node/node.go b/tm2/pkg/bft/node/node.go index 08f1b3c58f9..7f16d6780c7 100644 --- a/tm2/pkg/bft/node/node.go +++ b/tm2/pkg/bft/node/node.go @@ -12,8 +12,6 @@ import ( "sync" "time" - goErrors "errors" - "github.com/gnolang/gno/tm2/pkg/bft/appconn" "github.com/gnolang/gno/tm2/pkg/bft/state/eventstore/file" "github.com/gnolang/gno/tm2/pkg/p2p/conn" @@ -604,12 +602,10 @@ func (n *Node) OnStart() error { } // Start the transport. - lAddr := n.config.P2P.ExternalAddress - if lAddr == "" { - lAddr = n.config.P2P.ListenAddress - } + // The listen address for the transport needs to be an address within reach of the machine NIC + listenAddress := p2pTypes.NetAddressString(n.nodeKey.ID(), n.config.P2P.ListenAddress) - addr, err := p2pTypes.NewNetAddressFromString(p2pTypes.NetAddressString(n.nodeKey.ID(), lAddr)) + addr, err := p2pTypes.NewNetAddressFromString(listenAddress) if err != nil { return fmt.Errorf("unable to parse network address, %w", err) } @@ -903,7 +899,7 @@ func makeNodeInfo( nodeInfo := p2pTypes.NodeInfo{ VersionSet: vset, - PeerID: nodeKey.ID(), + NetAddress: nil, // The shared address depends on the configuration Network: genDoc.ChainID, Version: version.Version, Channels: []byte{ @@ -918,13 +914,44 @@ func makeNodeInfo( }, } + // Make sure the discovery channel is shared with peers + // in case peer discovery is enabled if config.P2P.PeerExchange { nodeInfo.Channels = append(nodeInfo.Channels, discovery.Channel) } + // Grab the supplied listen address. + // This address needs to be valid, but it can be unspecified. + // If the listen address is unspecified (port / IP unbound), + // then this address cannot be used by peers for dialing + addr, err := p2pTypes.NewNetAddressFromString( + p2pTypes.NetAddressString(nodeKey.ID(), config.P2P.ListenAddress), + ) + if err != nil { + return p2pTypes.NodeInfo{}, fmt.Errorf("unable to parse network address, %w", err) + } + + // Use the transport listen address as the advertised address + nodeInfo.NetAddress = addr + + // Prepare the advertised dial address (if any) + // for the node, which other peers can use to dial + if config.P2P.ExternalAddress != "" { + addr, err = p2pTypes.NewNetAddressFromString( + p2pTypes.NetAddressString( + nodeKey.ID(), + config.P2P.ExternalAddress, + ), + ) + if err != nil { + return p2pTypes.NodeInfo{}, fmt.Errorf("invalid p2p external address: %w", err) + } + + nodeInfo.NetAddress = addr + } + // Validate the node info - err := nodeInfo.Validate() - if err != nil && !goErrors.Is(err, p2pTypes.ErrUnspecifiedIP) { + if err := nodeInfo.Validate(); err != nil { return p2pTypes.NodeInfo{}, fmt.Errorf("unable to validate node info, %w", err) } diff --git a/tm2/pkg/internal/p2p/p2p.go b/tm2/pkg/internal/p2p/p2p.go index 1e650e0cd25..0c8f1529b85 100644 --- a/tm2/pkg/internal/p2p/p2p.go +++ b/tm2/pkg/internal/p2p/p2p.go @@ -70,12 +70,12 @@ func MakeConnectedPeers( VersionSet: versionset.VersionSet{ versionset.VersionInfo{Name: "p2p", Version: "v0.0.0"}, }, - PeerID: key.ID(), - Network: "testing", - Software: "p2ptest", - Version: "v1.2.3-rc.0-deadbeef", - Channels: cfg.Channels, - Moniker: fmt.Sprintf("node-%d", index), + NetAddress: addr, + Network: "testing", + Software: "p2ptest", + Version: "v1.2.3-rc.0-deadbeef", + Channels: cfg.Channels, + Moniker: fmt.Sprintf("node-%d", index), Other: p2pTypes.NodeInfoOther{ TxIndex: "off", RPCAddress: fmt.Sprintf("127.0.0.1:%d", 0), @@ -231,7 +231,7 @@ func (mp *Peer) TrySend(_ byte, _ []byte) bool { return true } func (mp *Peer) Send(_ byte, _ []byte) bool { return true } func (mp *Peer) NodeInfo() p2pTypes.NodeInfo { return p2pTypes.NodeInfo{ - PeerID: mp.id, + NetAddress: mp.addr, } } func (mp *Peer) Status() conn.ConnectionStatus { return conn.ConnectionStatus{} } diff --git a/tm2/pkg/p2p/discovery/discovery.go b/tm2/pkg/p2p/discovery/discovery.go index d884b118c75..7a9da3726c0 100644 --- a/tm2/pkg/p2p/discovery/discovery.go +++ b/tm2/pkg/p2p/discovery/discovery.go @@ -160,7 +160,7 @@ func (r *Reactor) Receive(chID byte, peer p2p.PeerConn, msgBytes []byte) { // Validate the message if err := msg.ValidateBasic(); err != nil { - r.Logger.Error("unable to validate discovery message", "err", err) + r.Logger.Warn("unable to validate discovery message", "err", err) return } @@ -168,7 +168,7 @@ func (r *Reactor) Receive(chID byte, peer p2p.PeerConn, msgBytes []byte) { switch msg := msg.(type) { case *Request: if err := r.handleDiscoveryRequest(peer); err != nil { - r.Logger.Error("unable to handle discovery request", "err", err) + r.Logger.Warn("unable to handle discovery request", "err", err) } case *Response: // Make the peers available for dialing on the switch @@ -186,9 +186,21 @@ func (r *Reactor) handleDiscoveryRequest(peer p2p.PeerConn) error { peers = make([]*types.NetAddress, 0, len(localPeers)) ) - // Exclude the private peers from being shared + // Exclude the private peers from being shared, + // as well as peers who are not dialable localPeers = slices.DeleteFunc(localPeers, func(p p2p.PeerConn) bool { - return p.IsPrivate() + var ( + // Private peers are peers whose information is kept private to the node + privatePeer = p.IsPrivate() + // The reason we don't validate the net address with .Routable() + // is because of legacy logic that supports local loopbacks as advertised + // peer addresses. Introducing a .Routable() constraint will filter all + // local loopback addresses shared by peers, and will cause local deployments + // (and unit test deployments) to break and require additional setup + invalidDialAddress = p.NodeInfo().DialAddress().Validate() != nil + ) + + return privatePeer || invalidDialAddress }) // Check if there is anything to share, @@ -207,7 +219,8 @@ func (r *Reactor) handleDiscoveryRequest(peer p2p.PeerConn) error { } for _, p := range localPeers { - peers = append(peers, p.SocketAddr()) + // Make sure only routable peers are shared + peers = append(peers, p.NodeInfo().DialAddress()) } // Create the response, and marshal diff --git a/tm2/pkg/p2p/discovery/discovery_test.go b/tm2/pkg/p2p/discovery/discovery_test.go index 17404e6039a..91741c648db 100644 --- a/tm2/pkg/p2p/discovery/discovery_test.go +++ b/tm2/pkg/p2p/discovery/discovery_test.go @@ -166,7 +166,7 @@ func TestReactor_DiscoveryResponse(t *testing.T) { slices.ContainsFunc(resp.Peers, func(addr *types.NetAddress) bool { for _, localP := range peers { - if localP.SocketAddr().Equals(*addr) { + if localP.NodeInfo().DialAddress().Equals(*addr) { return true } } @@ -317,7 +317,7 @@ func TestReactor_DiscoveryResponse(t *testing.T) { slices.ContainsFunc(resp.Peers, func(addr *types.NetAddress) bool { for _, localP := range peers { - if localP.SocketAddr().Equals(*addr) { + if localP.NodeInfo().DialAddress().Equals(*addr) { return true } } @@ -373,7 +373,7 @@ func TestReactor_DiscoveryResponse(t *testing.T) { peerAddrs := make([]*types.NetAddress, 0, len(peers)) for _, p := range peers { - peerAddrs = append(peerAddrs, p.SocketAddr()) + peerAddrs = append(peerAddrs, p.NodeInfo().DialAddress()) } // Prepare the message diff --git a/tm2/pkg/p2p/mock/peer.go b/tm2/pkg/p2p/mock/peer.go index e5a01952831..5be34121924 100644 --- a/tm2/pkg/p2p/mock/peer.go +++ b/tm2/pkg/p2p/mock/peer.go @@ -57,7 +57,7 @@ func GeneratePeers(t *testing.T, count int) []*Peer { }, NodeInfoFn: func() types.NodeInfo { return types.NodeInfo{ - PeerID: key.ID(), + NetAddress: addr, } }, SocketAddrFn: func() *types.NetAddress { diff --git a/tm2/pkg/p2p/peer.go b/tm2/pkg/p2p/peer.go index 135bf4b250c..dcca81ca097 100644 --- a/tm2/pkg/p2p/peer.go +++ b/tm2/pkg/p2p/peer.go @@ -160,7 +160,7 @@ func (p *peer) OnStop() { // ID returns the peer's ID - the hex encoded hash of its pubkey. func (p *peer) ID() types.ID { - return p.nodeInfo.PeerID + return p.nodeInfo.ID() } // NodeInfo returns a copy of the peer's NodeInfo. diff --git a/tm2/pkg/p2p/peer_test.go b/tm2/pkg/p2p/peer_test.go index a74ea9e96a4..75f5172ee66 100644 --- a/tm2/pkg/p2p/peer_test.go +++ b/tm2/pkg/p2p/peer_test.go @@ -243,7 +243,9 @@ func TestPeer_Properties(t *testing.T) { }, }, nodeInfo: types.NodeInfo{ - PeerID: id, + NetAddress: &types.NetAddress{ + ID: id, + }, }, connInfo: &ConnInfo{ Outbound: testCase.outbound, diff --git a/tm2/pkg/p2p/switch.go b/tm2/pkg/p2p/switch.go index 7784c1f3989..c96e429973e 100644 --- a/tm2/pkg/p2p/switch.go +++ b/tm2/pkg/p2p/switch.go @@ -407,57 +407,76 @@ func (sw *MultiplexSwitch) runRedialLoop(ctx context.Context) { peersToDial = make([]*types.NetAddress, 0) ) + // Gather addresses of persistent peers that are missing or + // not already in the dial queue sw.persistentPeers.Range(func(key, value any) bool { var ( id = key.(types.ID) addr = value.(*types.NetAddress) ) - // Check if the peer is part of the peer set - // or is scheduled for dialing - if peers.Has(id) || sw.dialQueue.Has(addr) { - return true + if !peers.Has(id) && !sw.dialQueue.Has(addr) { + peersToDial = append(peersToDial, addr) } - peersToDial = append(peersToDial, addr) - return true }) if len(peersToDial) == 0 { - // No persistent peers are missing + // No persistent peers need dialing return } - // Calculate the dial items + // Prepare dial items with the appropriate backoff dialItems := make([]dial.Item, 0, len(peersToDial)) - for _, p := range peersToDial { - item := getBackoffItem(p.ID) + for _, addr := range peersToDial { + item := getBackoffItem(addr.ID) + if item == nil { - dialItem := dial.Item{ - Time: time.Now(), - Address: p, - } + // First attempt + now := time.Now() + + dialItems = append(dialItems, + dial.Item{ + Time: now, + Address: addr, + }, + ) - dialItems = append(dialItems, dialItem) - setBackoffItem(p.ID, &backoffItem{dialItem.Time, 0}) + setBackoffItem(addr.ID, &backoffItem{ + lastDialTime: now, + attempts: 0, + }) continue } - setBackoffItem(p.ID, &backoffItem{ - lastDialTime: time.Now().Add( + // Subsequent attempt: apply backoff + var ( + attempts = item.attempts + 1 + dialTime = time.Now().Add( calculateBackoff( item.attempts, time.Second, 10*time.Minute, ), - ), - attempts: item.attempts + 1, + ) + ) + + dialItems = append(dialItems, + dial.Item{ + Time: dialTime, + Address: addr, + }, + ) + + setBackoffItem(addr.ID, &backoffItem{ + lastDialTime: dialTime, + attempts: attempts, }) } - // Add the peers to the dial queue + // Add these items to the dial queue sw.dialItems(dialItems...) } diff --git a/tm2/pkg/p2p/switch_test.go b/tm2/pkg/p2p/switch_test.go index b10ab3faba5..e5f472cc28e 100644 --- a/tm2/pkg/p2p/switch_test.go +++ b/tm2/pkg/p2p/switch_test.go @@ -727,7 +727,7 @@ func TestMultiplexSwitch_DialPeers(t *testing.T) { // as the transport (node) p.NodeInfoFn = func() types.NodeInfo { return types.NodeInfo{ - PeerID: addr.ID, + NetAddress: &addr, } } diff --git a/tm2/pkg/p2p/transport.go b/tm2/pkg/p2p/transport.go index 255fa60152b..3d64a48f437 100644 --- a/tm2/pkg/p2p/transport.go +++ b/tm2/pkg/p2p/transport.go @@ -139,6 +139,7 @@ func (mt *MultiplexTransport) Close() error { } mt.cancelFn() + return mt.listener.Close() } diff --git a/tm2/pkg/p2p/transport_test.go b/tm2/pkg/p2p/transport_test.go index 5ec4efda1ad..840eb974e76 100644 --- a/tm2/pkg/p2p/transport_test.go +++ b/tm2/pkg/p2p/transport_test.go @@ -237,7 +237,7 @@ func TestMultiplexTransport_Accept(t *testing.T) { ni := types.NodeInfo{ Network: network, // common network - PeerID: id, + NetAddress: na, Version: "v1.0.0-rc.0", Moniker: fmt.Sprintf("node-%d", index), VersionSet: make(versionset.VersionSet, 0), // compatible version set @@ -317,7 +317,7 @@ func TestMultiplexTransport_Accept(t *testing.T) { ni := types.NodeInfo{ Network: chainID, - PeerID: id, + NetAddress: na, Version: "v1.0.0-rc.0", Moniker: fmt.Sprintf("node-%d", index), VersionSet: make(versionset.VersionSet, 0), // compatible version set @@ -389,7 +389,7 @@ func TestMultiplexTransport_Accept(t *testing.T) { ni := types.NodeInfo{ Network: network, // common network - PeerID: key.ID(), + NetAddress: na, Version: "v1.0.0-rc.0", Moniker: fmt.Sprintf("node-%d", index), VersionSet: make(versionset.VersionSet, 0), // compatible version set @@ -467,7 +467,7 @@ func TestMultiplexTransport_Accept(t *testing.T) { ni := types.NodeInfo{ Network: network, // common network - PeerID: key.ID(), + NetAddress: na, Version: "v1.0.0-rc.0", Moniker: fmt.Sprintf("node-%d", index), VersionSet: make(versionset.VersionSet, 0), // compatible version set diff --git a/tm2/pkg/p2p/types/node_info.go b/tm2/pkg/p2p/types/node_info.go index 8452cb43cb8..4080ff2d8aa 100644 --- a/tm2/pkg/p2p/types/node_info.go +++ b/tm2/pkg/p2p/types/node_info.go @@ -14,7 +14,6 @@ const ( ) var ( - ErrInvalidPeerID = errors.New("invalid peer ID") ErrInvalidVersion = errors.New("invalid node version") ErrInvalidMoniker = errors.New("invalid node moniker") ErrInvalidRPCAddress = errors.New("invalid node RPC address") @@ -30,8 +29,8 @@ type NodeInfo struct { // Set of protocol versions VersionSet versionset.VersionSet `json:"version_set"` - // Unique peer identifier - PeerID ID `json:"id"` + // The advertised net address of the peer + NetAddress *NetAddress `json:"net_address"` // Check compatibility. // Channels are HexBytes so easier to read as JSON @@ -54,12 +53,27 @@ type NodeInfoOther struct { // Validate checks the self-reported NodeInfo is safe. // It returns an error if there // are too many Channels, if there are any duplicate Channels, -// if the ListenAddr is malformed, or if the ListenAddr is a host name +// if the NetAddress is malformed, or if the NetAddress is a host name // that can not be resolved to some IP func (info NodeInfo) Validate() error { - // Validate the ID - if err := info.PeerID.Validate(); err != nil { - return fmt.Errorf("%w, %w", ErrInvalidPeerID, err) + // There are a few checks that need to be performed when validating + // the node info's net address: + // - the ID needs to be valid + // - the FORMAT of the net address needs to be valid + // + // The key nuance here is that the net address is not being validated + // for its "dialability", but whether it's of the correct format. + // + // Unspecified IPs are tolerated (ex. 0.0.0.0 or ::), + // because of legacy logic that assumes node info + // can have unspecified IPs (ex. no external address is set, use + // the listen address which is bound to 0.0.0.0). + // + // These types of IPs are caught during the + // real peer info sharing process, since they are undialable + _, err := NewNetAddressFromString(NetAddressString(info.NetAddress.ID, info.NetAddress.DialString())) + if err != nil { + return fmt.Errorf("invalid net address in node info, %w", err) } // Validate Version @@ -100,7 +114,12 @@ func (info NodeInfo) Validate() error { // ID returns the local node ID func (info NodeInfo) ID() ID { - return info.PeerID + return info.NetAddress.ID +} + +// DialAddress is the advertised peer dial address (share-able) +func (info NodeInfo) DialAddress() *NetAddress { + return info.NetAddress } // CompatibleWith checks if two NodeInfo are compatible with each other. diff --git a/tm2/pkg/p2p/types/node_info_test.go b/tm2/pkg/p2p/types/node_info_test.go index d03d77e608f..575d8ae5fbd 100644 --- a/tm2/pkg/p2p/types/node_info_test.go +++ b/tm2/pkg/p2p/types/node_info_test.go @@ -2,23 +2,43 @@ package types import ( "fmt" + "net" "testing" + "github.com/gnolang/gno/tm2/pkg/crypto" "github.com/gnolang/gno/tm2/pkg/versionset" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestNodeInfo_Validate(t *testing.T) { t.Parallel() + generateNetAddress := func() *NetAddress { + var ( + key = GenerateNodeKey() + address = "127.0.0.1:8080" + ) + + tcpAddr, err := net.ResolveTCPAddr("tcp", address) + require.NoError(t, err) + + addr, err := NewNetAddress(key.ID(), tcpAddr) + require.NoError(t, err) + + return addr + } + t.Run("invalid peer ID", func(t *testing.T) { t.Parallel() info := &NodeInfo{ - PeerID: "", // zero + NetAddress: &NetAddress{ + ID: "", // zero + }, } - assert.ErrorIs(t, info.Validate(), ErrInvalidPeerID) + assert.ErrorIs(t, info.Validate(), crypto.ErrZeroID) }) t.Run("invalid version", func(t *testing.T) { @@ -47,8 +67,8 @@ func TestNodeInfo_Validate(t *testing.T) { t.Parallel() info := &NodeInfo{ - PeerID: GenerateNodeKey().ID(), - Version: testCase.version, + NetAddress: generateNetAddress(), + Version: testCase.version, } assert.ErrorIs(t, info.Validate(), ErrInvalidVersion) @@ -86,8 +106,8 @@ func TestNodeInfo_Validate(t *testing.T) { t.Parallel() info := &NodeInfo{ - PeerID: GenerateNodeKey().ID(), - Moniker: testCase.moniker, + NetAddress: generateNetAddress(), + Moniker: testCase.moniker, } assert.ErrorIs(t, info.Validate(), ErrInvalidMoniker) @@ -121,8 +141,8 @@ func TestNodeInfo_Validate(t *testing.T) { t.Parallel() info := &NodeInfo{ - PeerID: GenerateNodeKey().ID(), - Moniker: "valid moniker", + NetAddress: generateNetAddress(), + Moniker: "valid moniker", Other: NodeInfoOther{ RPCAddress: testCase.rpcAddress, }, @@ -162,9 +182,9 @@ func TestNodeInfo_Validate(t *testing.T) { t.Parallel() info := &NodeInfo{ - PeerID: GenerateNodeKey().ID(), - Moniker: "valid moniker", - Channels: testCase.channels, + NetAddress: generateNetAddress(), + Moniker: "valid moniker", + Channels: testCase.channels, } assert.ErrorIs(t, info.Validate(), testCase.expectedErr) @@ -176,9 +196,9 @@ func TestNodeInfo_Validate(t *testing.T) { t.Parallel() info := &NodeInfo{ - PeerID: GenerateNodeKey().ID(), - Moniker: "valid moniker", - Channels: []byte{10, 20, 30}, + NetAddress: generateNetAddress(), + Moniker: "valid moniker", + Channels: []byte{10, 20, 30}, Other: NodeInfoOther{ RPCAddress: "0.0.0.0:26657", }, From 6367a3eacc0a6c5c39e95e3421af4ab47c7d5d33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Milo=C5=A1=20=C5=BDivkovi=C4=87?= Date: Fri, 7 Feb 2025 09:28:05 +0100 Subject: [PATCH 133/143] feat: add support for validating tx signatures in `gnogenesis` (#3690) ## Description This PR adds support for verifying genesis transaction signatures in `gnogenesis verify`, allowing us to quickly diagnose a potential issue with a touched `genesis.json` --- .github/workflows/genesis-verify.yml | 2 +- contribs/gnogenesis/internal/verify/verify.go | 29 +++++- .../gnogenesis/internal/verify/verify_test.go | 97 +++++++++++++++++++ tm2/pkg/crypto/mock/mock.go | 2 +- 4 files changed, 126 insertions(+), 4 deletions(-) diff --git a/.github/workflows/genesis-verify.yml b/.github/workflows/genesis-verify.yml index acc41cc99ad..a9e9a43e55a 100644 --- a/.github/workflows/genesis-verify.yml +++ b/.github/workflows/genesis-verify.yml @@ -14,7 +14,7 @@ jobs: strategy: fail-fast: false matrix: - testnet: [ "test5.gno.land" ] + testnet: [ ] # Currently, all active testnet deployment genesis.json are legacy runs-on: ubuntu-latest steps: - name: Checkout code diff --git a/contribs/gnogenesis/internal/verify/verify.go b/contribs/gnogenesis/internal/verify/verify.go index 9022711ce49..c69f41cad4d 100644 --- a/contribs/gnogenesis/internal/verify/verify.go +++ b/contribs/gnogenesis/internal/verify/verify.go @@ -12,7 +12,10 @@ import ( "github.com/gnolang/gno/tm2/pkg/commands" ) -var errInvalidGenesisState = errors.New("invalid genesis state type") +var ( + errInvalidGenesisState = errors.New("invalid genesis state type") + errInvalidTxSignature = errors.New("invalid tx signature") +) type verifyCfg struct { common.Cfg @@ -60,10 +63,32 @@ func execVerify(cfg *verifyCfg, io commands.IO) error { } // Validate the initial transactions - for _, tx := range state.Txs { + for index, tx := range state.Txs { if validateErr := tx.Tx.ValidateBasic(); validateErr != nil { return fmt.Errorf("invalid transacton, %w", validateErr) } + + // Genesis txs can only be signed by 1 account. + // Basic tx validation ensures there is at least 1 signer + signer := tx.Tx.GetSignatures()[0] + + // Grab the signature bytes of the tx. + // Genesis transactions are signed with + // account number and sequence set to 0 + signBytes, err := tx.Tx.GetSignBytes(genesis.ChainID, 0, 0) + if err != nil { + return fmt.Errorf("unable to get tx signature payload, %w", err) + } + + // Verify the signature using the public key + if !signer.PubKey.VerifyBytes(signBytes, signer.Signature) { + return fmt.Errorf( + "%w #%d, by signer %s", + errInvalidTxSignature, + index, + signer.PubKey.Address(), + ) + } } // Validate the initial balances diff --git a/contribs/gnogenesis/internal/verify/verify_test.go b/contribs/gnogenesis/internal/verify/verify_test.go index 130bd5e09bc..cc80c0423de 100644 --- a/contribs/gnogenesis/internal/verify/verify_test.go +++ b/contribs/gnogenesis/internal/verify/verify_test.go @@ -8,8 +8,12 @@ import ( "github.com/gnolang/gno/gno.land/pkg/gnoland" "github.com/gnolang/gno/tm2/pkg/bft/types" "github.com/gnolang/gno/tm2/pkg/commands" + "github.com/gnolang/gno/tm2/pkg/crypto/ed25519" "github.com/gnolang/gno/tm2/pkg/crypto/mock" + "github.com/gnolang/gno/tm2/pkg/sdk/bank" + "github.com/gnolang/gno/tm2/pkg/std" "github.com/gnolang/gno/tm2/pkg/testutils" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -63,6 +67,99 @@ func TestGenesis_Verify(t *testing.T) { require.Error(t, cmdErr) }) + t.Run("invalid tx signature", func(t *testing.T) { + t.Parallel() + + g := getValidTestGenesis() + + testTable := []struct { + name string + signBytesFn func(tx *std.Tx) []byte + }{ + { + name: "invalid chain ID", + signBytesFn: func(tx *std.Tx) []byte { + // Sign the transaction, but with a chain ID + // that differs from the genesis chain ID + signBytes, err := tx.GetSignBytes(g.ChainID+"wrong", 0, 0) + require.NoError(t, err) + + return signBytes + }, + }, + { + name: "invalid account params", + signBytesFn: func(tx *std.Tx) []byte { + // Sign the transaction, but with an + // account number that is not 0 + signBytes, err := tx.GetSignBytes(g.ChainID, 10, 0) + require.NoError(t, err) + + return signBytes + }, + }, + } + + for _, testCase := range testTable { + t.Run(testCase.name, func(t *testing.T) { + t.Parallel() + + tempFile, cleanup := testutils.NewTestFile(t) + t.Cleanup(cleanup) + + // Generate the transaction + signer := ed25519.GenPrivKey() + + sendMsg := bank.MsgSend{ + FromAddress: signer.PubKey().Address(), + ToAddress: signer.PubKey().Address(), + Amount: std.NewCoins(std.NewCoin("ugnot", 10)), + } + + tx := std.Tx{ + Msgs: []std.Msg{sendMsg}, + Fee: std.Fee{ + GasWanted: 1000000, + GasFee: std.NewCoin("ugnot", 20), + }, + } + + // Sign the transaction + signBytes := testCase.signBytesFn(&tx) + + signature, err := signer.Sign(signBytes) + require.NoError(t, err) + + tx.Signatures = append(tx.Signatures, std.Signature{ + PubKey: signer.PubKey(), + Signature: signature, + }) + + g.AppState = gnoland.GnoGenesisState{ + Balances: []gnoland.Balance{}, + Txs: []gnoland.TxWithMetadata{ + { + Tx: tx, + }, + }, + } + + require.NoError(t, g.SaveAs(tempFile.Name())) + + // Create the command + cmd := NewVerifyCmd(commands.NewTestIO()) + args := []string{ + "--genesis-path", + tempFile.Name(), + } + + // Run the command + cmdErr := cmd.ParseAndRun(context.Background(), args) + assert.ErrorIs(t, cmdErr, errInvalidTxSignature) + }) + } + }) + t.Run("invalid balances", func(t *testing.T) { t.Parallel() diff --git a/tm2/pkg/crypto/mock/mock.go b/tm2/pkg/crypto/mock/mock.go index 9ea1c5d66dc..b3fe8c5e69f 100644 --- a/tm2/pkg/crypto/mock/mock.go +++ b/tm2/pkg/crypto/mock/mock.go @@ -42,7 +42,7 @@ func (privKey PrivKeyMock) Equals(other crypto.PrivKey) bool { func GenPrivKey() PrivKeyMock { randstr := random.RandStr(12) - return PrivKeyMock([]byte(randstr)) + return []byte(randstr) } // ------------------------------------- From 4f91841910a7fac4b93017208dab3856c3c0fa24 Mon Sep 17 00:00:00 2001 From: Alexis Colin Date: Fri, 7 Feb 2025 17:36:36 +0900 Subject: [PATCH 134/143] fix(gnoweb): header links with webquery (#3671) This PR fixes an issue where gnoweb URLs contain both a query (? prefix) and a gnowebquery ($ prefix). Previously, the header links appended the gnowebquery after the query, whereas it should be placed before the query. This update ensures the correct ordering of URL components. cf issue: https://github.com/gnolang/gno/issues/3355#issuecomment-2597023818 --------- Co-authored-by: Guilhem Fanton <8671905+gfanton@users.noreply.github.com> --- .../pkg/gnoweb/components/layout_header.go | 26 ++++--- gno.land/pkg/gnoweb/handler.go | 20 +++--- gno.land/pkg/gnoweb/{ => weburl}/url.go | 45 +++++++------ gno.land/pkg/gnoweb/{ => weburl}/url_test.go | 67 ++++++++++++++++++- 4 files changed, 117 insertions(+), 41 deletions(-) rename gno.land/pkg/gnoweb/{ => weburl}/url.go (87%) rename gno.land/pkg/gnoweb/{ => weburl}/url_test.go (87%) diff --git a/gno.land/pkg/gnoweb/components/layout_header.go b/gno.land/pkg/gnoweb/components/layout_header.go index d446212c6e0..8ab5e95b391 100644 --- a/gno.land/pkg/gnoweb/components/layout_header.go +++ b/gno.land/pkg/gnoweb/components/layout_header.go @@ -2,6 +2,8 @@ package components import ( "net/url" + + "github.com/gnolang/gno/gno.land/pkg/gnoweb/weburl" ) type HeaderLink struct { @@ -13,38 +15,44 @@ type HeaderLink struct { type HeaderData struct { RealmPath string + RealmURL weburl.GnoURL Breadcrumb BreadcrumbData - WebQuery url.Values Links []HeaderLink ChainId string Remote string } -func StaticHeaderLinks(realmPath string, webQuery url.Values) []HeaderLink { +func StaticHeaderLinks(u weburl.GnoURL) []HeaderLink { + contentURL, sourceURL, helpURL := u, u, u + contentURL.WebQuery = url.Values{} + sourceURL.WebQuery = url.Values{"source": {""}} + helpURL.WebQuery = url.Values{"help": {""}} + return []HeaderLink{ { Label: "Content", - URL: realmPath, + URL: contentURL.EncodeWebURL(), Icon: "ico-info", - IsActive: isActive(webQuery, "Content"), + IsActive: isActive(u.WebQuery, "Content"), }, { Label: "Source", - URL: realmPath + "$source", + URL: sourceURL.EncodeWebURL(), Icon: "ico-code", - IsActive: isActive(webQuery, "Source"), + IsActive: isActive(u.WebQuery, "Source"), }, { Label: "Docs", - URL: realmPath + "$help", + URL: helpURL.EncodeWebURL(), Icon: "ico-docs", - IsActive: isActive(webQuery, "Docs"), + IsActive: isActive(u.WebQuery, "Docs"), }, } } func EnrichHeaderData(data HeaderData) HeaderData { - data.Links = StaticHeaderLinks(data.RealmPath, data.WebQuery) + data.RealmPath = data.RealmURL.EncodeURL() + data.Links = StaticHeaderLinks(data.RealmURL) return data } diff --git a/gno.land/pkg/gnoweb/handler.go b/gno.land/pkg/gnoweb/handler.go index 4c1dae31261..b5ee98614f3 100644 --- a/gno.land/pkg/gnoweb/handler.go +++ b/gno.land/pkg/gnoweb/handler.go @@ -11,6 +11,7 @@ import ( "time" "github.com/gnolang/gno/gno.land/pkg/gnoweb/components" + "github.com/gnolang/gno/gno.land/pkg/gnoweb/weburl" "github.com/gnolang/gno/gno.land/pkg/sdk/vm" // For error types ) @@ -111,7 +112,7 @@ func (h *WebHandler) Get(w http.ResponseWriter, r *http.Request) { // prepareIndexBodyView prepares the data and main view for the index. func (h *WebHandler) prepareIndexBodyView(r *http.Request, indexData *components.IndexData) (int, *components.View) { - gnourl, err := ParseGnoURL(r.URL) + gnourl, err := weburl.ParseGnoURL(r.URL) if err != nil { h.Logger.Warn("unable to parse url path", "path", r.URL.Path, "error", err) return http.StatusNotFound, components.StatusErrorComponent("invalid path") @@ -120,9 +121,8 @@ func (h *WebHandler) prepareIndexBodyView(r *http.Request, indexData *components breadcrumb := generateBreadcrumbPaths(gnourl) indexData.HeadData.Title = h.Static.Domain + " - " + gnourl.Path indexData.HeaderData = components.HeaderData{ - RealmPath: gnourl.Encode(EncodePath | EncodeArgs | EncodeQuery | EncodeNoEscape), Breadcrumb: breadcrumb, - WebQuery: gnourl.WebQuery, + RealmURL: *gnourl, ChainId: h.Static.ChainId, Remote: h.Static.RemoteHelp, } @@ -137,7 +137,7 @@ func (h *WebHandler) prepareIndexBodyView(r *http.Request, indexData *components } // GetPackageView handles package pages. -func (h *WebHandler) GetPackageView(gnourl *GnoURL) (int, *components.View) { +func (h *WebHandler) GetPackageView(gnourl *weburl.GnoURL) (int, *components.View) { // Handle Help page if gnourl.WebQuery.Has("help") { return h.GetHelpView(gnourl) @@ -157,7 +157,7 @@ func (h *WebHandler) GetPackageView(gnourl *GnoURL) (int, *components.View) { return h.GetRealmView(gnourl) } -func (h *WebHandler) GetRealmView(gnourl *GnoURL) (int, *components.View) { +func (h *WebHandler) GetRealmView(gnourl *weburl.GnoURL) (int, *components.View) { var content bytes.Buffer meta, err := h.Client.RenderRealm(&content, gnourl.Path, gnourl.EncodeArgs()) @@ -181,7 +181,7 @@ func (h *WebHandler) GetRealmView(gnourl *GnoURL) (int, *components.View) { }) } -func (h *WebHandler) GetHelpView(gnourl *GnoURL) (int, *components.View) { +func (h *WebHandler) GetHelpView(gnourl *weburl.GnoURL) (int, *components.View) { fsigs, err := h.Client.Functions(gnourl.Path) if err != nil { h.Logger.Error("unable to fetch path functions", "error", err) @@ -219,7 +219,7 @@ func (h *WebHandler) GetHelpView(gnourl *GnoURL) (int, *components.View) { }) } -func (h *WebHandler) GetSourceView(gnourl *GnoURL) (int, *components.View) { +func (h *WebHandler) GetSourceView(gnourl *weburl.GnoURL) (int, *components.View) { pkgPath := gnourl.Path files, err := h.Client.Sources(pkgPath) if err != nil { @@ -262,7 +262,7 @@ func (h *WebHandler) GetSourceView(gnourl *GnoURL) (int, *components.View) { }) } -func (h *WebHandler) GetDirectoryView(gnourl *GnoURL) (int, *components.View) { +func (h *WebHandler) GetDirectoryView(gnourl *weburl.GnoURL) (int, *components.View) { pkgPath := strings.TrimSuffix(gnourl.Path, "/") files, err := h.Client.Sources(pkgPath) if err != nil { @@ -282,7 +282,7 @@ func (h *WebHandler) GetDirectoryView(gnourl *GnoURL) (int, *components.View) { }) } -func GetClientErrorStatusPage(_ *GnoURL, err error) (int, *components.View) { +func GetClientErrorStatusPage(_ *weburl.GnoURL, err error) (int, *components.View) { if err == nil { return http.StatusOK, nil } @@ -299,7 +299,7 @@ func GetClientErrorStatusPage(_ *GnoURL, err error) (int, *components.View) { } } -func generateBreadcrumbPaths(url *GnoURL) components.BreadcrumbData { +func generateBreadcrumbPaths(url *weburl.GnoURL) components.BreadcrumbData { split := strings.Split(url.Path, "/") var data components.BreadcrumbData diff --git a/gno.land/pkg/gnoweb/url.go b/gno.land/pkg/gnoweb/weburl/url.go similarity index 87% rename from gno.land/pkg/gnoweb/url.go rename to gno.land/pkg/gnoweb/weburl/url.go index 9127225d490..cbe861e9e42 100644 --- a/gno.land/pkg/gnoweb/url.go +++ b/gno.land/pkg/gnoweb/weburl/url.go @@ -1,4 +1,4 @@ -package gnoweb +package weburl import ( "errors" @@ -97,20 +97,12 @@ func (gnoURL GnoURL) Encode(encodeFlags EncodeFlag) string { if encodeFlags.Has(EncodeWebQuery) && len(gnoURL.WebQuery) > 0 { urlstr.WriteRune('$') - if noEscape { - urlstr.WriteString(NoEscapeQuery(gnoURL.WebQuery)) - } else { - urlstr.WriteString(gnoURL.WebQuery.Encode()) - } + urlstr.WriteString(EncodeValues(gnoURL.WebQuery, !noEscape)) } if encodeFlags.Has(EncodeQuery) && len(gnoURL.Query) > 0 { urlstr.WriteRune('?') - if noEscape { - urlstr.WriteString(NoEscapeQuery(gnoURL.Query)) - } else { - urlstr.WriteString(gnoURL.Query.Encode()) - } + urlstr.WriteString(EncodeValues(gnoURL.Query, !noEscape)) } return urlstr.String() @@ -140,7 +132,7 @@ func (gnoURL GnoURL) EncodeURL() string { // EncodeWebURL encodes the path, package arguments, web query, and query into a string. // This function provides the full representation of the URL. func (gnoURL GnoURL) EncodeWebURL() string { - return gnoURL.Encode(EncodePath | EncodeArgs | EncodeWebQuery | EncodeQuery) + return gnoURL.Encode(EncodePath | EncodeArgs | EncodeWebQuery | EncodeQuery | EncodeNoEscape) } // IsPure checks if the URL path represents a pure path. @@ -226,11 +218,11 @@ func ParseGnoURL(u *url.URL) (*GnoURL, error) { }, nil } -// NoEscapeQuery generates a URL-encoded query string from the given url.Values, -// without escaping the keys and values. The query parameters are sorted by key. -func NoEscapeQuery(v url.Values) string { - // Encode encodes the values into “URL encoded” form - // ("bar=baz&foo=quux") sorted by key. +// EncodeValues generates a URL-encoded query string from the given url.Values. +// This function is a modified version of Go's `url.Values.Encode()`: https://pkg.go.dev/net/url#Values.Encode +// It takes an additional `escape` boolean argument that disables escaping on keys and values. +// Additionally, if an empty string value is passed, it omits the `=` sign, resulting in `?key` instead of `?key=` to enhance URL readability. +func EncodeValues(v url.Values, escape bool) string { if len(v) == 0 { return "" } @@ -240,16 +232,29 @@ func NoEscapeQuery(v url.Values) string { keys = append(keys, k) } slices.Sort(keys) + for _, k := range keys { vs := v[k] - keyEscaped := k + keyEncoded := k + if escape { + keyEncoded = url.QueryEscape(k) + } for _, v := range vs { if buf.Len() > 0 { buf.WriteByte('&') } - buf.WriteString(keyEscaped) + buf.WriteString(keyEncoded) + + if len(v) == 0 { + continue // Skip `=` for empty values + } + buf.WriteByte('=') - buf.WriteString(v) + if escape { + buf.WriteString(url.QueryEscape(v)) + } else { + buf.WriteString(v) + } } } return buf.String() diff --git a/gno.land/pkg/gnoweb/url_test.go b/gno.land/pkg/gnoweb/weburl/url_test.go similarity index 87% rename from gno.land/pkg/gnoweb/url_test.go rename to gno.land/pkg/gnoweb/weburl/url_test.go index 7a491eaa149..682832f5b0d 100644 --- a/gno.land/pkg/gnoweb/url_test.go +++ b/gno.land/pkg/gnoweb/weburl/url_test.go @@ -1,4 +1,4 @@ -package gnoweb +package weburl import ( "net/url" @@ -301,7 +301,7 @@ func TestEncode(t *testing.T) { }, }, EncodeFlags: EncodeWebQuery | EncodeNoEscape, - Expected: "$fun$c=B$ ar&help=", + Expected: "$fun$c=B$ ar&help", }, { @@ -450,6 +450,69 @@ func TestEncode(t *testing.T) { EncodeFlags: EncodePath | EncodeArgs | EncodeQuery, Expected: "/r/demo/foo:example?hello=42", }, + + { + Name: "WebQuery with empty value", + GnoURL: GnoURL{ + Path: "/r/demo/foo", + WebQuery: url.Values{ + "source": {""}, + }, + }, + EncodeFlags: EncodePath | EncodeWebQuery | EncodeNoEscape, + Expected: "/r/demo/foo$source", + }, + + { + Name: "WebQuery with nil", + GnoURL: GnoURL{ + Path: "/r/demo/foo", + WebQuery: url.Values{ + "debug": nil, + }, + }, + EncodeFlags: EncodePath | EncodeWebQuery, + Expected: "/r/demo/foo$", + }, + + { + Name: "WebQuery with regular value", + GnoURL: GnoURL{ + Path: "/r/demo/foo", + WebQuery: url.Values{ + "key": {"value"}, + }, + }, + EncodeFlags: EncodePath | EncodeWebQuery, + Expected: "/r/demo/foo$key=value", + }, + + { + Name: "WebQuery mixing empty and nil and filled values", + GnoURL: GnoURL{ + Path: "/r/demo/foo", + WebQuery: url.Values{ + "source": {""}, + "debug": nil, + "user": {"Alice"}, + }, + }, + EncodeFlags: EncodePath | EncodeWebQuery, + Expected: "/r/demo/foo$source&user=Alice", + }, + + { + Name: "WebQuery mixing nil and filled values", + GnoURL: GnoURL{ + Path: "/r/demo/foo", + WebQuery: url.Values{ + "debug": nil, + "user": {"Alice"}, + }, + }, + EncodeFlags: EncodePath | EncodeWebQuery, + Expected: "/r/demo/foo$user=Alice", + }, } for _, tc := range testCases { From b4ebf6c81dae104f2da8adaf3515f833bcd27484 Mon Sep 17 00:00:00 2001 From: Manfred Touron <94029+moul@users.noreply.github.com> Date: Fri, 7 Feb 2025 09:55:30 +0100 Subject: [PATCH 135/143] feat(examples): add p/moul/cow (#3325) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Package `cow` provides a Copy-on-Write AVL tree implementation. I rebased my commits to make it easier to review the differences with the original `avl.Tree`. Here’s the commit with the changes: https://github.com/gnolang/gno/pull/3325/commits/dd4e91c2cade677f1cb3d9db109f7ebdf88c3659 --------- Signed-off-by: moul <94029+moul@users.noreply.github.com> Co-authored-by: Nathan Toups <612924+n2p5@users.noreply.github.com> --- examples/gno.land/p/moul/cow/gno.mod | 1 + examples/gno.land/p/moul/cow/node.gno | 518 ++++++++++++++ examples/gno.land/p/moul/cow/node_test.gno | 795 +++++++++++++++++++++ examples/gno.land/p/moul/cow/tree.gno | 164 +++++ examples/gno.land/p/moul/cow/tree_test.gno | 392 ++++++++++ 5 files changed, 1870 insertions(+) create mode 100644 examples/gno.land/p/moul/cow/gno.mod create mode 100644 examples/gno.land/p/moul/cow/node.gno create mode 100644 examples/gno.land/p/moul/cow/node_test.gno create mode 100644 examples/gno.land/p/moul/cow/tree.gno create mode 100644 examples/gno.land/p/moul/cow/tree_test.gno diff --git a/examples/gno.land/p/moul/cow/gno.mod b/examples/gno.land/p/moul/cow/gno.mod new file mode 100644 index 00000000000..e5dec0bc5b4 --- /dev/null +++ b/examples/gno.land/p/moul/cow/gno.mod @@ -0,0 +1 @@ +module gno.land/p/moul/cow diff --git a/examples/gno.land/p/moul/cow/node.gno b/examples/gno.land/p/moul/cow/node.gno new file mode 100644 index 00000000000..0c30871d7c4 --- /dev/null +++ b/examples/gno.land/p/moul/cow/node.gno @@ -0,0 +1,518 @@ +package cow + +//---------------------------------------- +// Node + +// Node represents a node in an AVL tree. +type Node struct { + key string // key is the unique identifier for the node. + value interface{} // value is the data stored in the node. + height int8 // height is the height of the node in the tree. + size int // size is the number of nodes in the subtree rooted at this node. + leftNode *Node // leftNode is the left child of the node. + rightNode *Node // rightNode is the right child of the node. +} + +// NewNode creates a new node with the given key and value. +func NewNode(key string, value interface{}) *Node { + return &Node{ + key: key, + value: value, + height: 0, + size: 1, + } +} + +// Size returns the size of the subtree rooted at the node. +func (node *Node) Size() int { + if node == nil { + return 0 + } + return node.size +} + +// IsLeaf checks if the node is a leaf node (has no children). +func (node *Node) IsLeaf() bool { + return node.height == 0 +} + +// Key returns the key of the node. +func (node *Node) Key() string { + return node.key +} + +// Value returns the value of the node. +func (node *Node) Value() interface{} { + return node.value +} + +// _copy creates a copy of the node (excluding value). +func (node *Node) _copy() *Node { + if node.height == 0 { + panic("Why are you copying a value node?") + } + return &Node{ + key: node.key, + height: node.height, + size: node.size, + leftNode: node.leftNode, + rightNode: node.rightNode, + } +} + +// Has checks if a node with the given key exists in the subtree rooted at the node. +func (node *Node) Has(key string) (has bool) { + if node == nil { + return false + } + if node.key == key { + return true + } + if node.height == 0 { + return false + } + if key < node.key { + return node.getLeftNode().Has(key) + } + return node.getRightNode().Has(key) +} + +// Get searches for a node with the given key in the subtree rooted at the node +// and returns its index, value, and whether it exists. +func (node *Node) Get(key string) (index int, value interface{}, exists bool) { + if node == nil { + return 0, nil, false + } + + if node.height == 0 { + if node.key == key { + return 0, node.value, true + } + if node.key < key { + return 1, nil, false + } + return 0, nil, false + } + + if key < node.key { + return node.getLeftNode().Get(key) + } + + rightNode := node.getRightNode() + index, value, exists = rightNode.Get(key) + index += node.size - rightNode.size + return index, value, exists +} + +// GetByIndex retrieves the key-value pair of the node at the given index +// in the subtree rooted at the node. +func (node *Node) GetByIndex(index int) (key string, value interface{}) { + if node.height == 0 { + if index == 0 { + return node.key, node.value + } + panic("GetByIndex asked for invalid index") + } + // TODO: could improve this by storing the sizes + leftNode := node.getLeftNode() + if index < leftNode.size { + return leftNode.GetByIndex(index) + } + return node.getRightNode().GetByIndex(index - leftNode.size) +} + +// Set inserts a new node with the given key-value pair into the subtree rooted at the node, +// and returns the new root of the subtree and whether an existing node was updated. +// +// XXX consider a better way to do this... perhaps split Node from Node. +func (node *Node) Set(key string, value interface{}) (newSelf *Node, updated bool) { + if node == nil { + return NewNode(key, value), false + } + + // Always create a new node for leaf nodes + if node.height == 0 { + return node.setLeaf(key, value) + } + + // Copy the node before modifying + newNode := node._copy() + if key < node.key { + newNode.leftNode, updated = node.getLeftNode().Set(key, value) + } else { + newNode.rightNode, updated = node.getRightNode().Set(key, value) + } + + if !updated { + newNode.calcHeightAndSize() + return newNode.balance(), updated + } + + return newNode, updated +} + +// setLeaf inserts a new leaf node with the given key-value pair into the subtree rooted at the node, +// and returns the new root of the subtree and whether an existing node was updated. +func (node *Node) setLeaf(key string, value interface{}) (newSelf *Node, updated bool) { + if key == node.key { + return NewNode(key, value), true + } + + if key < node.key { + return &Node{ + key: node.key, + height: 1, + size: 2, + leftNode: NewNode(key, value), + rightNode: node, + }, false + } + + return &Node{ + key: key, + height: 1, + size: 2, + leftNode: node, + rightNode: NewNode(key, value), + }, false +} + +// Remove deletes the node with the given key from the subtree rooted at the node. +// returns the new root of the subtree, the new leftmost leaf key (if changed), +// the removed value and the removal was successful. +func (node *Node) Remove(key string) ( + newNode *Node, newKey string, value interface{}, removed bool, +) { + if node == nil { + return nil, "", nil, false + } + if node.height == 0 { + if key == node.key { + return nil, "", node.value, true + } + return node, "", nil, false + } + if key < node.key { + var newLeftNode *Node + newLeftNode, newKey, value, removed = node.getLeftNode().Remove(key) + if !removed { + return node, "", value, false + } + if newLeftNode == nil { // left node held value, was removed + return node.rightNode, node.key, value, true + } + node = node._copy() + node.leftNode = newLeftNode + node.calcHeightAndSize() + node = node.balance() + return node, newKey, value, true + } + + var newRightNode *Node + newRightNode, newKey, value, removed = node.getRightNode().Remove(key) + if !removed { + return node, "", value, false + } + if newRightNode == nil { // right node held value, was removed + return node.leftNode, "", value, true + } + node = node._copy() + node.rightNode = newRightNode + if newKey != "" { + node.key = newKey + } + node.calcHeightAndSize() + node = node.balance() + return node, "", value, true +} + +// getLeftNode returns the left child of the node. +func (node *Node) getLeftNode() *Node { + return node.leftNode +} + +// getRightNode returns the right child of the node. +func (node *Node) getRightNode() *Node { + return node.rightNode +} + +// rotateRight performs a right rotation on the node and returns the new root. +// NOTE: overwrites node +// TODO: optimize balance & rotate +func (node *Node) rotateRight() *Node { + node = node._copy() + l := node.getLeftNode() + _l := l._copy() + + _lrCached := _l.rightNode + _l.rightNode = node + node.leftNode = _lrCached + + node.calcHeightAndSize() + _l.calcHeightAndSize() + + return _l +} + +// rotateLeft performs a left rotation on the node and returns the new root. +// NOTE: overwrites node +// TODO: optimize balance & rotate +func (node *Node) rotateLeft() *Node { + node = node._copy() + r := node.getRightNode() + _r := r._copy() + + _rlCached := _r.leftNode + _r.leftNode = node + node.rightNode = _rlCached + + node.calcHeightAndSize() + _r.calcHeightAndSize() + + return _r +} + +// calcHeightAndSize updates the height and size of the node based on its children. +// NOTE: mutates height and size +func (node *Node) calcHeightAndSize() { + node.height = maxInt8(node.getLeftNode().height, node.getRightNode().height) + 1 + node.size = node.getLeftNode().size + node.getRightNode().size +} + +// calcBalance calculates the balance factor of the node. +func (node *Node) calcBalance() int { + return int(node.getLeftNode().height) - int(node.getRightNode().height) +} + +// balance balances the subtree rooted at the node and returns the new root. +// NOTE: assumes that node can be modified +// TODO: optimize balance & rotate +func (node *Node) balance() (newSelf *Node) { + balance := node.calcBalance() + if balance >= -1 { + return node + } + if balance > 1 { + if node.getLeftNode().calcBalance() >= 0 { + // Left Left Case + return node.rotateRight() + } + // Left Right Case + left := node.getLeftNode() + node.leftNode = left.rotateLeft() + return node.rotateRight() + } + + if node.getRightNode().calcBalance() <= 0 { + // Right Right Case + return node.rotateLeft() + } + + // Right Left Case + right := node.getRightNode() + node.rightNode = right.rotateRight() + return node.rotateLeft() +} + +// Shortcut for TraverseInRange. +func (node *Node) Iterate(start, end string, cb func(*Node) bool) bool { + return node.TraverseInRange(start, end, true, true, cb) +} + +// Shortcut for TraverseInRange. +func (node *Node) ReverseIterate(start, end string, cb func(*Node) bool) bool { + return node.TraverseInRange(start, end, false, true, cb) +} + +// TraverseInRange traverses all nodes, including inner nodes. +// Start is inclusive and end is exclusive when ascending, +// Start and end are inclusive when descending. +// Empty start and empty end denote no start and no end. +// If leavesOnly is true, only visit leaf nodes. +// NOTE: To simulate an exclusive reverse traversal, +// just append 0x00 to start. +func (node *Node) TraverseInRange(start, end string, ascending bool, leavesOnly bool, cb func(*Node) bool) bool { + if node == nil { + return false + } + afterStart := (start == "" || start < node.key) + startOrAfter := (start == "" || start <= node.key) + beforeEnd := false + if ascending { + beforeEnd = (end == "" || node.key < end) + } else { + beforeEnd = (end == "" || node.key <= end) + } + + // Run callback per inner/leaf node. + stop := false + if (!node.IsLeaf() && !leavesOnly) || + (node.IsLeaf() && startOrAfter && beforeEnd) { + stop = cb(node) + if stop { + return stop + } + } + if node.IsLeaf() { + return stop + } + + if ascending { + // check lower nodes, then higher + if afterStart { + stop = node.getLeftNode().TraverseInRange(start, end, ascending, leavesOnly, cb) + } + if stop { + return stop + } + if beforeEnd { + stop = node.getRightNode().TraverseInRange(start, end, ascending, leavesOnly, cb) + } + } else { + // check the higher nodes first + if beforeEnd { + stop = node.getRightNode().TraverseInRange(start, end, ascending, leavesOnly, cb) + } + if stop { + return stop + } + if afterStart { + stop = node.getLeftNode().TraverseInRange(start, end, ascending, leavesOnly, cb) + } + } + + return stop +} + +// TraverseByOffset traverses all nodes, including inner nodes. +// A limit of math.MaxInt means no limit. +func (node *Node) TraverseByOffset(offset, limit int, descending bool, leavesOnly bool, cb func(*Node) bool) bool { + if node == nil { + return false + } + + // fast paths. these happen only if TraverseByOffset is called directly on a leaf. + if limit <= 0 || offset >= node.size { + return false + } + if node.IsLeaf() { + if offset > 0 { + return false + } + return cb(node) + } + + // go to the actual recursive function. + return node.traverseByOffset(offset, limit, descending, leavesOnly, cb) +} + +// TraverseByOffset traverses the subtree rooted at the node by offset and limit, +// in either ascending or descending order, and applies the callback function to each traversed node. +// If leavesOnly is true, only leaf nodes are visited. +func (node *Node) traverseByOffset(offset, limit int, descending bool, leavesOnly bool, cb func(*Node) bool) bool { + // caller guarantees: offset < node.size; limit > 0. + if !leavesOnly { + if cb(node) { + return true + } + } + first, second := node.getLeftNode(), node.getRightNode() + if descending { + first, second = second, first + } + if first.IsLeaf() { + // either run or skip, based on offset + if offset > 0 { + offset-- + } else { + cb(first) + limit-- + if limit <= 0 { + return false + } + } + } else { + // possible cases: + // 1 the offset given skips the first node entirely + // 2 the offset skips none or part of the first node, but the limit requires some of the second node. + // 3 the offset skips none or part of the first node, and the limit stops our search on the first node. + if offset >= first.size { + offset -= first.size // 1 + } else { + if first.traverseByOffset(offset, limit, descending, leavesOnly, cb) { + return true + } + // number of leaves which could actually be called from inside + delta := first.size - offset + offset = 0 + if delta >= limit { + return true // 3 + } + limit -= delta // 2 + } + } + + // because of the caller guarantees and the way we handle the first node, + // at this point we know that limit > 0 and there must be some values in + // this second node that we include. + + // => if the second node is a leaf, it has to be included. + if second.IsLeaf() { + return cb(second) + } + // => if it is not a leaf, it will still be enough to recursively call this + // function with the updated offset and limit + return second.traverseByOffset(offset, limit, descending, leavesOnly, cb) +} + +// Only used in testing... +func (node *Node) lmd() *Node { + if node.height == 0 { + return node + } + return node.getLeftNode().lmd() +} + +// Only used in testing... +func (node *Node) rmd() *Node { + if node.height == 0 { + return node + } + return node.getRightNode().rmd() +} + +func maxInt8(a, b int8) int8 { + if a > b { + return a + } + return b +} + +// Equal compares two nodes for structural equality. +// WARNING: This is an expensive operation that recursively traverses the entire tree structure. +// It should only be used in tests or when absolutely necessary. +func (node *Node) Equal(other *Node) bool { + // Handle nil cases + if node == nil || other == nil { + return node == other + } + + // Compare node properties + if node.key != other.key || + node.value != other.value || + node.height != other.height || + node.size != other.size { + return false + } + + // Compare children + leftEqual := (node.leftNode == nil && other.leftNode == nil) || + (node.leftNode != nil && other.leftNode != nil && node.leftNode.Equal(other.leftNode)) + if !leftEqual { + return false + } + + rightEqual := (node.rightNode == nil && other.rightNode == nil) || + (node.rightNode != nil && other.rightNode != nil && node.rightNode.Equal(other.rightNode)) + return rightEqual +} diff --git a/examples/gno.land/p/moul/cow/node_test.gno b/examples/gno.land/p/moul/cow/node_test.gno new file mode 100644 index 00000000000..c7225fe1ab0 --- /dev/null +++ b/examples/gno.land/p/moul/cow/node_test.gno @@ -0,0 +1,795 @@ +package cow + +import ( + "fmt" + "sort" + "strings" + "testing" +) + +func TestTraverseByOffset(t *testing.T) { + const testStrings = `Alfa +Alfred +Alpha +Alphabet +Beta +Beth +Book +Browser` + tt := []struct { + name string + desc bool + }{ + {"ascending", false}, + {"descending", true}, + } + + for _, tt := range tt { + t.Run(tt.name, func(t *testing.T) { + sl := strings.Split(testStrings, "\n") + + // sort a first time in the order opposite to how we'll be traversing + // the tree, to ensure that we are not just iterating through with + // insertion order. + sort.Strings(sl) + if !tt.desc { + reverseSlice(sl) + } + + r := NewNode(sl[0], nil) + for _, v := range sl[1:] { + r, _ = r.Set(v, nil) + } + + // then sort sl in the order we'll be traversing it, so that we can + // compare the result with sl. + reverseSlice(sl) + + var result []string + for i := 0; i < len(sl); i++ { + r.TraverseByOffset(i, 1, tt.desc, true, func(n *Node) bool { + result = append(result, n.Key()) + return false + }) + } + + if !slicesEqual(sl, result) { + t.Errorf("want %v got %v", sl, result) + } + + for l := 2; l <= len(sl); l++ { + // "slices" + for i := 0; i <= len(sl); i++ { + max := i + l + if max > len(sl) { + max = len(sl) + } + exp := sl[i:max] + actual := []string{} + + r.TraverseByOffset(i, l, tt.desc, true, func(tr *Node) bool { + actual = append(actual, tr.Key()) + return false + }) + if !slicesEqual(exp, actual) { + t.Errorf("want %v got %v", exp, actual) + } + } + } + }) + } +} + +func TestHas(t *testing.T) { + tests := []struct { + name string + input []string + hasKey string + expected bool + }{ + { + "has key in non-empty tree", + []string{"C", "A", "B", "E", "D"}, + "B", + true, + }, + { + "does not have key in non-empty tree", + []string{"C", "A", "B", "E", "D"}, + "F", + false, + }, + { + "has key in single-node tree", + []string{"A"}, + "A", + true, + }, + { + "does not have key in single-node tree", + []string{"A"}, + "B", + false, + }, + { + "does not have key in empty tree", + []string{}, + "A", + false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var tree *Node + for _, key := range tt.input { + tree, _ = tree.Set(key, nil) + } + + result := tree.Has(tt.hasKey) + + if result != tt.expected { + t.Errorf("Expected %v, got %v", tt.expected, result) + } + }) + } +} + +func TestGet(t *testing.T) { + tests := []struct { + name string + input []string + getKey string + expectIdx int + expectVal interface{} + expectExists bool + }{ + { + "get existing key", + []string{"C", "A", "B", "E", "D"}, + "B", + 1, + nil, + true, + }, + { + "get non-existent key (smaller)", + []string{"C", "A", "B", "E", "D"}, + "@", + 0, + nil, + false, + }, + { + "get non-existent key (larger)", + []string{"C", "A", "B", "E", "D"}, + "F", + 5, + nil, + false, + }, + { + "get from empty tree", + []string{}, + "A", + 0, + nil, + false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var tree *Node + for _, key := range tt.input { + tree, _ = tree.Set(key, nil) + } + + idx, val, exists := tree.Get(tt.getKey) + + if idx != tt.expectIdx { + t.Errorf("Expected index %d, got %d", tt.expectIdx, idx) + } + + if val != tt.expectVal { + t.Errorf("Expected value %v, got %v", tt.expectVal, val) + } + + if exists != tt.expectExists { + t.Errorf("Expected exists %t, got %t", tt.expectExists, exists) + } + }) + } +} + +func TestGetByIndex(t *testing.T) { + tests := []struct { + name string + input []string + idx int + expectKey string + expectVal interface{} + expectPanic bool + }{ + { + "get by valid index", + []string{"C", "A", "B", "E", "D"}, + 2, + "C", + nil, + false, + }, + { + "get by valid index (smallest)", + []string{"C", "A", "B", "E", "D"}, + 0, + "A", + nil, + false, + }, + { + "get by valid index (largest)", + []string{"C", "A", "B", "E", "D"}, + 4, + "E", + nil, + false, + }, + { + "get by invalid index (negative)", + []string{"C", "A", "B", "E", "D"}, + -1, + "", + nil, + true, + }, + { + "get by invalid index (out of range)", + []string{"C", "A", "B", "E", "D"}, + 5, + "", + nil, + true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var tree *Node + for _, key := range tt.input { + tree, _ = tree.Set(key, nil) + } + + if tt.expectPanic { + defer func() { + if r := recover(); r == nil { + t.Errorf("Expected a panic but didn't get one") + } + }() + } + + key, val := tree.GetByIndex(tt.idx) + + if !tt.expectPanic { + if key != tt.expectKey { + t.Errorf("Expected key %s, got %s", tt.expectKey, key) + } + + if val != tt.expectVal { + t.Errorf("Expected value %v, got %v", tt.expectVal, val) + } + } + }) + } +} + +func TestRemove(t *testing.T) { + tests := []struct { + name string + input []string + removeKey string + expected []string + }{ + { + "remove leaf node", + []string{"C", "A", "B", "D"}, + "B", + []string{"A", "C", "D"}, + }, + { + "remove node with one child", + []string{"C", "A", "B", "D"}, + "A", + []string{"B", "C", "D"}, + }, + { + "remove node with two children", + []string{"C", "A", "B", "E", "D"}, + "C", + []string{"A", "B", "D", "E"}, + }, + { + "remove root node", + []string{"C", "A", "B", "E", "D"}, + "C", + []string{"A", "B", "D", "E"}, + }, + { + "remove non-existent key", + []string{"C", "A", "B", "E", "D"}, + "F", + []string{"A", "B", "C", "D", "E"}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var tree *Node + for _, key := range tt.input { + tree, _ = tree.Set(key, nil) + } + + tree, _, _, _ = tree.Remove(tt.removeKey) + + result := make([]string, 0) + tree.Iterate("", "", func(n *Node) bool { + result = append(result, n.Key()) + return false + }) + + if !slicesEqual(tt.expected, result) { + t.Errorf("want %v got %v", tt.expected, result) + } + }) + } +} + +func TestTraverse(t *testing.T) { + tests := []struct { + name string + input []string + expected []string + }{ + { + "empty tree", + []string{}, + []string{}, + }, + { + "single node tree", + []string{"A"}, + []string{"A"}, + }, + { + "small tree", + []string{"C", "A", "B", "E", "D"}, + []string{"A", "B", "C", "D", "E"}, + }, + { + "large tree", + []string{"H", "D", "L", "B", "F", "J", "N", "A", "C", "E", "G", "I", "K", "M", "O"}, + []string{"A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O"}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var tree *Node + for _, key := range tt.input { + tree, _ = tree.Set(key, nil) + } + + t.Run("iterate", func(t *testing.T) { + var result []string + tree.Iterate("", "", func(n *Node) bool { + result = append(result, n.Key()) + return false + }) + if !slicesEqual(tt.expected, result) { + t.Errorf("want %v got %v", tt.expected, result) + } + }) + + t.Run("ReverseIterate", func(t *testing.T) { + var result []string + tree.ReverseIterate("", "", func(n *Node) bool { + result = append(result, n.Key()) + return false + }) + expected := make([]string, len(tt.expected)) + copy(expected, tt.expected) + for i, j := 0, len(expected)-1; i < j; i, j = i+1, j-1 { + expected[i], expected[j] = expected[j], expected[i] + } + if !slicesEqual(expected, result) { + t.Errorf("want %v got %v", expected, result) + } + }) + + t.Run("TraverseInRange", func(t *testing.T) { + var result []string + start, end := "C", "M" + tree.TraverseInRange(start, end, true, true, func(n *Node) bool { + result = append(result, n.Key()) + return false + }) + expected := make([]string, 0) + for _, key := range tt.expected { + if key >= start && key < end { + expected = append(expected, key) + } + } + if !slicesEqual(expected, result) { + t.Errorf("want %v got %v", expected, result) + } + }) + }) + } +} + +func TestRotateWhenHeightDiffers(t *testing.T) { + tests := []struct { + name string + input []string + expected []string + }{ + { + "right rotation when left subtree is higher", + []string{"E", "C", "A", "B", "D"}, + []string{"A", "B", "C", "E", "D"}, + }, + { + "left rotation when right subtree is higher", + []string{"A", "C", "E", "D", "F"}, + []string{"A", "C", "D", "E", "F"}, + }, + { + "left-right rotation", + []string{"E", "A", "C", "B", "D"}, + []string{"A", "B", "C", "E", "D"}, + }, + { + "right-left rotation", + []string{"A", "E", "C", "B", "D"}, + []string{"A", "B", "C", "E", "D"}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var tree *Node + for _, key := range tt.input { + tree, _ = tree.Set(key, nil) + } + + // perform rotation or balance + tree = tree.balance() + + // check tree structure + var result []string + tree.Iterate("", "", func(n *Node) bool { + result = append(result, n.Key()) + return false + }) + + if !slicesEqual(tt.expected, result) { + t.Errorf("want %v got %v", tt.expected, result) + } + }) + } +} + +func TestRotateAndBalance(t *testing.T) { + tests := []struct { + name string + input []string + expected []string + }{ + { + "right rotation", + []string{"A", "B", "C", "D", "E"}, + []string{"A", "B", "C", "D", "E"}, + }, + { + "left rotation", + []string{"E", "D", "C", "B", "A"}, + []string{"A", "B", "C", "D", "E"}, + }, + { + "left-right rotation", + []string{"C", "A", "E", "B", "D"}, + []string{"A", "B", "C", "D", "E"}, + }, + { + "right-left rotation", + []string{"C", "E", "A", "D", "B"}, + []string{"A", "B", "C", "D", "E"}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var tree *Node + for _, key := range tt.input { + tree, _ = tree.Set(key, nil) + } + + tree = tree.balance() + + var result []string + tree.Iterate("", "", func(n *Node) bool { + result = append(result, n.Key()) + return false + }) + + if !slicesEqual(tt.expected, result) { + t.Errorf("want %v got %v", tt.expected, result) + } + }) + } +} + +func slicesEqual(w1, w2 []string) bool { + if len(w1) != len(w2) { + return false + } + for i := 0; i < len(w1); i++ { + if w1[0] != w2[0] { + return false + } + } + return true +} + +func maxint8(a, b int8) int8 { + if a > b { + return a + } + return b +} + +func reverseSlice(ss []string) { + for i := 0; i < len(ss)/2; i++ { + j := len(ss) - 1 - i + ss[i], ss[j] = ss[j], ss[i] + } +} + +func TestNodeStructuralSharing(t *testing.T) { + t.Run("unmodified paths remain shared", func(t *testing.T) { + root := NewNode("B", 2) + root, _ = root.Set("A", 1) + root, _ = root.Set("C", 3) + + originalRight := root.rightNode + newRoot, _ := root.Set("A", 10) + + if newRoot.rightNode != originalRight { + t.Error("Unmodified right subtree should remain shared") + } + }) + + t.Run("multiple modifications reuse shared structure", func(t *testing.T) { + // Create initial tree + root := NewNode("B", 2) + root, _ = root.Set("A", 1) + root, _ = root.Set("C", 3) + + // Store original nodes + originalRight := root.rightNode + + // First modification + mod1, _ := root.Set("A", 10) + + // Second modification + mod2, _ := mod1.Set("C", 30) + + // Check sharing in first modification + if mod1.rightNode != originalRight { + t.Error("First modification should share unmodified right subtree") + } + + // Check that second modification creates new right node + if mod2.rightNode == originalRight { + t.Error("Second modification should create new right node") + } + }) +} + +func TestNodeCopyOnWrite(t *testing.T) { + t.Run("copy preserves structure", func(t *testing.T) { + root := NewNode("B", 2) + root, _ = root.Set("A", 1) + root, _ = root.Set("C", 3) + + // Only copy non-leaf nodes + if !root.IsLeaf() { + copied := root._copy() + if copied == root { + t.Error("Copy should create new instance") + } + + // Create temporary trees to use Equal method + original := &Tree{node: root} + copiedTree := &Tree{node: copied} + if !original.Equal(copiedTree) { + t.Error("Copied node should preserve structure") + } + } + }) + + t.Run("removal copy pattern", func(t *testing.T) { + // Create a more complex tree to test removal + root := NewNode("B", 2) + root, _ = root.Set("A", 1) + root, _ = root.Set("C", 3) + root, _ = root.Set("D", 4) // Add this to ensure proper tree structure + + // Store references to original nodes + originalRight := root.rightNode + originalRightRight := originalRight.rightNode + + // Remove "A" which should only affect the left subtree + newRoot, _, _, _ := root.Remove("A") + + // Verify right subtree remains unchanged and shared + if newRoot.rightNode != originalRight { + t.Error("Right subtree should remain shared during removal of left node") + } + + // Also verify deeper nodes remain shared + if newRoot.rightNode.rightNode != originalRightRight { + t.Error("Deep right subtree should remain shared during removal") + } + + // Verify original tree is unchanged + if _, _, exists := root.Get("A"); !exists { + t.Error("Original tree should remain unchanged") + } + }) + + t.Run("copy leaf node panic", func(t *testing.T) { + leaf := NewNode("A", 1) + + defer func() { + if r := recover(); r == nil { + t.Error("Expected panic when copying leaf node") + } + }() + + // This should panic with our specific message + leaf._copy() + }) +} + +func TestNodeEqual(t *testing.T) { + tests := []struct { + name string + node1 func() *Node + node2 func() *Node + expected bool + }{ + { + name: "nil nodes", + node1: func() *Node { return nil }, + node2: func() *Node { return nil }, + expected: true, + }, + { + name: "one nil node", + node1: func() *Node { return NewNode("A", 1) }, + node2: func() *Node { return nil }, + expected: false, + }, + { + name: "single leaf nodes equal", + node1: func() *Node { return NewNode("A", 1) }, + node2: func() *Node { return NewNode("A", 1) }, + expected: true, + }, + { + name: "single leaf nodes different key", + node1: func() *Node { return NewNode("A", 1) }, + node2: func() *Node { return NewNode("B", 1) }, + expected: false, + }, + { + name: "single leaf nodes different value", + node1: func() *Node { return NewNode("A", 1) }, + node2: func() *Node { return NewNode("A", 2) }, + expected: false, + }, + { + name: "complex trees equal", + node1: func() *Node { + n, _ := NewNode("B", 2).Set("A", 1) + n, _ = n.Set("C", 3) + return n + }, + node2: func() *Node { + n, _ := NewNode("B", 2).Set("A", 1) + n, _ = n.Set("C", 3) + return n + }, + expected: true, + }, + { + name: "complex trees different structure", + node1: func() *Node { + // Create a tree with structure: + // B + // / \ + // A D + n := NewNode("B", 2) + n, _ = n.Set("A", 1) + n, _ = n.Set("D", 4) + return n + }, + node2: func() *Node { + // Create a tree with structure: + // C + // / \ + // A D + n := NewNode("C", 3) + n, _ = n.Set("A", 1) + n, _ = n.Set("D", 4) + return n + }, + expected: false, // These trees should be different + }, + { + name: "complex trees same structure despite different insertion order", + node1: func() *Node { + n, _ := NewNode("B", 2).Set("A", 1) + n, _ = n.Set("C", 3) + return n + }, + node2: func() *Node { + n, _ := NewNode("A", 1).Set("B", 2) + n, _ = n.Set("C", 3) + return n + }, + expected: true, + }, + { + name: "truly different structures", + node1: func() *Node { + n, _ := NewNode("B", 2).Set("A", 1) + return n // Tree with just two nodes + }, + node2: func() *Node { + n, _ := NewNode("B", 2).Set("C", 3) + return n // Different two-node tree + }, + expected: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + node1 := tt.node1() + node2 := tt.node2() + result := node1.Equal(node2) + if result != tt.expected { + t.Errorf("Expected Equal to return %v for %s", tt.expected, tt.name) + println("\nComparison failed:") + println("Tree 1:") + printTree(node1, 0) + println("Tree 2:") + printTree(node2, 0) + } + }) + } +} + +// Helper function to print tree structure +func printTree(node *Node, level int) { + if node == nil { + return + } + indent := strings.Repeat(" ", level) + println(fmt.Sprintf("%sKey: %s, Value: %v, Height: %d, Size: %d", + indent, node.key, node.value, node.height, node.size)) + printTree(node.leftNode, level+1) + printTree(node.rightNode, level+1) +} diff --git a/examples/gno.land/p/moul/cow/tree.gno b/examples/gno.land/p/moul/cow/tree.gno new file mode 100644 index 00000000000..befd0a414f6 --- /dev/null +++ b/examples/gno.land/p/moul/cow/tree.gno @@ -0,0 +1,164 @@ +// Package cow provides a Copy-on-Write (CoW) AVL tree implementation. +// This is a fork of gno.land/p/demo/avl that adds CoW functionality +// while maintaining the original AVL tree interface and properties. +// +// Copy-on-Write creates a copy of a data structure only when it is modified, +// while still presenting the appearance of a full copy. When a tree is cloned, +// it initially shares all its nodes with the original tree. Only when a +// modification is made to either the original or the clone are new nodes created, +// and only along the path from the root to the modified node. +// +// Key features: +// - O(1) cloning operation +// - Minimal memory usage through structural sharing +// - Full AVL tree functionality (self-balancing, ordered operations) +// - Thread-safe for concurrent reads of shared structures +// +// While the CoW mechanism handles structural copying automatically, users need +// to consider how to handle the values stored in the tree: +// +// 1. Simple Values (int, string, etc.): +// - These are copied by value automatically +// - No additional handling needed +// +// 2. Complex Values (structs, pointers): +// - Only the reference is copied by default +// - Users must implement their own deep copy mechanism if needed +// +// Example: +// +// // Create original tree +// original := cow.NewTree() +// original.Set("key1", "value1") +// +// // Create a clone - O(1) operation +// clone := original.Clone() +// +// // Modify clone - only affected nodes are copied +// clone.Set("key1", "modified") +// +// // Original remains unchanged +// val, _ := original.Get("key1") // Returns "value1" +package cow + +type IterCbFn func(key string, value interface{}) bool + +//---------------------------------------- +// Tree + +// The zero struct can be used as an empty tree. +type Tree struct { + node *Node +} + +// NewTree creates a new empty AVL tree. +func NewTree() *Tree { + return &Tree{ + node: nil, + } +} + +// Size returns the number of key-value pair in the tree. +func (tree *Tree) Size() int { + return tree.node.Size() +} + +// Has checks whether a key exists in the tree. +// It returns true if the key exists, otherwise false. +func (tree *Tree) Has(key string) (has bool) { + return tree.node.Has(key) +} + +// Get retrieves the value associated with the given key. +// It returns the value and a boolean indicating whether the key exists. +func (tree *Tree) Get(key string) (value interface{}, exists bool) { + _, value, exists = tree.node.Get(key) + return +} + +// GetByIndex retrieves the key-value pair at the specified index in the tree. +// It returns the key and value at the given index. +func (tree *Tree) GetByIndex(index int) (key string, value interface{}) { + return tree.node.GetByIndex(index) +} + +// Set inserts a key-value pair into the tree. +// If the key already exists, the value will be updated. +// It returns a boolean indicating whether the key was newly inserted or updated. +func (tree *Tree) Set(key string, value interface{}) (updated bool) { + newnode, updated := tree.node.Set(key, value) + tree.node = newnode + return updated +} + +// Remove removes a key-value pair from the tree. +// It returns the removed value and a boolean indicating whether the key was found and removed. +func (tree *Tree) Remove(key string) (value interface{}, removed bool) { + newnode, _, value, removed := tree.node.Remove(key) + tree.node = newnode + return value, removed +} + +// Iterate performs an in-order traversal of the tree within the specified key range. +// It calls the provided callback function for each key-value pair encountered. +// If the callback returns true, the iteration is stopped. +func (tree *Tree) Iterate(start, end string, cb IterCbFn) bool { + return tree.node.TraverseInRange(start, end, true, true, + func(node *Node) bool { + return cb(node.Key(), node.Value()) + }, + ) +} + +// ReverseIterate performs a reverse in-order traversal of the tree within the specified key range. +// It calls the provided callback function for each key-value pair encountered. +// If the callback returns true, the iteration is stopped. +func (tree *Tree) ReverseIterate(start, end string, cb IterCbFn) bool { + return tree.node.TraverseInRange(start, end, false, true, + func(node *Node) bool { + return cb(node.Key(), node.Value()) + }, + ) +} + +// IterateByOffset performs an in-order traversal of the tree starting from the specified offset. +// It calls the provided callback function for each key-value pair encountered, up to the specified count. +// If the callback returns true, the iteration is stopped. +func (tree *Tree) IterateByOffset(offset int, count int, cb IterCbFn) bool { + return tree.node.TraverseByOffset(offset, count, true, true, + func(node *Node) bool { + return cb(node.Key(), node.Value()) + }, + ) +} + +// ReverseIterateByOffset performs a reverse in-order traversal of the tree starting from the specified offset. +// It calls the provided callback function for each key-value pair encountered, up to the specified count. +// If the callback returns true, the iteration is stopped. +func (tree *Tree) ReverseIterateByOffset(offset int, count int, cb IterCbFn) bool { + return tree.node.TraverseByOffset(offset, count, false, true, + func(node *Node) bool { + return cb(node.Key(), node.Value()) + }, + ) +} + +// Equal returns true if the two trees contain the same key-value pairs. +// WARNING: This is an expensive operation that recursively traverses the entire tree structure. +// It should only be used in tests or when absolutely necessary. +func (tree *Tree) Equal(other *Tree) bool { + if tree == nil || other == nil { + return tree == other + } + return tree.node.Equal(other.node) +} + +// Clone creates a shallow copy of the tree +func (tree *Tree) Clone() *Tree { + if tree == nil { + return nil + } + return &Tree{ + node: tree.node, + } +} diff --git a/examples/gno.land/p/moul/cow/tree_test.gno b/examples/gno.land/p/moul/cow/tree_test.gno new file mode 100644 index 00000000000..6ee816455b8 --- /dev/null +++ b/examples/gno.land/p/moul/cow/tree_test.gno @@ -0,0 +1,392 @@ +package cow + +import ( + "testing" +) + +func TestNewTree(t *testing.T) { + tree := NewTree() + if tree.node != nil { + t.Error("Expected tree.node to be nil") + } +} + +func TestTreeSize(t *testing.T) { + tree := NewTree() + if tree.Size() != 0 { + t.Error("Expected empty tree size to be 0") + } + + tree.Set("key1", "value1") + tree.Set("key2", "value2") + if tree.Size() != 2 { + t.Error("Expected tree size to be 2") + } +} + +func TestTreeHas(t *testing.T) { + tree := NewTree() + tree.Set("key1", "value1") + + if !tree.Has("key1") { + t.Error("Expected tree to have key1") + } + + if tree.Has("key2") { + t.Error("Expected tree to not have key2") + } +} + +func TestTreeGet(t *testing.T) { + tree := NewTree() + tree.Set("key1", "value1") + + value, exists := tree.Get("key1") + if !exists || value != "value1" { + t.Error("Expected Get to return value1 and true") + } + + _, exists = tree.Get("key2") + if exists { + t.Error("Expected Get to return false for non-existent key") + } +} + +func TestTreeGetByIndex(t *testing.T) { + tree := NewTree() + tree.Set("key1", "value1") + tree.Set("key2", "value2") + + key, value := tree.GetByIndex(0) + if key != "key1" || value != "value1" { + t.Error("Expected GetByIndex(0) to return key1 and value1") + } + + key, value = tree.GetByIndex(1) + if key != "key2" || value != "value2" { + t.Error("Expected GetByIndex(1) to return key2 and value2") + } + + defer func() { + if r := recover(); r == nil { + t.Error("Expected GetByIndex to panic for out-of-range index") + } + }() + tree.GetByIndex(2) +} + +func TestTreeRemove(t *testing.T) { + tree := NewTree() + tree.Set("key1", "value1") + + value, removed := tree.Remove("key1") + if !removed || value != "value1" || tree.Size() != 0 { + t.Error("Expected Remove to remove key-value pair") + } + + _, removed = tree.Remove("key2") + if removed { + t.Error("Expected Remove to return false for non-existent key") + } +} + +func TestTreeIterate(t *testing.T) { + tree := NewTree() + tree.Set("key1", "value1") + tree.Set("key2", "value2") + tree.Set("key3", "value3") + + var keys []string + tree.Iterate("", "", func(key string, value interface{}) bool { + keys = append(keys, key) + return false + }) + + expectedKeys := []string{"key1", "key2", "key3"} + if !slicesEqual(keys, expectedKeys) { + t.Errorf("Expected keys %v, got %v", expectedKeys, keys) + } +} + +func TestTreeReverseIterate(t *testing.T) { + tree := NewTree() + tree.Set("key1", "value1") + tree.Set("key2", "value2") + tree.Set("key3", "value3") + + var keys []string + tree.ReverseIterate("", "", func(key string, value interface{}) bool { + keys = append(keys, key) + return false + }) + + expectedKeys := []string{"key3", "key2", "key1"} + if !slicesEqual(keys, expectedKeys) { + t.Errorf("Expected keys %v, got %v", expectedKeys, keys) + } +} + +func TestTreeIterateByOffset(t *testing.T) { + tree := NewTree() + tree.Set("key1", "value1") + tree.Set("key2", "value2") + tree.Set("key3", "value3") + + var keys []string + tree.IterateByOffset(1, 2, func(key string, value interface{}) bool { + keys = append(keys, key) + return false + }) + + expectedKeys := []string{"key2", "key3"} + if !slicesEqual(keys, expectedKeys) { + t.Errorf("Expected keys %v, got %v", expectedKeys, keys) + } +} + +func TestTreeReverseIterateByOffset(t *testing.T) { + tree := NewTree() + tree.Set("key1", "value1") + tree.Set("key2", "value2") + tree.Set("key3", "value3") + + var keys []string + tree.ReverseIterateByOffset(1, 2, func(key string, value interface{}) bool { + keys = append(keys, key) + return false + }) + + expectedKeys := []string{"key2", "key1"} + if !slicesEqual(keys, expectedKeys) { + t.Errorf("Expected keys %v, got %v", expectedKeys, keys) + } +} + +// Verify that Tree implements avl.ITree +// var _ avl.ITree = (*Tree)(nil) // TODO: fix gnovm bug: ./examples/gno.land/p/moul/cow: test pkg: panic: gno.land/p/moul/cow/tree_test.gno:166:5: name avl not defined in fileset with files [node.gno tree.gno node_test.gno tree_test.gno]: + +func TestCopyOnWrite(t *testing.T) { + // Create original tree + original := NewTree() + original.Set("A", 1) + original.Set("B", 2) + original.Set("C", 3) + + // Create a clone + clone := original.Clone() + + // Modify clone + clone.Set("B", 20) + clone.Set("D", 4) + + // Verify original is unchanged + if val, _ := original.Get("B"); val != 2 { + t.Errorf("Original tree was modified: expected B=2, got B=%v", val) + } + if original.Has("D") { + t.Error("Original tree was modified: found key D") + } + + // Verify clone has new values + if val, _ := clone.Get("B"); val != 20 { + t.Errorf("Clone not updated: expected B=20, got B=%v", val) + } + if val, _ := clone.Get("D"); val != 4 { + t.Errorf("Clone not updated: expected D=4, got D=%v", val) + } +} + +func TestCopyOnWriteEdgeCases(t *testing.T) { + t.Run("nil tree clone", func(t *testing.T) { + var original *Tree + clone := original.Clone() + if clone != nil { + t.Error("Expected nil clone from nil tree") + } + }) + + t.Run("empty tree clone", func(t *testing.T) { + original := NewTree() + clone := original.Clone() + + // Modify clone + clone.Set("A", 1) + + if original.Size() != 0 { + t.Error("Original empty tree was modified") + } + if clone.Size() != 1 { + t.Error("Clone was not modified") + } + }) + + t.Run("multiple clones", func(t *testing.T) { + original := NewTree() + original.Set("A", 1) + original.Set("B", 2) + + // Create multiple clones + clone1 := original.Clone() + clone2 := original.Clone() + clone3 := clone1.Clone() + + // Modify each clone differently + clone1.Set("A", 10) + clone2.Set("B", 20) + clone3.Set("C", 30) + + // Check original remains unchanged + if val, _ := original.Get("A"); val != 1 { + t.Errorf("Original modified: expected A=1, got A=%v", val) + } + if val, _ := original.Get("B"); val != 2 { + t.Errorf("Original modified: expected B=2, got B=%v", val) + } + + // Verify each clone has correct values + if val, _ := clone1.Get("A"); val != 10 { + t.Errorf("Clone1 incorrect: expected A=10, got A=%v", val) + } + if val, _ := clone2.Get("B"); val != 20 { + t.Errorf("Clone2 incorrect: expected B=20, got B=%v", val) + } + if val, _ := clone3.Get("C"); val != 30 { + t.Errorf("Clone3 incorrect: expected C=30, got C=%v", val) + } + }) + + t.Run("clone after removal", func(t *testing.T) { + original := NewTree() + original.Set("A", 1) + original.Set("B", 2) + original.Set("C", 3) + + // Remove a node and then clone + original.Remove("B") + clone := original.Clone() + + // Modify clone + clone.Set("B", 20) + + // Verify original state + if original.Has("B") { + t.Error("Original tree should not have key B") + } + + // Verify clone state + if val, _ := clone.Get("B"); val != 20 { + t.Errorf("Clone incorrect: expected B=20, got B=%v", val) + } + }) + + t.Run("concurrent modifications", func(t *testing.T) { + original := NewTree() + original.Set("A", 1) + original.Set("B", 2) + + clone1 := original.Clone() + clone2 := original.Clone() + + // Modify same key in different clones + clone1.Set("B", 20) + clone2.Set("B", 30) + + // Each clone should have its own value + if val, _ := clone1.Get("B"); val != 20 { + t.Errorf("Clone1 incorrect: expected B=20, got B=%v", val) + } + if val, _ := clone2.Get("B"); val != 30 { + t.Errorf("Clone2 incorrect: expected B=30, got B=%v", val) + } + }) + + t.Run("deep tree modifications", func(t *testing.T) { + original := NewTree() + // Create a deeper tree + keys := []string{"M", "F", "T", "B", "H", "P", "Z"} + for _, k := range keys { + original.Set(k, k) + } + + clone := original.Clone() + + // Modify a deep node + clone.Set("H", "modified") + + // Check original remains unchanged + if val, _ := original.Get("H"); val != "H" { + t.Errorf("Original modified: expected H='H', got H=%v", val) + } + + // Verify clone modification + if val, _ := clone.Get("H"); val != "modified" { + t.Errorf("Clone incorrect: expected H='modified', got H=%v", val) + } + }) + + t.Run("rebalancing test", func(t *testing.T) { + original := NewTree() + // Insert nodes that will cause rotations + keys := []string{"A", "B", "C", "D", "E"} + for _, k := range keys { + original.Set(k, k) + } + + clone := original.Clone() + + // Add more nodes to clone to trigger rebalancing + clone.Set("F", "F") + clone.Set("G", "G") + + // Verify original structure remains unchanged + originalKeys := collectKeys(original) + expectedOriginal := []string{"A", "B", "C", "D", "E"} + if !slicesEqual(originalKeys, expectedOriginal) { + t.Errorf("Original tree structure changed: got %v, want %v", originalKeys, expectedOriginal) + } + + // Verify clone has all keys + cloneKeys := collectKeys(clone) + expectedClone := []string{"A", "B", "C", "D", "E", "F", "G"} + if !slicesEqual(cloneKeys, expectedClone) { + t.Errorf("Clone tree structure incorrect: got %v, want %v", cloneKeys, expectedClone) + } + }) + + t.Run("value mutation test", func(t *testing.T) { + type MutableValue struct { + Data string + } + + original := NewTree() + mutable := &MutableValue{Data: "original"} + original.Set("key", mutable) + + clone := original.Clone() + + // Modify the mutable value + mutable.Data = "modified" + + // Both original and clone should see the modification + // because we're not deep copying values + origVal, _ := original.Get("key") + cloneVal, _ := clone.Get("key") + + if origVal.(*MutableValue).Data != "modified" { + t.Error("Original value not modified as expected") + } + if cloneVal.(*MutableValue).Data != "modified" { + t.Error("Clone value not modified as expected") + } + }) +} + +// Helper function to collect all keys in order +func collectKeys(tree *Tree) []string { + var keys []string + tree.Iterate("", "", func(key string, _ interface{}) bool { + keys = append(keys, key) + return false + }) + return keys +} From f75a77a818573c6a7c9967b2bfc9f4ab91cca1c9 Mon Sep 17 00:00:00 2001 From: Manfred Touron <94029+moul@users.noreply.github.com> Date: Fri, 7 Feb 2025 12:21:23 +0100 Subject: [PATCH 136/143] chore(examples): add microposts example (#3660) Signed-off-by: moul <94029+moul@users.noreply.github.com> --- examples/gno.land/r/moul/microposts/README.md | 5 ++++ examples/gno.land/r/moul/microposts/gno.mod | 1 + .../r/moul/microposts/microposts_test.gno | 3 +++ examples/gno.land/r/moul/microposts/post.gno | 18 +++++++++++++ examples/gno.land/r/moul/microposts/realm.gno | 25 +++++++++++++++++++ 5 files changed, 52 insertions(+) create mode 100644 examples/gno.land/r/moul/microposts/README.md create mode 100644 examples/gno.land/r/moul/microposts/gno.mod create mode 100644 examples/gno.land/r/moul/microposts/microposts_test.gno create mode 100644 examples/gno.land/r/moul/microposts/post.gno create mode 100644 examples/gno.land/r/moul/microposts/realm.gno diff --git a/examples/gno.land/r/moul/microposts/README.md b/examples/gno.land/r/moul/microposts/README.md new file mode 100644 index 00000000000..5c7763020cd --- /dev/null +++ b/examples/gno.land/r/moul/microposts/README.md @@ -0,0 +1,5 @@ +# fork of `leon/fosdem25/microposts` + +removing optional lines to make the code more concise for slides. + +Original work here: https://gno.land/r/leon/fosdem25/microposts diff --git a/examples/gno.land/r/moul/microposts/gno.mod b/examples/gno.land/r/moul/microposts/gno.mod new file mode 100644 index 00000000000..00386f6e856 --- /dev/null +++ b/examples/gno.land/r/moul/microposts/gno.mod @@ -0,0 +1 @@ +module gno.land/r/moul/microposts diff --git a/examples/gno.land/r/moul/microposts/microposts_test.gno b/examples/gno.land/r/moul/microposts/microposts_test.gno new file mode 100644 index 00000000000..61929081e34 --- /dev/null +++ b/examples/gno.land/r/moul/microposts/microposts_test.gno @@ -0,0 +1,3 @@ +package microposts + +// empty file just to make sure that `gno test` tries to parse the implementation. diff --git a/examples/gno.land/r/moul/microposts/post.gno b/examples/gno.land/r/moul/microposts/post.gno new file mode 100644 index 00000000000..0832d8ac3c6 --- /dev/null +++ b/examples/gno.land/r/moul/microposts/post.gno @@ -0,0 +1,18 @@ +package microposts + +import ( + "std" + "time" +) + +type Post struct { + text string + author std.Address + createdAt time.Time +} + +func (p Post) String() string { + out := p.text + "\n" + out += "_" + p.createdAt.Format("02 Jan 2006, 15:04") + ", by " + p.author.String() + "_" + return out +} diff --git a/examples/gno.land/r/moul/microposts/realm.gno b/examples/gno.land/r/moul/microposts/realm.gno new file mode 100644 index 00000000000..a03b6dd958b --- /dev/null +++ b/examples/gno.land/r/moul/microposts/realm.gno @@ -0,0 +1,25 @@ +package microposts + +import ( + "std" + "strconv" + "time" +) + +var posts []*Post + +func CreatePost(text string) { + posts = append(posts, &Post{ + text: text, + author: std.PrevRealm().Addr(), // provided by env + createdAt: time.Now(), + }) +} + +func Render(_ string) string { + out := "# Posts\n" + for i := len(posts) - 1; i >= 0; i-- { + out += "### Post " + strconv.Itoa(i) + "\n" + posts[i].String() + } + return out +} From c41a056491c35cb926afbc2736657ec073d08657 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Milo=C5=A1=20=C5=BDivkovi=C4=87?= Date: Fri, 7 Feb 2025 16:25:03 +0100 Subject: [PATCH 137/143] fix: skip failed txs in Portal Loop reset (#3699) ## Description This PR sets the Portal Loop to skip backing up and looping failed transactions during a loop run. --- misc/loop/cmd/snapshotter.go | 8 ++++++-- misc/loop/go.mod | 2 +- misc/loop/go.sum | 4 ++-- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/misc/loop/cmd/snapshotter.go b/misc/loop/cmd/snapshotter.go index 0173f9aad03..eef4be36d2a 100644 --- a/misc/loop/cmd/snapshotter.go +++ b/misc/loop/cmd/snapshotter.go @@ -18,7 +18,7 @@ import ( "github.com/docker/docker/client" "github.com/docker/go-connections/nat" "github.com/gnolang/tx-archive/backup" - "github.com/gnolang/tx-archive/backup/client/http" + "github.com/gnolang/tx-archive/backup/client/rpc" "github.com/gnolang/tx-archive/backup/writer/standard" ) @@ -202,6 +202,10 @@ func (s snapshotter) backupTXs(ctx context.Context, rpcURL string) error { cfg.FromBlock = 1 cfg.Watch = false + // We want to skip failed txs on the Portal Loop reset, + // because they might (unexpectedly) succeed + cfg.SkipFailedTx = true + instanceBackupFile, err := os.Create(s.instanceBackupFile) if err != nil { return err @@ -211,7 +215,7 @@ func (s snapshotter) backupTXs(ctx context.Context, rpcURL string) error { w := standard.NewWriter(instanceBackupFile) // Create the tx-archive backup service - c, err := http.NewClient(rpcURL) + c, err := rpc.NewHTTPClient(rpcURL) if err != nil { return fmt.Errorf("could not create tx-archive client, %w", err) } diff --git a/misc/loop/go.mod b/misc/loop/go.mod index c72101c7c1e..34a25043916 100644 --- a/misc/loop/go.mod +++ b/misc/loop/go.mod @@ -8,7 +8,7 @@ require ( github.com/docker/docker v25.0.6+incompatible github.com/docker/go-connections v0.4.0 github.com/gnolang/gno v0.1.0-nightly.20240627 - github.com/gnolang/tx-archive v0.4.2 + github.com/gnolang/tx-archive v0.5.0 github.com/prometheus/client_golang v1.17.0 github.com/sirupsen/logrus v1.9.3 ) diff --git a/misc/loop/go.sum b/misc/loop/go.sum index 634dbdac082..56698812723 100644 --- a/misc/loop/go.sum +++ b/misc/loop/go.sum @@ -70,8 +70,8 @@ github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHqu github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= -github.com/gnolang/tx-archive v0.4.2 h1:xBBqLLKY9riv9yxpQgVhItCWxIji2rX6xNFmCY1cEOQ= -github.com/gnolang/tx-archive v0.4.2/go.mod h1:AGUBGO+DCLuKL80a1GJRnpcJ5gxVd9L4jEJXQB9uXp4= +github.com/gnolang/tx-archive v0.5.0 h1:npM+TfM3ufF2nz1V6hq+RLkCklPbADRZXBjiyPxXVu4= +github.com/gnolang/tx-archive v0.5.0/go.mod h1:thbXpyYT57ITGABl3hH4ftLSdO8eXaPFPi5hl6jZ2UE= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= From be61d7d4e1ba6c1ec271960bca378a95714232d3 Mon Sep 17 00:00:00 2001 From: Emmanuel T Odeke Date: Fri, 7 Feb 2025 18:44:20 +0100 Subject: [PATCH 138/143] perf(tm2/pkg/amino): reduce RAM heavy-handedness by *bytes.Buffer pooled reuse (#3489) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This change comes from an analysis of a bunch of RAM and CPU profiles and noticing that realm storage needs to invoke amino.MustMarshalAny but that in the profile for TestStdlibs, it was consuming 1.28GB. ```shell ROUTINE ======================== github.com/gnolang/gno/tm2/pkg/amino.MustMarshalAny in /Users/emmanuelodeke/go/src/github.com/gnolang/gno/tm2/pkg/amino/amino.go 0 1.28GB (flat, cum) 0.61% of Total . . 80:func MustMarshalAny(o interface{}) []byte { . 1.28GB 81: return gcdc.MustMarshalAny(o) . . 82:} . . 83: . . 84:func MarshalAnySized(o interface{}) ([]byte, error) { . . 85: return gcdc.MarshalAnySized(o) . . 86:} ``` and ```shell focus=MarshalAny Showing nodes accounting for 1303.02MB, 0.6% of 217023.96MB total Dropped 13 nodes (cum <= 1085.12MB) ----------------------------------------------------------+------------- flat flat% sum% cum cum% calls calls% + context ----------------------------------------------------------+------------- 539.49MB 100% | bytes.(*Buffer).grow 539.49MB 0.25% 0.25% 539.49MB 0.25% | bytes.growSlice ----------------------------------------------------------+------------- 706.50MB 100% | bytes.(*Buffer).Write 167.01MB 0.077% 0.33% 706.50MB 0.33% | bytes.(*Buffer).grow 539.49MB 76.36% | bytes.growSlice ----------------------------------------------------------+------------- 93MB 58.68% | github.com/gnolang/gno/tm2/pkg/amino.(*Codec).encodeReflectBinaryInterface (inline) 56.50MB 35.65% | github.com/gnolang/gno/tm2/pkg/amino.(*Codec).encodeReflectBinaryStruct (inline) 9MB 5.68% | github.com/gnolang/gno/tm2/pkg/amino.(*Codec).encodeReflectBinaryList (inline) 158.51MB 0.073% 0.4% 158.51MB 0.073% | bytes.NewBuffer ----------------------------------------------------------+------------- 145.01MB 57.77% | github.com/gnolang/gno/tm2/pkg/amino.(*Codec).writeFieldIfNotEmpty 86MB 34.26% | github.com/gnolang/gno/tm2/pkg/amino.(*Codec).encodeReflectBinaryInterface 20MB 7.97% | github.com/gnolang/gno/tm2/pkg/amino.(*Codec).encodeReflectBinaryList 85.50MB 0.039% 0.44% 251.01MB 0.12% | github.com/gnolang/gno/tm2/pkg/amino.encodeFieldNumberAndTyp3 165.51MB 65.94% | bytes.(*Buffer).Write ----------------------------------------------------------+------------- 77.01MB 100% | github.com/gnolang/gno/tm2/pkg/amino.EncodeByteSlice 61.50MB 0.028% 0.47% 77.01MB 0.035% | github.com/gnolang/gno/tm2/pkg/amino.EncodeUvarint 15.51MB 20.14% | bytes.(*Buffer).Write ----------------------------------------------------------+------------- ``` but after this change, we see more than 560MB shaved off ```shell ROUTINE ======================== github.com/gnolang/gno/tm2/pkg/amino.MustMarshalAny in /Users/emmanuelodeke/go/src/github.com/gnolang/gno/tm2/pkg/amino/amino.go 0 560.95MB (flat, cum) 0.26% of Total . . 80:func MustMarshalAny(o interface{}) []byte { . 560.95MB 81: return gcdc.MustMarshalAny(o) . . 82:} . . 83: . . 84:func MarshalAnySized(o interface{}) ([]byte, error) { . . 85: return gcdc.MarshalAnySized(o) . . 86:} ``` and ```shell ----------------------------------------------------------+------------- 16.35MB 52.46% | github.com/gnolang/gno/tm2/pkg/amino.EncodeByteSlice 14.81MB 47.54% | github.com/gnolang/gno/tm2/pkg/amino.writeMaybeBare 0 0% 0.26% 31.16MB 0.014% | bytes.(*Buffer).Write 31.16MB 100% | bytes.(*Buffer).grow ----------------------------------------------------------+------------- 31.16MB 100% | bytes.(*Buffer).Write 0 0% 0.26% 31.16MB 0.014% | bytes.(*Buffer).grow 31.16MB 100% | bytes.growSlice ----------------------------------------------------------+------------- ``` and even more after the change on ensuring that tm2/pkg/amino benchmarks could run we have quite good improvements! Running out of RAM is much worse than a couple of microseconds so we can tolerate an increase in some CPU time benchmarks. ```shell name old time/op new time/op delta Binary/EmptyStruct:encode-8 3.86µs ± 5% 3.92µs ± 5% ~ (p=0.548 n=5+5) Binary/EmptyStruct:decode-8 3.79µs ± 5% 3.79µs ± 6% ~ (p=0.690 n=5+5) Binary/PrimitivesStruct:encode-8 35.5µs ± 2% 36.5µs ± 5% ~ (p=0.151 n=5+5) Binary/PrimitivesStruct:decode-8 35.0µs ± 2% 38.6µs ±11% +10.17% (p=0.016 n=5+5) Binary/ShortArraysStruct:encode-8 5.91µs ± 6% 6.36µs ± 8% +7.61% (p=0.032 n=5+5) Binary/ShortArraysStruct:decode-8 6.07µs ±21% 6.39µs ± 8% ~ (p=0.151 n=5+5) Binary/ArraysStruct:encode-8 95.1µs ± 8% 100.6µs ± 7% ~ (p=0.222 n=5+5) Binary/ArraysStruct:decode-8 91.3µs ± 5% 98.5µs ±12% ~ (p=0.222 n=5+5) Binary/ArraysArraysStruct:encode-8 131µs ± 3% 132µs ± 6% ~ (p=0.841 n=5+5) Binary/ArraysArraysStruct:decode-8 136µs ± 9% 134µs ± 3% ~ (p=0.548 n=5+5) Binary/SlicesStruct:encode-8 85.4µs ± 1% 92.3µs ± 9% +8.15% (p=0.008 n=5+5) Binary/SlicesStruct:decode-8 87.1µs ± 8% 94.8µs ± 7% ~ (p=0.056 n=5+5) Binary/SlicesSlicesStruct:encode-8 506µs ± 2% 545µs ± 9% ~ (p=0.151 n=5+5) Binary/SlicesSlicesStruct:decode-8 506µs ± 3% 523µs ± 3% ~ (p=0.095 n=5+5) Binary/PointersStruct:encode-8 56.8µs ± 4% 65.5µs ±20% +15.43% (p=0.016 n=5+5) Binary/PointersStruct:decode-8 57.5µs ± 3% 55.9µs ± 3% ~ (p=0.095 n=5+5) Binary/PointerSlicesStruct:encode-8 162µs ± 4% 172µs ±21% ~ (p=0.841 n=5+5) Binary/PointerSlicesStruct:decode-8 163µs ± 5% 185µs ±13% ~ (p=0.095 n=5+5) Binary/ComplexSt:encode-8 314µs ± 3% 354µs ±11% +12.90% (p=0.008 n=5+5) Binary/ComplexSt:decode-8 319µs ± 2% 338µs ± 4% +5.87% (p=0.008 n=5+5) Binary/EmbeddedSt1:encode-8 39.8µs ± 7% 39.3µs ± 8% ~ (p=1.000 n=5+5) Binary/EmbeddedSt1:decode-8 37.0µs ± 4% 37.8µs ± 6% ~ (p=0.690 n=5+5) Binary/EmbeddedSt2:encode-8 316µs ± 7% 307µs ± 3% ~ (p=0.222 n=5+5) Binary/EmbeddedSt2:decode-8 316µs ± 3% 306µs ± 2% ~ (p=0.095 n=5+5) Binary/EmbeddedSt3:encode-8 217µs ± 7% 201µs ± 1% -7.26% (p=0.008 n=5+5) Binary/EmbeddedSt3:decode-8 222µs ±10% 204µs ± 2% -8.50% (p=0.032 n=5+5) Binary/EmbeddedSt4:encode-8 332µs ± 4% 325µs ± 3% ~ (p=0.421 n=5+5) Binary/EmbeddedSt4:decode-8 332µs ± 4% 324µs ± 5% ~ (p=0.095 n=5+5) Binary/EmbeddedSt5:encode-8 218µs ± 2% 212µs ± 3% ~ (p=0.056 n=5+5) Binary/EmbeddedSt5:decode-8 224µs ± 8% 209µs ± 1% -6.85% (p=0.008 n=5+5) Binary/AminoMarshalerStruct1:encode-8 9.03µs ± 6% 8.97µs ±12% ~ (p=0.841 n=5+5) Binary/AminoMarshalerStruct1:decode-8 8.91µs ± 5% 8.81µs ± 4% ~ (p=0.841 n=5+5) Binary/AminoMarshalerStruct2:encode-8 13.2µs ±10% 12.2µs ± 2% -7.26% (p=0.008 n=5+5) Binary/AminoMarshalerStruct2:decode-8 13.2µs ± 6% 12.5µs ± 5% ~ (p=0.095 n=5+5) Binary/AminoMarshalerStruct3:encode-8 7.17µs ± 3% 7.50µs ± 8% ~ (p=0.548 n=5+5) Binary/AminoMarshalerStruct3:decode-8 7.12µs ± 4% 7.84µs ±10% +10.12% (p=0.016 n=5+5) Binary/AminoMarshalerInt4:encode-8 6.60µs ± 5% 6.96µs ±11% ~ (p=0.421 n=5+5) Binary/AminoMarshalerInt4:decode-8 6.79µs ±12% 7.04µs ±15% ~ (p=0.690 n=5+5) Binary/AminoMarshalerInt5:encode-8 6.64µs ± 4% 6.92µs ± 5% +4.09% (p=0.032 n=5+5) Binary/AminoMarshalerInt5:decode-8 6.55µs ± 3% 7.76µs ±10% +18.44% (p=0.008 n=5+5) Binary/AminoMarshalerStruct6:encode-8 11.7µs ± 5% 13.2µs ±10% +13.09% (p=0.008 n=5+5) Binary/AminoMarshalerStruct6:decode-8 11.4µs ± 3% 11.6µs ± 2% ~ (p=0.222 n=5+5) Binary/AminoMarshalerStruct7:encode-8 9.86µs ± 1% 10.10µs ±19% ~ (p=0.310 n=5+5) Binary/AminoMarshalerStruct7:decode-8 9.55µs ± 3% 9.75µs ±10% ~ (p=0.690 n=5+5) name old alloc/op new alloc/op delta Binary/EmptyStruct:encode-8 1.50kB ± 0% 1.41kB ± 0% -6.32% (p=0.008 n=5+5) Binary/EmptyStruct:decode-8 1.50kB ± 0% 1.41kB ± 0% -6.32% (p=0.008 n=5+5) Binary/PrimitivesStruct:encode-8 10.4kB ± 0% 9.6kB ± 0% -7.82% (p=0.008 n=5+5) Binary/PrimitivesStruct:decode-8 10.4kB ± 0% 9.6kB ± 0% -7.82% (p=0.000 n=4+5) Binary/ShortArraysStruct:encode-8 2.11kB ± 0% 1.92kB ± 0% -9.04% (p=0.008 n=5+5) Binary/ShortArraysStruct:decode-8 2.11kB ± 0% 1.92kB ± 0% -9.04% (p=0.008 n=5+5) Binary/ArraysStruct:encode-8 25.9kB ± 0% 22.0kB ± 0% -15.04% (p=0.008 n=5+5) Binary/ArraysStruct:decode-8 25.9kB ± 0% 22.0kB ± 0% -15.04% (p=0.008 n=5+5) Binary/ArraysArraysStruct:encode-8 37.7kB ± 0% 25.3kB ± 0% -33.07% (p=0.008 n=5+5) Binary/ArraysArraysStruct:decode-8 37.7kB ± 0% 25.3kB ± 0% -33.07% (p=0.008 n=5+5) Binary/SlicesStruct:encode-8 28.2kB ± 0% 25.1kB ± 0% -10.96% (p=0.008 n=5+5) Binary/SlicesStruct:decode-8 28.2kB ± 0% 25.1kB ± 0% -10.97% (p=0.008 n=5+5) Binary/SlicesSlicesStruct:encode-8 183kB ± 0% 147kB ± 0% -19.92% (p=0.008 n=5+5) Binary/SlicesSlicesStruct:decode-8 183kB ± 0% 147kB ± 0% -19.92% (p=0.008 n=5+5) Binary/PointersStruct:encode-8 14.4kB ± 0% 13.6kB ± 0% -5.64% (p=0.008 n=5+5) Binary/PointersStruct:decode-8 14.4kB ± 0% 13.6kB ± 0% -5.64% (p=0.008 n=5+5) Binary/PointerSlicesStruct:encode-8 43.9kB ± 0% 40.2kB ± 0% -8.49% (p=0.008 n=5+5) Binary/PointerSlicesStruct:decode-8 43.9kB ± 0% 40.2kB ± 0% -8.49% (p=0.008 n=5+5) Binary/ComplexSt:encode-8 95.3kB ± 0% 78.2kB ± 0% -17.97% (p=0.008 n=5+5) Binary/ComplexSt:decode-8 95.3kB ± 0% 78.2kB ± 0% -17.97% (p=0.008 n=5+5) Binary/EmbeddedSt1:encode-8 11.3kB ± 0% 10.2kB ± 0% -9.62% (p=0.000 n=5+4) Binary/EmbeddedSt1:decode-8 11.3kB ± 0% 10.2kB ± 0% -9.61% (p=0.000 n=5+4) Binary/EmbeddedSt2:encode-8 95.5kB ± 0% 78.3kB ± 0% -17.96% (p=0.008 n=5+5) Binary/EmbeddedSt2:decode-8 95.5kB ± 0% 78.4kB ± 0% -17.94% (p=0.008 n=5+5) Binary/EmbeddedSt3:encode-8 68.3kB ± 0% 56.6kB ± 0% -17.22% (p=0.008 n=5+5) Binary/EmbeddedSt3:decode-8 68.3kB ± 0% 56.6kB ± 0% -17.21% (p=0.008 n=5+5) Binary/EmbeddedSt4:encode-8 97.2kB ± 0% 82.3kB ± 0% -15.32% (p=0.008 n=5+5) Binary/EmbeddedSt4:decode-8 97.2kB ± 0% 82.3kB ± 0% -15.31% (p=0.008 n=5+5) Binary/EmbeddedSt5:encode-8 65.9kB ± 0% 55.3kB ± 0% -16.19% (p=0.008 n=5+5) Binary/EmbeddedSt5:decode-8 66.0kB ± 0% 55.3kB ± 0% -16.18% (p=0.008 n=5+5) Binary/AminoMarshalerStruct1:encode-8 2.87kB ± 0% 2.66kB ± 0% -7.23% (p=0.008 n=5+5) Binary/AminoMarshalerStruct1:decode-8 2.87kB ± 0% 2.66kB ± 0% -7.23% (p=0.008 n=5+5) Binary/AminoMarshalerStruct2:encode-8 4.58kB ± 0% 3.62kB ± 0% -20.95% (p=0.008 n=5+5) Binary/AminoMarshalerStruct2:decode-8 4.58kB ± 0% 3.62kB ± 0% -20.95% (p=0.008 n=5+5) Binary/AminoMarshalerStruct3:encode-8 2.42kB ± 0% 2.31kB ± 0% -4.62% (p=0.008 n=5+5) Binary/AminoMarshalerStruct3:decode-8 2.42kB ± 0% 2.31kB ± 0% -4.62% (p=0.008 n=5+5) Binary/AminoMarshalerInt4:encode-8 2.38kB ± 0% 2.15kB ± 0% -9.38% (p=0.008 n=5+5) Binary/AminoMarshalerInt4:decode-8 2.38kB ± 0% 2.15kB ± 0% -9.38% (p=0.008 n=5+5) Binary/AminoMarshalerInt5:encode-8 2.36kB ± 0% 2.27kB ± 0% -4.07% (p=0.008 n=5+5) Binary/AminoMarshalerInt5:decode-8 2.36kB ± 0% 2.27kB ± 0% -4.07% (p=0.008 n=5+5) Binary/AminoMarshalerStruct6:encode-8 3.51kB ± 0% 3.19kB ± 0% -9.05% (p=0.008 n=5+5) Binary/AminoMarshalerStruct6:decode-8 3.51kB ± 0% 3.19kB ± 0% -9.05% (p=0.008 n=5+5) Binary/AminoMarshalerStruct7:encode-8 2.89kB ± 0% 2.67kB ± 0% -7.72% (p=0.008 n=5+5) Binary/AminoMarshalerStruct7:decode-8 2.89kB ± 0% 2.67kB ± 0% -7.72% (p=0.008 n=5+5) name old allocs/op new allocs/op delta Binary/EmptyStruct:encode-8 38.0 ± 0% 36.0 ± 0% -5.26% (p=0.008 n=5+5) Binary/EmptyStruct:decode-8 38.0 ± 0% 36.0 ± 0% -5.26% (p=0.008 n=5+5) Binary/PrimitivesStruct:encode-8 439 ± 0% 429 ± 0% -2.28% (p=0.008 n=5+5) Binary/PrimitivesStruct:decode-8 439 ± 0% 429 ± 0% -2.28% (p=0.008 n=5+5) Binary/ShortArraysStruct:encode-8 56.0 ± 0% 52.0 ± 0% -7.14% (p=0.008 n=5+5) Binary/ShortArraysStruct:decode-8 56.0 ± 0% 52.0 ± 0% -7.14% (p=0.008 n=5+5) Binary/ArraysStruct:encode-8 977 ± 0% 919 ± 0% -5.94% (p=0.008 n=5+5) Binary/ArraysStruct:decode-8 977 ± 0% 919 ± 0% -5.94% (p=0.008 n=5+5) Binary/ArraysArraysStruct:encode-8 1.28k ± 0% 1.08k ± 0% -15.05% (p=0.008 n=5+5) Binary/ArraysArraysStruct:decode-8 1.28k ± 0% 1.08k ± 0% -15.05% (p=0.008 n=5+5) Binary/SlicesStruct:encode-8 1.01k ± 0% 0.97k ± 0% -3.77% (p=0.008 n=5+5) Binary/SlicesStruct:decode-8 1.01k ± 0% 0.97k ± 0% -3.77% (p=0.008 n=5+5) Binary/SlicesSlicesStruct:encode-8 6.33k ± 0% 5.95k ± 0% -5.90% (p=0.008 n=5+5) Binary/SlicesSlicesStruct:decode-8 6.33k ± 0% 5.95k ± 0% -5.90% (p=0.008 n=5+5) Binary/PointersStruct:encode-8 637 ± 0% 627 ± 0% -1.57% (p=0.008 n=5+5) Binary/PointersStruct:decode-8 637 ± 0% 627 ± 0% -1.57% (p=0.008 n=5+5) Binary/PointerSlicesStruct:encode-8 1.62k ± 0% 1.56k ± 0% -3.28% (p=0.008 n=5+5) Binary/PointerSlicesStruct:decode-8 1.62k ± 0% 1.56k ± 0% -3.28% (p=0.008 n=5+5) Binary/ComplexSt:encode-8 3.37k ± 0% 3.22k ± 0% -4.62% (p=0.008 n=5+5) Binary/ComplexSt:decode-8 3.37k ± 0% 3.22k ± 0% -4.62% (p=0.008 n=5+5) Binary/EmbeddedSt1:encode-8 453 ± 0% 440 ± 0% -2.87% (p=0.008 n=5+5) Binary/EmbeddedSt1:decode-8 453 ± 0% 440 ± 0% -2.87% (p=0.008 n=5+5) Binary/EmbeddedSt2:encode-8 3.37k ± 0% 3.22k ± 0% -4.62% (p=0.008 n=5+5) Binary/EmbeddedSt2:decode-8 3.37k ± 0% 3.22k ± 0% -4.62% (p=0.008 n=5+5) Binary/EmbeddedSt3:encode-8 2.32k ± 0% 2.20k ± 0% -5.38% (p=0.008 n=5+5) Binary/EmbeddedSt3:decode-8 2.32k ± 0% 2.20k ± 0% -5.38% (p=0.008 n=5+5) Binary/EmbeddedSt4:encode-8 3.67k ± 0% 3.54k ± 0% -3.73% (p=0.008 n=5+5) Binary/EmbeddedSt4:decode-8 3.67k ± 0% 3.54k ± 0% -3.73% (p=0.008 n=5+5) Binary/EmbeddedSt5:encode-8 2.32k ± 0% 2.20k ± 0% -5.00% (p=0.008 n=5+5) Binary/EmbeddedSt5:decode-8 2.32k ± 0% 2.20k ± 0% -5.00% (p=0.008 n=5+5) Binary/AminoMarshalerStruct1:encode-8 97.0 ± 0% 94.0 ± 0% -3.09% (p=0.008 n=5+5) Binary/AminoMarshalerStruct1:decode-8 97.0 ± 0% 94.0 ± 0% -3.09% (p=0.008 n=5+5) Binary/AminoMarshalerStruct2:encode-8 149 ± 0% 133 ± 0% -10.74% (p=0.008 n=5+5) Binary/AminoMarshalerStruct2:decode-8 149 ± 0% 133 ± 0% -10.74% (p=0.008 n=5+5) Binary/AminoMarshalerStruct3:encode-8 77.0 ± 0% 76.0 ± 0% -1.30% (p=0.008 n=5+5) Binary/AminoMarshalerStruct3:decode-8 77.0 ± 0% 76.0 ± 0% -1.30% (p=0.008 n=5+5) Binary/AminoMarshalerInt4:encode-8 71.0 ± 0% 68.0 ± 0% -4.23% (p=0.008 n=5+5) Binary/AminoMarshalerInt4:decode-8 71.0 ± 0% 68.0 ± 0% -4.23% (p=0.008 n=5+5) Binary/AminoMarshalerInt5:encode-8 74.0 ± 0% 73.0 ± 0% -1.35% (p=0.008 n=5+5) Binary/AminoMarshalerInt5:decode-8 74.0 ± 0% 73.0 ± 0% -1.35% (p=0.008 n=5+5) Binary/AminoMarshalerStruct6:encode-8 122 ± 0% 117 ± 0% -4.10% (p=0.008 n=5+5) Binary/AminoMarshalerStruct6:decode-8 122 ± 0% 117 ± 0% -4.10% (p=0.008 n=5+5) Binary/AminoMarshalerStruct7:encode-8 101 ± 0% 98 ± 0% -2.97% (p=0.008 n=5+5) Binary/AminoMarshalerStruct7:decode-8 101 ± 0% 98 ± 0% -2.97% (p=0.008 n=5+5) ``` Fixes #3488 --------- Co-authored-by: Morgan Bazalgette --- contribs/gnodev/go.mod | 1 + contribs/gnodev/go.sum | 2 ++ contribs/gnofaucet/go.mod | 1 + contribs/gnofaucet/go.sum | 2 ++ contribs/gnogenesis/go.mod | 1 + contribs/gnogenesis/go.sum | 2 ++ contribs/gnohealth/go.mod | 1 + contribs/gnohealth/go.sum | 2 ++ contribs/gnokeykc/go.mod | 1 + contribs/gnokeykc/go.sum | 2 ++ contribs/gnomigrate/go.mod | 1 + contribs/gnomigrate/go.sum | 2 ++ go.mod | 1 + go.sum | 2 ++ misc/autocounterd/go.mod | 1 + misc/autocounterd/go.sum | 2 ++ misc/loop/go.mod | 1 + misc/loop/go.sum | 2 ++ tm2/pkg/amino/amino.go | 48 ++++++++++++++++++++++------------ tm2/pkg/amino/binary_encode.go | 35 +++++++++++++++++-------- tm2/pkg/amino/codec.go | 5 ++-- tm2/pkg/amino/json_encode.go | 5 ++-- tm2/pkg/amino/wellknown.go | 5 ++-- 23 files changed, 91 insertions(+), 34 deletions(-) diff --git a/contribs/gnodev/go.mod b/contribs/gnodev/go.mod index a4c106a24ee..0ad16ba9bb3 100644 --- a/contribs/gnodev/go.mod +++ b/contribs/gnodev/go.mod @@ -79,6 +79,7 @@ require ( github.com/rs/xid v1.6.0 // indirect github.com/sig-0/insertion-queue v0.0.0-20241004125609-6b3ca841346b // indirect github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 // indirect + github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect github.com/yuin/goldmark v1.7.8 // indirect github.com/yuin/goldmark-emoji v1.0.2 // indirect diff --git a/contribs/gnodev/go.sum b/contribs/gnodev/go.sum index e87c2de6441..f4bf32aafd5 100644 --- a/contribs/gnodev/go.sum +++ b/contribs/gnodev/go.sum @@ -226,6 +226,8 @@ github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOf github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= +github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= github.com/yuin/goldmark v1.3.7/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= diff --git a/contribs/gnofaucet/go.mod b/contribs/gnofaucet/go.mod index 32d2e322098..88c05e0d778 100644 --- a/contribs/gnofaucet/go.mod +++ b/contribs/gnofaucet/go.mod @@ -32,6 +32,7 @@ require ( github.com/rs/cors v1.11.1 // indirect github.com/rs/xid v1.6.0 // indirect github.com/sig-0/insertion-queue v0.0.0-20241004125609-6b3ca841346b // indirect + github.com/valyala/bytebufferpool v1.0.0 // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect go.opentelemetry.io/otel v1.34.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.34.0 // indirect diff --git a/contribs/gnofaucet/go.sum b/contribs/gnofaucet/go.sum index 5b3cfdc3289..e6743b75960 100644 --- a/contribs/gnofaucet/go.sum +++ b/contribs/gnofaucet/go.sum @@ -126,6 +126,8 @@ github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOf github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= +github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= go.etcd.io/bbolt v1.3.11 h1:yGEzV1wPz2yVCLsD8ZAiGHhHVlczyC9d1rP43/VCRJ0= go.etcd.io/bbolt v1.3.11/go.mod h1:dksAq7YMXoljX0xu6VF5DMZGbhYYoLUalEiSySYAS4I= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= diff --git a/contribs/gnogenesis/go.mod b/contribs/gnogenesis/go.mod index 5b28c8774c8..8af370f8169 100644 --- a/contribs/gnogenesis/go.mod +++ b/contribs/gnogenesis/go.mod @@ -36,6 +36,7 @@ require ( github.com/rs/xid v1.6.0 // indirect github.com/sig-0/insertion-queue v0.0.0-20241004125609-6b3ca841346b // indirect github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 // indirect + github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/zondax/hid v0.9.2 // indirect github.com/zondax/ledger-go v0.14.3 // indirect go.etcd.io/bbolt v1.3.11 // indirect diff --git a/contribs/gnogenesis/go.sum b/contribs/gnogenesis/go.sum index dcd853e9148..e3462f9c431 100644 --- a/contribs/gnogenesis/go.sum +++ b/contribs/gnogenesis/go.sum @@ -133,6 +133,8 @@ github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOf github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= +github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/zondax/hid v0.9.2 h1:WCJFnEDMiqGF64nlZz28E9qLVZ0KSJ7xpc5DLEyma2U= github.com/zondax/hid v0.9.2/go.mod h1:l5wttcP0jwtdLjqjMMWFVEE7d1zO0jvSPA9OPZxWpEM= github.com/zondax/ledger-go v0.14.3 h1:wEpJt2CEcBJ428md/5MgSLsXLBos98sBOyxNmCjfUCw= diff --git a/contribs/gnohealth/go.mod b/contribs/gnohealth/go.mod index 203dac360b7..76d7cd9c437 100644 --- a/contribs/gnohealth/go.mod +++ b/contribs/gnohealth/go.mod @@ -23,6 +23,7 @@ require ( github.com/rs/xid v1.6.0 // indirect github.com/sig-0/insertion-queue v0.0.0-20241004125609-6b3ca841346b // indirect github.com/stretchr/testify v1.10.0 // indirect + github.com/valyala/bytebufferpool v1.0.0 // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect go.opentelemetry.io/otel v1.34.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.34.0 // indirect diff --git a/contribs/gnohealth/go.sum b/contribs/gnohealth/go.sum index e51cadf1564..3c8b5de45f2 100644 --- a/contribs/gnohealth/go.sum +++ b/contribs/gnohealth/go.sum @@ -118,6 +118,8 @@ github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOf github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= +github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= go.etcd.io/bbolt v1.3.11 h1:yGEzV1wPz2yVCLsD8ZAiGHhHVlczyC9d1rP43/VCRJ0= go.etcd.io/bbolt v1.3.11/go.mod h1:dksAq7YMXoljX0xu6VF5DMZGbhYYoLUalEiSySYAS4I= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= diff --git a/contribs/gnokeykc/go.mod b/contribs/gnokeykc/go.mod index 73e51f6b25e..3abcf3d834f 100644 --- a/contribs/gnokeykc/go.mod +++ b/contribs/gnokeykc/go.mod @@ -38,6 +38,7 @@ require ( github.com/sig-0/insertion-queue v0.0.0-20241004125609-6b3ca841346b // indirect github.com/stretchr/testify v1.10.0 // indirect github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 // indirect + github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/zondax/hid v0.9.2 // indirect github.com/zondax/ledger-go v0.14.3 // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect diff --git a/contribs/gnokeykc/go.sum b/contribs/gnokeykc/go.sum index 7a058c85750..6b4f81dfcf5 100644 --- a/contribs/gnokeykc/go.sum +++ b/contribs/gnokeykc/go.sum @@ -139,6 +139,8 @@ github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOf github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= +github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/zalando/go-keyring v0.2.3 h1:v9CUu9phlABObO4LPWycf+zwMG7nlbb3t/B5wa97yms= github.com/zalando/go-keyring v0.2.3/go.mod h1:HL4k+OXQfJUWaMnqyuSOc0drfGPX2b51Du6K+MRgZMk= github.com/zondax/hid v0.9.2 h1:WCJFnEDMiqGF64nlZz28E9qLVZ0KSJ7xpc5DLEyma2U= diff --git a/contribs/gnomigrate/go.mod b/contribs/gnomigrate/go.mod index 83d88c354e7..96f6dc9bdc6 100644 --- a/contribs/gnomigrate/go.mod +++ b/contribs/gnomigrate/go.mod @@ -33,6 +33,7 @@ require ( github.com/rs/xid v1.6.0 // indirect github.com/sig-0/insertion-queue v0.0.0-20241004125609-6b3ca841346b // indirect github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 // indirect + github.com/valyala/bytebufferpool v1.0.0 // indirect go.etcd.io/bbolt v1.3.11 // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect go.opentelemetry.io/otel v1.34.0 // indirect diff --git a/contribs/gnomigrate/go.sum b/contribs/gnomigrate/go.sum index dcd853e9148..e3462f9c431 100644 --- a/contribs/gnomigrate/go.sum +++ b/contribs/gnomigrate/go.sum @@ -133,6 +133,8 @@ github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOf github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= +github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/zondax/hid v0.9.2 h1:WCJFnEDMiqGF64nlZz28E9qLVZ0KSJ7xpc5DLEyma2U= github.com/zondax/hid v0.9.2/go.mod h1:l5wttcP0jwtdLjqjMMWFVEE7d1zO0jvSPA9OPZxWpEM= github.com/zondax/ledger-go v0.14.3 h1:wEpJt2CEcBJ428md/5MgSLsXLBos98sBOyxNmCjfUCw= diff --git a/go.mod b/go.mod index ce58b8f7998..027ba6359bc 100644 --- a/go.mod +++ b/go.mod @@ -27,6 +27,7 @@ require ( github.com/sig-0/insertion-queue v0.0.0-20241004125609-6b3ca841346b github.com/stretchr/testify v1.10.0 github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 + github.com/valyala/bytebufferpool v1.0.0 github.com/yuin/goldmark v1.7.8 github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc go.etcd.io/bbolt v1.3.11 diff --git a/go.sum b/go.sum index 046d9c8c75a..5fd4cddd627 100644 --- a/go.sum +++ b/go.sum @@ -148,6 +148,8 @@ github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOf github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= +github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/yuin/goldmark v1.4.15/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yuin/goldmark v1.7.8 h1:iERMLn0/QJeHFhxSt3p6PeN9mGnvIKSpG9YYorDMnic= github.com/yuin/goldmark v1.7.8/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E= diff --git a/misc/autocounterd/go.mod b/misc/autocounterd/go.mod index 730a3d901b7..972975d4fb0 100644 --- a/misc/autocounterd/go.mod +++ b/misc/autocounterd/go.mod @@ -29,6 +29,7 @@ require ( github.com/sig-0/insertion-queue v0.0.0-20241004125609-6b3ca841346b // indirect github.com/stretchr/testify v1.10.0 // indirect github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 // indirect + github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/zondax/hid v0.9.2 // indirect github.com/zondax/ledger-go v0.14.3 // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect diff --git a/misc/autocounterd/go.sum b/misc/autocounterd/go.sum index 3d0eae7661b..6d6d87fa01a 100644 --- a/misc/autocounterd/go.sum +++ b/misc/autocounterd/go.sum @@ -133,6 +133,8 @@ github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOf github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= +github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/zondax/hid v0.9.2 h1:WCJFnEDMiqGF64nlZz28E9qLVZ0KSJ7xpc5DLEyma2U= github.com/zondax/hid v0.9.2/go.mod h1:l5wttcP0jwtdLjqjMMWFVEE7d1zO0jvSPA9OPZxWpEM= github.com/zondax/ledger-go v0.14.3 h1:wEpJt2CEcBJ428md/5MgSLsXLBos98sBOyxNmCjfUCw= diff --git a/misc/loop/go.mod b/misc/loop/go.mod index 34a25043916..4c5a3f41839 100644 --- a/misc/loop/go.mod +++ b/misc/loop/go.mod @@ -56,6 +56,7 @@ require ( github.com/sig-0/insertion-queue v0.0.0-20241004125609-6b3ca841346b // indirect github.com/stretchr/testify v1.10.0 // indirect github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 // indirect + github.com/valyala/bytebufferpool v1.0.0 // indirect go.etcd.io/bbolt v1.3.11 // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0 // indirect diff --git a/misc/loop/go.sum b/misc/loop/go.sum index 56698812723..c5aed820f5e 100644 --- a/misc/loop/go.sum +++ b/misc/loop/go.sum @@ -179,6 +179,8 @@ github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOf github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= +github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/zondax/hid v0.9.2 h1:WCJFnEDMiqGF64nlZz28E9qLVZ0KSJ7xpc5DLEyma2U= diff --git a/tm2/pkg/amino/amino.go b/tm2/pkg/amino/amino.go index 262f5d9a54e..b8942c49029 100644 --- a/tm2/pkg/amino/amino.go +++ b/tm2/pkg/amino/amino.go @@ -219,7 +219,8 @@ func (cdc *Codec) MarshalSized(o interface{}) ([]byte, error) { cdc.doAutoseal() // Write the bytes here. - buf := new(bytes.Buffer) + buf := poolBytesBuffer.Get() + defer poolBytesBuffer.Put(buf) // Write the bz without length-prefixing. bz, err := cdc.Marshal(o) @@ -239,7 +240,7 @@ func (cdc *Codec) MarshalSized(o interface{}) ([]byte, error) { return nil, err } - return buf.Bytes(), nil + return copyBytes(buf.Bytes()), nil } // MarshalSizedWriter writes the bytes as would be returned from @@ -271,8 +272,8 @@ func (cdc *Codec) MarshalAnySized(o interface{}) ([]byte, error) { cdc.doAutoseal() // Write the bytes here. - buf := new(bytes.Buffer) - + buf := poolBytesBuffer.Get() + defer poolBytesBuffer.Put(buf) // Write the bz without length-prefixing. bz, err := cdc.MarshalAny(o) if err != nil { @@ -291,7 +292,7 @@ func (cdc *Codec) MarshalAnySized(o interface{}) ([]byte, error) { return nil, err } - return buf.Bytes(), nil + return copyBytes(buf.Bytes()), nil } func (cdc *Codec) MustMarshalAnySized(o interface{}) []byte { @@ -357,7 +358,9 @@ func (cdc *Codec) MarshalReflect(o interface{}) ([]byte, error) { // Encode Amino:binary bytes. var bz []byte - buf := new(bytes.Buffer) + buf := poolBytesBuffer.Get() + defer poolBytesBuffer.Put(buf) + rt := rv.Type() info, err := cdc.getTypeInfoWLock(rt) if err != nil { @@ -377,7 +380,7 @@ func (cdc *Codec) MarshalReflect(o interface{}) ([]byte, error) { if err = cdc.writeFieldIfNotEmpty(buf, 1, info, FieldOptions{}, FieldOptions{}, rv, writeEmpty); err != nil { return nil, err } - bz = buf.Bytes() + bz = copyBytes(buf.Bytes()) } else { // The passed in BinFieldNum is only relevant for when the type is to // be encoded unpacked (elements are Typ3_ByteLength). In that case, @@ -387,7 +390,7 @@ func (cdc *Codec) MarshalReflect(o interface{}) ([]byte, error) { if err != nil { return nil, err } - bz = buf.Bytes() + bz = copyBytes(buf.Bytes()) } // If bz is empty, prefer nil. if len(bz) == 0 { @@ -443,16 +446,23 @@ func (cdc *Codec) MarshalAny(o interface{}) ([]byte, error) { } // Encode as interface. - buf := new(bytes.Buffer) + buf := poolBytesBuffer.Get() + defer poolBytesBuffer.Put(buf) err = cdc.encodeReflectBinaryInterface(buf, iinfo, reflect.ValueOf(&ivar).Elem(), FieldOptions{}, true) if err != nil { return nil, err } - bz := buf.Bytes() + bz := copyBytes(buf.Bytes()) return bz, nil } +func copyBytes(bz []byte) []byte { + cp := make([]byte, len(bz)) + copy(cp, bz) + return cp +} + // Panics if error. func (cdc *Codec) MustMarshalAny(o interface{}) []byte { bz, err := cdc.MarshalAny(o) @@ -764,7 +774,8 @@ func (cdc *Codec) JSONMarshal(o interface{}) ([]byte, error) { return []byte("null"), nil } rt := rv.Type() - w := new(bytes.Buffer) + w := poolBytesBuffer.Get() + defer poolBytesBuffer.Put(w) info, err := cdc.getTypeInfoWLock(rt) if err != nil { return nil, err @@ -772,7 +783,8 @@ func (cdc *Codec) JSONMarshal(o interface{}) ([]byte, error) { if err = cdc.encodeReflectJSON(w, info, rv, FieldOptions{}); err != nil { return nil, err } - return w.Bytes(), nil + + return copyBytes(w.Bytes()), nil } func (cdc *Codec) MarshalJSONAny(o interface{}) ([]byte, error) { @@ -802,12 +814,14 @@ func (cdc *Codec) MarshalJSONAny(o interface{}) ([]byte, error) { } // Encode as interface. - buf := new(bytes.Buffer) + buf := poolBytesBuffer.Get() + defer poolBytesBuffer.Put(buf) + err = cdc.encodeReflectJSONInterface(buf, iinfo, reflect.ValueOf(&ivar).Elem(), FieldOptions{}) if err != nil { return nil, err } - bz := buf.Bytes() + bz := copyBytes(buf.Bytes()) return bz, nil } @@ -863,12 +877,12 @@ func (cdc *Codec) MarshalJSONIndent(o interface{}, prefix, indent string) ([]byt if err != nil { return nil, err } + var out bytes.Buffer - err = json.Indent(&out, bz, prefix, indent) - if err != nil { + if err := json.Indent(&out, bz, prefix, indent); err != nil { return nil, err } - return out.Bytes(), nil + return copyBytes(out.Bytes()), nil } // ---------------------------------------- diff --git a/tm2/pkg/amino/binary_encode.go b/tm2/pkg/amino/binary_encode.go index 426cc520604..45758329284 100644 --- a/tm2/pkg/amino/binary_encode.go +++ b/tm2/pkg/amino/binary_encode.go @@ -1,12 +1,13 @@ package amino import ( - "bytes" "encoding/binary" "errors" "fmt" "io" "reflect" + + "github.com/valyala/bytebufferpool" ) const beOptionByte = 0x01 @@ -209,6 +210,8 @@ func (cdc *Codec) encodeReflectBinary(w io.Writer, info *TypeInfo, rv reflect.Va return err } +var poolBytesBuffer = new(bytebufferpool.Pool) + func (cdc *Codec) encodeReflectBinaryInterface(w io.Writer, iinfo *TypeInfo, rv reflect.Value, fopts FieldOptions, bare bool, ) (err error) { @@ -250,7 +253,9 @@ func (cdc *Codec) encodeReflectBinaryInterface(w io.Writer, iinfo *TypeInfo, rv // For Proto3 compatibility, encode interfaces as google.protobuf.Any // Write field #1, TypeURL - buf := bytes.NewBuffer(nil) + buf := poolBytesBuffer.Get() + defer poolBytesBuffer.Put(buf) + { fnum := uint32(1) err = encodeFieldNumberAndTyp3(buf, fnum, Typ3ByteLength) @@ -269,7 +274,9 @@ func (cdc *Codec) encodeReflectBinaryInterface(w io.Writer, iinfo *TypeInfo, rv { // google.protobuf.Any values must be a struct, or an unpacked list which // is indistinguishable from a struct. - buf2 := bytes.NewBuffer(nil) + buf2 := poolBytesBuffer.Get() + defer poolBytesBuffer.Put(buf2) + if !cinfo.IsStructOrUnpacked(fopts) { writeEmpty := false // Encode with an implicit struct, with a single field with number 1. @@ -356,7 +363,8 @@ func (cdc *Codec) encodeReflectBinaryList(w io.Writer, info *TypeInfo, rv reflec // Proto3 byte-length prefixing incurs alloc cost on the encoder. // Here we incur it for unpacked form for ease of dev. - buf := bytes.NewBuffer(nil) + buf := poolBytesBuffer.Get() + defer poolBytesBuffer.Put(buf) // If elem is not already a ByteLength type, write in packed form. // This is a Proto wart due to Proto backwards compatibility issues. @@ -393,6 +401,9 @@ func (cdc *Codec) encodeReflectBinaryList(w io.Writer, info *TypeInfo, rv reflec einfo.Elem.ReprType.Type.Kind() != reflect.Uint8 && einfo.Elem.ReprType.GetTyp3(fopts) != Typ3ByteLength + elemBuf := poolBytesBuffer.Get() + defer poolBytesBuffer.Put(elemBuf) + // Write elems in unpacked form. for i := 0; i < rv.Len(); i++ { // Write elements as repeated fields of the parent struct. @@ -431,20 +442,21 @@ func (cdc *Codec) encodeReflectBinaryList(w io.Writer, info *TypeInfo, rv reflec // form) are represented as lists of implicit structs. if writeImplicit { // Write field key for Value field of implicit struct. - buf2 := new(bytes.Buffer) - err = encodeFieldNumberAndTyp3(buf2, 1, Typ3ByteLength) + + err = encodeFieldNumberAndTyp3(elemBuf, 1, Typ3ByteLength) if err != nil { return } // Write field value of implicit struct to buf2. efopts := fopts efopts.BinFieldNum = 0 // dontcare - err = cdc.encodeReflectBinary(buf2, einfo, derv, efopts, false, 0) + err = cdc.encodeReflectBinary(elemBuf, einfo, derv, efopts, false, 0) if err != nil { return } // Write implicit struct to buf. - err = EncodeByteSlice(buf, buf2.Bytes()) + err = EncodeByteSlice(buf, elemBuf.Bytes()) + elemBuf.Reset() if err != nil { return } @@ -497,7 +509,8 @@ func (cdc *Codec) encodeReflectBinaryStruct(w io.Writer, info *TypeInfo, rv refl // Proto3 incurs a cost in writing non-root structs. // Here we incur it for root structs as well for ease of dev. - buf := bytes.NewBuffer(nil) + buf := poolBytesBuffer.Get() + defer poolBytesBuffer.Put(buf) for _, field := range info.Fields { // Get type info for field. @@ -553,7 +566,7 @@ func encodeFieldNumberAndTyp3(w io.Writer, num uint32, typ Typ3) (err error) { } func (cdc *Codec) writeFieldIfNotEmpty( - buf *bytes.Buffer, + buf *bytebufferpool.ByteBuffer, fieldNum uint32, finfo *TypeInfo, structsFopts FieldOptions, // the wrapping struct's FieldOptions if any @@ -579,7 +592,7 @@ func (cdc *Codec) writeFieldIfNotEmpty( if !isWriteEmpty && lBeforeValue == lAfterValue-1 && buf.Bytes()[buf.Len()-1] == 0x00 { // rollback typ3/fieldnum and last byte if // not a pointer and empty: - buf.Truncate(lBeforeKey) + buf.Set(buf.Bytes()[:lBeforeKey]) } return nil } diff --git a/tm2/pkg/amino/codec.go b/tm2/pkg/amino/codec.go index 3fa7634e3ad..ba24f49a808 100644 --- a/tm2/pkg/amino/codec.go +++ b/tm2/pkg/amino/codec.go @@ -1,7 +1,6 @@ package amino import ( - "bytes" "fmt" "io" "reflect" @@ -113,7 +112,9 @@ func (info *TypeInfo) String() string { // before it's fully populated. return "" } - buf := new(bytes.Buffer) + buf := poolBytesBuffer.Get() + defer poolBytesBuffer.Put(buf) + buf.Write([]byte("TypeInfo{")) buf.Write([]byte(fmt.Sprintf("Type:%v,", info.Type))) if info.ConcreteInfo.Registered { diff --git a/tm2/pkg/amino/json_encode.go b/tm2/pkg/amino/json_encode.go index 113c3486565..99e1b445917 100644 --- a/tm2/pkg/amino/json_encode.go +++ b/tm2/pkg/amino/json_encode.go @@ -1,7 +1,6 @@ package amino import ( - "bytes" "encoding/json" "fmt" "io" @@ -156,7 +155,9 @@ func (cdc *Codec) encodeReflectJSONInterface(w io.Writer, iinfo *TypeInfo, rv re } // Write Value to buffer - buf := new(bytes.Buffer) + buf := poolBytesBuffer.Get() + defer poolBytesBuffer.Put(buf) + cdc.encodeReflectJSON(buf, cinfo, crv, fopts) value := buf.Bytes() if len(value) == 0 { diff --git a/tm2/pkg/amino/wellknown.go b/tm2/pkg/amino/wellknown.go index 7720c2894d9..4053c23e893 100644 --- a/tm2/pkg/amino/wellknown.go +++ b/tm2/pkg/amino/wellknown.go @@ -3,7 +3,6 @@ package amino // NOTE: We must not depend on protubuf libraries for serialization. import ( - "bytes" "fmt" "io" "reflect" @@ -342,7 +341,9 @@ func encodeReflectBinaryWellKnown(w io.Writer, info *TypeInfo, rv reflect.Value, } // Maybe recurse with length-prefixing. if !bare { - buf := bytes.NewBuffer(nil) + buf := poolBytesBuffer.Get() + defer poolBytesBuffer.Put(buf) + ok, err = encodeReflectBinaryWellKnown(buf, info, rv, fopts, true) if err != nil { return false, err From 568836c7162cfdbd0ca0f982f0804bfd878ed016 Mon Sep 17 00:00:00 2001 From: Norman Date: Sun, 9 Feb 2025 00:13:49 +0100 Subject: [PATCH 139/143] chore: remove 'SelfContained' Signed-off-by: Norman --- examples/no_cycles_test.go | 2 +- gno.land/cmd/gnoland/start.go | 2 +- gno.land/pkg/integration/node_testing.go | 4 +--- gno.land/pkg/integration/pkgloader.go | 5 ++--- gnovm/cmd/gno/mod.go | 2 +- gnovm/pkg/packages/expand_patterns.go | 6 ------ gnovm/pkg/packages/load.go | 23 +++++++---------------- gnovm/pkg/packages/load_test.go | 5 ++--- 8 files changed, 15 insertions(+), 34 deletions(-) diff --git a/examples/no_cycles_test.go b/examples/no_cycles_test.go index ef9322833b5..b9436f12934 100644 --- a/examples/no_cycles_test.go +++ b/examples/no_cycles_test.go @@ -24,7 +24,7 @@ var injectedTestingLibs = []string{"encoding/json", "fmt", "os", "internal/os_te // TestNoCycles checks that there is no import cycles in stdlibs and non-draft examples func TestNoCycles(t *testing.T) { // find examples and stdlibs - cfg := &packages.LoadConfig{SelfContained: true, Deps: true, Fetcher: pkgdownload.NewNoopFetcher()} + cfg := &packages.LoadConfig{Deps: true, Fetcher: pkgdownload.NewNoopFetcher()} pkgs, err := packages.Load(cfg, filepath.Join(gnoenv.RootDir(), "examples", "...")) require.NoError(t, err) diff --git a/gno.land/cmd/gnoland/start.go b/gno.land/cmd/gnoland/start.go index 87fc4f7b79a..7f4fca93884 100644 --- a/gno.land/cmd/gnoland/start.go +++ b/gno.land/cmd/gnoland/start.go @@ -426,7 +426,7 @@ func generateGenesisFile(io commands.IO, genesisFile string, privKey crypto.Priv // Load examples folder examplesDir := filepath.Join(c.gnoRootDir, "examples") - loadCfg := &packages.LoadConfig{IO: io, SelfContained: true} + loadCfg := &packages.LoadConfig{IO: io} pkgsTxs, err := gnoland.LoadPackagesFromDir(loadCfg, examplesDir, txSender, genesisDeployFee) if err != nil { return fmt.Errorf("unable to load examples folder: %w", err) diff --git a/gno.land/pkg/integration/node_testing.go b/gno.land/pkg/integration/node_testing.go index 630d2ad0edb..677d37b1221 100644 --- a/gno.land/pkg/integration/node_testing.go +++ b/gno.land/pkg/integration/node_testing.go @@ -8,7 +8,6 @@ import ( "github.com/gnolang/gno/gno.land/pkg/gnoland" "github.com/gnolang/gno/gno.land/pkg/gnoland/ugnot" - "github.com/gnolang/gno/gnovm/pkg/packages" abci "github.com/gnolang/gno/tm2/pkg/bft/abci/types" tmcfg "github.com/gnolang/gno/tm2/pkg/bft/config" "github.com/gnolang/gno/tm2/pkg/bft/node" @@ -136,8 +135,7 @@ func LoadDefaultPackages(t TestingTS, creator bft.Address, gnoroot string) []gno examplesDir := filepath.Join(gnoroot, "examples") defaultFee := std.NewFee(50000, std.MustParseCoin(ugnot.ValueString(1000000))) - cfg := &packages.LoadConfig{SelfContained: true} - txs, err := gnoland.LoadPackagesFromDir(cfg, examplesDir, creator, defaultFee) + txs, err := gnoland.LoadPackagesFromDir(nil, examplesDir, creator, defaultFee) require.NoError(t, err) return txs diff --git a/gno.land/pkg/integration/pkgloader.go b/gno.land/pkg/integration/pkgloader.go index a79901dfb3c..b68e30be54e 100644 --- a/gno.land/pkg/integration/pkgloader.go +++ b/gno.land/pkg/integration/pkgloader.go @@ -88,8 +88,7 @@ func (pl *PkgsLoader) LoadPackages(creatorKey crypto.PrivKey, fee std.Fee, depos func (pl *PkgsLoader) LoadAllPackagesFromDir(path string) error { // list all packages from target path - cfg := &packages.LoadConfig{SelfContained: true} - pkgslist, err := packages.Load(cfg, filepath.Join(path, "...")) + pkgslist, err := packages.Load(nil, filepath.Join(path, "...")) if err != nil { return fmt.Errorf("listing gno packages: %w", err) } @@ -105,7 +104,7 @@ func (pl *PkgsLoader) LoadAllPackagesFromDir(path string) error { func (pl *PkgsLoader) LoadPackage(pkgDir string, name string) error { examples := filepath.Join(gnoenv.RootDir(), "examples", "...") - cfg := &packages.LoadConfig{Deps: true, SelfContained: true, DepsPatterns: []string{examples}} + cfg := &packages.LoadConfig{Deps: true, DepsPatterns: []string{examples}} pkgs, err := packages.Load(cfg, pkgDir) if err != nil { return fmt.Errorf("%q: loading: %w", pkgDir, err) diff --git a/gnovm/cmd/gno/mod.go b/gnovm/cmd/gno/mod.go index 8d9a8a6ec95..6a1174d4aca 100644 --- a/gnovm/cmd/gno/mod.go +++ b/gnovm/cmd/gno/mod.go @@ -352,7 +352,7 @@ func execModWhy(args []string, io commands.IO) error { return flag.ErrHelp } - conf := &packages.LoadConfig{SelfContained: true, Fetcher: testPackageFetcher} + conf := &packages.LoadConfig{Fetcher: testPackageFetcher} pkgs, err := packages.Load(conf, ".") if err != nil { return err diff --git a/gnovm/pkg/packages/expand_patterns.go b/gnovm/pkg/packages/expand_patterns.go index 43b7e36e973..8f2d0a9be88 100644 --- a/gnovm/pkg/packages/expand_patterns.go +++ b/gnovm/pkg/packages/expand_patterns.go @@ -90,12 +90,6 @@ func expandPatterns(conf *LoadConfig, patterns ...string) ([]*pkgMatch, error) { switch patKind { case patternKindRecursiveRemote: return nil, fmt.Errorf("%s: recursive remote patterns are not supported", match) - - case patternKindRemote: - // XXX: weird - if conf.SelfContained { - return nil, fmt.Errorf("%s: remote patterns are not supported in self-contained mode", match) - } case patternKindSingleFile: return nil, fmt.Errorf("unexpected single pattern at this point") } diff --git a/gnovm/pkg/packages/load.go b/gnovm/pkg/packages/load.go index ec21475fb7b..aa19b0b08e1 100644 --- a/gnovm/pkg/packages/load.go +++ b/gnovm/pkg/packages/load.go @@ -22,14 +22,13 @@ import ( // FIXME: should not include pkgs imported in test except for matches and only when Test flag is set type LoadConfig struct { - IO commands.IO - Fetcher pkgdownload.PackageFetcher - Deps bool - Cache PkgList - SelfContained bool - AllowEmpty bool - DepsPatterns []string - Fset *token.FileSet + IO commands.IO + Fetcher pkgdownload.PackageFetcher + Deps bool + Cache PkgList + AllowEmpty bool + DepsPatterns []string + Fset *token.FileSet } var injectedTestingLibs = []string{"encoding/json", "fmt", "internal/os_test", "os"} @@ -161,14 +160,6 @@ func Load(conf *LoadConfig, patterns ...string) (PkgList, error) { continue } - if conf.SelfContained { - pkg.Errors = append(pkg.Errors, &Error{ - Pos: pkg.Dir, - Msg: fmt.Sprintf("package %q not found (self-contained)", imp.PkgPath), - }) - continue - } - dir := gnomod.PackageDir("", module.Version{Path: imp.PkgPath}) if err := downloadPackage(conf, imp.PkgPath, dir); err != nil { pkg.Errors = append(pkg.Errors, &Error{ diff --git a/gnovm/pkg/packages/load_test.go b/gnovm/pkg/packages/load_test.go index 32134ae4af9..7e5383f2fc5 100644 --- a/gnovm/pkg/packages/load_test.go +++ b/gnovm/pkg/packages/load_test.go @@ -169,9 +169,8 @@ func TestSortPkgs(t *testing.T) { func TestLoadNonDraftExamples(t *testing.T) { examples := filepath.Join("..", "..", "..", "examples", "...") conf := LoadConfig{ - Deps: true, - Fetcher: pkgdownload.NewNoopFetcher(), - SelfContained: true, + Deps: true, + Fetcher: pkgdownload.NewNoopFetcher(), } pkgs, err := Load(&conf, examples) From 847f3254f684453d42545f65f9416504792c3320 Mon Sep 17 00:00:00 2001 From: Norman Date: Sun, 9 Feb 2025 00:14:15 +0100 Subject: [PATCH 140/143] chore: add testdata Signed-off-by: Norman --- gnovm/pkg/packages/testdata/workspace-1/foo.gno | 5 +++++ gnovm/pkg/packages/testdata/workspace-1/foo_test.gno | 7 +++++++ gnovm/pkg/packages/testdata/workspace-1/gno.mod | 1 + gnovm/pkg/packages/testdata/workspace-2/bar/bar.gno | 9 +++++++++ .../packages/testdata/workspace-2/bar/bar_test.gno | 11 +++++++++++ gnovm/pkg/packages/testdata/workspace-2/bar/gno.mod | 1 + gnovm/pkg/packages/testdata/workspace-2/foo/foo.gno | 5 +++++ gnovm/pkg/packages/testdata/workspace-2/foo/gno.mod | 1 + gnovm/pkg/packages/testdata/workspace-2/lib.gno | 5 +++++ gnovm/pkg/packages/testdata/workspace-2/main.gno | 5 +++++ 10 files changed, 50 insertions(+) create mode 100644 gnovm/pkg/packages/testdata/workspace-1/foo.gno create mode 100644 gnovm/pkg/packages/testdata/workspace-1/foo_test.gno create mode 100644 gnovm/pkg/packages/testdata/workspace-1/gno.mod create mode 100644 gnovm/pkg/packages/testdata/workspace-2/bar/bar.gno create mode 100644 gnovm/pkg/packages/testdata/workspace-2/bar/bar_test.gno create mode 100644 gnovm/pkg/packages/testdata/workspace-2/bar/gno.mod create mode 100644 gnovm/pkg/packages/testdata/workspace-2/foo/foo.gno create mode 100644 gnovm/pkg/packages/testdata/workspace-2/foo/gno.mod create mode 100644 gnovm/pkg/packages/testdata/workspace-2/lib.gno create mode 100644 gnovm/pkg/packages/testdata/workspace-2/main.gno diff --git a/gnovm/pkg/packages/testdata/workspace-1/foo.gno b/gnovm/pkg/packages/testdata/workspace-1/foo.gno new file mode 100644 index 00000000000..b415646b776 --- /dev/null +++ b/gnovm/pkg/packages/testdata/workspace-1/foo.gno @@ -0,0 +1,5 @@ +package foo + +func Foo() { + +} diff --git a/gnovm/pkg/packages/testdata/workspace-1/foo_test.gno b/gnovm/pkg/packages/testdata/workspace-1/foo_test.gno new file mode 100644 index 00000000000..94e35a23eaf --- /dev/null +++ b/gnovm/pkg/packages/testdata/workspace-1/foo_test.gno @@ -0,0 +1,7 @@ +package foo + +import "testing" + +func TestFoo(t *testing.T) { + Foo() +} diff --git a/gnovm/pkg/packages/testdata/workspace-1/gno.mod b/gnovm/pkg/packages/testdata/workspace-1/gno.mod new file mode 100644 index 00000000000..c53b59f1314 --- /dev/null +++ b/gnovm/pkg/packages/testdata/workspace-1/gno.mod @@ -0,0 +1 @@ +module gno.example.com/r/wspace1/foo \ No newline at end of file diff --git a/gnovm/pkg/packages/testdata/workspace-2/bar/bar.gno b/gnovm/pkg/packages/testdata/workspace-2/bar/bar.gno new file mode 100644 index 00000000000..b585483e11e --- /dev/null +++ b/gnovm/pkg/packages/testdata/workspace-2/bar/bar.gno @@ -0,0 +1,9 @@ +package bar + +import ( + "gno.example.com/r/wspace2/foo" +) + +func Bar() { + foo.Foo() +} diff --git a/gnovm/pkg/packages/testdata/workspace-2/bar/bar_test.gno b/gnovm/pkg/packages/testdata/workspace-2/bar/bar_test.gno new file mode 100644 index 00000000000..826356e4912 --- /dev/null +++ b/gnovm/pkg/packages/testdata/workspace-2/bar/bar_test.gno @@ -0,0 +1,11 @@ +package bar_test + +import ( + "testing" + + "gno.example.com/r/wspace2/bar" +) + +func TestBar(t *testing.T) { + bar.Bar() +} diff --git a/gnovm/pkg/packages/testdata/workspace-2/bar/gno.mod b/gnovm/pkg/packages/testdata/workspace-2/bar/gno.mod new file mode 100644 index 00000000000..c8309ba7853 --- /dev/null +++ b/gnovm/pkg/packages/testdata/workspace-2/bar/gno.mod @@ -0,0 +1 @@ +module gno.example.com/r/wspace2/bar \ No newline at end of file diff --git a/gnovm/pkg/packages/testdata/workspace-2/foo/foo.gno b/gnovm/pkg/packages/testdata/workspace-2/foo/foo.gno new file mode 100644 index 00000000000..b415646b776 --- /dev/null +++ b/gnovm/pkg/packages/testdata/workspace-2/foo/foo.gno @@ -0,0 +1,5 @@ +package foo + +func Foo() { + +} diff --git a/gnovm/pkg/packages/testdata/workspace-2/foo/gno.mod b/gnovm/pkg/packages/testdata/workspace-2/foo/gno.mod new file mode 100644 index 00000000000..fa56b4ca975 --- /dev/null +++ b/gnovm/pkg/packages/testdata/workspace-2/foo/gno.mod @@ -0,0 +1 @@ +module gno.example.com/r/wspace2/foo \ No newline at end of file diff --git a/gnovm/pkg/packages/testdata/workspace-2/lib.gno b/gnovm/pkg/packages/testdata/workspace-2/lib.gno new file mode 100644 index 00000000000..da96e79bb0d --- /dev/null +++ b/gnovm/pkg/packages/testdata/workspace-2/lib.gno @@ -0,0 +1,5 @@ +package main + +func lib() { + +} diff --git a/gnovm/pkg/packages/testdata/workspace-2/main.gno b/gnovm/pkg/packages/testdata/workspace-2/main.gno new file mode 100644 index 00000000000..bb129b83733 --- /dev/null +++ b/gnovm/pkg/packages/testdata/workspace-2/main.gno @@ -0,0 +1,5 @@ +package main + +func main() { + lib() +} From a673eccf2b888b4df21bf09f7cbf90f074b9344a Mon Sep 17 00:00:00 2001 From: Norman Date: Sun, 9 Feb 2025 01:17:34 +0100 Subject: [PATCH 141/143] chore: rm uneeded instance Signed-off-by: Norman --- gnovm/pkg/gnolang/files_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gnovm/pkg/gnolang/files_test.go b/gnovm/pkg/gnolang/files_test.go index 389d54af205..a7a50312520 100644 --- a/gnovm/pkg/gnolang/files_test.go +++ b/gnovm/pkg/gnolang/files_test.go @@ -39,7 +39,7 @@ func TestFiles(t *testing.T) { rootDir, err := filepath.Abs(filepath.FromSlash("../../..")) require.NoError(t, err) - pkgs, err := packages.Load(&packages.LoadConfig{}, filepath.Join(rootDir, "examples", "...")) + pkgs, err := packages.Load(nil, filepath.Join(rootDir, "examples", "...")) require.NoError(t, err) newOpts := func() *test.TestOptions { From e07af669878bf1115726995a33144a5f60ae44a5 Mon Sep 17 00:00:00 2001 From: Norman Date: Sun, 9 Feb 2025 01:17:50 +0100 Subject: [PATCH 142/143] chore: revert uneeded change Signed-off-by: Norman --- gnovm/pkg/transpiler/transpiler.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gnovm/pkg/transpiler/transpiler.go b/gnovm/pkg/transpiler/transpiler.go index b83ef867641..bd4bb1b1bc9 100644 --- a/gnovm/pkg/transpiler/transpiler.go +++ b/gnovm/pkg/transpiler/transpiler.go @@ -227,7 +227,7 @@ func (ctx *transpileCtx) transformCallExpr(_ *astutil.Cursor, ce *ast.CallExpr) // its replacement is a type with the method AssertOriginCall, this system // will incorrectly add a `nil` as the first argument. // A full fix requires understanding scope; the Go standard library recommends - // using go/types, which for proper functioning requires an packages + // using go/types, which for proper functioning requires an importer // which can work with Gno. This is deferred for a future PR. id, ok := fe.X.(*ast.Ident) if !ok { From 6735ef476e53262f5f40b58b59e4b0f3ab15763f Mon Sep 17 00:00:00 2001 From: Norman Date: Sun, 9 Feb 2025 14:19:53 +0100 Subject: [PATCH 143/143] chore: add tests and cut uneeded features Signed-off-by: Norman --- .../internal/txs/txs_add_packages.go | 2 +- gno.land/cmd/gnoland/start.go | 2 +- gnovm/cmd/gno/list.go | 2 +- gnovm/cmd/gno/mod.go | 4 +- gnovm/cmd/gno/test.go | 2 +- gnovm/cmd/gno/tool_lint.go | 2 +- gnovm/pkg/gnolang/debugger_test.go | 2 +- gnovm/pkg/packages/analyze_packages.go | 45 ++- gnovm/pkg/packages/download_deps.go | 15 +- gnovm/pkg/packages/download_deps_test.go | 14 +- gnovm/pkg/packages/expand_patterns.go | 213 -------------- gnovm/pkg/packages/load.go | 36 +-- gnovm/pkg/packages/load_test.go | 257 ++++++++++++++++- gnovm/pkg/packages/patterns.go | 212 +++++++++++++- gnovm/pkg/packages/patterns_test.go | 267 ++++++++++++++++++ .../examplespkgfetcher/examplespkgfetcher.go | 13 +- gnovm/pkg/packages/testdata/emptydir/.gitkeep | 0 .../gno.example.com/p/demo/avl/avl.gno | 5 + .../gno.example.com/p/demo/avl/gno.mod | 1 + .../testdata/workspace-1/emptydir/.gitkeep | 0 .../testdata/workspace-2/bar/baz/baz.gno | 5 + gnovm/pkg/packages/utils.go | 12 + 22 files changed, 836 insertions(+), 275 deletions(-) delete mode 100644 gnovm/pkg/packages/expand_patterns.go create mode 100644 gnovm/pkg/packages/patterns_test.go create mode 100644 gnovm/pkg/packages/testdata/emptydir/.gitkeep create mode 100644 gnovm/pkg/packages/testdata/examples/gno.example.com/p/demo/avl/avl.gno create mode 100644 gnovm/pkg/packages/testdata/examples/gno.example.com/p/demo/avl/gno.mod create mode 100644 gnovm/pkg/packages/testdata/workspace-1/emptydir/.gitkeep create mode 100644 gnovm/pkg/packages/testdata/workspace-2/bar/baz/baz.gno create mode 100644 gnovm/pkg/packages/utils.go diff --git a/contribs/gnogenesis/internal/txs/txs_add_packages.go b/contribs/gnogenesis/internal/txs/txs_add_packages.go index daabc726bf0..94295a0663f 100644 --- a/contribs/gnogenesis/internal/txs/txs_add_packages.go +++ b/contribs/gnogenesis/internal/txs/txs_add_packages.go @@ -125,7 +125,7 @@ func execTxsAddPackages( parsedTxs := make([]gnoland.TxWithMetadata, 0) for _, path := range args { // Generate transactions from the packages (recursively) - loadCfg := &packages.LoadConfig{IO: io, Deps: true} + loadCfg := &packages.LoadConfig{Out: io.Err(), Deps: true} txs, err := gnoland.LoadPackagesFromDir(loadCfg, path, creator, genesisDeployFee) if err != nil { return fmt.Errorf("unable to load txs from directory, %w", err) diff --git a/gno.land/cmd/gnoland/start.go b/gno.land/cmd/gnoland/start.go index 7f4fca93884..fb9a623e797 100644 --- a/gno.land/cmd/gnoland/start.go +++ b/gno.land/cmd/gnoland/start.go @@ -426,7 +426,7 @@ func generateGenesisFile(io commands.IO, genesisFile string, privKey crypto.Priv // Load examples folder examplesDir := filepath.Join(c.gnoRootDir, "examples") - loadCfg := &packages.LoadConfig{IO: io} + loadCfg := &packages.LoadConfig{Out: io.Err()} pkgsTxs, err := gnoland.LoadPackagesFromDir(loadCfg, examplesDir, txSender, genesisDeployFee) if err != nil { return fmt.Errorf("unable to load examples folder: %w", err) diff --git a/gnovm/cmd/gno/list.go b/gnovm/cmd/gno/list.go index 6ca32b3eb4e..1545754a190 100644 --- a/gnovm/cmd/gno/list.go +++ b/gnovm/cmd/gno/list.go @@ -51,7 +51,7 @@ func execList(cfg *listCfg, args []string, io commands.IO) error { return flag.ErrHelp } - conf := &packages.LoadConfig{IO: io, Fetcher: testPackageFetcher} + conf := &packages.LoadConfig{Out: io.Err(), Fetcher: testPackageFetcher} if cfg.deps { conf.Deps = true diff --git a/gnovm/cmd/gno/mod.go b/gnovm/cmd/gno/mod.go index 6a1174d4aca..e3b2bc5244b 100644 --- a/gnovm/cmd/gno/mod.go +++ b/gnovm/cmd/gno/mod.go @@ -238,7 +238,7 @@ func execModDownload(cfg *modDownloadCfg, args []string, io commands.IO) error { return fmt.Errorf("validate: %w", err) } - conf := &packages.LoadConfig{IO: io, Fetcher: fetcher} + conf := &packages.LoadConfig{Out: io.Err(), Fetcher: fetcher} if err := packages.DownloadDeps(conf, path, gnoMod); err != nil { return err } @@ -311,7 +311,7 @@ func execModTidy(cfg *modTidyCfg, args []string, io commands.IO) error { } if cfg.recursive { - loadCfg := packages.LoadConfig{IO: io, Fetcher: testPackageFetcher} + loadCfg := packages.LoadConfig{Out: io.Err(), Fetcher: testPackageFetcher} pkgs, err := packages.Load(&loadCfg, filepath.Join(wd, "...")) if err != nil { return err diff --git a/gnovm/cmd/gno/test.go b/gnovm/cmd/gno/test.go index 87bd4b00ce0..d51ce11e28f 100644 --- a/gnovm/cmd/gno/test.go +++ b/gnovm/cmd/gno/test.go @@ -170,7 +170,7 @@ func execTest(cfg *testCfg, args []string, io commands.IO) error { // Find targets for test. conf := &packages.LoadConfig{ - IO: io, + Out: io.Err(), Fetcher: testPackageFetcher, DepsPatterns: depsPatterns, Deps: true, diff --git a/gnovm/cmd/gno/tool_lint.go b/gnovm/cmd/gno/tool_lint.go index 9588463c293..cdbe5a5b626 100644 --- a/gnovm/cmd/gno/tool_lint.go +++ b/gnovm/cmd/gno/tool_lint.go @@ -94,7 +94,7 @@ func execLint(cfg *lintCfg, args []string, io commands.IO) error { rootDir = gnoenv.RootDir() } - loadCfg := &packages.LoadConfig{IO: io, Fetcher: testPackageFetcher, Deps: true} + loadCfg := &packages.LoadConfig{Out: io.Err(), Fetcher: testPackageFetcher, Deps: true} if cfg.rootExamples { examples := filepath.Join(gnoenv.RootDir(), "examples", "...") loadCfg.DepsPatterns = append(loadCfg.DepsPatterns, examples) diff --git a/gnovm/pkg/gnolang/debugger_test.go b/gnovm/pkg/gnolang/debugger_test.go index cd80e2dcc80..a524ef20def 100644 --- a/gnovm/pkg/gnolang/debugger_test.go +++ b/gnovm/pkg/gnolang/debugger_test.go @@ -90,7 +90,7 @@ func TestDebug(t *testing.T) { cont := brk + "continue\n" cont2 := "break 21\ncontinue\n" - pkgs, err := packages.Load(&packages.LoadConfig{}, filepath.FromSlash("../../../examples/...")) + pkgs, err := packages.Load(nil, filepath.FromSlash("../../../examples/...")) require.NoError(t, err) runDebugTest(t, debugTarget, []dtest{ diff --git a/gnovm/pkg/packages/analyze_packages.go b/gnovm/pkg/packages/analyze_packages.go index 0964ce0cba3..e8bfbc3b1d4 100644 --- a/gnovm/pkg/packages/analyze_packages.go +++ b/gnovm/pkg/packages/analyze_packages.go @@ -4,6 +4,7 @@ import ( "errors" "fmt" "go/token" + "io" "os" "path" "path/filepath" @@ -13,9 +14,10 @@ import ( "github.com/gnolang/gno/gnovm/pkg/gnoenv" "github.com/gnolang/gno/gnovm/pkg/gnolang" "github.com/gnolang/gno/gnovm/pkg/gnomod" + "github.com/gnolang/gno/gnovm/pkg/packages/pkgdownload" ) -func readPackages(matches []*pkgMatch, known PkgList, fset *token.FileSet) (PkgList, error) { +func readPackages(out io.Writer, fetcher pkgdownload.PackageFetcher, matches []*pkgMatch, known PkgList, fset *token.FileSet) (PkgList, error) { if fset == nil { fset = token.NewFileSet() } @@ -31,7 +33,7 @@ func readPackages(matches []*pkgMatch, known PkgList, fset *token.FileSet) (PkgL } else if known.GetByDir(pkgMatch.Dir) != nil { continue } else { - pkg = readPkgDir(pkgMatch.Dir, "", fset) + pkg = readPkgDir(out, fetcher, pkgMatch.Dir, "", fset) } pkg.Match = pkgMatch.Match pkgs = append(pkgs, pkg) @@ -67,7 +69,7 @@ func readCLAPkg(patterns []string, fset *token.FileSet) (*Package, error) { return readPkgFiles(pkg, files, fset), nil } -func readPkgDir(pkgDir string, importPath string, fset *token.FileSet) *Package { +func readPkgDir(out io.Writer, fetcher pkgdownload.PackageFetcher, pkgDir string, importPath string, fset *token.FileSet) *Package { pkg := &Package{ Dir: pkgDir, Files: FilesMap{}, @@ -87,12 +89,45 @@ func readPkgDir(pkgDir string, importPath string, fset *token.FileSet) *Package }) return pkg } - pkg.ImportPath = libPath + pkg.ImportPath = filepath.ToSlash(libPath) + } + + // FIXME: concurrency + don't overwrite + modCachePath := gnomod.ModCachePath() + if strings.HasPrefix(filepath.Clean(pkg.Dir), modCachePath) { + pkgPath, err := filepath.Rel(modCachePath, pkg.Dir) + if err != nil { + pkg.Errors = append(pkg.Errors, &Error{ + Pos: pkg.Dir, + Msg: fmt.Errorf("pkgpath from cache dir: %w", err).Error(), + }) + return pkg + } + pkgPath = path.Clean(pkgPath) + _, err = os.Stat(pkg.Dir) + if err != nil { + if os.IsNotExist(err) { + if err := downloadPackage(out, fetcher, pkgPath, pkg.Dir); err != nil { + pkg.Errors = append(pkg.Errors, &Error{ + Pos: pkg.Dir, + Msg: err.Error(), + }) + return pkg + } + pkg.ImportPath = pkgPath + } else { + pkg.Errors = append(pkg.Errors, &Error{ + Pos: pkg.Dir, + Msg: fmt.Errorf("stat: %w", err).Error(), + }) + return pkg + } + } } } files := []string{} - entries, err := os.ReadDir(pkgDir) + entries, err := os.ReadDir(pkg.Dir) if err != nil { pkg.Errors = append(pkg.Errors, &Error{ Pos: pkg.Dir, diff --git a/gnovm/pkg/packages/download_deps.go b/gnovm/pkg/packages/download_deps.go index 4e948490d05..4415eae483f 100644 --- a/gnovm/pkg/packages/download_deps.go +++ b/gnovm/pkg/packages/download_deps.go @@ -3,6 +3,7 @@ package packages import ( "errors" "fmt" + "io" "os" "path/filepath" "strings" @@ -14,8 +15,8 @@ import ( ) // DownloadDeps recursively fetches the imports of a local package while following a given gno.mod replace directives -func DownloadDeps(conf *LoadConfig, pkgDir string, gnoMod *gnomod.File) error { - if conf.Fetcher == nil { +func DownloadDeps(out io.Writer, fetcher pkgdownload.PackageFetcher, pkgDir string, gnoMod *gnomod.File) error { + if fetcher == nil { return errors.New("fetcher is nil") } @@ -39,11 +40,11 @@ func DownloadDeps(conf *LoadConfig, pkgDir string, gnoMod *gnomod.File) error { depDir := gnomod.PackageDir("", module.Version{Path: resolvedPkgPath}) - if err := downloadPackage(conf, resolvedPkgPath, depDir); err != nil { + if err := downloadPackage(out, fetcher, resolvedPkgPath, depDir); err != nil { return fmt.Errorf("download import %q of %q: %w", resolvedPkgPath, pkgDir, err) } - if err := DownloadDeps(conf, depDir, gnoMod); err != nil { + if err := DownloadDeps(out, fetcher, depDir, gnoMod); err != nil { return err } } @@ -52,7 +53,7 @@ func DownloadDeps(conf *LoadConfig, pkgDir string, gnoMod *gnomod.File) error { } // Download downloads a remote gno package by pkg path and store it at dst -func downloadPackage(conf *LoadConfig, pkgPath string, dst string) error { +func downloadPackage(out io.Writer, fetcher pkgdownload.PackageFetcher, pkgPath string, dst string) error { modFilePath := filepath.Join(dst, "gno.mod") if _, err := os.Stat(modFilePath); err == nil { @@ -62,9 +63,9 @@ func downloadPackage(conf *LoadConfig, pkgPath string, dst string) error { return fmt.Errorf("stat downloaded module %q at %q: %w", pkgPath, dst, err) } - conf.IO.ErrPrintfln("gno: downloading %s", pkgPath) + fmt.Fprintf(out, "gno: downloading %s\n", pkgPath) - if err := pkgdownload.Download(pkgPath, dst, conf.Fetcher); err != nil { + if err := pkgdownload.Download(pkgPath, dst, fetcher); err != nil { return err } diff --git a/gnovm/pkg/packages/download_deps_test.go b/gnovm/pkg/packages/download_deps_test.go index acb63f4f322..4fcdd459f94 100644 --- a/gnovm/pkg/packages/download_deps_test.go +++ b/gnovm/pkg/packages/download_deps_test.go @@ -1,7 +1,6 @@ package packages import ( - "bytes" "fmt" "os" "path/filepath" @@ -9,7 +8,6 @@ import ( "github.com/gnolang/gno/gnovm/pkg/gnomod" "github.com/gnolang/gno/gnovm/pkg/packages/pkgdownload/examplespkgfetcher" - "github.com/gnolang/gno/tm2/pkg/commands" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "golang.org/x/mod/modfile" @@ -103,9 +101,7 @@ func TestDownloadDeps(t *testing.T) { }, } { t.Run(tc.desc, func(t *testing.T) { - mockErr := bytes.NewBufferString("") - io := commands.NewTestIO() - io.SetErr(commands.WriteNopCloser(mockErr)) + mockErr := writeCloser{} dirPath := t.TempDir() @@ -115,12 +111,10 @@ func TestDownloadDeps(t *testing.T) { tmpGnoHome := t.TempDir() t.Setenv("GNOHOME", tmpGnoHome) - fetcher := examplespkgfetcher.New() - - conf := &LoadConfig{IO: io, Fetcher: fetcher} + fetcher := examplespkgfetcher.New("") // gno: downloading dependencies - err = DownloadDeps(conf, dirPath, &tc.modFile) + err = DownloadDeps(&mockErr, fetcher, dirPath, &tc.modFile) if tc.errorShouldContain != "" { require.ErrorContains(t, err, tc.errorShouldContain) } else { @@ -146,7 +140,7 @@ func TestDownloadDeps(t *testing.T) { mockErr.Reset() // Try fetching again. Should be cached - DownloadDeps(conf, dirPath, &tc.modFile) + DownloadDeps(&mockErr, fetcher, dirPath, &tc.modFile) for _, c := range tc.ioErrContains { assert.NotContains(t, mockErr.String(), c) } diff --git a/gnovm/pkg/packages/expand_patterns.go b/gnovm/pkg/packages/expand_patterns.go deleted file mode 100644 index 8f2d0a9be88..00000000000 --- a/gnovm/pkg/packages/expand_patterns.go +++ /dev/null @@ -1,213 +0,0 @@ -package packages - -import ( - "errors" - "fmt" - "io/fs" - "os" - "path" - "path/filepath" - "slices" - "strings" - - "github.com/gnolang/gno/gnovm/pkg/gnomod" - "golang.org/x/mod/module" -) - -/* -REARCH: -- patterns: - - single file -> not supported - - local directory -> add as package - - remote -> download and add dst - - recursive local -> walk directories at the pattern and select the ones that contain gno.mod or .gno files and add them as packages - - recursive remote -> not supported -*/ - -type pkgMatch struct { - Dir string - Match []string -} - -func expandPatterns(conf *LoadConfig, patterns ...string) ([]*pkgMatch, error) { - pkgMatches := []*pkgMatch{} - - addPkgDir := func(dir string, match *string) { - idx := slices.IndexFunc(pkgMatches, func(sum *pkgMatch) bool { return sum.Dir == dir }) - if idx == -1 { - matches := []string{} - if match != nil { - matches = append(matches, *match) - } - pkgMatches = append(pkgMatches, &pkgMatch{Dir: dir, Match: matches}) - return - } - if match == nil { - return - } - if slices.Contains(pkgMatches[idx].Match, *match) { - return - } - pkgMatches[idx].Match = append(pkgMatches[idx].Match, *match) - } - - kinds := make([]patternKind, 0, len(patterns)) - for _, match := range patterns { - patKind, err := getPatternKind(match) - if err != nil { - return nil, fmt.Errorf("%s: %w", match, err) - } - kinds = append(kinds, patKind) - } - - if slices.Contains(kinds, patternKindSingleFile) { - remaining := []string{} - remainingKinds := []patternKind{} - - files := make([]string, 0, len(patterns)) - for i, match := range patterns { - kind := kinds[i] - if kind != patternKindSingleFile { - remaining = append(remaining, match) - remainingKinds = append(remainingKinds, kind) - continue - } - if !strings.HasSuffix(match, ".gno") { - return nil, fmt.Errorf("named files must be .gno files: %s", match) - } - files = append(files, match) - } - - pkgMatches = append(pkgMatches, &pkgMatch{Dir: "command-line-arguments", Match: files}) - - patterns = remaining - kinds = remainingKinds - } - - for i, match := range patterns { - patKind := kinds[i] - - switch patKind { - case patternKindRecursiveRemote: - return nil, fmt.Errorf("%s: recursive remote patterns are not supported", match) - case patternKindSingleFile: - return nil, fmt.Errorf("unexpected single pattern at this point") - } - - pat, err := cleanPattern(match, patKind) - if err != nil { - return nil, fmt.Errorf("%s: %w", match, err) - } - - switch patKind { - case patternKindDirectory: - addPkgDir(pat, &match) - - case patternKindRemote: - dir := gnomod.PackageDir("", module.Version{Path: pat}) - if err := downloadPackage(conf, pat, dir); err != nil { - return nil, err - } - addPkgDir(dir, &match) - - case patternKindRecursiveLocal: - dirs, err := expandRecursive(pat) - if err != nil { - return nil, fmt.Errorf("%s: %w", pat, err) - } - if len(dirs) == 0 { - conf.IO.ErrPrintfln(`gno: warning: %q matched no packages`, match) - } - for _, dir := range dirs { - addPkgDir(dir, &match) - } - } - } - - return pkgMatches, nil -} - -func expandRecursive(pat string) ([]string, error) { - root, _ := filepath.Split(pat) - - info, err := os.Stat(root) - if err != nil { - return nil, err - } - - if !info.IsDir() { - return nil, errors.New("glob root is not a directory") - } - - // we swallow errors after this point as we want the most packages we can get - dirs := []string{} - _ = fs.WalkDir(os.DirFS(root), ".", func(path string, d fs.DirEntry, err error) error { - if err != nil { - return nil - } - if d.IsDir() { - return nil - } - dir, base := filepath.Split(path) - dir = filepath.Join(root, dir) - if slices.Contains(dirs, dir) { - return nil - } - if base == "gno.mod" || strings.HasSuffix(base, ".gno") { - dirs = append(dirs, dir) - } - return nil - }) - - return dirs, nil -} - -type patternKind int - -const ( - patternKindUnknown = iota - patternKindSingleFile - patternKindDirectory - patternKindRecursiveLocal - patternKindRemote - patternKindRecursiveRemote -) - -func getPatternKind(pat string) (patternKind, error) { - isLitteral := PatternIsLiteral(pat) - - if patternIsRemote(pat) { - if isLitteral { - return patternKindRemote, nil - } - if filepath.Base(pat) != "..." { - return patternKindUnknown, fmt.Errorf("%s: partial globs are not supported", pat) - } - return patternKindRecursiveRemote, nil - } - - if !isLitteral { - return patternKindRecursiveLocal, nil - } - - info, err := os.Stat(pat) - if err != nil { - return patternKindUnknown, err - } - if info.IsDir() { - return patternKindDirectory, nil - } - - return patternKindSingleFile, nil -} - -func cleanPattern(pat string, kind patternKind) (string, error) { - switch kind { - case patternKindSingleFile, patternKindDirectory, patternKindRecursiveLocal: - return filepath.Abs(pat) - case patternKindRemote, patternKindRecursiveRemote: - return path.Clean(pat), nil - default: - return "", fmt.Errorf("unknown pattern kind %d", kind) - } -} diff --git a/gnovm/pkg/packages/load.go b/gnovm/pkg/packages/load.go index aa19b0b08e1..ab5866629c9 100644 --- a/gnovm/pkg/packages/load.go +++ b/gnovm/pkg/packages/load.go @@ -4,6 +4,7 @@ import ( "errors" "fmt" "go/token" + "io" "os" "path/filepath" "slices" @@ -15,20 +16,19 @@ import ( "github.com/gnolang/gno/gnovm/pkg/gnomod" "github.com/gnolang/gno/gnovm/pkg/packages/pkgdownload" "github.com/gnolang/gno/gnovm/pkg/packages/pkgdownload/rpcpkgfetcher" - "github.com/gnolang/gno/tm2/pkg/commands" "golang.org/x/mod/module" ) // FIXME: should not include pkgs imported in test except for matches and only when Test flag is set type LoadConfig struct { - IO commands.IO Fetcher pkgdownload.PackageFetcher Deps bool Cache PkgList AllowEmpty bool DepsPatterns []string Fset *token.FileSet + Out io.Writer } var injectedTestingLibs = []string{"encoding/json", "fmt", "internal/os_test", "os"} @@ -38,8 +38,8 @@ func IsInjectedTestingStdlib(pkgPath string) bool { } func (conf *LoadConfig) applyDefaults() { - if conf.IO == nil { - conf.IO = commands.NewTestIO() + if conf.Out == nil { + conf.Out = io.Discard } if conf.Fetcher == nil { conf.Fetcher = rpcpkgfetcher.New(nil) @@ -49,32 +49,18 @@ func (conf *LoadConfig) applyDefaults() { } } -func LoadWorkspace(conf *LoadConfig, dir string) (PkgList, error) { - absDir, err := filepath.Abs(dir) - if err != nil { - return nil, err - } - - workRoot, err := gnomod.FindRootDir(absDir) - if err != nil { - return nil, err - } - - return Load(conf, filepath.Join(workRoot, "...")) -} - func Load(conf *LoadConfig, patterns ...string) (PkgList, error) { if conf == nil { conf = &LoadConfig{} } conf.applyDefaults() - expanded, err := expandPatterns(conf, patterns...) + expanded, err := expandPatterns(conf.Out, patterns...) if err != nil { return nil, err } - pkgs, err := readPackages(expanded, nil, conf.Fset) + pkgs, err := readPackages(conf.Out, conf.Fetcher, expanded, nil, conf.Fset) if err != nil { return nil, err } @@ -89,7 +75,7 @@ func Load(conf *LoadConfig, patterns ...string) (PkgList, error) { return pkgs, nil } - extra, err := expandPatterns(conf, conf.DepsPatterns...) + extra, err := expandPatterns(conf.Out, conf.DepsPatterns...) if err != nil { return nil, err } @@ -97,7 +83,7 @@ func Load(conf *LoadConfig, patterns ...string) (PkgList, error) { m.Match = []string{} } - extraPkgs, err := readPackages(extra, pkgs, conf.Fset) + extraPkgs, err := readPackages(conf.Out, conf.Fetcher, extra, pkgs, conf.Fset) if err != nil { return nil, err } @@ -156,19 +142,19 @@ func Load(conf *LoadConfig, patterns ...string) (PkgList, error) { } pkg.Errors = append(pkg.Errors, err) } - markForVisit(readPkgDir(dir, imp.PkgPath, conf.Fset)) + markForVisit(readPkgDir(conf.Out, conf.Fetcher, dir, imp.PkgPath, conf.Fset)) continue } dir := gnomod.PackageDir("", module.Version{Path: imp.PkgPath}) - if err := downloadPackage(conf, imp.PkgPath, dir); err != nil { + if err := downloadPackage(conf.Out, conf.Fetcher, imp.PkgPath, dir); err != nil { pkg.Errors = append(pkg.Errors, &Error{ Pos: pkg.Dir, Msg: fmt.Sprintf("download %q: %v", imp.PkgPath, err), }) continue } - markForVisit(readPkgDir(dir, imp.PkgPath, conf.Fset)) + markForVisit(readPkgDir(conf.Out, conf.Fetcher, dir, imp.PkgPath, conf.Fset)) } loaded = append(loaded, pkg) diff --git a/gnovm/pkg/packages/load_test.go b/gnovm/pkg/packages/load_test.go index 7e5383f2fc5..b8a2722b8e0 100644 --- a/gnovm/pkg/packages/load_test.go +++ b/gnovm/pkg/packages/load_test.go @@ -1,15 +1,20 @@ package packages import ( + "fmt" "os" + "path" "path/filepath" + "strings" "testing" + "github.com/gnolang/gno/gnovm/pkg/gnomod" "github.com/gnolang/gno/gnovm/pkg/packages/pkgdownload" "github.com/gnolang/gno/gnovm/pkg/packages/pkgdownload/examplespkgfetcher" "github.com/gnolang/gno/tm2/pkg/testutils" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "golang.org/x/mod/module" ) func TestListAndNonDraftPkgs(t *testing.T) { @@ -76,7 +81,7 @@ func TestListAndNonDraftPkgs(t *testing.T) { } // List packages - pkgs, err := Load(&LoadConfig{AllowEmpty: true, Fetcher: examplespkgfetcher.New()}, filepath.Join(dirPath, "...")) + pkgs, err := Load(&LoadConfig{AllowEmpty: true, Fetcher: examplespkgfetcher.New("")}, filepath.Join(dirPath, "...")) require.NoError(t, err) assert.Equal(t, len(tc.outListPkgs), len(pkgs)) for _, p := range pkgs { @@ -183,3 +188,253 @@ func TestLoadNonDraftExamples(t *testing.T) { require.Empty(t, pkg.Errors) } } + +func TestDataLoad(t *testing.T) { + cwd, err := os.Getwd() + require.NoError(t, err) + + localFromSlash := func(p string) string { + cp := path.Clean(p) + fp := filepath.FromSlash(cp) + if filepath.IsAbs(fp) { + return fp + } + return "." + string(filepath.Separator) + fp + } + + // XXX: this won't guarantee clean state, since we only have one remote test it's okay but we need to fix paralelization + homeDir := t.TempDir() + t.Setenv("GNOHOME", homeDir) + + tcs := []struct { + name string + // workdir string + patterns []string + conf *LoadConfig + res PkgList + errShouldContain string + ioerrShouldContain string + }{ + { + name: "workspace-1-root", + patterns: []string{localFromSlash("./testdata/workspace-1")}, + res: PkgList{{ + ImportPath: "gno.example.com/r/wspace1/foo", + Name: "foo", + Root: filepath.Join(cwd, "testdata", "workspace-1"), + ModPath: "gno.example.com/r/wspace1/foo", + Dir: filepath.Join(cwd, "testdata", "workspace-1"), + Match: []string{localFromSlash("./testdata/workspace-1")}, + Files: FilesMap{ + FileKindPackageSource: {"foo.gno"}, + FileKindTest: {"foo_test.gno"}, + }, + Imports: map[FileKind][]string{ + FileKindTest: {"testing"}, + }, + }}, + }, + { + name: "workspace-1-root-abs", + patterns: []string{filepath.Join(cwd, "testdata", "workspace-1")}, + res: PkgList{{ + ImportPath: "gno.example.com/r/wspace1/foo", + Name: "foo", + Root: filepath.Join(cwd, "testdata", "workspace-1"), + ModPath: "gno.example.com/r/wspace1/foo", + Dir: filepath.Join(cwd, "testdata", "workspace-1"), + Match: []string{filepath.Join(cwd, "testdata", "workspace-1")}, + Files: FilesMap{ + FileKindPackageSource: {"foo.gno"}, + FileKindTest: {"foo_test.gno"}, + }, + Imports: map[FileKind][]string{ + FileKindTest: {"testing"}, + }, + }}, + }, + { + name: "workspace-1-recursive", + patterns: []string{localFromSlash("./testdata/workspace-1/...")}, + res: PkgList{{ + ImportPath: "gno.example.com/r/wspace1/foo", + Name: "foo", + Root: filepath.Join(cwd, "testdata", "workspace-1"), + ModPath: "gno.example.com/r/wspace1/foo", + Dir: filepath.Join(cwd, "testdata", "workspace-1"), + Match: []string{localFromSlash("./testdata/workspace-1/...")}, + Files: FilesMap{ + FileKindPackageSource: {"foo.gno"}, + FileKindTest: {"foo_test.gno"}, + }, + Imports: map[FileKind][]string{ + FileKindTest: {"testing"}, + }, + }}, + }, + { + name: "workspace-2-root", + patterns: []string{localFromSlash("./testdata/workspace-2")}, + res: PkgList{{ + Name: "main", + Dir: filepath.Join(cwd, "testdata", "workspace-2"), + Match: []string{localFromSlash("./testdata/workspace-2")}, + Files: FilesMap{ + FileKindPackageSource: {"lib.gno", "main.gno"}, + }, + }}, + }, + { + name: "workspace-2-recursive", + patterns: []string{localFromSlash("./testdata/workspace-2/...")}, + res: PkgList{{ + Name: "main", + Dir: filepath.Join(cwd, "testdata", "workspace-2"), + Match: []string{localFromSlash("./testdata/workspace-2/...")}, + Files: FilesMap{ + FileKindPackageSource: {"lib.gno", "main.gno"}, + }, + }, { + ImportPath: "gno.example.com/r/wspace2/bar", + Name: "bar", + Dir: filepath.Join(cwd, "testdata", "workspace-2", "bar"), + Root: filepath.Join(cwd, "testdata", "workspace-2", "bar"), + ModPath: "gno.example.com/r/wspace2/bar", + Match: []string{localFromSlash("./testdata/workspace-2/...")}, + Files: FilesMap{ + FileKindPackageSource: {"bar.gno"}, + FileKindXTest: {"bar_test.gno"}, + }, + Imports: FilesMap{ + FileKindPackageSource: {"gno.example.com/r/wspace2/foo"}, + FileKindXTest: {"gno.example.com/r/wspace2/bar", "testing"}, + }, + }, { + ImportPath: "gno.example.com/r/wspace2/bar/baz", + Name: "baz", + Dir: filepath.Join(cwd, "testdata", "workspace-2", "bar", "baz"), + Root: filepath.Join(cwd, "testdata", "workspace-2", "bar"), + ModPath: "gno.example.com/r/wspace2/bar", + Match: []string{localFromSlash("./testdata/workspace-2/...")}, + Files: FilesMap{ + FileKindPackageSource: {"baz.gno"}, + }, + }, { + ImportPath: "gno.example.com/r/wspace2/foo", + ModPath: "gno.example.com/r/wspace2/foo", + Name: "foo", + Dir: filepath.Join(cwd, "testdata", "workspace-2", "foo"), + Root: filepath.Join(cwd, "testdata", "workspace-2", "foo"), + Match: []string{localFromSlash("./testdata/workspace-2/...")}, + Files: FilesMap{ + FileKindPackageSource: {"foo.gno"}, + }, + }}, + }, + { + name: "stdlibs", + patterns: []string{"math/bits"}, + res: PkgList{{ + ImportPath: "math/bits", + Name: "bits", + Dir: filepath.Join(StdlibDir("math"), "bits"), + Match: []string{"math/bits"}, + Files: FilesMap{ + FileKindPackageSource: { + "bits.gno", + "bits_errors.gno", + "bits_tables.gno", + }, + FileKindTest: { + "export_test.gno", + }, + FileKindXTest: { + "bits_test.gno", + }, + }, + Imports: map[FileKind][]string{ + FileKindPackageSource: {"errors"}, + FileKindXTest: {"math/bits", "testing"}, + }, + }}, + }, + { + name: "remote", + patterns: []string{"gno.example.com/p/demo/avl"}, + res: PkgList{{ + ImportPath: "gno.example.com/p/demo/avl", + Name: "avl", + Dir: gnomod.PackageDir("", module.Version{Path: "gno.example.com/p/demo/avl"}), + Root: gnomod.PackageDir("", module.Version{Path: "gno.example.com/p/demo/avl"}), + ModPath: "gno.example.com/p/demo/avl", + Match: []string{"gno.example.com/p/demo/avl"}, + Files: FilesMap{ + FileKindPackageSource: {"avl.gno"}, + }, + }}, + ioerrShouldContain: `gno: downloading gno.example.com/p/demo/avl`, + }, + { + name: "err-stdlibs-recursive", + patterns: []string{"strings/..."}, + errShouldContain: "recursive remote patterns are not supported", + }, + { + name: "err-remote-recursive", + patterns: []string{"gno.example.com/test/strings/..."}, + errShouldContain: "recursive remote patterns are not supported", + }, + { + name: "err-recursive-noroot", + patterns: []string{"./testdata/notexists/..."}, + errShouldContain: "no such file or directory", + }, + { + name: "warn-recursive-nothing", + patterns: []string{localFromSlash("./testdata/workspace-1/emptydir/...")}, + errShouldContain: "no packages", + ioerrShouldContain: fmt.Sprintf(`gno: warning: %q matched no packages`, localFromSlash("./testdata/workspace-1/emptydir/...")), + }, + } + for _, tc := range tcs { + t.Run(tc.name, func(t *testing.T) { + errBuf := &writeCloser{} + conf := &LoadConfig{Out: errBuf, Fetcher: examplespkgfetcher.New(filepath.Join("testdata", "examples"))} + + res, err := Load(conf, tc.patterns...) + + if tc.errShouldContain == "" { + require.NoError(t, err) + } else { + require.ErrorContains(t, err, tc.errShouldContain) + } + + if tc.ioerrShouldContain != "" { + require.Contains(t, errBuf.String(), tc.ioerrShouldContain) + } else { + require.Equal(t, errBuf.String(), "") + } + + // normalize res + for _, pkg := range res { + pkg.ImportsSpecs = nil + if len(pkg.Errors) == 0 { + pkg.Errors = nil + } + if len(pkg.Imports) == 0 { + pkg.Imports = nil + } + } + + require.EqualValues(t, tc.res, res) + }) + } +} + +type writeCloser struct { + strings.Builder +} + +func (wc *writeCloser) Close() error { + return nil +} diff --git a/gnovm/pkg/packages/patterns.go b/gnovm/pkg/packages/patterns.go index 220101694c9..80c7c9cf568 100644 --- a/gnovm/pkg/packages/patterns.go +++ b/gnovm/pkg/packages/patterns.go @@ -1,9 +1,217 @@ package packages import ( + "errors" + "fmt" + "io" + "io/fs" + "os" + "path" + "path/filepath" + "slices" + "sort" "strings" + + "github.com/gnolang/gno/gnovm/pkg/gnolang" + "github.com/gnolang/gno/gnovm/pkg/gnomod" + "golang.org/x/mod/module" +) + +type pkgMatch struct { + Dir string + Match []string +} + +func expandPatterns(warn io.Writer, patterns ...string) ([]*pkgMatch, error) { + pkgMatches := []*pkgMatch(nil) + + addPkgDir := func(dir string, match *string) { + idx := slices.IndexFunc(pkgMatches, func(sum *pkgMatch) bool { return sum.Dir == dir }) + if idx == -1 { + matches := []string{} + if match != nil { + matches = append(matches, *match) + } + pkgMatches = append(pkgMatches, &pkgMatch{Dir: dir, Match: matches}) + return + } + if match == nil { + return + } + if slices.Contains(pkgMatches[idx].Match, *match) { + return + } + pkgMatches[idx].Match = append(pkgMatches[idx].Match, *match) + } + + kinds := make([]patternKind, 0, len(patterns)) + for _, match := range patterns { + patKind, err := getPatternKind(match) + if err != nil { + return nil, fmt.Errorf("%s: %w", match, err) + } + kinds = append(kinds, patKind) + } + + if slices.Contains(kinds, patternKindSingleFile) { + remaining := []string{} + remainingKinds := []patternKind{} + + files := make([]string, 0, len(patterns)) + for i, match := range patterns { + kind := kinds[i] + if kind != patternKindSingleFile { + remaining = append(remaining, match) + remainingKinds = append(remainingKinds, kind) + continue + } + if !strings.HasSuffix(match, ".gno") { + return nil, fmt.Errorf("named files must be .gno files: %s", match) + } + files = append(files, match) + } + + pkgMatches = append(pkgMatches, &pkgMatch{Dir: "command-line-arguments", Match: files}) + + patterns = remaining + kinds = remainingKinds + } + + for i, match := range patterns { + patKind := kinds[i] + + switch patKind { + case patternKindRecursiveRemote: + return nil, fmt.Errorf("%s: recursive remote patterns are not supported", match) + case patternKindSingleFile: + return nil, fmt.Errorf("unexpected single pattern at this point") + } + + pat, err := cleanPattern(match, patKind) + if err != nil { + return nil, fmt.Errorf("%s: %w", match, err) + } + + switch patKind { + case patternKindDirectory: + addPkgDir(pat, &match) + + case patternKindRemote: + var dir string + if gnolang.IsStdlib(pat) { + dir = StdlibDir(pat) + } else { + dir = gnomod.PackageDir("", module.Version{Path: pat}) + } + addPkgDir(dir, &match) + + case patternKindRecursiveLocal: + dirs, err := expandRecursive(pat) + if err != nil { + return nil, fmt.Errorf("%s: %w", pat, err) + } + if len(dirs) == 0 { + _, _ = warn.Write([]byte(fmt.Sprintf(`gno: warning: %q matched no packages`, match))) + } + for _, dir := range dirs { + addPkgDir(dir, &match) + } + } + } + + sort.Slice(pkgMatches, func(i, j int) bool { + return pkgMatches[i].Dir < pkgMatches[j].Dir + }) + + return pkgMatches, nil +} + +func expandRecursive(pat string) ([]string, error) { + root, _ := filepath.Split(pat) + + info, err := os.Stat(root) + if err != nil { + return nil, err + } + + if !info.IsDir() { + return nil, errors.New("glob root is not a directory") + } + + // we swallow errors after this point as we want the most packages we can get + dirs := []string{} + _ = fs.WalkDir(os.DirFS(root), ".", func(path string, d fs.DirEntry, err error) error { + if err != nil { + return nil + } + if d.IsDir() { + return nil + } + dir, base := filepath.Split(path) + dir = filepath.Join(root, dir) + if slices.Contains(dirs, dir) { + return nil + } + if base == "gno.mod" || strings.HasSuffix(base, ".gno") || base == "LICENSE" || base == "README.md" { + dirs = append(dirs, dir) + } + return nil + }) + + return dirs, nil +} + +type patternKind int + +const ( + patternKindUnknown = iota + patternKindSingleFile + patternKindDirectory + patternKindRecursiveLocal + patternKindRemote + patternKindRecursiveRemote ) +func getPatternKind(pat string) (patternKind, error) { + isLitteral := patternIsLiteral(pat) + + if !filepath.IsAbs(pat) && (patternIsRemote(pat) || gnolang.IsStdlib(pat)) { + if isLitteral { + return patternKindRemote, nil + } + dir, base := filepath.Split(pat) + if base != "..." || strings.Contains(dir, "...") { + return patternKindUnknown, fmt.Errorf("%s: partial globs are not supported", pat) + } + return patternKindRecursiveRemote, nil + } + + if !isLitteral { + dir, base := filepath.Split(pat) + if base != "..." || strings.Contains(dir, "...") { + return patternKindUnknown, fmt.Errorf("%s: partial globs are not supported", pat) + } + return patternKindRecursiveLocal, nil + } + + if strings.HasSuffix(pat, ".gno") { + return patternKindSingleFile, nil + } + + return patternKindDirectory, nil +} + +func cleanPattern(pat string, kind patternKind) (string, error) { + switch kind { + case patternKindSingleFile, patternKindDirectory, patternKindRecursiveLocal: + return filepath.Abs(pat) + case patternKindRemote, patternKindRecursiveRemote: + return path.Clean(pat), nil + default: + return "", fmt.Errorf("unknown pattern kind %d", kind) + } +} + // patternIsRemote reports wether a pattern is starting with a domain func patternIsRemote(path string) bool { if len(path) == 0 { @@ -19,9 +227,9 @@ func patternIsRemote(path string) bool { return strings.ContainsRune(path[:slashIdx], '.') } -// PatternIsLiteral reports whether the pattern is free of wildcards. +// patternIsLiteral reports whether the pattern is free of wildcards. // // A literal pattern must match at most one package. -func PatternIsLiteral(pattern string) bool { +func patternIsLiteral(pattern string) bool { return !strings.Contains(pattern, "...") } diff --git a/gnovm/pkg/packages/patterns_test.go b/gnovm/pkg/packages/patterns_test.go new file mode 100644 index 00000000000..1ffb635ed41 --- /dev/null +++ b/gnovm/pkg/packages/patterns_test.go @@ -0,0 +1,267 @@ +package packages + +import ( + "fmt" + "os" + "path" + "path/filepath" + "strings" + "testing" + + "github.com/gnolang/gno/gnovm/pkg/gnomod" + "github.com/stretchr/testify/require" + "golang.org/x/mod/module" +) + +func TestPatternKind(t *testing.T) { + tcs := []struct { + name string + pat string + kind patternKind + errShouldContain string + }{{ + name: "single-file", + pat: "strings.gno", + kind: patternKindSingleFile, + }, { + name: "abs-single-file", + pat: "/strings.gno", + kind: patternKindSingleFile, + }, { + name: "dir", + pat: "./strings", + kind: patternKindDirectory, + }, { + name: "dir-recursive", + pat: "./strings/...", + kind: patternKindRecursiveLocal, + }, { + name: "parent-dir", + pat: "../strings", + kind: patternKindDirectory, + }, { + name: "parent-dir-recursive", + pat: "../strings/...", + kind: patternKindRecursiveLocal, + }, { + name: "abs-dir", + pat: "/strings", + kind: patternKindDirectory, + }, { + name: "abs-dir-recursive", + pat: "/strings/...", + kind: patternKindRecursiveLocal, + }, { + name: "stdlib", + pat: "strings", + kind: patternKindRemote, + }, { + name: "stdlib-recursive", + pat: "strings/...", + kind: patternKindRecursiveRemote, + }, { + name: "remote", + pat: "gno.example.com/r/test/foo", + kind: patternKindRemote, + }, { + name: "remote-recursive", + pat: "gno.example.com/r/test/foo/...", + kind: patternKindRecursiveRemote, + }, { + name: "err-partial-recursive", + pat: "./foo/.../bar", + errShouldContain: "./foo/.../bar: partial globs are not supported", + }, { + name: "err-partial-recursive-2", + pat: "./foo/...bar", + errShouldContain: "./foo/...bar: partial globs are not supported", + }, { + name: "err-partial-remote-recursive", + pat: "gno.example.com/r/test/.../foo", + errShouldContain: "gno.example.com/r/test/.../foo: partial globs are not supported", + }, { + name: "err-partial-stdlib-recursive", + pat: "test/.../foo", + errShouldContain: "test/.../foo: partial globs are not supported", + }} + + for _, tc := range tcs { + t.Run(tc.name, func(t *testing.T) { + kind, err := getPatternKind(tc.pat) + if tc.errShouldContain == "" { + require.NoError(t, err) + } else { + require.ErrorContains(t, err, tc.errShouldContain) + } + require.Equal(t, tc.kind, kind) + }) + } +} + +func TestStdlibDir(t *testing.T) { + dir := StdlibDir("foo/bar") + expectedDir := filepath.Join("..", "..", "stdlibs", "foo", "bar") + + absExpectedDir, err := filepath.Abs(expectedDir) + require.NoError(t, err) + + require.Equal(t, absExpectedDir, dir) +} + +func TestDataExpandPatterns(t *testing.T) { + cwd, err := os.Getwd() + require.NoError(t, err) + + localFromSlash := func(p string) string { + cp := path.Clean(p) + fp := filepath.FromSlash(cp) + if filepath.IsAbs(fp) { + return fp + } + return "." + string(filepath.Separator) + fp + } + + tcs := []struct { + name string + // workdir string + patterns []string + conf *LoadConfig + res []*pkgMatch + errShouldContain string + warnShouldContain string + }{ + { + name: "workspace-1-root", + patterns: []string{localFromSlash("./testdata/workspace-1")}, + res: []*pkgMatch{{ + Dir: filepath.Join(cwd, "testdata", "workspace-1"), + Match: []string{localFromSlash("./testdata/workspace-1")}, + }}, + }, + { + name: "workspace-1-recursive", + patterns: []string{localFromSlash("./testdata/workspace-1/...")}, + res: []*pkgMatch{{ + Dir: filepath.Join(cwd, "testdata", "workspace-1"), + Match: []string{localFromSlash("./testdata/workspace-1/...")}, + }}, + }, + { + name: "workspace-1-abs-root", + patterns: []string{filepath.Join(cwd, "testdata", "workspace-1")}, + res: []*pkgMatch{{ + Dir: filepath.Join(cwd, "testdata", "workspace-1"), + Match: []string{filepath.Join(cwd, "testdata", "workspace-1")}, + }}, + }, + { + name: "workspace-1-abs-recursive", + patterns: []string{filepath.Join(cwd, "testdata", "workspace-1", "...")}, + res: []*pkgMatch{{ + Dir: filepath.Join(cwd, "testdata", "workspace-1"), + Match: []string{filepath.Join(cwd, "testdata", "workspace-1", "...")}, + }}, + }, + { + name: "workspace-2-root", + patterns: []string{localFromSlash("./testdata/workspace-2")}, + res: []*pkgMatch{{ + Dir: filepath.Join(cwd, "testdata", "workspace-2"), + Match: []string{localFromSlash("./testdata/workspace-2")}, + }}, + }, + { + name: "workspace-2-recursive", + patterns: []string{localFromSlash("./testdata/workspace-2/...")}, + res: []*pkgMatch{{ + Dir: filepath.Join(cwd, "testdata", "workspace-2"), + Match: []string{localFromSlash("./testdata/workspace-2/...")}, + }, { + Dir: filepath.Join(cwd, "testdata", "workspace-2", "bar"), + Match: []string{localFromSlash("./testdata/workspace-2/...")}, + }, { + Dir: filepath.Join(cwd, "testdata", "workspace-2", "bar", "baz"), + Match: []string{localFromSlash("./testdata/workspace-2/...")}, + }, { + Dir: filepath.Join(cwd, "testdata", "workspace-2", "foo"), + Match: []string{localFromSlash("./testdata/workspace-2/...")}, + }}, + }, + { + name: "workspace-2-recursive-dup", + patterns: []string{localFromSlash("./testdata/workspace-2/..."), localFromSlash("./testdata/workspace-2/bar")}, + res: []*pkgMatch{{ + Dir: filepath.Join(cwd, "testdata", "workspace-2"), + Match: []string{localFromSlash("./testdata/workspace-2/...")}, + }, { + Dir: filepath.Join(cwd, "testdata", "workspace-2", "bar"), + Match: []string{localFromSlash("./testdata/workspace-2/..."), localFromSlash("./testdata/workspace-2/bar")}, + }, { + Dir: filepath.Join(cwd, "testdata", "workspace-2", "bar", "baz"), + Match: []string{localFromSlash("./testdata/workspace-2/...")}, + }, { + Dir: filepath.Join(cwd, "testdata", "workspace-2", "foo"), + Match: []string{localFromSlash("./testdata/workspace-2/...")}, + }}, + }, + { + name: "stdlibs", + patterns: []string{"strings", "math"}, + res: []*pkgMatch{{ + Dir: StdlibDir("math"), + Match: []string{"math"}, + }, { + Dir: StdlibDir("strings"), + Match: []string{"strings"}, + }}, + }, + { + name: "remote", + patterns: []string{"gno.example.com/test/strings", "gno.example.com/test/math"}, + res: []*pkgMatch{{ + Dir: gnomod.PackageDir("", module.Version{Path: "gno.example.com/test/math"}), + Match: []string{"gno.example.com/test/math"}, + }, { + Dir: gnomod.PackageDir("", module.Version{Path: "gno.example.com/test/strings"}), + Match: []string{"gno.example.com/test/strings"}, + }}, + }, + { + name: "err-stdlibs-recursive", + patterns: []string{"strings/..."}, + errShouldContain: "recursive remote patterns are not supported", + }, + { + name: "err-remote-recursive", + patterns: []string{"gno.example.com/test/strings/..."}, + errShouldContain: "recursive remote patterns are not supported", + }, + { + name: "err-recursive-noroot", + patterns: []string{"./testdata/notexists/..."}, + errShouldContain: "no such file or directory", + }, + { + name: "warn-recursive-nothing", + patterns: []string{localFromSlash("./testdata/workspace-1/emptydir/...")}, + warnShouldContain: fmt.Sprintf(`gno: warning: %q matched no packages`, localFromSlash("./testdata/workspace-1/emptydir/...")), + }, + } + for _, tc := range tcs { + t.Run(tc.name, func(t *testing.T) { + warn := &strings.Builder{} + res, err := expandPatterns(warn, tc.patterns...) + if tc.errShouldContain == "" { + require.NoError(t, err) + } else { + require.ErrorContains(t, err, tc.errShouldContain) + } + if tc.warnShouldContain != "" { + require.Contains(t, warn.String(), tc.warnShouldContain) + } else { + require.Equal(t, warn.String(), "") + } + require.EqualValues(t, tc.res, res) + }) + } +} diff --git a/gnovm/pkg/packages/pkgdownload/examplespkgfetcher/examplespkgfetcher.go b/gnovm/pkg/packages/pkgdownload/examplespkgfetcher/examplespkgfetcher.go index 56b567e9525..b24cef7ba62 100644 --- a/gnovm/pkg/packages/pkgdownload/examplespkgfetcher/examplespkgfetcher.go +++ b/gnovm/pkg/packages/pkgdownload/examplespkgfetcher/examplespkgfetcher.go @@ -12,17 +12,22 @@ import ( "github.com/gnolang/gno/gnovm/pkg/packages/pkgdownload" ) -type ExamplesPackageFetcher struct{} +type ExamplesPackageFetcher struct { + examplesDir string +} var _ pkgdownload.PackageFetcher = (*ExamplesPackageFetcher)(nil) -func New() pkgdownload.PackageFetcher { - return &ExamplesPackageFetcher{} +func New(examplesDir string) pkgdownload.PackageFetcher { + if examplesDir == "" { + examplesDir = filepath.Join(gnoenv.RootDir(), "examples") + } + return &ExamplesPackageFetcher{examplesDir: examplesDir} } // FetchPackage implements [pkgdownload.PackageFetcher]. func (e *ExamplesPackageFetcher) FetchPackage(pkgPath string) ([]*gnovm.MemFile, error) { - pkgDir := filepath.Join(gnoenv.RootDir(), "examples", filepath.FromSlash(pkgPath)) + pkgDir := filepath.Join(e.examplesDir, filepath.FromSlash(pkgPath)) entries, err := os.ReadDir(pkgDir) if os.IsNotExist(err) { diff --git a/gnovm/pkg/packages/testdata/emptydir/.gitkeep b/gnovm/pkg/packages/testdata/emptydir/.gitkeep new file mode 100644 index 00000000000..e69de29bb2d diff --git a/gnovm/pkg/packages/testdata/examples/gno.example.com/p/demo/avl/avl.gno b/gnovm/pkg/packages/testdata/examples/gno.example.com/p/demo/avl/avl.gno new file mode 100644 index 00000000000..2963cf5cc31 --- /dev/null +++ b/gnovm/pkg/packages/testdata/examples/gno.example.com/p/demo/avl/avl.gno @@ -0,0 +1,5 @@ +package avl + +func NewTree() { + +} diff --git a/gnovm/pkg/packages/testdata/examples/gno.example.com/p/demo/avl/gno.mod b/gnovm/pkg/packages/testdata/examples/gno.example.com/p/demo/avl/gno.mod new file mode 100644 index 00000000000..fa2f68214e6 --- /dev/null +++ b/gnovm/pkg/packages/testdata/examples/gno.example.com/p/demo/avl/gno.mod @@ -0,0 +1 @@ +module gno.example.com/p/demo/avl \ No newline at end of file diff --git a/gnovm/pkg/packages/testdata/workspace-1/emptydir/.gitkeep b/gnovm/pkg/packages/testdata/workspace-1/emptydir/.gitkeep new file mode 100644 index 00000000000..e69de29bb2d diff --git a/gnovm/pkg/packages/testdata/workspace-2/bar/baz/baz.gno b/gnovm/pkg/packages/testdata/workspace-2/bar/baz/baz.gno new file mode 100644 index 00000000000..116a3befecf --- /dev/null +++ b/gnovm/pkg/packages/testdata/workspace-2/bar/baz/baz.gno @@ -0,0 +1,5 @@ +package baz + +func Baz() { + +} diff --git a/gnovm/pkg/packages/utils.go b/gnovm/pkg/packages/utils.go new file mode 100644 index 00000000000..8878ef82fbf --- /dev/null +++ b/gnovm/pkg/packages/utils.go @@ -0,0 +1,12 @@ +package packages + +import ( + "path/filepath" + + "github.com/gnolang/gno/gnovm/pkg/gnoenv" +) + +func StdlibDir(name string) string { + root := gnoenv.RootDir() + return filepath.Join(root, "gnovm", "stdlibs", filepath.FromSlash(name)) +}