Skip to content

Commit

Permalink
test-explorer: make run streaming output
Browse files Browse the repository at this point in the history
  • Loading branch information
xhd2015 committed May 29, 2024
1 parent da25b0b commit ac4ccdb
Show file tree
Hide file tree
Showing 7 changed files with 283 additions and 253 deletions.
4 changes: 2 additions & 2 deletions cmd/xgo/runtime_gen/core/version.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ import (
)

const VERSION = "1.0.37"
const REVISION = "5d0b62062accb5c87ec7643e925d351ce65e3b59+1"
const NUMBER = 241
const REVISION = "da25b0b8838244b76b23707349c5a2b343abc5d9+1"
const NUMBER = 242

// these fields will be filled by compiler
const XGO_VERSION = ""
Expand Down
256 changes: 69 additions & 187 deletions cmd/xgo/test-explorer/debug.go
Original file line number Diff line number Diff line change
@@ -1,19 +1,14 @@
package test_explorer

import (
"bufio"
"context"
"fmt"
"io"
"net/http"
"os"
"path/filepath"
"time"

"github.com/xhd2015/xgo/support/cmd"
"github.com/xhd2015/xgo/support/fileutil"
"github.com/xhd2015/xgo/support/netutil"
"github.com/xhd2015/xgo/support/session"
"github.com/xhd2015/xgo/support/strutil"
)

Expand All @@ -36,191 +31,78 @@ type DebugDestroyRequest struct {
}

func setupDebugHandler(server *http.ServeMux, projectDir string, getTestConfig func() (*TestConfig, error)) {
sessionManager := session.NewSessionManager()

server.HandleFunc("/debug", func(w http.ResponseWriter, r *http.Request) {
netutil.SetCORSHeaders(w)
netutil.HandleJSON(w, r, func(ctx context.Context, r *http.Request) (interface{}, error) {
var req *DebugRequest
err := parseBody(r.Body, &req)
if err != nil {
return nil, err
}
if req == nil || req.Item == nil || req.Item.File == "" {
return nil, netutil.ParamErrorf("requires file")
}
if req.Item.Name == "" {
return nil, netutil.ParamErrorf("requires name")
}

file := req.Item.File
isFile, err := fileutil.IsFile(file)
if err != nil {
return nil, err
}
if !isFile {
return nil, fmt.Errorf("cannot debug multiple tests")
}
absDir, err := filepath.Abs(projectDir)
if err != nil {
return nil, err
}

parsedFlags, parsedArgs, err := getTestFlags(absDir, file, req.Item.Name)
if err != nil {
return nil, err
}

relPath, err := filepath.Rel(absDir, file)
if err != nil {
return nil, err
}

config, err := getTestConfig()
if err != nil {
return nil, err
}

id, session, err := sessionManager.Start()
if err != nil {
return nil, err
}

pr, pw := io.Pipe()

// go func() { xxx }
// - build with gcflags="all=-N -l"
// - start dlv
// - output prompt
go func() {
defer session.SendEvents(&TestingItemEvent{
Event: Event_TestEnd,
})
debug := func(projectDir string, file string, stdout io.Writer, stderr io.Writer) error {
goCmd := config.GetGoCmd()
tmpDir, err := os.MkdirTemp("", "go-test-debug")
if err != nil {
return err
}
defer os.RemoveAll(tmpDir)
binName := "debug.bin"
baseName := filepath.Base(file)
if baseName != "" {
binName = baseName + "-" + binName
}

// TODO: find a way to automatically set breakpoint
// dlvInitFile := filepath.Join(tmpDir, "dlv-init.txt")
// err = ioutil.WriteFile(dlvInitFile, []byte(fmt.Sprintf("break %s:%d\n", file, req.Item.Line)), 0755)
// if err != nil {
// return err
// }
relPathDir := filepath.Dir(relPath)
tmpBin := filepath.Join(tmpDir, binName)

flags := []string{"test", "-c", "-o", tmpBin, "-gcflags=all=-N -l"}
flags = append(flags, config.Flags...)
flags = append(flags, parsedFlags...)
flags = append(flags, "./"+relPathDir)
err = cmd.Dir(projectDir).Debug().Stderr(stderr).Stdout(stdout).Run(goCmd, flags...)
if err != nil {
return err
}
err = netutil.ServePort(2345, true, 500*time.Millisecond, func(port int) {
// user need to set breakpoint explicitly
fmt.Fprintf(stderr, "dlv listen on localhost:%d\n", port)
fmt.Fprintf(stderr, "Debug with IDEs:\n")
fmt.Fprintf(stderr, " > VSCode: add the following config to .vscode/launch.json configurations:")
fmt.Fprintf(stderr, "\n%s\n", strutil.IndentLines(formatVscodeConfig(port), " "))
fmt.Fprintf(stderr, " > GoLand: click Add Configuration > Go Remote > localhost:%d\n", port)
fmt.Fprintf(stderr, " > Terminal: dlv connect localhost:%d\n", port)
}, func(port int) error {
// dlv exec --api-version=2 --listen=localhost:2345 --accept-multiclient --headless ./debug.bin
return cmd.Dir(filepath.Dir(file)).Debug().Stderr(stderr).Stdout(stdout).Run("dlv",
append([]string{
"exec",
"--api-version=2",
"--check-go-version=false",
// NOTE: --init is ignored if --headless
// "--init", dlvInitFile,
"--headless",
// "--allow-non-terminal-interactive=true",
fmt.Sprintf("--listen=localhost:%d", port),
tmpBin, "--", "-test.v", "-test.run", fmt.Sprintf("^%s$", req.Item.Name),
}, parsedArgs...)...,
)
})
if err != nil {
return err
}
return nil
}
err := debug(projectDir, file, io.MultiWriter(os.Stdout, pw), io.MultiWriter(os.Stderr, pw))
if err != nil {
session.SendEvents(&TestingItemEvent{
Event: Event_Output,
Msg: "err: " + err.Error(),
})
}
}()

go func() {
scanner := bufio.NewScanner(pr)
for scanner.Scan() {
data := scanner.Bytes()
session.SendEvents(&TestingItemEvent{
Event: Event_Output,
Msg: string(data),
})
}
}()
return &DebugResponse{ID: id}, nil
})
})
setupPollHandler(server, "/debug", projectDir, getTestConfig, debug)
}

