Skip to content

Commit ae6942f

Browse files
ihuh0CQ Bot
authored andcommitted
[go_test_parser] Wrap go tests in their own parser.
This splits out the logic from testparser to specifically use the go parsing logic for known go tests. Bug: 435723394 Change-Id: I5c1beb8cb1936afe63121fd2230e07184c938d28 Reviewed-on: https://fuchsia-review.googlesource.com/c/fuchsia/+/1335566 Reviewed-by: Erick Tryzelaar <[email protected]> Reviewed-by: Oliver Newman <[email protected]> Fuchsia-Auto-Submit: Ina Huh <[email protected]> Reviewed-by: Clayton Wilkinson <[email protected]> Commit-Queue: Ina Huh <[email protected]>
1 parent 420da65 commit ae6942f

File tree

16 files changed

+464
-207
lines changed

16 files changed

+464
-207
lines changed

build/go/go_test.gni

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,14 +66,21 @@ template("go_test") {
6666

6767
if (is_host) {
6868
_go_build_target_name = "${target_name}_go_build"
69+
_test_data_target = "${target_name}_go_test_data"
70+
host_test_data(_test_data_target) {
71+
sources = [ "$root_out_dir/$_output_name" ]
72+
deps = [ ":${_go_build_target_name}" ]
73+
}
6974
host_test(target_name) {
70-
binary_path = "$root_out_dir/$_output_name"
75+
binary_path = get_label_info("//tools/go_test_parser", "root_out_dir") +
76+
"/go_test_parser"
7177

7278
timeout = "5m"
7379
if (defined(invoker.timeout)) {
7480
timeout = invoker.timeout
7581
}
7682
args = [
83+
rebase_path("$root_out_dir/$_output_name", root_build_dir),
7784
"-test.timeout",
7885
timeout,
7986
"-test.v", # Emit detailed test case information.
@@ -82,7 +89,11 @@ template("go_test") {
8289
args += invoker.args
8390
}
8491

85-
deps = [ ":${_go_build_target_name}" ]
92+
deps = [
93+
":${_go_build_target_name}",
94+
":${_test_data_target}",
95+
"//tools/go_test_parser",
96+
]
8697

8798
if (defined(invoker.deps)) {
8899
deps += invoker.deps

src/sys/pkg/tests/system-tests/system-tests.gni

Lines changed: 13 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
# Use of this source code is governed by a BSD-style license that can be
33
# found in the LICENSE file.
44

5-
import("//build/testing/host_test.gni")
5+
import("//build/go/go_test.gni")
66

77
if (is_host) {
88
# Add this to go_test()'s environments to disable a test in infra
@@ -232,12 +232,11 @@ template("system_test_upgrade_suite") {
232232

233233
foreach(test, _tests) {
234234
_test_name = get_label_info(test, "name")
235-
_test_binary = "$root_out_dir/$_test_name"
236235

237236
_host_test_target_name = "${target_name}_${_test_name}"
238237
_deps += [ ":${_host_test_target_name}" ]
239238

240-
_args = [ "-test.v" ] # Print test detailed case status
239+
_args = []
241240

242241
foreach(build, _builds) {
243242
if (defined(build.release_build_id)) {
@@ -272,10 +271,6 @@ template("system_test_upgrade_suite") {
272271
if (defined(invoker.timeout)) {
273272
assert(invoker.timeout != "",
274273
"'${_host_test_target_name}' cannot have an empty timeout")
275-
_args += [
276-
"-test.timeout",
277-
invoker.timeout,
278-
]
279274
}
280275

281276
if (defined(invoker.pave_timeout)) {
@@ -400,10 +395,10 @@ template("system_test_upgrade_suite") {
400395
_args += [ "--resolver-mode=constant" ]
401396
}
402397

403-
host_test(_host_test_target_name) {
404-
binary_path = _test_binary
398+
go_test(_host_test_target_name) {
399+
library = "//src/sys/pkg/tests/system-tests/upgrade_test:gopkg"
405400
args = _args
406-
deps = [ test ]
401+
forward_variables_from(invoker, [ "timeout" ])
407402

408403
environments = []
409404
foreach(env, invoker.environments) {
@@ -490,12 +485,11 @@ template("system_test_reboot_suite") {
490485

491486
foreach(test, _tests) {
492487
_test_name = get_label_info(test, "name")
493-
_test_binary = "$root_out_dir/$_test_name"
494488

495489
_host_test_target_name = "${target_name}_${_test_name}"
496490
_deps += [ ":${_host_test_target_name}" ]
497491

498-
_args = [ "-test.v" ] # Print test detailed case status
492+
_args = []
499493

500494
if (defined(invoker.release_builder)) {
501495
_args += [
@@ -521,10 +515,6 @@ template("system_test_reboot_suite") {
521515
if (defined(invoker.timeout)) {
522516
assert(invoker.timeout != "",
523517
"'${_host_test_target_name}' cannot have an empty timeout")
524-
_args += [
525-
"-test.timeout",
526-
invoker.timeout,
527-
]
528518
}
529519

530520
if (defined(invoker.cycle_timeout)) {
@@ -570,10 +560,10 @@ template("system_test_reboot_suite") {
570560
_args += [ "--check-abr=false" ]
571561
}
572562

573-
host_test(_host_test_target_name) {
574-
binary_path = _test_binary
563+
go_test(_host_test_target_name) {
564+
library = "//src/sys/pkg/tests/system-tests/reboot_test:gopkg"
575565
args = _args
576-
deps = [ test ]
566+
forward_variables_from(invoker, [ "timeout" ])
577567

578568
environments = []
579569
foreach(env, invoker.environments) {
@@ -655,12 +645,11 @@ template("system_test_recovery_suite") {
655645

656646
foreach(test, _tests) {
657647
_test_name = get_label_info(test, "name")
658-
_test_binary = "$root_out_dir/$_test_name"
659648

660649
_host_test_target_name = "${target_name}_${_test_name}"
661650
_deps += [ ":${_host_test_target_name}" ]
662651

663-
_args = [ "-test.v" ] # Print test detailed case status
652+
_args = []
664653

665654
if (defined(invoker.release_builder)) {
666655
_args += [
@@ -686,10 +675,6 @@ template("system_test_recovery_suite") {
686675
if (defined(invoker.timeout)) {
687676
assert(invoker.timeout != "",
688677
"'${_host_test_target_name}' cannot have an empty timeout")
689-
_args += [
690-
"-test.timeout",
691-
invoker.timeout,
692-
]
693678
}
694679

695680
if (defined(invoker.cycle_timeout)) {
@@ -726,10 +711,10 @@ template("system_test_recovery_suite") {
726711
_args += [ "--check-abr=false" ]
727712
}
728713

729-
host_test(_host_test_target_name) {
730-
binary_path = _test_binary
714+
go_test(_host_test_target_name) {
715+
library = "//src/sys/pkg/tests/system-tests/recovery_test:gopkg"
731716
args = _args
732-
deps = [ test ]
717+
forward_variables_from(invoker, [ "timeout" ])
733718

734719
environments = []
735720
foreach(env, invoker.environments) {

tools/BUILD.gn

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,7 @@ group("tests_no_e2e") {
117117
"//tools/fidlcat/tests",
118118
"//tools/fuzz:tests",
119119
"//tools/gn_desc:host_tests($host_toolchain)",
120+
"//tools/go_test_parser:tests($host_toolchain)",
120121
"//tools/host_test_component:tests($host_toolchain)",
121122
"//tools/integration:tests($host_toolchain)",
122123
"//tools/jq5:tests($host_toolchain)",

tools/go_test_parser/BUILD.gn

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
# Copyright 2025 The Fuchsia Authors. All rights reserved.
2+
# Use of this source code is governed by a BSD-style license that can be
3+
# found in the LICENSE file.
4+
5+
import("//build/go/go_binary.gni")
6+
import("//build/go/go_library.gni")
7+
import("//build/go/go_test.gni")
8+
9+
go_library("go_test_parser_lib") {
10+
sources = [
11+
"gotest.go",
12+
"gotest_test.go",
13+
]
14+
15+
deps = [ "//tools/testing/runtests" ]
16+
}
17+
18+
group("tests") {
19+
testonly = true
20+
deps = [
21+
":go_test_parser_cmd_tests",
22+
":go_test_parser_lib_tests",
23+
]
24+
}
25+
26+
go_test("go_test_parser_lib_tests") {
27+
library = ":go_test_parser_lib"
28+
deps = [ "//third_party/golibs:github.com/google/go-cmp" ]
29+
}
30+
31+
go_library("main") {
32+
source_dir = "cmd"
33+
sources = [
34+
"main.go",
35+
"main_test.go",
36+
]
37+
deps = [
38+
":go_test_parser_lib",
39+
"//tools/testing/testrunner:constants",
40+
]
41+
}
42+
43+
go_binary("go_test_parser") {
44+
library = ":main"
45+
}
46+
47+
go_test("go_test_parser_cmd_tests") {
48+
library = ":main"
49+
deps = [ "//third_party/golibs:github.com/google/go-cmp" ]
50+
}

tools/go_test_parser/OWNERS

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
2+
3+

tools/go_test_parser/cmd/main.go

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
// Copyright 2025 The Fuchsia Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
package main
6+
7+
import (
8+
"bytes"
9+
"context"
10+
"encoding/json"
11+
"flag"
12+
"fmt"
13+
"io"
14+
"os"
15+
16+
"go.fuchsia.dev/fuchsia/tools/go_test_parser"
17+
"go.fuchsia.dev/fuchsia/tools/lib/subprocess"
18+
"go.fuchsia.dev/fuchsia/tools/testing/testrunner/constants"
19+
)
20+
21+
func usage() {
22+
fmt.Printf(`go_test_parser [go test command]
23+
24+
Reads stdout from the go test command, and writes a JSON formatted summary to stdout
25+
of any error messages parsed from the logs.
26+
`)
27+
}
28+
29+
func indentJSON(jsonBytes []byte) ([]byte, error) {
30+
buffer := bytes.NewBuffer([]byte{})
31+
err := json.Indent(buffer, jsonBytes, "", "\t")
32+
return buffer.Bytes(), err
33+
}
34+
35+
func mainImpl() error {
36+
flag.Usage = usage
37+
38+
// Parse any global flags (e.g. those for glog)
39+
flag.Parse()
40+
41+
args := flag.Args()
42+
43+
var testErr error
44+
stdoutForParsing := new(bytes.Buffer)
45+
testStdout := io.MultiWriter(os.Stdout, stdoutForParsing)
46+
r := &subprocess.Runner{Env: os.Environ()}
47+
fmt.Fprintf(os.Stdout, "Running %s\n", args[0])
48+
if err := r.Run(context.Background(), args, subprocess.RunOptions{Stdout: testStdout}); err != nil {
49+
testErr = fmt.Errorf("Error running test: %w", err)
50+
}
51+
52+
if outputSummaryPath := os.Getenv(constants.TestOutputSummaryPathEnvKey); outputSummaryPath != "" {
53+
result := go_test_parser.Parse(stdoutForParsing.Bytes())
54+
jsonData, err := json.Marshal(result)
55+
if err != nil {
56+
return fmt.Errorf("Error marshaling JSON: %w", err)
57+
}
58+
indentedData, err := indentJSON(jsonData)
59+
if err != nil {
60+
return fmt.Errorf("Error indenting JSON: %w", err)
61+
}
62+
output, err := os.Create(outputSummaryPath)
63+
if err != nil {
64+
return fmt.Errorf("Error creating output path %s: %w", outputSummaryPath, err)
65+
}
66+
if _, err := output.Write(indentedData); err != nil {
67+
return fmt.Errorf("Error writing output: %w", err)
68+
}
69+
}
70+
return testErr
71+
}
72+
73+
func main() {
74+
if err := mainImpl(); err != nil {
75+
fmt.Fprintf(os.Stderr, "%s\n", err)
76+
os.Exit(1)
77+
}
78+
}

tools/go_test_parser/cmd/main_test.go

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
// Copyright 2025 The Fuchsia Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
package main
6+
7+
import (
8+
"bytes"
9+
"encoding/json"
10+
"testing"
11+
12+
"github.com/google/go-cmp/cmp"
13+
)
14+
15+
func compactJson(jsonBytes []byte) []byte {
16+
buffer := bytes.NewBuffer([]byte{})
17+
json.Compact(buffer, jsonBytes)
18+
return buffer.Bytes()
19+
}
20+
21+
func TestIndentJSON(t *testing.T) {
22+
want := `[
23+
{
24+
"display_name": "tests::ignored_test",
25+
"suite_name": "tests",
26+
"case_name": "ignored_test",
27+
"status": "Skip",
28+
"duration_nanos": 0,
29+
"format": "Rust"
30+
},
31+
{
32+
"display_name": "tests::test_add_hundred",
33+
"suite_name": "tests",
34+
"case_name": "test_add_hundred",
35+
"status": "Pass",
36+
"duration_nanos": 0,
37+
"format": "Rust"
38+
},
39+
{
40+
"display_name": "tests::test_add",
41+
"suite_name": "tests",
42+
"case_name": "test_add",
43+
"status": "Fail",
44+
"duration_nanos": 0,
45+
"format": "Rust"
46+
}
47+
]`
48+
result, err := indentJSON(compactJson([]byte(want)))
49+
if err != nil {
50+
t.Errorf("TestIndentJSON failed: %v", err)
51+
}
52+
if diff := cmp.Diff(string(result), want); diff != "" {
53+
t.Errorf("indentJSON returned wrong output(diff): %s\n", diff)
54+
}
55+
}

tools/testing/testparser/gotest.go renamed to tools/go_test_parser/gotest.go

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1-
// Copyright 2020 The Fuchsia Authors. All rights reserved.
1+
// Copyright 2025 The Fuchsia Authors. All rights reserved.
22
// Use of this source code is governed by a BSD-style license that can be
33
// found in the LICENSE file.
44

5-
package testparser
5+
package go_test_parser
66

77
import (
8+
"bytes"
89
"fmt"
910
"regexp"
1011
"time"
@@ -18,6 +19,20 @@ var (
1819
goTestPanicPattern = regexp.MustCompile(`^panic: test timed out after (\S+)$`)
1920
)
2021

22+
// Parse takes stdout from a go test program and returns structured results.
23+
// If no structured results were identified, an empty slice is returned.
24+
func Parse(stdout []byte) []runtests.TestCaseResult {
25+
lines := bytes.Split(stdout, []byte{'\n'})
26+
cases := parseGoTest(lines)
27+
28+
// Ensure that an empty set of cases is serialized to JSON as an empty
29+
// array, not as null.
30+
if cases == nil {
31+
cases = []runtests.TestCaseResult{}
32+
}
33+
return cases
34+
}
35+
2136
func parseGoTest(lines [][]byte) []runtests.TestCaseResult {
2237
var res []runtests.TestCaseResult
2338
var preambleName string

0 commit comments

Comments
 (0)