diff --git a/cmd/xgo/runtime_gen/core/version.go b/cmd/xgo/runtime_gen/core/version.go index 375e9370..913600e7 100755 --- a/cmd/xgo/runtime_gen/core/version.go +++ b/cmd/xgo/runtime_gen/core/version.go @@ -7,8 +7,8 @@ import ( ) const VERSION = "1.0.36" -const REVISION = "b1fa6d6f3a19df8888bf2c0eb103ddff88257582+1" -const NUMBER = 226 +const REVISION = "110daef2be989ffe0f7a2111e4a8e75272a4b6d3+1" +const NUMBER = 227 // these fields will be filled by compiler const XGO_VERSION = "" diff --git a/cmd/xgo/test-explorer/config.go b/cmd/xgo/test-explorer/config.go index b20f7f43..1da470c8 100644 --- a/cmd/xgo/test-explorer/config.go +++ b/cmd/xgo/test-explorer/config.go @@ -2,7 +2,13 @@ package test_explorer import ( "encoding/json" + "errors" "fmt" + "io/ioutil" + "os" + "strings" + + "github.com/xhd2015/xgo/support/goinfo" ) type TestConfig struct { @@ -10,6 +16,25 @@ type TestConfig struct { GoCmd string Exclude []string Env map[string]interface{} + + // test flags passed to go test + // common usages: + // -p=12 parallel programs + // -parallel=12 parallel test cases within the same test + // according to our test, -p is more useful than -parallel + Flags []string +} + +func (c *TestConfig) CmdEnv() []string { + if c == nil || len(c.Env) == 0 { + return nil + } + var env []string + env = append(env, os.Environ()...) + for k, v := range c.Env { + env = append(env, fmt.Sprintf("%s=%s", k, fmt.Sprint(v))) + } + return env } type GoConfig struct { @@ -71,17 +96,111 @@ func parseTestConfig(config string) (*TestConfig, error) { conf.Exclude = []string{e} } case []interface{}: - for _, x := range e { - s, ok := x.(string) - if !ok { - return nil, fmt.Errorf("exclude requires string, actual: %T", x) - } - conf.Exclude = append(conf.Exclude, s) + list, err := toStringList(e) + if err != nil { + return nil, fmt.Errorf("exclude: %w", err) } + conf.Exclude = list default: return nil, fmt.Errorf("exclude requires string or list, actual: %T", e) } } + e, ok = m["flags"] + if ok { + list, err := toStringList(e) + if err != nil { + return nil, fmt.Errorf("flags: %w", err) + } + conf.Flags = list + } return conf, nil } + +func parseConfigAndMergeOptions(configFile string, opts *Options) (*TestConfig, error) { + data, readErr := ioutil.ReadFile(configFile) + if readErr != nil { + if !errors.Is(readErr, os.ErrNotExist) { + return nil, readErr + } + readErr = nil + } + var testConfig *TestConfig + if len(data) > 0 { + var err error + testConfig, err = parseTestConfig(string(data)) + if err != nil { + return nil, fmt.Errorf("parse test.config.json: %w", err) + } + } + if testConfig == nil { + testConfig = &TestConfig{} + } + if opts.GoCommand != "" { + testConfig.GoCmd = opts.GoCommand + } else if testConfig.GoCmd == "" { + testConfig.GoCmd = opts.DefaultGoCommand + } + testConfig.Exclude = append(testConfig.Exclude, opts.Exclude...) + testConfig.Flags = append(testConfig.Flags, opts.Flags...) + return testConfig, nil +} + +func validateGoVersion(testConfig *TestConfig) error { + if testConfig.Go != nil || (testConfig.Go.Min == "" && testConfig.Go.Max == "") { + return nil + } + // check go version + goVersionStr, err := goinfo.GetGoVersionOutput("go") + if err != nil { + return err + } + goVersion, err := goinfo.ParseGoVersion(goVersionStr) + if err != nil { + return err + } + if testConfig.Go.Min != "" { + minVer, _ := goinfo.ParseGoVersionNumber(strings.TrimPrefix(testConfig.Go.Min, "go")) + if minVer != nil { + if compareGoVersion(goVersion, minVer, true) < 0 { + return fmt.Errorf("go version %s < %s", strings.TrimPrefix(goVersionStr, "go version "), testConfig.Go.Min) + } + } + } + if testConfig.Go.Max != "" { + maxVer, _ := goinfo.ParseGoVersionNumber(strings.TrimPrefix(testConfig.Go.Max, "go")) + if maxVer != nil { + if compareGoVersion(goVersion, maxVer, true) > 0 { + return fmt.Errorf("go version %s > %s", strings.TrimPrefix(goVersionStr, "go version "), testConfig.Go.Max) + } + } + } + return nil +} + +func parseConfigAndValidate(configFile string, opts *Options) error { + testConfig, err := parseConfigAndMergeOptions(configFile, opts) + if err != nil { + return err + } + return validateGoVersion(testConfig) +} + +func toStringList(e interface{}) ([]string, error) { + if e == nil { + return nil, nil + } + list, ok := e.([]interface{}) + if !ok { + return nil, fmt.Errorf("requires []string, actual: %T", e) + } + strList := make([]string, 0, len(list)) + for _, x := range list { + s, ok := x.(string) + if !ok { + return nil, fmt.Errorf("elements requires string, actual: %T", x) + } + strList = append(strList, s) + } + return strList, nil +} diff --git a/cmd/xgo/test-explorer/session.go b/cmd/xgo/test-explorer/session.go index a9728857..94c26070 100644 --- a/cmd/xgo/test-explorer/session.go +++ b/cmd/xgo/test-explorer/session.go @@ -54,10 +54,11 @@ type PollSessionResult struct { } type session struct { - dir string - goCmd string - exclude []string - env []string + dir string + goCmd string + exclude []string + env []string + testFlags []string item *TestingItem @@ -266,7 +267,8 @@ func (c *session) Start() error { if c.goCmd != "" { goCmd = c.goCmd } - testFlags := append([]string{"test", "-json"}, testArgs...) + testFlags := append([]string{"test", "-json"}, c.testFlags...) + testFlags = append(testFlags, testArgs...) fmt.Printf("%s %v\n", goCmd, testFlags) err := cmd.Env(c.env).Dir(c.dir). @@ -393,8 +395,7 @@ func (c *session) sendEvent(event *TestingItemEvent) { } // TODO: add /session/destroy -func setupSessionHandler(server *http.ServeMux, projectDir string, config *TestConfig, env []string) { - +func setupSessionHandler(server *http.ServeMux, projectDir string, getTestConfig func() (*TestConfig, error)) { var nextID int64 = 0 var sessionMapping sync.Map @@ -410,14 +411,21 @@ func setupSessionHandler(server *http.ServeMux, projectDir string, config *TestC return nil, netutil.ParamErrorf("requires file") } + config, err := getTestConfig() + if err != nil { + return nil, err + } + idInt := atomic.AddInt64(&nextID, 1) - id := fmt.Sprintf("session_%d", idInt) + // to avoid stale requests from older pages + id := fmt.Sprintf("session_%s_%d", time.Now().Format("2006-01-02_15:04:05"), idInt) sess := &session{ - dir: projectDir, - goCmd: config.GoCmd, - exclude: config.Exclude, - env: env, + dir: projectDir, + goCmd: config.GoCmd, + exclude: config.Exclude, + env: config.CmdEnv(), + testFlags: config.Flags, eventCh: make(chan *TestingItemEvent, 100), item: req.TestingItem, diff --git a/cmd/xgo/test-explorer/test_explorer.go b/cmd/xgo/test-explorer/test_explorer.go index e379af84..b202f8a9 100644 --- a/cmd/xgo/test-explorer/test_explorer.go +++ b/cmd/xgo/test-explorer/test_explorer.go @@ -5,7 +5,6 @@ import ( "context" _ "embed" "encoding/json" - "errors" "fmt" "go/ast" "go/parser" @@ -33,6 +32,7 @@ type Options struct { GoCommand string ProjectDir string Exclude []string + Flags []string } func Main(args []string, opts *Options) error { @@ -69,6 +69,15 @@ func Main(args []string, opts *Options) error { i++ continue } + if arg == "--flag" || arg == "--flags" { + // e.g. -parallel + if i+1 >= n { + return fmt.Errorf("%s requires value", arg) + } + opts.Flags = append(opts.Flags, args[i+1]) + i++ + continue + } if !strings.HasPrefix(arg, "-") { continue } @@ -162,66 +171,17 @@ func handle(opts *Options) error { } configFile := filepath.Join(opts.ProjectDir, "test.config.json") - - data, readErr := ioutil.ReadFile(configFile) - if readErr != nil { - if !errors.Is(readErr, os.ErrNotExist) { - return readErr - } - readErr = nil - } - var testConfig *TestConfig - if len(data) > 0 { - var err error - testConfig, err = parseTestConfig(string(data)) - if err != nil { - return fmt.Errorf("parse test.config.json: %w", err) - } - } - if testConfig == nil { - testConfig = &TestConfig{} - } - if opts.GoCommand != "" { - testConfig.GoCmd = opts.GoCommand - } else if testConfig.GoCmd == "" { - testConfig.GoCmd = opts.DefaultGoCommand + err := parseConfigAndValidate(configFile, opts) + if err != nil { + return err } - testConfig.Exclude = append(testConfig.Exclude, opts.Exclude...) - // check go version - if testConfig.Go != nil && (testConfig.Go.Min != "" || testConfig.Go.Max != "") { - goVersionStr, err := goinfo.GetGoVersionOutput("go") + getTestConfig := func() (*TestConfig, error) { + conf, err := parseConfigAndMergeOptions(configFile, opts) if err != nil { - return err - } - goVersion, err := goinfo.ParseGoVersion(goVersionStr) - if err != nil { - return err - } - if testConfig.Go.Min != "" { - minVer, _ := goinfo.ParseGoVersionNumber(strings.TrimPrefix(testConfig.Go.Min, "go")) - if minVer != nil { - if compareGoVersion(goVersion, minVer, true) < 0 { - return fmt.Errorf("go version %s < %s", strings.TrimPrefix(goVersionStr, "go version "), testConfig.Go.Min) - } - } - } - if testConfig.Go.Max != "" { - maxVer, _ := goinfo.ParseGoVersionNumber(strings.TrimPrefix(testConfig.Go.Max, "go")) - if maxVer != nil { - if compareGoVersion(goVersion, maxVer, true) > 0 { - return fmt.Errorf("go version %s > %s", strings.TrimPrefix(goVersionStr, "go version "), testConfig.Go.Max) - } - } - } - } - - var env []string - if len(testConfig.Env) > 0 { - env = append(env, os.Environ()...) - for k, v := range testConfig.Env { - env = append(env, fmt.Sprintf("%s=%s", k, fmt.Sprint(v))) + return nil, fmt.Errorf("read test config:%w", err) } + return conf, nil } server := &http.ServeMux{} @@ -289,12 +249,16 @@ func handle(opts *Options) error { if err != nil { return nil, err } + config, err := getTestConfig() + if err != nil { + return nil, err + } - return run(req, testConfig.GoCmd, env) + return run(req, config.GoCmd, config.CmdEnv(), config.Flags) }) }) - setupSessionHandler(server, opts.ProjectDir, testConfig, env) + setupSessionHandler(server, opts.ProjectDir, getTestConfig) server.HandleFunc("/openVscode", func(w http.ResponseWriter, r *http.Request) { netutil.SetCORSHeaders(w) @@ -485,7 +449,7 @@ func getDetail(req *DetailRequest) (*DetailResponse, error) { Content: string(content)[i:j], }, nil } -func run(req *RunRequest, goCmd string, env []string) (*RunResult, error) { +func run(req *RunRequest, goCmd string, env []string, testFlags []string) (*RunResult, error) { if req == nil || req.BaseRequest == nil || req.File == "" { return nil, fmt.Errorf("requires file") } @@ -498,6 +462,7 @@ func run(req *RunRequest, goCmd string, env []string) (*RunResult, error) { if req.Verbose { args = append(args, "-v") } + args = append(args, testFlags...) if goCmd == "" { goCmd = "go" } diff --git a/cmd/xgo/version.go b/cmd/xgo/version.go index 408402c6..bc384440 100644 --- a/cmd/xgo/version.go +++ b/cmd/xgo/version.go @@ -3,8 +3,8 @@ package main import "fmt" const VERSION = "1.0.36" -const REVISION = "b1fa6d6f3a19df8888bf2c0eb103ddff88257582+1" -const NUMBER = 226 +const REVISION = "110daef2be989ffe0f7a2111e4a8e75272a4b6d3+1" +const NUMBER = 227 func getRevision() string { revSuffix := "" diff --git a/runtime/core/version.go b/runtime/core/version.go index 375e9370..913600e7 100644 --- a/runtime/core/version.go +++ b/runtime/core/version.go @@ -7,8 +7,8 @@ import ( ) const VERSION = "1.0.36" -const REVISION = "b1fa6d6f3a19df8888bf2c0eb103ddff88257582+1" -const NUMBER = 226 +const REVISION = "110daef2be989ffe0f7a2111e4a8e75272a4b6d3+1" +const NUMBER = 227 // these fields will be filled by compiler const XGO_VERSION = ""