Skip to content
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
2 changes: 2 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,5 @@
*.ll text eol=lf
*.sh text eol=lf
_demo/go/export/libexport.h.want text eol=lf
# Golden test output must retain its exact bytes during checkout.
internal/test2json/testdata/*.test -text
12 changes: 9 additions & 3 deletions .github/workflows/llgo.yml
Original file line number Diff line number Diff line change
Expand Up @@ -889,15 +889,21 @@ jobs:
LLGO_BUILD_CACHE: "1"
run: dev/test_wasm_workers.sh

- name: Test SIMD behavior on WASI
- name: Test SIMD behavior on WebAssembly
if: matrix.suite == 'test-command'
shell: bash
env:
GOEXPERIMENT: simd
run: |
GOOS=wasip1 GOARCH=wasm go test -exec=wasmtime -count=1 -timeout=2m ./test/simd/...
# The unoptimized testing framework exceeds Wasmtime's locals limit.
"$RUNNER_TEMP/llgo-bin/llgo" test -O2 -target wasi -emulator \
# WAMR's classic interpreter supports the legacy EH required by
# LLGo's WASI pthread runtime, but cannot execute SIMD instructions.
# Keep vector arguments inside Wasm during panic/recover. Use the
# LLVM version that produced LLGo's IR for native Wasm SjLj lowering.
env EM_LLVM_ROOT="$(llvm-config --bindir)" \
EMCC_CFLAGS="-fwasm-exceptions -sSUPPORT_LONGJMP=wasm" \
EM_CACHE="$RUNNER_TEMP/simd-emcc-cache" LLGO_BUILD_CACHE=off \
"$RUNNER_TEMP/llgo-bin/llgo" test -O2 -target emscripten -emulator \
-v -count=1 -timeout=2m ./test/simd/...

- name: Compare JavaScript call semantics with Go
Expand Down
7 changes: 5 additions & 2 deletions cmd/internal/test/test.go
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,11 @@ func buildTestArgs(customArgs []string) []string {
}
}

appendBool(flags.Verbose, "-test.v")
if flags.TestJSON {
args = append(args, "-test.v=test2json")
} else {
appendBool(flags.Verbose, "-test.v")
}
appendString(flags.TestRun, "-test.run=")
appendString(flags.TestBench, "-test.bench=")
appendString(flags.TestList, "-test.list=")
Expand All @@ -214,7 +218,6 @@ func buildTestArgs(customArgs []string) []string {
appendBool(flags.TestFailfast, "-test.failfast")
appendString(flags.TestShuffle, "-test.shuffle=")

appendBool(flags.TestJSON, "-test.json")
appendBool(flags.TestFullpath, "-test.fullpath")

appendBool(flags.TestBenchmem, "-test.benchmem")
Expand Down
3 changes: 2 additions & 1 deletion cmd/internal/test/test_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -287,7 +287,8 @@ func TestBuildTestArgs(t *testing.T) {
flags.TestGoCoverDir = "/tmp/cover"
},
customArgs: nil,
wantContain: []string{"-test.json", "-test.gocoverdir=/tmp/cover"},
wantContain: []string{"-test.v=test2json", "-test.gocoverdir=/tmp/cover"},
wantAbsent: []string{"-test.json", "-test.v"},
},
{
name: "coverage is configured by the build driver",
Expand Down
12 changes: 6 additions & 6 deletions dev/test_wasm_wasi_threads.py
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ def main():
run_llgo(env, ["run", "-target", "wasi", "-emulator",
str(ROOT / "internal/build/testdata/wasm-wasi-threaded-fs")],
"wasi threaded filesystem ok")
run_llgo(env, ["test", "-target", "wasi", "-emulator",
run_llgo(env, ["test", "-v", "-target", "wasi", "-emulator",
str(ROOT / "test/std/errors")], "PASS")
# Compiling test/go on a cold CI runner and running the GC race are
# separate budgets. Verbose, inherited output identifies a slow test
Expand Down Expand Up @@ -179,17 +179,17 @@ def main():
"-test.run=" + pattern],
env=env, check=True, timeout=300)
print(f"WAMR GC test {name}: {time.monotonic() - started:.2f}s", flush=True)
run_llgo(env, ["test", "-target", "wasi", "-emulator", "-run",
run_llgo(env, ["test", "-v", "-target", "wasi", "-emulator", "-run",
"^TestPoolAfterGC$", str(ROOT / "test/std/sync")], "PASS",
timeout=300)
run_llgo(env, ["test", "-target", "wasi", "-emulator",
run_llgo(env, ["test", "-v", "-target", "wasi", "-emulator",
str(ROOT / "test/std/go/importer")], "PASS")
run_llgo(env, ["test", "-target", "wasi", "-emulator", "-run",
run_llgo(env, ["test", "-v", "-target", "wasi", "-emulator", "-run",
"^(TestTBasicMethods|FuzzExample)$",
str(ROOT / "test/std/testing")], "PASS")
run_llgo(env, ["test", "-target", "wasi", "-emulator",
run_llgo(env, ["test", "-v", "-target", "wasi", "-emulator",
str(ROOT / "test/std/weak")], "PASS")
run_llgo(env, ["test", "-target", "wasi", "-emulator",
run_llgo(env, ["test", "-v", "-target", "wasi", "-emulator",
"-run", "^TestConcurrentSelectProposeReplyStress$",
str(ROOT / "test")], "PASS")
subprocess.run(
Expand Down
40 changes: 23 additions & 17 deletions internal/build/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -197,8 +197,9 @@ type Config struct {
TestRunSequential bool
// TestFailFast stops launching test binaries after the first failure.
TestFailFast bool
// TestJSON suppresses parent-generated plain-text success summaries.
// TestJSON streams package-attributed test2json events.
TestJSON bool
testOutput *testOutputPolicy
LinkOptions LinkOptions
DebugArtifactMode DebugArtifactMode
// DebugArtifactModeSet distinguishes an explicit mode from the build default.
Expand Down Expand Up @@ -802,6 +803,7 @@ func buildInvocation(inv Invocation, plan *initialBuildPlan) (result []Package,
}
}
if mode == ModeTest {
configureTestOutput(conf, inv.Args, initial)
initial, err = filterTestPackages(initial, conf.OutFile)
if err != nil {
return nil, err
Expand Down Expand Up @@ -1269,24 +1271,28 @@ func executeInitialPackageLink(ctx *context, link *initialPackageLink, verbose,
return nil, flash.FlashDevice(linkCtx.crossCompile.Device, envMap, linkCtx.buildConf.Port, verbose)
}
case ModeRun, ModeTest, ModeCmpTest:
if link.conf.Target == "" {
if link.conf.Mode == ModeTest && (link.conf.Target == "" || namedTargetUsesEmulatorPath(link.conf) && !link.conf.CompileOnly) {
runner := goCompatibleWasmRunner(link.conf)
if link.conf.Mode == ModeTest {
program := &testProgram{
coverage: link.conf.coverage != nil,
app: link.outFmts.Out,
pkgDir: link.pkg.Dir,
pkgName: strings.TrimSuffix(link.pkg.PkgPath, ".test"),
runner: runner,
runnerEnv: envMap,
profile: string(linkCtx.crossCompile.WasmProfile),
}
if cleanupTemp {
program.temporaryOutputs = link.outFmts
cleanupTemp = false // runNativeTest now owns the temporary output.
}
return program, nil
if link.conf.Target != "" {
runner = linkCtx.crossCompile.Emulator
}
program := &testProgram{
coverage: link.conf.coverage != nil,
app: link.outFmts.Out,
pkgDir: link.pkg.Dir,
pkgName: strings.TrimSuffix(link.pkg.PkgPath, ".test"),
runner: runner,
runnerEnv: envMap,
profile: string(linkCtx.crossCompile.WasmProfile),
}
if cleanupTemp {
program.temporaryOutputs = link.outFmts
cleanupTemp = false // runNativeTest now owns the temporary output.
}
return program, nil
}
if link.conf.Target == "" {
runner := goCompatibleWasmRunner(link.conf)
if runner != "" && link.conf.Mode == ModeRun {
return nil, runInEmulator(linkCtx.commands, runner, string(linkCtx.crossCompile.WasmProfile), envMap, link.pkg.Dir, link.pkg.PkgPath, link.conf, link.conf.Mode, verbose)
}
Expand Down
16 changes: 13 additions & 3 deletions internal/build/build_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1489,16 +1489,16 @@ func TestExtest(t *testing.T) {
if testingBuilds != 1 {
t.Errorf("testing package builds = %d, want 1 shared build", testingBuilds)
}
if count := strings.Count(got, "PASS\n"); count != 4 {
t.Errorf("PASS count = %d, want 4; output:\n%s", count, got)
if strings.Contains(got, "PASS\n") {
t.Errorf("non-verbose package-list mode exposed successful test output:\n%s", got)
}
for _, pkg := range []string{
"github.com/xgo-dev/llgo/cl/_testgo/runextest",
"github.com/xgo-dev/llgo/cl/_testgo/runextest/bar",
"github.com/xgo-dev/llgo/cl/_testgo/runextest/bar/barinner",
"github.com/xgo-dev/llgo/cl/_testgo/runextest/foo",
} {
if !strings.Contains(got, "ok \t"+pkg+"\n") {
if !strings.Contains(got, "ok \t"+pkg+"\t") {
t.Errorf("output does not contain result for %s:\n%s", pkg, got)
}
}
Expand Down Expand Up @@ -1996,6 +1996,16 @@ func TestExecuteInitialPackageLinkCompileOnlyNamedTargetDoesNotExecute(t *testin
if data, err := os.ReadFile(output); err != nil || string(data) != "linked" {
t.Fatalf("linked output = %q, %v", data, err)
}
// Executed emulator tests must join the same output-aware test batch as
// native programs. The linker helper writes an opaque artifact, so any
// attempt to launch it here would fail this assertion.
conf.CompileOnly, conf.Emulator = false, true
ctx.crossCompile.Emulator = "host-runner {}"
link.plan = &mainLinkPlan{outputPath: output}
program, err = executeInitialPackageLink(ctx, link, false, false)
if err != nil || program == nil || program.runner != ctx.crossCompile.Emulator || program.pkgName != "example.com/wasm-test" {
t.Fatalf("emulator test did not return a deferred test program: %+v, %v", program, err)
}
}

func TestExecuteInitialPackageLinkRawWasmRunUsesHostRunner(t *testing.T) {
Expand Down
2 changes: 1 addition & 1 deletion internal/build/coverage_errors_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ func TestCoverageRunErrors(t *testing.T) {
}
conf.TestJSON = true
if err := runCoveredTest(commandEnv{}, program, conf, &stdout, &stderr); err == nil {
t.Fatal("ignored test2json start failure")
t.Fatal("ignored test process start failure in JSON mode")
}
if err := c.merge(dir); err == nil {
t.Fatal("accepted a directory as an input profile")
Expand Down
67 changes: 31 additions & 36 deletions internal/build/coverage_run.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import (
"os"
"os/exec"
"path/filepath"
"slices"
"strings"
"time"
)
Expand Down Expand Up @@ -139,10 +138,7 @@ func runCoveredTest(commands commandEnv, program testProgram, conf *Config, stdo
copyConf := *conf
copyConf.coverage = nil
if conf.TestJSON {
args = slices.DeleteFunc(args, func(arg string) bool {
return arg == "-test.json" || arg == "-test.v" || strings.HasPrefix(arg, "-test.v=")
})
args = append(args, "-test.v=test2json")
args = testJSONArgs(args)
}
copyConf.RunArgs = args
if conf.PrintCommands {
Expand All @@ -151,46 +147,50 @@ func runCoveredTest(commands commandEnv, program testProgram, conf *Config, stdo
copyConf.PrintCommands = false
}
var output bytes.Buffer
var converter *exec.Cmd
var input io.WriteCloser
var testOutput io.Writer = &output
if conf.TestJSON {
// Start before the test so test2json measures real elapsed time and
// preserves event timestamps, even when the parent buffers packages.
converter = c.commands.configure(exec.Command(c.goCommand, "tool", "test2json", "-t", "-p", program.pkgName))
converter.Stdout = stdout
converter.Stderr = stderr
input, err = converter.StdinPipe()
if err != nil {
return err
}
if err := converter.Start(); err != nil {
input.Close()
return err
policy := conf.testOutput
if policy == nil {
policy = newTestOutputPolicy(conf, c.local, 1)
}
stream := policy.stream
var metadata testOutputMetadata
if stream {
testOutput = stdout
if !policy.direct {
if conf.TestJSON {
metadata.coverageLimit = 128 + len(c.options.Packages)
testOutput = io.MultiWriter(stdout, &metadata)
} else {
testOutput = io.MultiWriter(stdout, &output)
}
}
testOutput = io.MultiWriter(&output, input)
}
start := time.Now()
err = runNativeTest(commands, program, &copyConf, testOutput, testOutput)
elapsed := time.Since(start)
if metadata.written && metadata.last != '\n' || output.Len() != 0 && output.Bytes()[output.Len()-1] != '\n' {
fmt.Fprintln(testOutput)
}
mergeErr := c.merge(profile)
err = errors.Join(err, mergeErr)
var report bytes.Buffer
show := c.local
for _, arg := range args {
show = show || arg == "-test.v" || arg == "-test.v=true" ||
strings.HasPrefix(arg, "-test.bench=") || strings.HasPrefix(arg, "-test.list=")
}
if !conf.TestJSON && (show || err != nil) {
if !stream && (policy.show || err != nil) {
report.Write(output.Bytes())
}
if mergeErr != nil {
fmt.Fprintln(&report, mergeErr)
}
if err != nil {
fmt.Fprintf(&report, "FAIL\t%s\t%.3fs\n", program.pkgName, elapsed.Seconds())
prefix := ""
if conf.TestJSON {
prefix = "\x16"
}
fmt.Fprintf(&report, "%sFAIL\t%s\t%.3fs\n", prefix, program.pkgName, elapsed.Seconds())
} else {
suffix := ""
if !c.local && len(metadata.coverage) != 0 {
suffix = "\t" + string(metadata.coverage)
}
for _, line := range strings.Split(output.String(), "\n") {
if index := strings.Index(line, "coverage: "); !c.local && index >= 0 {
// Go 1.20 prefixes this line with the package path. Like
Expand All @@ -199,17 +199,12 @@ func runCoveredTest(commands commandEnv, program testProgram, conf *Config, stdo
break
}
}
if bytes.Contains(output.Bytes(), []byte("testing: warning: no tests to run")) {
if metadata.noTests || bytes.Contains(output.Bytes(), []byte(noTestsMarker)) {
suffix += " [no tests to run]"
}
fmt.Fprintf(&report, "ok \t%s\t%.3fs%s\n", program.pkgName, elapsed.Seconds(), suffix)
}
if conf.TestJSON {
_, writeErr := input.Write(report.Bytes())
err = errors.Join(err, writeErr, input.Close(), converter.Wait())
} else {
_, writeErr := stdout.Write(report.Bytes())
err = errors.Join(err, writeErr)
}
_, writeErr := stdout.Write(report.Bytes())
err = errors.Join(err, writeErr)
return err
}
Loading
Loading