server.HandleFunc("/debug/pollStatus", func(w http.ResponseWriter, r *http.Request) {
netutil.SetCORSHeaders(w)
netutil.HandleJSON(w, r, func(ctx context.Context, r *http.Request) (interface{}, error) {
var req *DebugPollRequest
err := parseBody(r.Body, &req)
if err != nil {
return nil, err
}
if req.ID == "" {
return nil, netutil.ParamErrorf("requires id")
}
session, err := sessionManager.Get(req.ID)
if err != nil {
return nil, err
}
func debug(ctx *RunContext) error {
projectDir := ctx.ProjectDir
file := ctx.File
relPath := ctx.RelPath
name := ctx.Name
stdout := ctx.Stdout
stderr := ctx.Stderr
goCmd := ctx.GoCmd
buildFlags := ctx.BuildFlags
args := ctx.Args
env := ctx.Env

tmpDir, err := os.MkdirTemp("", "go-test-debug")
if err != nil {
return err
}
defer os.RemoveAll(tmpDir)
binName := "debug.bin"
baseName := filepath.Base(file)
if baseName != "" {
binName = baseName + "-" + binName
}

events, err := session.PollEvents()
if err != nil {
return nil, err
}
return &DebugPollResponse{
Events: convTestingEvents(events),
}, nil
})
})
server.HandleFunc("/debug/destroy", func(w http.ResponseWriter, r *http.Request) {
netutil.SetCORSHeaders(w)
netutil.HandleJSON(w, r, func(ctx context.Context, r *http.Request) (interface{}, error) {
var req *DebugDestroyRequest
err := parseBody(r.Body, &req)
if err != nil {
return nil, err
}
if req.ID == "" {
return nil, netutil.ParamErrorf("requires id")
}
err = sessionManager.Destroy(req.ID)
if err != nil {
return nil, err
}
return nil, nil
})
// TODO: find a way to automatically set breakpoint
// dlvInitFile := filepath.Join(tmpDir, "dlv-init.txt")
// err = ioutil.WriteFile(dlvInitFile, []byte(fmt.Sprintf("break %s:%d\n", file, req.Item.Line)), 0755)
// if err != nil {
// return err
// }
relPathDir := filepath.Dir(relPath)
tmpBin := filepath.Join(tmpDir, binName)

flags := []string{"test", "-c", "-o", tmpBin, "-gcflags=all=-N -l"}
flags = append(flags, buildFlags...)
flags = append(flags, "./"+relPathDir)
err = cmd.Dir(projectDir).Debug().Stderr(stderr).Stdout(stdout).Run(goCmd, flags...)
if err != nil {
return err
}
err = netutil.ServePort(2345, true, 500*time.Millisecond, func(port int) {
// user need to set breakpoint explicitly
fmt.Fprintf(stderr, "dlv listen on localhost:%d\n", port)
fmt.Fprintf(stderr, "Debug with IDEs:\n")
fmt.Fprintf(stderr, " > VSCode: add the following config to .vscode/launch.json configurations:")
fmt.Fprintf(stderr, "\n%s\n", strutil.IndentLines(formatVscodeConfig(port), " "))
fmt.Fprintf(stderr, " > GoLand: click Add Configuration > Go Remote > localhost:%d\n", port)
fmt.Fprintf(stderr, " > Terminal: dlv connect localhost:%d\n", port)
}, func(port int) error {
// dlv exec --api-version=2 --listen=localhost:2345 --accept-multiclient --headless ./debug.bin
return cmd.Dir(filepath.Dir(file)).Debug().Stderr(stderr).Stdout(stdout).
Env(env).
Run("dlv",
append([]string{
"exec",
"--api-version=2",
"--check-go-version=false",
// NOTE: --init is ignored if --headless
// "--init", dlvInitFile,
"--headless",
// "--allow-non-terminal-interactive=true",
fmt.Sprintf("--listen=localhost:%d", port),
tmpBin, "--", "-test.v", "-test.run", fmt.Sprintf("^%s$", name),
}, args...)...,
)
})
if err != nil {
return err
}
return nil
}

func formatVscodeConfig(port int) string {
Expand Down
Loading

0 comments on commit ac4ccdb

Please sign in to comment.