Skip to content

Commit

Permalink
feat(gnovm): enable debugger for gno test
Browse files Browse the repository at this point in the history
This change brings interactive debugging to gno tests, using -debug
flag.
  • Loading branch information
mvertes committed Dec 19, 2024
1 parent a85a53d commit 0f6b357
Show file tree
Hide file tree
Showing 6 changed files with 55 additions and 18 deletions.
2 changes: 1 addition & 1 deletion gnovm/cmd/gno/lint.go
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ func execLint(cfg *lintCfg, args []string, io commands.IO) error {

// Handle runtime errors
hasRuntimeErr := catchRuntimeError(pkgPath, io.Err(), func() {
tm := test.Machine(testStore, stdout, memPkg.Path)
tm := test.Machine(testStore, stdout, memPkg.Path, false)
defer tm.Release()

// Check package
Expand Down
17 changes: 17 additions & 0 deletions gnovm/cmd/gno/test.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ type testCfg struct {
updateGoldenTests bool
printRuntimeMetrics bool
printEvents bool
debug bool
debugAddr string
}

func newTestCmd(io commands.IO) *commands.Command {
Expand Down Expand Up @@ -143,6 +145,20 @@ func (c *testCfg) RegisterFlags(fs *flag.FlagSet) {
false,
"print emitted events",
)

fs.BoolVar(
&c.debug,
"debug",
false,
"enable interactive debugger using stdin and stdout",
)

fs.StringVar(
&c.debugAddr,
"debug-addr",
"",
"enable interactive debugger using tcp address in the form [host]:port",
)
}

func execTest(cfg *testCfg, args []string, io commands.IO) error {
Expand Down Expand Up @@ -187,6 +203,7 @@ func execTest(cfg *testCfg, args []string, io commands.IO) error {
opts.Verbose = cfg.verbose
opts.Metrics = cfg.printRuntimeMetrics
opts.Events = cfg.printEvents
opts.Debug = cfg.debug

buildErrCount := 0
testErrCount := 0
Expand Down
22 changes: 11 additions & 11 deletions gnovm/pkg/gnolang/debugger.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,19 +43,19 @@ type Debugger struct {
out io.Writer // debugger output, defaults to Stdout
scanner *bufio.Scanner // to parse input per line

state DebugState // current state of debugger
lastCmd string // last debugger command
lastArg string // last debugger command arguments
loc Location // source location of the current machine instruction
prevLoc Location // source location of the previous machine instruction
breakpoints []Location // list of breakpoints set by user, as source locations
call []Location // for function tracking, ideally should be provided by machine frame
frameLevel int // frame level of the current machine instruction
getSrc func(string) string // helper to access source from repl or others
state DebugState // current state of debugger
lastCmd string // last debugger command
lastArg string // last debugger command arguments
loc Location // source location of the current machine instruction
prevLoc Location // source location of the previous machine instruction
breakpoints []Location // list of breakpoints set by user, as source locations
call []Location // for function tracking, ideally should be provided by machine frame
frameLevel int // frame level of the current machine instruction
getSrc func(string, string) string // helper to access source from repl or others
}

// Enable makes the debugger d active, using in as input reader, out as output writer and f as a source helper.
func (d *Debugger) Enable(in io.Reader, out io.Writer, f func(string) string) {
func (d *Debugger) Enable(in io.Reader, out io.Writer, f func(string, string) string) {
d.in = in
d.out = out
d.enabled = true
Expand Down Expand Up @@ -505,7 +505,7 @@ func debugList(m *Machine, arg string) (err error) {
if err != nil {
// Use optional getSrc helper as fallback to get source.
if m.Debugger.getSrc != nil {
src = m.Debugger.getSrc(loc.File)
src = m.Debugger.getSrc(loc.PkgPath, loc.File)
}
if src == "" {
return err
Expand Down
2 changes: 1 addition & 1 deletion gnovm/pkg/repl/repl.go
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ func (r *Repl) Process(input string) (out string, err error) {
r.state.id++

if r.debug {
r.state.machine.Debugger.Enable(os.Stdin, os.Stdout, func(file string) string {
r.state.machine.Debugger.Enable(os.Stdin, os.Stdout, func(ppath, file string) string {
return r.state.files[file]
})
r.debug = false
Expand Down
1 change: 1 addition & 0 deletions gnovm/pkg/test/filetest.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ func (opts *TestOptions) runFiletest(filename string, source []byte) (string, er
Store: opts.TestStore.BeginTransaction(cw, cw),
Context: ctx,
MaxAllocBytes: maxAlloc,
Debug: opts.Debug,
})
defer m.Release()
result := opts.runTest(m, pkgPath, filename, source)
Expand Down
29 changes: 24 additions & 5 deletions gnovm/pkg/test/test.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,11 +71,12 @@ func Context(pkgPath string, send std.Coins) *teststd.TestExecContext {
}

// Machine is a minimal machine, set up with just the Store, Output and Context.
func Machine(testStore gno.Store, output io.Writer, pkgPath string) *gno.Machine {
func Machine(testStore gno.Store, output io.Writer, pkgPath string, debug bool) *gno.Machine {
return gno.NewMachineWithOptions(gno.MachineOptions{
Store: testStore,
Output: output,
Context: Context(pkgPath, nil),
Debug: debug,
})
}

Expand Down Expand Up @@ -108,6 +109,8 @@ type TestOptions struct {
Output io.Writer
// Used for os.Stderr, and for printing errors.
Error io.Writer
// Debug enables the interactive debugger on gno tests.
Debug bool

// Not set by NewTestOptions:

Expand Down Expand Up @@ -288,9 +291,8 @@ func (opts *TestOptions) runTestFiles(
// reset store ops, if any - we only need them for some filetests.
opts.TestStore.SetLogStoreOps(false)

// Check if we already have the package - it may have been eagerly
// loaded.
m = Machine(gs, opts.WriterForStore(), memPkg.Path)
// Check if we already have the package - it may have been eagerly loaded.
m = Machine(gs, opts.WriterForStore(), memPkg.Path, opts.Debug)
m.Alloc = alloc
if opts.TestStore.GetMemPackage(memPkg.Path) == nil {
m.RunMemPackage(memPkg, true)
Expand All @@ -310,14 +312,31 @@ func (opts *TestOptions) runTestFiles(
// - Run the test files before this for loop (but persist it to store;
// RunFiles doesn't do that currently)
// - Wrap here.
m = Machine(gs, opts.Output, memPkg.Path)
m = Machine(gs, opts.Output, memPkg.Path, opts.Debug)
m.Alloc = alloc
m.SetActivePackage(pv)

testingpv := m.Store.GetPackage("testing", false)
testingtv := gno.TypedValue{T: &gno.PackageType{}, V: testingpv}
testingcx := &gno.ConstExpr{TypedValue: testingtv}

if opts.Debug {
fileContent := func(ppath, name string) string {
p := filepath.Join(opts.RootDir, ppath, name)
b, err := os.ReadFile(p)
if err != nil {
p = filepath.Join(opts.RootDir, "gnovm", "stdlibs", ppath, name)
b, err = os.ReadFile(p)
}
if err != nil {
p = filepath.Join(opts.RootDir, "examples", ppath, name)
b, err = os.ReadFile(p)
}
return string(b)
}
m.Debugger.Enable(os.Stdin, os.Stdout, fileContent)
}

eval := m.Eval(gno.Call(
gno.Sel(testingcx, "RunTest"), // Call testing.RunTest
gno.Str(opts.RunFlag), // run flag
Expand Down

0 comments on commit 0f6b357

Please sign in to comment.