Skip to content

Commit 423906e

Browse files
authored
Add test2json and UTOF output to bazel-driven go tests (#54655)
<!--Please give us some feedback on your experience writing this PR ! https://app.datadoghq.com/forms/43db4c02-6837-400c-8083-692e141b1b88 !--> ### What does this PR do? Generates test2json output and UTOF output for Bazel-run tests. ### Motivation We want to switch over to using Bazel for running our unit tests, this helps us produce output that is closer to the existing jobs to reduce friction during the switch. ### Describe how you validated your changes ### Additional Notes This generates a bit of noise on the UTOF output because it treats the multiple runs of the same tests (with different tag sets) as retries, which get printed out. This will be dealt with separately. Co-authored-by: alex.lopez <alex.lopez@datadoghq.com>
1 parent 078d86b commit 423906e

8 files changed

Lines changed: 475 additions & 74 deletions

File tree

.bazelrc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@ common:ci --config=adms
108108
common:ci --config=lint
109109
common:ci --noexperimental_convenience_symlinks # not CI-suitable: "These symlinks are only for the user's convenience"
110110
common:ci --remote_download_regex=.*/test\.xml$ # force-download test.xml even under --remote_download_outputs=toplevel, so junit collection can read it locally
111+
common:ci --remote_download_regex=.*/test\.log$ # force-download test.log for test2json postprocessing
111112

112113
# Project/Language configs --------------------------------------------------------------------------------------
113114
import %workspace%/bazel/configs/go_tests.bazelrc

.gitlab/build/bazel/test.yml

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,17 +14,19 @@
1414
aud: ci-identities
1515
variables:
1616
EXTERNAL_LINKS_PATH: external_links_$CI_JOB_ID.json
17-
FLAVOR: base
18-
BEP_FILE: $CI_PROJECT_DIR/bazel-bep-$FLAVOR.json
17+
BEP_FILE: $CI_PROJECT_DIR/bazel-bep.json
18+
RESULT_JSON: test_output.json
1919
after_script:
20-
- dda inv -- -e bazel.collect-junit --flavor=$FLAVOR --output-tgz=junit-${CI_JOB_NAME}.tgz --bep-file=$BEP_FILE || true
20+
- dda inv -- -e bazel.process-test-results --bep-file=$BEP_FILE --junit-tar=junit-${CI_JOB_NAME}.tgz --result-json=$RESULT_JSON
2121
- !reference [.upload_junit_source]
2222
artifacts:
2323
expire_in: 2 weeks
2424
when: always
2525
paths:
26+
- $RESULT_JSON
27+
- test_output_unified.json
2628
- junit-*.tgz
27-
- bazel-bep-*.json
29+
- bazel-bep.json
2830
reports:
2931
annotations:
3032
- $EXTERNAL_LINKS_PATH
@@ -42,8 +44,10 @@ bazel:coverage:linux-amd64:
4244
- cp "$(bazel info output_path)/_coverage/_coverage_report.dat" "$CI_PROJECT_DIR/coverage-bazel-$CI_JOB_NAME_SLUG.out"
4345
artifacts:
4446
paths:
47+
- $RESULT_JSON
48+
- test_output_unified.json
4549
- junit-*.tgz
46-
- bazel-bep-*.json
50+
- bazel-bep.json
4751
- coverage-bazel-*.out
4852
variables:
4953
KUBERNETES_CPU_REQUEST: 16
@@ -81,8 +85,10 @@ bazel:test:macos-arm64:
8185
- !reference [.bazel:test:reporting, after_script]
8286
artifacts:
8387
paths:
88+
- $RESULT_JSON
89+
- test_output_unified.json
8490
- junit-*.tgz
85-
- bazel-bep-*.json
91+
- bazel-bep.json
8692
- bazel-exec.log
8793

8894
# Windows splits the build from the Go tests, unlike the other platforms: the

LICENSE-3rdparty.csv

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -848,6 +848,9 @@ core,github.com/awslabs/operatorpkg/unstructured,Apache-2.0,"Copyright Amazon.co
848848
core,github.com/aymerick/raymond/lexer,MIT,Copyright (c) 2015 Aymerick JEHANNE
849849
core,github.com/bahlo/generic-list-go,BSD-3-Clause,Copyright (c) 2009 The Go Authors. All rights reserved
850850
core,github.com/bazelbuild/rules_go/go/runfiles,Apache-2.0,Benjamin Staffin <benley@gmail.com> | Brian Silverman <bsilver16384@gmail.com> | Damien Martin-Guillerez <dmarting@google.com> | David Chen <dzc@google.com> | David Santiago <david.santiago@gmail.com> | David Zbarsky <dzbarsky@gmail.com> | Fabian Meumertzheim <fabian@meumertzhe.im> | Google Inc. | Han-Wen Nienhuys <hanwen@google.com> | Ian Cottrell <iancottrell@google.com> | Jake Voytko <jake@reviewninja.com> | Jay Conrod <jayconrod@google.com> | Josh Powell <powelljo@us.ibm.com> | Justine Alexandra Roberts Tunney <jart@google.com> | Kristina Chodorow <kchodorow@google.com> | Lukacs Berki <lberki@google.com> | Tyler French <french.tyler.d@gmail.com> | Yuki Yugui Sonoda <yugui@yugui.jp>
851+
core,github.com/bazelbuild/rules_go/go/tools/bzltestutil,Apache-2.0,Benjamin Staffin <benley@gmail.com> | Brian Silverman <bsilver16384@gmail.com> | Damien Martin-Guillerez <dmarting@google.com> | David Chen <dzc@google.com> | David Santiago <david.santiago@gmail.com> | David Zbarsky <dzbarsky@gmail.com> | Fabian Meumertzheim <fabian@meumertzhe.im> | Google Inc. | Han-Wen Nienhuys <hanwen@google.com> | Ian Cottrell <iancottrell@google.com> | Jake Voytko <jake@reviewninja.com> | Jay Conrod <jayconrod@google.com> | Josh Powell <powelljo@us.ibm.com> | Justine Alexandra Roberts Tunney <jart@google.com> | Kristina Chodorow <kchodorow@google.com> | Lukacs Berki <lberki@google.com> | Tyler French <french.tyler.d@gmail.com> | Yuki Yugui Sonoda <yugui@yugui.jp>
852+
core,github.com/bazelbuild/rules_go/go/tools/bzltestutil/chdir,Apache-2.0,Benjamin Staffin <benley@gmail.com> | Brian Silverman <bsilver16384@gmail.com> | Damien Martin-Guillerez <dmarting@google.com> | David Chen <dzc@google.com> | David Santiago <david.santiago@gmail.com> | David Zbarsky <dzbarsky@gmail.com> | Fabian Meumertzheim <fabian@meumertzhe.im> | Google Inc. | Han-Wen Nienhuys <hanwen@google.com> | Ian Cottrell <iancottrell@google.com> | Jake Voytko <jake@reviewninja.com> | Jay Conrod <jayconrod@google.com> | Josh Powell <powelljo@us.ibm.com> | Justine Alexandra Roberts Tunney <jart@google.com> | Kristina Chodorow <kchodorow@google.com> | Lukacs Berki <lberki@google.com> | Tyler French <french.tyler.d@gmail.com> | Yuki Yugui Sonoda <yugui@yugui.jp>
853+
core,github.com/bazelbuild/rules_go/go/tools/coverdata,Apache-2.0,Benjamin Staffin <benley@gmail.com> | Brian Silverman <bsilver16384@gmail.com> | Damien Martin-Guillerez <dmarting@google.com> | David Chen <dzc@google.com> | David Santiago <david.santiago@gmail.com> | David Zbarsky <dzbarsky@gmail.com> | Fabian Meumertzheim <fabian@meumertzhe.im> | Google Inc. | Han-Wen Nienhuys <hanwen@google.com> | Ian Cottrell <iancottrell@google.com> | Jake Voytko <jake@reviewninja.com> | Jay Conrod <jayconrod@google.com> | Josh Powell <powelljo@us.ibm.com> | Justine Alexandra Roberts Tunney <jart@google.com> | Kristina Chodorow <kchodorow@google.com> | Lukacs Berki <lberki@google.com> | Tyler French <french.tyler.d@gmail.com> | Yuki Yugui Sonoda <yugui@yugui.jp>
851854
core,github.com/bboreham/go-loser,Apache-2.0,bboreham <bjboreham@gmail.com>
852855
core,github.com/beevik/ntp,BSD-2-Clause,Al Cutter (AlCutter) | Andrey Smirnov (smira) | Anton Tolchanov (knyar) | Ask Bjørn Hansen (abh) | Brett Vickers (beevik) | Christian Cedercrantz (chrisceder) | Christopher Batey (chbatey) | Copyright © 2015-2023 Brett Vickers. All rights reserved | Leonid Evdokimov (darkk) | Meng Zhuo (mengzhuo) | Mikhail Salosin (AlphaB) | Silves-Xiang (silves-xiang)
853856
core,github.com/benbjohnson/clock,MIT,Copyright (c) 2014 Ben Johnson
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
load("@rules_go//go:def.bzl", "go_binary", "go_library", "go_test")
2+
3+
go_library(
4+
name = "testlogs_to_json_lib",
5+
srcs = ["testlogs_to_json.go"],
6+
importpath = "github.com/DataDog/datadog-agent/bazel/tools/testlogs_to_json",
7+
visibility = ["//visibility:private"],
8+
deps = ["@rules_go//go/tools/bzltestutil"],
9+
)
10+
11+
go_binary(
12+
name = "testlogs_to_json",
13+
embed = [":testlogs_to_json_lib"],
14+
visibility = ["//visibility:public"],
15+
)
16+
17+
# gazelle:dd_agent_go_test off
18+
go_test(
19+
name = "testlogs_to_json_test",
20+
srcs = ["testlogs_to_json_test.go"],
21+
embed = [":testlogs_to_json_lib"],
22+
gotags = [], # keep
23+
)
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
// Unless explicitly stated otherwise all files in this repository are licensed
2+
// under the Apache License Version 2.0.
3+
// This product includes software developed at Datadog (https://www.datadoghq.com/).
4+
// Copyright 2026-present Datadog, Inc.
5+
6+
package main
7+
8+
import (
9+
"bufio"
10+
"errors"
11+
"flag"
12+
"fmt"
13+
"io"
14+
"os"
15+
"strings"
16+
17+
"github.com/bazelbuild/rules_go/go/tools/bzltestutil"
18+
)
19+
20+
type manifestEntry struct {
21+
pkg string
22+
logPath string
23+
}
24+
25+
type options struct {
26+
manifestPath string
27+
outputPath string
28+
}
29+
30+
func main() {
31+
if err := run(os.Args[1:], os.Stdout, os.Stderr); err != nil {
32+
fmt.Fprintln(os.Stderr, err)
33+
os.Exit(1)
34+
}
35+
}
36+
37+
func run(args []string, stdout, stderr io.Writer) error {
38+
opts, err := parseFlags(args)
39+
if err != nil {
40+
return err
41+
}
42+
43+
entries, err := readManifest(opts.manifestPath)
44+
if err != nil {
45+
return err
46+
}
47+
48+
var out io.Writer = stdout
49+
var outFile *os.File
50+
if opts.outputPath != "" && opts.outputPath != "-" {
51+
outFile, err = os.Create(opts.outputPath)
52+
if err != nil {
53+
return fmt.Errorf("create output %q: %w", opts.outputPath, err)
54+
}
55+
defer outFile.Close()
56+
out = outFile
57+
}
58+
59+
if err := convert(entries, out); err != nil {
60+
return err
61+
}
62+
63+
fmt.Fprintf(stderr, "Converted %d Bazel test logs to test2json\n", len(entries))
64+
return nil
65+
}
66+
67+
func parseFlags(args []string) (options, error) {
68+
var opts options
69+
fs := flag.NewFlagSet("testlogs_to_json", flag.ContinueOnError)
70+
fs.SetOutput(io.Discard)
71+
fs.StringVar(&opts.manifestPath, "manifest", "", "Path to a tab-separated manifest: <go import path>\\t<test.log path>")
72+
fs.StringVar(&opts.outputPath, "output", "-", "Path to write test2json JSONL output, or '-' for stdout")
73+
if err := fs.Parse(args); err != nil {
74+
return opts, err
75+
}
76+
if opts.manifestPath == "" {
77+
return opts, errors.New("missing required -manifest")
78+
}
79+
if fs.NArg() != 0 {
80+
return opts, fmt.Errorf("unexpected positional arguments: %s", strings.Join(fs.Args(), " "))
81+
}
82+
return opts, nil
83+
}
84+
85+
func readManifest(path string) ([]manifestEntry, error) {
86+
f, err := os.Open(path)
87+
if err != nil {
88+
return nil, fmt.Errorf("open manifest %q: %w", path, err)
89+
}
90+
defer f.Close()
91+
92+
var entries []manifestEntry
93+
scanner := bufio.NewScanner(f)
94+
for scanner.Scan() {
95+
line := scanner.Text()
96+
if strings.TrimSpace(line) == "" {
97+
continue
98+
}
99+
pkg, logPath, ok := strings.Cut(line, "\t")
100+
if !ok || pkg == "" || logPath == "" {
101+
return nil, fmt.Errorf("invalid manifest line: expected <go import path>\\t<test.log path>, got %q", line)
102+
}
103+
if strings.Contains(logPath, "\t") {
104+
return nil, fmt.Errorf("invalid manifest line: too many tab-separated fields, got %q", line)
105+
}
106+
entries = append(entries, manifestEntry{pkg: pkg, logPath: logPath})
107+
}
108+
if err := scanner.Err(); err != nil {
109+
return nil, fmt.Errorf("read manifest %q: %w", path, err)
110+
}
111+
return entries, nil
112+
}
113+
114+
func convert(entries []manifestEntry, out io.Writer) error {
115+
for _, entry := range entries {
116+
f, err := os.Open(entry.logPath)
117+
if err != nil {
118+
return fmt.Errorf("open test log %q for package %s: %w", entry.logPath, entry.pkg, err)
119+
}
120+
121+
converter := bzltestutil.NewConverter(out, entry.pkg, bzltestutil.Timestamp)
122+
_, copyErr := io.Copy(converter, f)
123+
closeErr := f.Close()
124+
if copyErr != nil {
125+
return fmt.Errorf("convert test log %q for package %s: %w", entry.logPath, entry.pkg, copyErr)
126+
}
127+
if closeErr != nil {
128+
return fmt.Errorf("close test log %q for package %s: %w", entry.logPath, entry.pkg, closeErr)
129+
}
130+
if err := converter.Close(); err != nil {
131+
return fmt.Errorf("close converter for test log %q package %s: %w", entry.logPath, entry.pkg, err)
132+
}
133+
}
134+
return nil
135+
}
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
// Unless explicitly stated otherwise all files in this repository are licensed
2+
// under the Apache License Version 2.0.
3+
// This product includes software developed at Datadog (https://www.datadoghq.com/).
4+
// Copyright 2026-present Datadog, Inc.
5+
6+
package main
7+
8+
import (
9+
"bytes"
10+
"encoding/json"
11+
"os"
12+
"path/filepath"
13+
"strings"
14+
"testing"
15+
)
16+
17+
func TestReadManifest(t *testing.T) {
18+
dir := t.TempDir()
19+
manifestPath := filepath.Join(dir, "manifest.tsv")
20+
if err := os.WriteFile(manifestPath, []byte(strings.Join([]string{
21+
"",
22+
"github.com/DataDog/datadog-agent/pkg/foo\t/path/to/foo.log",
23+
"github.com/DataDog/datadog-agent/pkg/bar\t/path with spaces/bar.log",
24+
}, "\n")), 0o644); err != nil {
25+
t.Fatal(err)
26+
}
27+
28+
entries, err := readManifest(manifestPath)
29+
if err != nil {
30+
t.Fatal(err)
31+
}
32+
33+
want := []manifestEntry{
34+
{pkg: "github.com/DataDog/datadog-agent/pkg/foo", logPath: "/path/to/foo.log"},
35+
{pkg: "github.com/DataDog/datadog-agent/pkg/bar", logPath: "/path with spaces/bar.log"},
36+
}
37+
if len(entries) != len(want) {
38+
t.Fatalf("got %d entries, want %d: %#v", len(entries), len(want), entries)
39+
}
40+
for i := range want {
41+
if entries[i] != want[i] {
42+
t.Fatalf("entry %d = %#v, want %#v", i, entries[i], want[i])
43+
}
44+
}
45+
}
46+
47+
func TestReadManifestRejectsInvalidLine(t *testing.T) {
48+
dir := t.TempDir()
49+
manifestPath := filepath.Join(dir, "manifest.tsv")
50+
if err := os.WriteFile(manifestPath, []byte("github.com/DataDog/datadog-agent/pkg/foo /path/to/foo.log\n"), 0o644); err != nil {
51+
t.Fatal(err)
52+
}
53+
54+
_, err := readManifest(manifestPath)
55+
if err == nil || !strings.Contains(err.Error(), "invalid manifest line") {
56+
t.Fatalf("expected invalid manifest error, got %v", err)
57+
}
58+
}
59+
60+
func TestConvertMultipleLogs(t *testing.T) {
61+
dir := t.TempDir()
62+
fooLog := filepath.Join(dir, "foo.log")
63+
barLog := filepath.Join(dir, "bar.log")
64+
if err := os.WriteFile(fooLog, []byte("=== RUN TestFoo\n--- PASS: TestFoo (0.01s)\nPASS\n"), 0o644); err != nil {
65+
t.Fatal(err)
66+
}
67+
if err := os.WriteFile(barLog, []byte("=== RUN TestBar\n bar_test.go:12: boom\n--- FAIL: TestBar (0.02s)\nFAIL\n"), 0o644); err != nil {
68+
t.Fatal(err)
69+
}
70+
71+
var out bytes.Buffer
72+
err := convert([]manifestEntry{
73+
{pkg: "github.com/DataDog/datadog-agent/pkg/foo", logPath: fooLog},
74+
{pkg: "github.com/DataDog/datadog-agent/pkg/bar", logPath: barLog},
75+
}, &out)
76+
if err != nil {
77+
t.Fatal(err)
78+
}
79+
80+
var events []map[string]any
81+
for _, line := range strings.Split(strings.TrimSpace(out.String()), "\n") {
82+
var event map[string]any
83+
if err := json.Unmarshal([]byte(line), &event); err != nil {
84+
t.Fatalf("invalid JSON line %q: %v", line, err)
85+
}
86+
events = append(events, event)
87+
}
88+
89+
assertEvent(t, events, "github.com/DataDog/datadog-agent/pkg/foo", "TestFoo", "pass")
90+
assertEvent(t, events, "github.com/DataDog/datadog-agent/pkg/foo", "", "pass")
91+
assertEvent(t, events, "github.com/DataDog/datadog-agent/pkg/bar", "TestBar", "fail")
92+
assertEvent(t, events, "github.com/DataDog/datadog-agent/pkg/bar", "", "fail")
93+
}
94+
95+
func assertEvent(t *testing.T, events []map[string]any, pkg, testName, action string) {
96+
t.Helper()
97+
for _, event := range events {
98+
if event["Package"] == pkg && event["Action"] == action {
99+
if testName == "" {
100+
if _, ok := event["Test"]; !ok {
101+
return
102+
}
103+
continue
104+
}
105+
if event["Test"] == testName {
106+
return
107+
}
108+
}
109+
}
110+
t.Fatalf("did not find event package=%q test=%q action=%q in %#v", pkg, testName, action, events)
111+
}

0 commit comments

Comments
 (0)