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
76 changes: 43 additions & 33 deletions eng/_util/buildutil/testjson.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,44 +49,40 @@ func (f *TestJSONFlags) AppendToCmdline(cmdline []string) []string {
func (f *TestJSONFlags) RunTestCmd(cmdline []string) (err error) {
var writers []io.Writer
var needJSON bool
var rawTestOutFile string
var removeRawTestOutFile bool
var rawTestOut *os.File

if f != nil {
if f.JUnitOutFile != "" {
var jf *os.File
if err := os.MkdirAll(filepath.Dir(f.JUnitOutFile), 0o755); err != nil {
return fmt.Errorf("failed to create directory for JUnit output: %v", err)
if f.RawTestOutFile != "" || f.JUnitOutFile != "" {
rawTestOutFile = f.RawTestOutFile
if rawTestOutFile != "" {
if err := os.MkdirAll(filepath.Dir(rawTestOutFile), 0o755); err != nil {
return fmt.Errorf("failed to create directory for raw test output: %v", err)
}
rawTestOut, err = os.Create(rawTestOutFile)
} else {
rawTestOut, err = os.CreateTemp("", "go-test-json-*.txt")
}
if jf, err = os.Create(f.JUnitOutFile); err != nil {
return err
if err != nil {
return fmt.Errorf("failed to create raw test output: %v", err)
}
defer func() {
err = errors.Join(err, jf.Close())
}()
c := json2junit.NewConverterWithOptions(jf, &json2junit.Options{
IncludePackageInTestName: true,
})
defer func() {
err = errors.Join(err, c.Close())
}()
writers = append(writers, c)
needJSON = true
}
if f.RawTestOutFile != "" {
var rf *os.File
if err := os.MkdirAll(filepath.Dir(f.RawTestOutFile), 0o755); err != nil {
return fmt.Errorf("failed to create directory for raw test output: %v", err)
if rawTestOutFile == "" {
rawTestOutFile = rawTestOut.Name()
removeRawTestOutFile = true
}
if removeRawTestOutFile {
defer func() {
err = errors.Join(err, os.Remove(rawTestOutFile))
}()
}
if rf, err = os.Create(f.RawTestOutFile); err != nil {
return err
if f.RawTestOutFile != "" {
writers = append(writers, &testJSONSummaryConverter{w: os.Stdout})
}
writers = append(writers, rawTestOut)
if f.RawTestOutFile == "" {
writers = append(writers, os.Stdout)
}
defer func() {
err = errors.Join(err, rf.Close())
}()
writers = append(
writers,
&testJSONSummaryConverter{w: os.Stdout},
rf,
)
needJSON = true
} else {
// If we don't summarize, we need to write directly to stdout.
Expand All @@ -97,7 +93,21 @@ func (f *TestJSONFlags) RunTestCmd(cmdline []string) (err error) {
cmdline = append(cmdline, "-json")
}

return RunCmdMultiWriter(cmdline, writers...)
runErr := RunCmdMultiWriter(cmdline, writers...)
if rawTestOut != nil {
runErr = errors.Join(runErr, rawTestOut.Close())
}

if f != nil && f.JUnitOutFile != "" {
// Convert after the command exits so a converter error can't close the
// test process' stdout pipe and truncate the raw output.
runErr = errors.Join(runErr, json2junit.ConvertFileWithOptions(
f.JUnitOutFile,
rawTestOutFile,
&json2junit.Options{IncludePackageInTestName: true},
))
}
return runErr
}

// testJSONSummaryConverter reads Go JSON test output and writes a summary that
Expand Down
94 changes: 94 additions & 0 deletions eng/_util/buildutil/testjson_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
// Copyright (c) Microsoft Corporation.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package buildutil

import (
"fmt"
"os"
"path/filepath"
"strings"
"testing"
)

func TestRunTestCmdConvertsRawOutputAfterCommand(t *testing.T) {
t.Setenv("GO_WANT_TESTJSON_HELPER_PROCESS", "good-json")

tmpDir := t.TempDir()
rawOut := filepath.Join(tmpDir, "raw.jsonl")
junitOut := filepath.Join(tmpDir, "test-results.xml")
flags := &TestJSONFlags{
JUnitOutFile: junitOut,
RawTestOutFile: rawOut,
}

if err := flags.RunTestCmd([]string{os.Args[0], "-test.run=TestRunTestCmdHelperProcess", "--"}); err != nil {
t.Fatal(err)
}

raw, err := os.ReadFile(rawOut)
if err != nil {
t.Fatal(err)
}
if !strings.Contains(string(raw), "TestDescribeConflict") {
t.Fatalf("raw output does not contain test event:\n%s", raw)
}

junit, err := os.ReadFile(junitOut)
if err != nil {
t.Fatal(err)
}
if !strings.Contains(string(junit), `name="net/http.TestDescribeConflict"`) {
t.Fatalf("JUnit output does not contain package-prefixed test name:\n%s", junit)
}
}

func TestRunTestCmdPreservesRawOutputWhenJUnitConversionFails(t *testing.T) {
t.Setenv("GO_WANT_TESTJSON_HELPER_PROCESS", "bad-json")

tmpDir := t.TempDir()
rawOut := filepath.Join(tmpDir, "raw.jsonl")
junitOut := filepath.Join(tmpDir, "test-results.xml")
flags := &TestJSONFlags{
JUnitOutFile: junitOut,
RawTestOutFile: rawOut,
}

err := flags.RunTestCmd([]string{os.Args[0], "-test.run=TestRunTestCmdHelperProcess", "--"})
if err == nil {
t.Fatal("expected JUnit conversion to fail")
}
if !strings.Contains(err.Error(), "no run entry for TestMissing") {
t.Fatalf("expected missing run entry error, got %v", err)
}

raw, err := os.ReadFile(rawOut)
if err != nil {
t.Fatal(err)
}
if !strings.Contains(string(raw), "TestMissing") {
t.Fatalf("raw output does not contain failing converter line:\n%s", raw)
}
}

func TestRunTestCmdHelperProcess(t *testing.T) {
switch os.Getenv("GO_WANT_TESTJSON_HELPER_PROCESS") {
case "good-json":
fmt.Print(`{"Time":"2026-05-19T07:24:14Z","Action":"start","Package":"net/http"}
{"Time":"2026-05-19T07:24:14Z","Action":"run","Package":"net/http","Test":"TestDescribeConflict"}
{"Time":"2026-05-19T07:24:14Z","Action":"output","Package":"net/http","Test":"TestDescribeConflict","Output":"=== RUN TestDescribeConflict\n"}
{"Time":"2026-05-19T07:24:14Z","Action":"pass","Package":"net/http","Test":"TestDescribeConflict","Elapsed":0}
{"Time":"2026-05-19T07:24:14Z","Action":"pass","Package":"net/http","Elapsed":0}
`)
case "bad-json":
fmt.Print(`{"Time":"2026-05-19T07:24:14Z","Action":"start","Package":"net/http"}
{"Time":"2026-05-19T07:24:14Z","Action":"run","Package":"net/http","Test":"TestDescribeConflict"}
{"Time":"2026-05-19T07:24:14Z","Action":"pass","Package":"net/http","Test":"TestDescribeConflict","Elapsed":0}
{"Time":"2026-05-19T07:24:14Z","Action":"pass","Package":"net/http","Test":"TestMissing","Elapsed":0}
`)
default:
return
}
os.Exit(0)
}
2 changes: 1 addition & 1 deletion go
24 changes: 12 additions & 12 deletions patches/0002-Add-crypto-backend-GOEXPERIMENTs.patch
Original file line number Diff line number Diff line change
Expand Up @@ -290,7 +290,7 @@ index 89fd74eb823162..6d266ff641d721 100644
var Error error

diff --git a/src/internal/buildcfg/exp.go b/src/internal/buildcfg/exp.go
index c8506d41bb240f..ec32ea2b4bf13c 100644
index 3c7e2aa7d442d2..87b50da167e5e6 100644
--- a/src/internal/buildcfg/exp.go
+++ b/src/internal/buildcfg/exp.go
@@ -6,6 +6,7 @@ package buildcfg
Expand Down Expand Up @@ -340,15 +340,15 @@ index c8506d41bb240f..ec32ea2b4bf13c 100644
// Older versions (anything before V16) of dsymutil don't handle
// the .debug_rnglists section in DWARF5. See
// https://github.com/golang/go/issues/26379#issuecomment-2677068742
@@ -84,6 +110,7 @@ func ParseGOEXPERIMENT(goos, goarch, goexp string) (*ExperimentFlags, error) {
Dwarf5: dwarf5Supported,
RandomizedHeapBase64: true,
GreenTeaGC: true,
@@ -79,6 +105,7 @@ func ParseGOEXPERIMENT(goos, goarch, goexp string) (*ExperimentFlags, error) {
dwarf5Supported := (goos != "darwin" && goos != "ios" && goos != "aix")

baseline := goexperiment.Flags{
+ SystemCrypto: systemCryptoSupported, // if system crypto is supported, enable it by default
}
flags := &ExperimentFlags{
Flags: baseline,
@@ -123,6 +150,10 @@ func ParseGOEXPERIMENT(goos, goarch, goexp string) (*ExperimentFlags, error) {
RegabiWrappers: regabiSupported,
RegabiArgs: regabiSupported,
Dwarf5: dwarf5Supported,
@@ -124,6 +151,10 @@ func ParseGOEXPERIMENT(goos, goarch, goexp string) (*ExperimentFlags, error) {
// to build with any experiment flags.
flags.Flags = goexperiment.Flags{}
continue
Expand All @@ -359,7 +359,7 @@ index c8506d41bb240f..ec32ea2b4bf13c 100644
}
val := true
if strings.HasPrefix(f, "no") {
@@ -149,6 +180,9 @@ func ParseGOEXPERIMENT(goos, goarch, goexp string) (*ExperimentFlags, error) {
@@ -150,6 +181,9 @@ func ParseGOEXPERIMENT(goos, goarch, goexp string) (*ExperimentFlags, error) {
if flags.RegabiArgs && !flags.RegabiWrappers {
return nil, fmt.Errorf("GOEXPERIMENT regabiargs requires regabiwrappers")
}
Expand Down Expand Up @@ -522,10 +522,10 @@ index 17fe15270aba60..5f09ad11a701e2 100644
// enabled individually. Not all combinations work.
// The "regabi" GOEXPERIMENT is an alias for all "working"
diff --git a/src/internal/platform/supported.go b/src/internal/platform/supported.go
index 6cee00a7aebcdb..1182e37b86c0fc 100644
index 6f37e368596498..471c5cc430bb29 100644
--- a/src/internal/platform/supported.go
+++ b/src/internal/platform/supported.go
@@ -281,3 +281,15 @@ func FirstClass(goos, goarch string) bool {
@@ -279,3 +279,15 @@ func FirstClass(goos, goarch string) bool {
func Broken(goos, goarch string) bool {
return distInfo[OSArch{goos, goarch}].Broken
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: bot-for-go[bot] <199222863+bot-for-go[bot]@users.noreply.github.com>
Date: Tue, 19 May 2026 11:42:41 -0700
Subject: [PATCH] cmd/dist: fix JSON processing of trailing bytes

The testJSONFilter.process was implicitly depending
on the exact internal buffer size of the json.Decoder,
which happened to be large enough in v1 for most lines
that cmd/dist cared about.

However, the re-implementation of v1 using encoding/json/v2
changes the implicit buffer size used, which results in
some trailing bytes accidentally being discarded.

The correct use of json.Decoder.Buffered requires also
consulting the input io.Reader for additional trailing data.

Fixes #79498

Change-Id: If4bdac12b638d78e0870808f2285fb1731b0fae6
Reviewed-on: https://go-review.googlesource.com/c/go/+/779980
Reviewed-by: Michael Pratt <mpratt@google.com>
Reviewed-by: Damien Neil <dneil@google.com>
Auto-Submit: Damien Neil <dneil@google.com>
LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
---
src/cmd/dist/testjson.go | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/src/cmd/dist/testjson.go b/src/cmd/dist/testjson.go
index c190c665ebf779..8ae6e8905d578c 100644
--- a/src/cmd/dist/testjson.go
+++ b/src/cmd/dist/testjson.go
@@ -80,7 +80,8 @@ func (f *testJSONFilter) process(line []byte) {
// struct, or other additions outside of it. If humans are ever looking
// at the output, it's really nice to keep field order because it
// preserves a lot of regularity in the output.
- dec := json.NewDecoder(bytes.NewBuffer(line))
+ lineBuf := bytes.NewBuffer(line)
+ dec := json.NewDecoder(lineBuf)
dec.UseNumber()
val, err := decodeJSONValue(dec)
if err == nil && val.atom == json.Delim('{') {
@@ -105,6 +106,7 @@ func (f *testJSONFilter) process(line []byte) {
// Copy any trailing text. We expect at most a "\n" here, but
// there could be other text and we want to feed that through.
io.Copy(f.w, dec.Buffered())
+ io.Copy(f.w, lineBuf)
return
}
}
Loading