Skip to content
Open
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
24 changes: 12 additions & 12 deletions cl/_testgo/tpnamed/in.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,15 +46,15 @@ func WriteFile(fileName string) IO[error] {

// CHECK-LABEL: define void @"{{.*}}/cl/_testgo/tpnamed.main"(){{.*}} {
// CHECK-NEXT: _llgo_0:
// CHECK-NEXT: %0 = call [0 x i8] @"{{.*}}/cl/_testgo/tpnamed.RunIO{{\[\[0\]byte\]}}"(%"{{.*}}/cl/_testgo/tpnamed.IO{{\[\[0\]byte\]}}" { ptr @"__llgo_stub.{{.*}}/cl/_testgo/tpnamed.main$1", ptr null })
// CHECK-NEXT: %0 = call [0 x i8] @"{{.*}}/cl/_testgo/tpnamed.RunIO{{\[\[0\]uint8\]}}"(%"{{.*}}/cl/_testgo/tpnamed.IO{{\[\[0\]uint8\]}}" { ptr @"__llgo_stub.{{.*}}/cl/_testgo/tpnamed.main$1", ptr null })
// CHECK-NEXT: ret void
// CHECK-NEXT: }

func main() {

// CHECK-LABEL: define %"{{.*}}/cl/_testgo/tpnamed.Future{{\[\[0\]byte\]}}" @"{{.*}}/cl/_testgo/tpnamed.main$1"()
// CHECK-LABEL: define %"{{.*}}/cl/_testgo/tpnamed.Future{{\[\[0\]uint8\]}}" @"{{.*}}/cl/_testgo/tpnamed.main$1"()
// CHECK-NEXT: _llgo_0:
// CHECK-NEXT: ret %"{{.*}}/cl/_testgo/tpnamed.Future{{\[\[0\]byte\]}}" { ptr @"__llgo_stub.{{.*}}/cl/_testgo/tpnamed.main$1$1", ptr null }
// CHECK-NEXT: ret %"{{.*}}/cl/_testgo/tpnamed.Future{{\[\[0\]uint8\]}}" { ptr @"__llgo_stub.{{.*}}/cl/_testgo/tpnamed.main$1$1", ptr null }
// CHECK-NEXT: }

RunIO[Void](func() Future[Void] {
Expand Down Expand Up @@ -86,19 +86,19 @@ func RunIO[T any](call IO[T]) T {
// CHECK-NEXT: ret %"{{.*}}/runtime/internal/runtime.iface" %1
// CHECK-NEXT: }

// CHECK-LABEL: define linkonce %"{{.*}}/cl/_testgo/tpnamed.Future{{\[\[0\]byte\]}}" @"__llgo_stub.{{.*}}/cl/_testgo/tpnamed.main$1"(ptr %0){{.*}} {
// CHECK-LABEL: define linkonce %"{{.*}}/cl/_testgo/tpnamed.Future{{\[\[0\]uint8\]}}" @"__llgo_stub.{{.*}}/cl/_testgo/tpnamed.main$1"(ptr %0){{.*}} {
// CHECK-NEXT: _llgo_0:
// CHECK-NEXT: %1 = tail call %"{{.*}}/cl/_testgo/tpnamed.Future{{\[\[0\]byte\]}}" @"{{.*}}/cl/_testgo/tpnamed.main$1"()
// CHECK-NEXT: ret %"{{.*}}/cl/_testgo/tpnamed.Future{{\[\[0\]byte\]}}" %1
// CHECK-NEXT: %1 = tail call %"{{.*}}/cl/_testgo/tpnamed.Future{{\[\[0\]uint8\]}}" @"{{.*}}/cl/_testgo/tpnamed.main$1"()
// CHECK-NEXT: ret %"{{.*}}/cl/_testgo/tpnamed.Future{{\[\[0\]uint8\]}}" %1
// CHECK-NEXT: }

// CHECK-LABEL: define linkonce [0 x i8] @"{{.*}}/cl/_testgo/tpnamed.RunIO{{\[\[0\]byte\]}}"(%"{{.*}}/cl/_testgo/tpnamed.IO{{\[\[0\]byte\]}}" %0){{.*}} {
// CHECK-LABEL: define linkonce [0 x i8] @"{{.*}}/cl/_testgo/tpnamed.RunIO{{\[\[0\]uint8\]}}"(%"{{.*}}/cl/_testgo/tpnamed.IO{{\[\[0\]uint8\]}}" %0){{.*}} {
// CHECK-NEXT: _llgo_0:
// CHECK-NEXT: %1 = extractvalue %"{{.*}}/cl/_testgo/tpnamed.IO{{\[\[0\]byte\]}}" %0, 1
// CHECK-NEXT: %2 = extractvalue %"{{.*}}/cl/_testgo/tpnamed.IO{{\[\[0\]byte\]}}" %0, 0
// CHECK-NEXT: %3 = call %"{{.*}}/cl/_testgo/tpnamed.Future{{\[\[0\]byte\]}}" %2(ptr %1)
// CHECK-NEXT: %4 = extractvalue %"{{.*}}/cl/_testgo/tpnamed.Future{{\[\[0\]byte\]}}" %3, 1
// CHECK-NEXT: %5 = extractvalue %"{{.*}}/cl/_testgo/tpnamed.Future{{\[\[0\]byte\]}}" %3, 0
// CHECK-NEXT: %1 = extractvalue %"{{.*}}/cl/_testgo/tpnamed.IO{{\[\[0\]uint8\]}}" %0, 1
// CHECK-NEXT: %2 = extractvalue %"{{.*}}/cl/_testgo/tpnamed.IO{{\[\[0\]uint8\]}}" %0, 0
// CHECK-NEXT: %3 = call %"{{.*}}/cl/_testgo/tpnamed.Future{{\[\[0\]uint8\]}}" %2(ptr %1)
// CHECK-NEXT: %4 = extractvalue %"{{.*}}/cl/_testgo/tpnamed.Future{{\[\[0\]uint8\]}}" %3, 1
// CHECK-NEXT: %5 = extractvalue %"{{.*}}/cl/_testgo/tpnamed.Future{{\[\[0\]uint8\]}}" %3, 0
// CHECK-NEXT: %6 = icmp eq ptr %5, null
// CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.AssertNilDeref"(i1 %6)
// CHECK-NEXT: %7 = call [0 x i8] %5(ptr %4)
Expand Down
223 changes: 14 additions & 209 deletions cmd/internal/test/test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,9 @@
package test

import (
"bytes"
"fmt"
"io"
"os"
"os/exec"
"runtime"
"strconv"
"strings"
"sync"

"github.com/goplus/llgo/cmd/internal/base"
"github.com/goplus/llgo/cmd/internal/flags"
Expand All @@ -27,8 +21,6 @@ var Cmd = &base.Command{

var goBuildFlags *base.PassArgs

const parallelWorkerEnv = "LLGO_TEST_PARALLEL_WORKER"

func init() {
Cmd.Run = runCmd
goBuildFlags = flags.CaptureGoBuildFlags(Cmd)
Expand Down Expand Up @@ -73,216 +65,29 @@ func runCmd(cmd *base.Command, args []string) {

// Build test binary arguments from flags
conf.RunArgs = buildTestArgs(testBinaryArgs)
conf.TestFailFast = flags.TestFailfast
conf.TestJSON = flags.TestJSON
conf.TestRunSequential = testRunsMustBeSequential()

pkgArgs := cmd.Flag.Args()
parallelism := effectiveParallelism(conf.BuildParallelism)
if canRunPackagesInParallel(conf, pkgArgs, parallelism) {
pkgs, err := listTestPackages(conf, pkgArgs)
if err != nil {
fmt.Fprintln(os.Stderr, err)
mockable.Exit(1)
}
if len(pkgs) > 1 {
executable, err := os.Executable()
if err != nil {
fmt.Fprintln(os.Stderr, err)
mockable.Exit(1)
}
// flag.FlagSet stops at the first non-flag, so pkgArgs is a
// contiguous suffix of llgoArgs.
flagArgs := llgoArgs[:len(llgoArgs)-len(pkgArgs)]
var outputMu sync.Mutex
result := runTestPackages(pkgs, parallelism, flags.TestFailfast, func(pkg string) error {
childArgs := buildParallelChildArgs(flagArgs, pkg, testBinaryArgs)
child := exec.Command(executable, childArgs...)
child.Env = append(os.Environ(), parallelWorkerEnv+"=1")
var output bytes.Buffer
child.Stdout = &output
child.Stderr = &output
err := child.Run()

// Flush one completed package at a time so parallel child
// output cannot interleave.
outputMu.Lock()
if err != nil {
if _, ok := err.(*exec.ExitError); !ok {
fmt.Fprintf(os.Stderr, "failed to run test package %s: %v\n", pkg, err)
}
}
reportTestPackageResult(os.Stdout, os.Stderr, pkg, output.Bytes(), err, flags.TestJSON)
outputMu.Unlock()
return err
})
if result.skipped != 0 {
fmt.Fprintf(os.Stderr, "FAIL\t%d package(s) skipped by -failfast\n", result.skipped)
}
if result.failed {
mockable.Exit(1)
}
return
}
}

_, err := build.Do(pkgArgs, conf)
if err != nil {
fmt.Fprintln(os.Stderr, err)
mockable.Exit(1)
}
}

func canRunPackagesInParallel(conf *build.Config, pkgArgs []string, parallelism int) bool {
if os.Getenv(parallelWorkerEnv) != "" || conf.Target != "" || conf.CompileOnly || conf.OutFile != "" {
return false
}
if parallelism == 1 {
return false
}
for _, arg := range pkgArgs {
if strings.HasSuffix(arg, ".go") {
return false
}
}
// These flags name process-wide output files. Until LLGo merges or
// disambiguates them like cmd/go, keep the existing sequential behavior.
return flags.TestCoverProfile == "" &&
flags.TestCPUProfile == "" &&
flags.TestMemProfile == "" &&
flags.TestBlockProfile == "" &&
flags.TestMutexProfile == "" &&
flags.TestTrace == "" &&
flags.TestTestLogFile == "" &&
flags.TestFuzz == ""
}

func effectiveParallelism(parallelism int) int {
if parallelism == 0 {
parallelism = runtime.GOMAXPROCS(0)
}
if parallelism < 1 {
return 1
}
return parallelism
}

func listTestPackages(conf *build.Config, patterns []string) ([]string, error) {
if len(patterns) == 0 {
patterns = []string{"."}
}
tags := build.DefaultBuildTags(conf.Goarch, conf.Target)
if conf.Tags != "" {
tags += "," + conf.Tags
}
args := make([]string, 0, 4+len(conf.GoBuildFlags)+len(patterns))
args = append(args, "list", "-tags="+tags)
args = append(args, conf.GoBuildFlags...)
args = append(args, "--")
args = append(args, patterns...)

list := exec.Command("go", args...)
list.Env = append(os.Environ(), "GOOS="+conf.Goos, "GOARCH="+conf.Goarch)
var stderr bytes.Buffer
list.Stderr = &stderr
output, err := list.Output()
if err != nil {
if message := strings.TrimSpace(stderr.String()); message != "" {
return nil, fmt.Errorf("%s", message)
}
return nil, err
}

lines := strings.Fields(string(output))
pkgs := make([]string, 0, len(lines))
seen := make(map[string]bool, len(lines))
for _, pkg := range lines {
if !seen[pkg] {
seen[pkg] = true
pkgs = append(pkgs, pkg)
}
}
return pkgs, nil
}

func buildParallelChildArgs(flagArgs []string, pkg string, testBinaryArgs []string) []string {
args := make([]string, 0, 3+len(flagArgs)+len(testBinaryArgs))
args = append(args, "test")
for i := 0; i < len(flagArgs); i++ {
arg := flagArgs[i]
switch {
case arg == "-p" || arg == "--p":
i++ // The successfully parsed flag always has a following value.
case strings.HasPrefix(arg, "-p=") || strings.HasPrefix(arg, "--p="):
case arg == "--":
// The package pattern has already been resolved by go list, so
// the parent's flag terminator is no longer needed.
default:
args = append(args, arg)
}
}
// The parent owns package-level fan-out. Keep each worker's go/packages
// loading serial to avoid multiplying -p across child processes.
args = append(args, "-p=1")
args = append(args, pkg)
if len(testBinaryArgs) != 0 {
args = append(args, "-args")
args = append(args, testBinaryArgs...)
}
return args
}

func reportTestPackageResult(stdout, stderr io.Writer, pkg string, output []byte, err error, json bool) {
if len(output) != 0 {
_, _ = stdout.Write(output)
if output[len(output)-1] != '\n' {
fmt.Fprintln(stdout)
}
}
if err != nil {
fmt.Fprintf(stderr, "FAIL\t%s\n", pkg)
} else if !json {
fmt.Fprintf(stdout, "ok \t%s\n", pkg)
}
}

type testRunResult struct {
failed bool
skipped int
}

func runTestPackages(pkgs []string, parallelism int, failFast bool, run func(string) error) testRunResult {
if parallelism < 1 {
parallelism = 1
}
if parallelism > len(pkgs) {
parallelism = len(pkgs)
}
results := make(chan error, parallelism)
start := func(pkg string) {
go func() {
results <- run(pkg)
}()
}

next := 0
running := 0
for next < len(pkgs) && running < parallelism {
start(pkgs[next])
next++
running++
}
var result testRunResult
for running != 0 {
if err := <-results; err != nil {
result.failed = true
}
running--
if next < len(pkgs) && !(failFast && result.failed) {
start(pkgs[next])
next++
running++
}
}
result.skipped = len(pkgs) - next
return result
func testRunsMustBeSequential() bool {
// These flags either name output paths shared by every test binary or, for
// fuzzing, require one active test binary. Keep their execution sequential.
return flags.TestCoverProfile != "" ||
flags.TestCPUProfile != "" ||
flags.TestMemProfile != "" ||
flags.TestBlockProfile != "" ||
flags.TestMutexProfile != "" ||
flags.TestTrace != "" ||
flags.TestTestLogFile != "" ||
flags.TestFuzz != ""
}

// splitArgsAt splits args at the separator flag (e.g., "-args")
Expand Down
Loading
Loading