Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

test-explorer: make run streaming output #160

Merged
merged 1 commit into from
May 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
2 changes: 1 addition & 1 deletion cmd/xgo/test-explorer/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,6 @@
</body>
<!--after body because document.body may be null-->
<!--available in CN and Global-->
<script src="https://cdn.jsdelivr.net/npm/[email protected].8/index.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected].9/index.js"></script>
<!-- <script src="http://127.0.0.1:8081/npm-publish/index.js"></script> -->
</html>
Loading
Loading