From d6243b4c8443556c7591be6492c2d28a093ea918 Mon Sep 17 00:00:00 2001
From: Guilhem Fanton <8671905+gfanton@users.noreply.github.com>
Date: Mon, 22 Jan 2024 16:24:13 +0100
Subject: [PATCH] feat: improve gno linter with basic errors support (#1202)
I've improved the linter by using `RunMemPackage` in the same way that
`gno test` does for testing.
A direct use case for this PR is to provide a simple and reliable method
for editors to integrate `gno linting`.
(There might be a better way to achieve what I've done, of which I'm not
aware)
---
### Below a sample of `gno lint` output:
---
### And here's a brief demo using basic integration with emacs:

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).
---------
Signed-off-by: gfanton <8671905+gfanton@users.noreply.github.com>
---
CONTRIBUTING.md | 38 +++++-
gnovm/cmd/gno/lint.go | 124 +++++++++++++++++-
gnovm/cmd/gno/lint_test.go | 7 +
gnovm/cmd/gno/run_test.go | 4 +
gnovm/cmd/gno/test_test.go | 6 +-
.../testdata/gno_test/lint_bad_import.txtar | 19 +++
.../testdata/gno_test/lint_file_error.txtar | 20 +++
.../testdata/gno_test/lint_file_error_txtar | 20 +++
.../gno/testdata/gno_test/lint_no_error.txtar | 18 +++
.../testdata/gno_test/lint_no_gnomod.txtar | 19 +++
.../testdata/gno_test/lint_not_declared.txtar | 20 +++
gnovm/pkg/gnolang/debug.go | 35 +++++
gnovm/pkg/gnolang/preprocess.go | 21 +--
gnovm/pkg/repl/repl_test.go | 2 +-
gnovm/tests/file.go | 11 +-
.../tests/integ/package-not-declared/gno.mod | 1 +
.../tests/integ/package-not-declared/main.gno | 5 +
.../integ/undefined-variable-test/gno.mod | 1 +
.../undefined_variables_test.gno | 7 +
19 files changed, 360 insertions(+), 18 deletions(-)
create mode 100644 gnovm/cmd/gno/testdata/gno_test/lint_bad_import.txtar
create mode 100644 gnovm/cmd/gno/testdata/gno_test/lint_file_error.txtar
create mode 100644 gnovm/cmd/gno/testdata/gno_test/lint_file_error_txtar
create mode 100644 gnovm/cmd/gno/testdata/gno_test/lint_no_error.txtar
create mode 100644 gnovm/cmd/gno/testdata/gno_test/lint_no_gnomod.txtar
create mode 100644 gnovm/cmd/gno/testdata/gno_test/lint_not_declared.txtar
create mode 100644 gnovm/tests/integ/package-not-declared/gno.mod
create mode 100644 gnovm/tests/integ/package-not-declared/main.gno
create mode 100644 gnovm/tests/integ/undefined-variable-test/gno.mod
create mode 100644 gnovm/tests/integ/undefined-variable-test/undefined_variables_test.gno
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 1431bb1c1c6..a3f34b8a85e 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -104,6 +104,18 @@ To use *gofumpt* instead of *gofmt*, as hinted in the comment, you may either ha
cexpr system('go run -modfile /misc/devdeps/go.mod mvdan.cc/gofumpt -w ' . expand('%'))
```
+##### ViM Linting Support
+
+To integrate GNO linting in Vim, you can use Vim's `:make` command with a custom `makeprg` and `errorformat` to run the GNO linter and parse its output. Add the following configuration to your `.vimrc` file:
+
+```vim
+autocmd FileType gno setlocal makeprg=gno\ lint\ %
+autocmd FileType gno setlocal errorformat=%f:%l:\ %m
+
+" Optional: Key binding to run :make on the current file
+autocmd FileType gno nnoremap :make
+```
+
### ViM Support (with LSP)
There is an experimental and unofficial [Gno Language Server](https://github.com/jdkato/gnols)
@@ -172,7 +184,31 @@ Additionally, it's not possible to use `gofumpt` for code formatting with
2. Add to your emacs configuration file:
```lisp
-(add-to-list 'auto-mode-alist '("\\.gno\\'" . go-mode))
+(define-derived-mode gno-mode go-mode "GNO"
+ "Major mode for GNO files, an alias for go-mode."
+ (setq-local tab-width 8))
+(define-derived-mode gno-dot-mod-mode go-dot-mod-mode "GNO Mod"
+ "Major mode for GNO mod files, an alias for go-dot-mod-mode."
+ )
+```
+
+3. To integrate GNO linting with Flycheck, add the following to your Emacs configuration:
+```lisp
+(require 'flycheck)
+
+(flycheck-define-checker gno-lint
+ "A GNO syntax checker using the gno lint tool."
+ :command ("gno" "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
+ ;; https://github.com/python/mypy/issues/4746.
+ :predicate (lambda ()
+ (and (not (bound-and-true-p polymode-mode))
+ (flycheck-buffer-saved-p)))
+ :modes gno-mode)
+
+(add-to-list 'flycheck-checkers 'gno-lint)
```
#### Sublime Text
diff --git a/gnovm/cmd/gno/lint.go b/gnovm/cmd/gno/lint.go
index a90f432b7c9..68a5808b309 100644
--- a/gnovm/cmd/gno/lint.go
+++ b/gnovm/cmd/gno/lint.go
@@ -2,12 +2,17 @@ package main
import (
"context"
+ "errors"
"flag"
"fmt"
"os"
"path/filepath"
+ "regexp"
+ "strings"
"github.com/gnolang/gno/gnovm/pkg/gnoenv"
+ gno "github.com/gnolang/gno/gnovm/pkg/gnolang"
+ "github.com/gnolang/gno/gnovm/tests"
"github.com/gnolang/gno/tm2/pkg/commands"
osm "github.com/gnolang/gno/tm2/pkg/os"
)
@@ -37,8 +42,10 @@ func newLintCmd(io commands.IO) *commands.Command {
}
func (c *lintCfg) RegisterFlags(fs *flag.FlagSet) {
+ rootdir := gnoenv.RootDir()
+
fs.BoolVar(&c.verbose, "verbose", false, "verbose output when lintning")
- fs.StringVar(&c.rootDir, "root-dir", "", "clone location of github.com/gnolang/gno (gno tries to guess it)")
+ fs.StringVar(&c.rootDir, "root-dir", rootdir, "clone location of github.com/gnolang/gno (gno tries to guess it)")
fs.IntVar(&c.setExitStatus, "set-exit-status", 1, "set exit status to 1 if any issues are found")
}
@@ -71,7 +78,7 @@ func execLint(cfg *lintCfg, args []string, io commands.IO) error {
fmt.Fprintf(io.Err(), "Linting %q...\n", pkgPath)
}
- // 'gno.mod' exists?
+ // Check if 'gno.mod' exists
gnoModPath := filepath.Join(pkgPath, "gno.mod")
if !osm.FileExists(gnoModPath) {
addIssue(lintIssue{
@@ -82,20 +89,131 @@ func execLint(cfg *lintCfg, args []string, io commands.IO) error {
})
}
- // TODO: add more checkers
+ // Handle runtime errors
+ catchRuntimeError(pkgPath, addIssue, func() {
+ stdout, stdin, stderr := io.Out(), io.In(), io.Err()
+ testStore := tests.TestStore(
+ rootDir, "",
+ stdin, stdout, stderr,
+ tests.ImportModeStdlibsOnly,
+ )
+
+ targetPath := pkgPath
+ info, err := os.Stat(pkgPath)
+ if err == nil && !info.IsDir() {
+ targetPath = filepath.Dir(pkgPath)
+ }
+
+ memPkg := gno.ReadMemPackage(targetPath, targetPath)
+ tm := tests.TestMachine(testStore, stdout, memPkg.Name)
+
+ // Check package
+ tm.RunMemPackage(memPkg, true)
+
+ // Check test files
+ 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)
+ 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") {
+ // Keep only test files
+ testfiles.AddFiles(n)
+ }
+ }
+
+ tm.RunFiles(testfiles.Files...)
+ })
+
+ // TODO: Add more checkers
}
if hasError && cfg.setExitStatus != 0 {
os.Exit(cfg.setExitStatus)
}
+
return nil
}
+func guessSourcePath(pkg, source string) string {
+ if info, err := os.Stat(pkg); !os.IsNotExist(err) && !info.IsDir() {
+ pkg = filepath.Dir(pkg)
+ }
+
+ sourceJoin := filepath.Join(pkg, source)
+ if _, err := os.Stat(sourceJoin); !os.IsNotExist(err) {
+ return filepath.Clean(sourceJoin)
+ }
+
+ if _, err := os.Stat(source); !os.IsNotExist(err) {
+ return filepath.Clean(source)
+ }
+
+ return filepath.Clean(pkg)
+}
+
+// reParseRecover is a regex designed to parse error details from a string.
+// It extracts the file location, line number, and error message from a formatted error string.
+// XXX: Ideally, error handling should encapsulate location details within a dedicated error type.
+var reParseRecover = regexp.MustCompile(`^([^:]+):(\d+)(?::\d+)?:? *(.*)$`)
+
+func catchRuntimeError(pkgPath string, addIssue func(issue lintIssue), action func()) {
+ defer func() {
+ // Errors catched here mostly come from: gnovm/pkg/gnolang/preprocess.go
+ r := recover()
+ if r == nil {
+ return
+ }
+
+ var err error
+ switch verr := r.(type) {
+ case *gno.PreprocessError:
+ err = verr.Unwrap()
+ case error:
+ err = verr
+ case string:
+ err = errors.New(verr)
+ default:
+ panic(r)
+ }
+
+ var issue lintIssue
+ issue.Confidence = 1
+ issue.Code = lintGnoError
+
+ parsedError := strings.TrimSpace(err.Error())
+ parsedError = strings.TrimPrefix(parsedError, pkgPath+"/")
+
+ matches := reParseRecover.FindStringSubmatch(parsedError)
+ if len(matches) == 4 {
+ sourcepath := guessSourcePath(pkgPath, matches[1])
+ issue.Location = fmt.Sprintf("%s:%s", sourcepath, matches[2])
+ issue.Msg = strings.TrimSpace(matches[3])
+ } else {
+ issue.Location = fmt.Sprintf("%s:0", filepath.Clean(pkgPath))
+ issue.Msg = err.Error()
+ }
+
+ addIssue(issue)
+ }()
+
+ action()
+}
+
type lintCode int
const (
lintUnknown lintCode = 0
lintNoGnoMod lintCode = iota
+ lintGnoError
+
// TODO: add new linter codes here.
)
diff --git a/gnovm/cmd/gno/lint_test.go b/gnovm/cmd/gno/lint_test.go
index 0a747a03778..d700467965d 100644
--- a/gnovm/cmd/gno/lint_test.go
+++ b/gnovm/cmd/gno/lint_test.go
@@ -10,6 +10,12 @@ func TestLintApp(t *testing.T) {
}, {
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/undefined-variable-test/undefined_variables_test.gno"},
+ stderrShouldContain: "undefined_variables_test.gno:6: name toto not declared (code=2)",
+ }, {
+ args: []string{"lint", "--set-exit-status=0", "../../tests/integ/package-not-declared/main.gno"},
+ stderrShouldContain: "main.gno:4: name fmt not declared (code=2).",
}, {
args: []string{"lint", "--set-exit-status=0", "../../tests/integ/run-main/"},
stderrShouldContain: "./../../tests/integ/run-main: missing 'gno.mod' file (code=1).",
@@ -20,6 +26,7 @@ func TestLintApp(t *testing.T) {
args: []string{"lint", "--set-exit-status=0", "../../tests/integ/invalid-module-name/"},
// TODO: raise an error because gno.mod is invalid
},
+
// TODO: 'gno mod' is valid?
// TODO: is gno source valid?
// TODO: are dependencies valid?
diff --git a/gnovm/cmd/gno/run_test.go b/gnovm/cmd/gno/run_test.go
index e439a6bad8d..575798a78dc 100644
--- a/gnovm/cmd/gno/run_test.go
+++ b/gnovm/cmd/gno/run_test.go
@@ -63,6 +63,10 @@ func TestRunApp(t *testing.T) {
args: []string{"run", "-expr", "WithArg(-255)", "../../tests/integ/run-package"},
stdoutShouldContain: "out of range!",
},
+ {
+ args: []string{"run", "../../tests/integ/undefined-variable-test/undefined_variables_test.gno"},
+ recoverShouldContain: "--- preprocess stack ---", // should contain preprocess debug stack trace
+ },
// TODO: a test file
// TODO: args
// TODO: nativeLibs VS stdlibs
diff --git a/gnovm/cmd/gno/test_test.go b/gnovm/cmd/gno/test_test.go
index b1dcfb21d29..98320f41cc9 100644
--- a/gnovm/cmd/gno/test_test.go
+++ b/gnovm/cmd/gno/test_test.go
@@ -1,6 +1,8 @@
package main
import (
+ "os"
+ "strconv"
"testing"
"github.com/gnolang/gno/gnovm/pkg/integration"
@@ -9,8 +11,10 @@ import (
)
func Test_ScriptsTest(t *testing.T) {
+ updateScripts, _ := strconv.ParseBool(os.Getenv("UPDATE_SCRIPTS"))
p := testscript.Params{
- Dir: "testdata/gno_test",
+ UpdateScripts: updateScripts,
+ Dir: "testdata/gno_test",
}
if coverdir, ok := integration.ResolveCoverageDir(); ok {
diff --git a/gnovm/cmd/gno/testdata/gno_test/lint_bad_import.txtar b/gnovm/cmd/gno/testdata/gno_test/lint_bad_import.txtar
new file mode 100644
index 00000000000..946e1bcba35
--- /dev/null
+++ b/gnovm/cmd/gno/testdata/gno_test/lint_bad_import.txtar
@@ -0,0 +1,19 @@
+# testing gno lint command: bad import error
+
+! gno lint ./bad_file.gno
+
+cmp stdout stdout.golden
+cmp stderr stderr.golden
+
+-- bad_file.gno --
+package main
+
+import "python"
+
+func main() {
+ fmt.Println("Hello", 42)
+}
+
+-- stdout.golden --
+-- stderr.golden --
+bad_file.gno:1: unknown import path python (code=2).
diff --git a/gnovm/cmd/gno/testdata/gno_test/lint_file_error.txtar b/gnovm/cmd/gno/testdata/gno_test/lint_file_error.txtar
new file mode 100644
index 00000000000..9482eeb1f4f
--- /dev/null
+++ b/gnovm/cmd/gno/testdata/gno_test/lint_file_error.txtar
@@ -0,0 +1,20 @@
+# gno lint: test file error
+
+! gno lint ./i_have_error_test.gno
+
+cmp stdout stdout.golden
+cmp stderr stderr.golden
+
+-- i_have_error_test.gno --
+package main
+
+import "fmt"
+
+func TestIHaveSomeError() {
+ i := undefined_variable
+ fmt.Println("Hello", 42)
+}
+
+-- stdout.golden --
+-- stderr.golden --
+i_have_error_test.gno:6: name undefined_variable not declared (code=2).
diff --git a/gnovm/cmd/gno/testdata/gno_test/lint_file_error_txtar b/gnovm/cmd/gno/testdata/gno_test/lint_file_error_txtar
new file mode 100644
index 00000000000..9482eeb1f4f
--- /dev/null
+++ b/gnovm/cmd/gno/testdata/gno_test/lint_file_error_txtar
@@ -0,0 +1,20 @@
+# gno lint: test file error
+
+! gno lint ./i_have_error_test.gno
+
+cmp stdout stdout.golden
+cmp stderr stderr.golden
+
+-- i_have_error_test.gno --
+package main
+
+import "fmt"
+
+func TestIHaveSomeError() {
+ i := undefined_variable
+ fmt.Println("Hello", 42)
+}
+
+-- stdout.golden --
+-- stderr.golden --
+i_have_error_test.gno:6: name undefined_variable not declared (code=2).
diff --git a/gnovm/cmd/gno/testdata/gno_test/lint_no_error.txtar b/gnovm/cmd/gno/testdata/gno_test/lint_no_error.txtar
new file mode 100644
index 00000000000..95356b1ba2b
--- /dev/null
+++ b/gnovm/cmd/gno/testdata/gno_test/lint_no_error.txtar
@@ -0,0 +1,18 @@
+# testing simple gno lint command with any error
+
+gno lint ./good_file.gno
+
+cmp stdout stdout.golden
+cmp stdout stderr.golden
+
+-- good_file.gno --
+package main
+
+import "fmt"
+
+func main() {
+ fmt.Println("Hello", 42)
+}
+
+-- stdout.golden --
+-- stderr.golden --
diff --git a/gnovm/cmd/gno/testdata/gno_test/lint_no_gnomod.txtar b/gnovm/cmd/gno/testdata/gno_test/lint_no_gnomod.txtar
new file mode 100644
index 00000000000..52daa6f0e9b
--- /dev/null
+++ b/gnovm/cmd/gno/testdata/gno_test/lint_no_gnomod.txtar
@@ -0,0 +1,19 @@
+# gno lint: no gnomod
+
+! gno lint .
+
+cmp stdout stdout.golden
+cmp stderr stderr.golden
+
+-- good_file.gno --
+package main
+
+import "fmt"
+
+func main() {
+ fmt.Println("Hello", 42)
+}
+
+-- stdout.golden --
+-- stderr.golden --
+./.: missing 'gno.mod' file (code=1).
diff --git a/gnovm/cmd/gno/testdata/gno_test/lint_not_declared.txtar b/gnovm/cmd/gno/testdata/gno_test/lint_not_declared.txtar
new file mode 100644
index 00000000000..7bd74a34855
--- /dev/null
+++ b/gnovm/cmd/gno/testdata/gno_test/lint_not_declared.txtar
@@ -0,0 +1,20 @@
+# testing gno lint command: not declared error
+
+! gno lint ./bad_file.gno
+
+cmp stdout stdout.golden
+cmp stderr stderr.golden
+
+-- bad_file.gno --
+package main
+
+import "fmt"
+
+func main() {
+ hello.Foo()
+ fmt.Println("Hello", 42)
+}
+
+-- stdout.golden --
+-- stderr.golden --
+bad_file.gno:6: name hello not declared (code=2).
diff --git a/gnovm/pkg/gnolang/debug.go b/gnovm/pkg/gnolang/debug.go
index cb21da12ef2..c6e39dfaa5a 100644
--- a/gnovm/pkg/gnolang/debug.go
+++ b/gnovm/pkg/gnolang/debug.go
@@ -4,6 +4,7 @@ import (
"fmt"
"net/http"
"os"
+ "strings"
"time"
// Ignore pprof import, as the server does not
@@ -76,6 +77,40 @@ func (d debugging) Errorf(format string, args ...interface{}) {
}
}
+// PreprocessError wraps a processing error along with its associated
+// preprocessing stack for enhanced error reporting.
+type PreprocessError struct {
+ err error
+ stack []BlockNode
+}
+
+// Unwrap returns the encapsulated error message.
+func (p *PreprocessError) Unwrap() error {
+ return p.err
+}
+
+// Stack produces a string representation of the preprocessing stack
+// trace that was associated with the error occurrence.
+func (p *PreprocessError) Stack() string {
+ var stacktrace strings.Builder
+ for i := len(p.stack) - 1; i >= 0; i-- {
+ sbn := p.stack[i]
+ fmt.Fprintf(&stacktrace, "stack %d: %s\n", i, sbn.String())
+ }
+ return stacktrace.String()
+}
+
+// Error consolidates and returns the full error message, including
+// the actual error followed by its associated preprocessing stack.
+func (p *PreprocessError) Error() string {
+ var err strings.Builder
+ fmt.Fprintf(&err, "%s:\n", p.Unwrap())
+ fmt.Fprintln(&err, "--- preprocess stack ---")
+ fmt.Fprint(&err, p.Stack())
+ fmt.Fprintf(&err, "------------------------")
+ return err.String()
+}
+
// ----------------------------------------
// Exposed errors accessors
// File tests may access debug errors.
diff --git a/gnovm/pkg/gnolang/preprocess.go b/gnovm/pkg/gnolang/preprocess.go
index f4a42e1e583..c86edb0e515 100644
--- a/gnovm/pkg/gnolang/preprocess.go
+++ b/gnovm/pkg/gnolang/preprocess.go
@@ -154,24 +154,27 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node {
defer func() {
if r := recover(); r != nil {
- fmt.Println("--- preprocess stack ---")
- for i := len(stack) - 1; i >= 0; i-- {
- sbn := stack[i]
- fmt.Printf("stack %d: %s\n", i, sbn.String())
- }
- fmt.Println("------------------------")
// before re-throwing the error, append location information to message.
loc := last.GetLocation()
if nline := n.GetLine(); nline > 0 {
loc.Line = nline
}
- if rerr, ok := r.(error); ok {
+
+ var err error
+ rerr, ok := r.(error)
+ if ok {
// NOTE: gotuna/gorilla expects error exceptions.
- panic(errors.Wrap(rerr, loc.String()))
+ err = errors.Wrap(rerr, loc.String())
} else {
// NOTE: gotuna/gorilla expects error exceptions.
- panic(errors.New(fmt.Sprintf("%s: %v", loc.String(), r)))
+ err = errors.New(fmt.Sprintf("%s: %v", loc.String(), r))
}
+
+ // Re-throw the error after wrapping it with the preprocessing stack information.
+ panic(&PreprocessError{
+ err: err,
+ stack: stack,
+ })
}
}()
if debug {
diff --git a/gnovm/pkg/repl/repl_test.go b/gnovm/pkg/repl/repl_test.go
index 09c350dd49a..fbb2efe8890 100644
--- a/gnovm/pkg/repl/repl_test.go
+++ b/gnovm/pkg/repl/repl_test.go
@@ -69,7 +69,7 @@ var fixtures = []struct {
CodeSteps: []step{
{
Line: "importasdasd",
- Error: "recovered from panic: test/test1.gno:7: name importasdasd not declared",
+ Error: "test/test1.gno:7: name importasdasd not declared",
},
{
Line: "var a := 1",
diff --git a/gnovm/tests/file.go b/gnovm/tests/file.go
index a021628a385..70bed4eda50 100644
--- a/gnovm/tests/file.go
+++ b/gnovm/tests/file.go
@@ -241,15 +241,20 @@ func RunFileTest(rootDir string, path string, opts ...RunFileTestOption) error {
if pnc == nil {
panic(fmt.Sprintf("fail on %s: got nil error, want: %q", path, errWanted))
}
+
errstr := ""
- if tv, ok := pnc.(*gno.TypedValue); ok {
- errstr = tv.Sprint(m)
- } else {
+ switch v := pnc.(type) {
+ case *gno.TypedValue:
+ errstr = v.Sprint(m)
+ case *gno.PreprocessError:
+ errstr = v.Unwrap().Error()
+ default:
errstr = strings.TrimSpace(fmt.Sprintf("%v", pnc))
}
if errstr != errWanted {
panic(fmt.Sprintf("fail on %s: got %q, want: %q", path, errstr, errWanted))
}
+
// NOTE: ignores any gno.GetDebugErrors().
gno.ClearDebugErrors()
return nil // nothing more to do.
diff --git a/gnovm/tests/integ/package-not-declared/gno.mod b/gnovm/tests/integ/package-not-declared/gno.mod
new file mode 100644
index 00000000000..8b4e0375297
--- /dev/null
+++ b/gnovm/tests/integ/package-not-declared/gno.mod
@@ -0,0 +1 @@
+module gno.land/tests/nodeclared
\ No newline at end of file
diff --git a/gnovm/tests/integ/package-not-declared/main.gno b/gnovm/tests/integ/package-not-declared/main.gno
new file mode 100644
index 00000000000..bdbb2e7cfcb
--- /dev/null
+++ b/gnovm/tests/integ/package-not-declared/main.gno
@@ -0,0 +1,5 @@
+package main
+
+func Main() {
+ fmt.Println("hello world")
+}
diff --git a/gnovm/tests/integ/undefined-variable-test/gno.mod b/gnovm/tests/integ/undefined-variable-test/gno.mod
new file mode 100644
index 00000000000..0a75f00e83f
--- /dev/null
+++ b/gnovm/tests/integ/undefined-variable-test/gno.mod
@@ -0,0 +1 @@
+module gno.land/tests/undefined-test
\ No newline at end of file
diff --git a/gnovm/tests/integ/undefined-variable-test/undefined_variables_test.gno b/gnovm/tests/integ/undefined-variable-test/undefined_variables_test.gno
new file mode 100644
index 00000000000..0afdec36ef3
--- /dev/null
+++ b/gnovm/tests/integ/undefined-variable-test/undefined_variables_test.gno
@@ -0,0 +1,7 @@
+package main
+
+import "testing"
+
+func TestUndefinedVariables(t *testing.T) {
+ println("hello world: " + toto)
+}