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
1 change: 1 addition & 0 deletions .bazelrc
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ common:ci --config=adms
common:ci --config=lint
common:ci --noexperimental_convenience_symlinks # not CI-suitable: "These symlinks are only for the user's convenience"
common:ci --remote_download_regex=.*/test\.xml$ # force-download test.xml even under --remote_download_outputs=toplevel, so junit collection can read it locally
common:ci --remote_download_regex=.*/test\.log$ # force-download test.log for test2json postprocessing

# Project/Language configs --------------------------------------------------------------------------------------
import %workspace%/bazel/configs/go_tests.bazelrc
Expand Down
18 changes: 12 additions & 6 deletions .gitlab/build/bazel/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,19 @@
aud: ci-identities
variables:
EXTERNAL_LINKS_PATH: external_links_$CI_JOB_ID.json
FLAVOR: base
BEP_FILE: $CI_PROJECT_DIR/bazel-bep-$FLAVOR.json
BEP_FILE: $CI_PROJECT_DIR/bazel-bep.json
RESULT_JSON: test_output.json
after_script:
- dda inv -- -e bazel.collect-junit --flavor=$FLAVOR --output-tgz=junit-${CI_JOB_NAME}.tgz --bep-file=$BEP_FILE || true
- dda inv -- -e bazel.process-test-results --bep-file=$BEP_FILE --junit-tar=junit-${CI_JOB_NAME}.tgz --result-json=$RESULT_JSON

This comment was marked as outdated.

- !reference [.upload_junit_source]
artifacts:
expire_in: 2 weeks
when: always
paths:
- $RESULT_JSON
- test_output_unified.json
- junit-*.tgz
- bazel-bep-*.json
- bazel-bep.json
reports:
annotations:
- $EXTERNAL_LINKS_PATH
Expand All @@ -42,8 +44,10 @@ bazel:coverage:linux-amd64:
- cp "$(bazel info output_path)/_coverage/_coverage_report.dat" "$CI_PROJECT_DIR/coverage-bazel-$CI_JOB_NAME_SLUG.out"
artifacts:
paths:
- $RESULT_JSON
- test_output_unified.json
- junit-*.tgz
- bazel-bep-*.json
- bazel-bep.json
- coverage-bazel-*.out
variables:
KUBERNETES_CPU_REQUEST: 16
Expand Down Expand Up @@ -81,8 +85,10 @@ bazel:test:macos-arm64:
- !reference [.bazel:test:reporting, after_script]
artifacts:
paths:
- $RESULT_JSON
- test_output_unified.json
- junit-*.tgz
- bazel-bep-*.json
- bazel-bep.json
- bazel-exec.log

# Windows splits the build from the Go tests, unlike the other platforms: the
Expand Down
3 changes: 3 additions & 0 deletions LICENSE-3rdparty.csv
Original file line number Diff line number Diff line change
Expand Up @@ -848,6 +848,9 @@ core,github.com/awslabs/operatorpkg/unstructured,Apache-2.0,"Copyright Amazon.co
core,github.com/aymerick/raymond/lexer,MIT,Copyright (c) 2015 Aymerick JEHANNE
core,github.com/bahlo/generic-list-go,BSD-3-Clause,Copyright (c) 2009 The Go Authors. All rights reserved
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>
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>
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>
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>
core,github.com/bboreham/go-loser,Apache-2.0,bboreham <bjboreham@gmail.com>
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)
core,github.com/benbjohnson/clock,MIT,Copyright (c) 2014 Ben Johnson
Expand Down
23 changes: 23 additions & 0 deletions bazel/tools/testlogs_to_json/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
load("@rules_go//go:def.bzl", "go_binary", "go_library", "go_test")

go_library(
name = "testlogs_to_json_lib",
srcs = ["testlogs_to_json.go"],
importpath = "github.com/DataDog/datadog-agent/bazel/tools/testlogs_to_json",
visibility = ["//visibility:private"],
deps = ["@rules_go//go/tools/bzltestutil"],
)

go_binary(
name = "testlogs_to_json",
embed = [":testlogs_to_json_lib"],
visibility = ["//visibility:public"],
)

# gazelle:dd_agent_go_test off
go_test(
name = "testlogs_to_json_test",
srcs = ["testlogs_to_json_test.go"],
embed = [":testlogs_to_json_lib"],
gotags = [], # keep
)
135 changes: 135 additions & 0 deletions bazel/tools/testlogs_to_json/testlogs_to_json.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
// Unless explicitly stated otherwise all files in this repository are licensed
// under the Apache License Version 2.0.
// This product includes software developed at Datadog (https://www.datadoghq.com/).
// Copyright 2026-present Datadog, Inc.

package main

import (
"bufio"
"errors"
"flag"
"fmt"
"io"
"os"
"strings"

"github.com/bazelbuild/rules_go/go/tools/bzltestutil"
)

type manifestEntry struct {
pkg string
logPath string
}

type options struct {
manifestPath string
outputPath string
}

func main() {
if err := run(os.Args[1:], os.Stdout, os.Stderr); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}

func run(args []string, stdout, stderr io.Writer) error {
opts, err := parseFlags(args)
if err != nil {
return err
}

entries, err := readManifest(opts.manifestPath)
if err != nil {
return err
}

var out io.Writer = stdout
var outFile *os.File
if opts.outputPath != "" && opts.outputPath != "-" {
outFile, err = os.Create(opts.outputPath)
if err != nil {
return fmt.Errorf("create output %q: %w", opts.outputPath, err)
}
defer outFile.Close()
out = outFile
}

if err := convert(entries, out); err != nil {
return err
}

fmt.Fprintf(stderr, "Converted %d Bazel test logs to test2json\n", len(entries))
return nil
}

func parseFlags(args []string) (options, error) {
var opts options
fs := flag.NewFlagSet("testlogs_to_json", flag.ContinueOnError)
fs.SetOutput(io.Discard)
fs.StringVar(&opts.manifestPath, "manifest", "", "Path to a tab-separated manifest: <go import path>\\t<test.log path>")
fs.StringVar(&opts.outputPath, "output", "-", "Path to write test2json JSONL output, or '-' for stdout")
if err := fs.Parse(args); err != nil {
return opts, err
}
if opts.manifestPath == "" {
return opts, errors.New("missing required -manifest")
}
if fs.NArg() != 0 {
return opts, fmt.Errorf("unexpected positional arguments: %s", strings.Join(fs.Args(), " "))
}
return opts, nil
}

func readManifest(path string) ([]manifestEntry, error) {
f, err := os.Open(path)
if err != nil {
return nil, fmt.Errorf("open manifest %q: %w", path, err)
}
defer f.Close()

var entries []manifestEntry
scanner := bufio.NewScanner(f)
for scanner.Scan() {
line := scanner.Text()
if strings.TrimSpace(line) == "" {
continue
}
pkg, logPath, ok := strings.Cut(line, "\t")
if !ok || pkg == "" || logPath == "" {
return nil, fmt.Errorf("invalid manifest line: expected <go import path>\\t<test.log path>, got %q", line)
}
if strings.Contains(logPath, "\t") {
return nil, fmt.Errorf("invalid manifest line: too many tab-separated fields, got %q", line)
}
entries = append(entries, manifestEntry{pkg: pkg, logPath: logPath})
}
if err := scanner.Err(); err != nil {
return nil, fmt.Errorf("read manifest %q: %w", path, err)
}
return entries, nil
}

func convert(entries []manifestEntry, out io.Writer) error {
for _, entry := range entries {
f, err := os.Open(entry.logPath)
if err != nil {
return fmt.Errorf("open test log %q for package %s: %w", entry.logPath, entry.pkg, err)
}

converter := bzltestutil.NewConverter(out, entry.pkg, bzltestutil.Timestamp)
_, copyErr := io.Copy(converter, f)
closeErr := f.Close()
if copyErr != nil {
return fmt.Errorf("convert test log %q for package %s: %w", entry.logPath, entry.pkg, copyErr)
}
if closeErr != nil {
return fmt.Errorf("close test log %q for package %s: %w", entry.logPath, entry.pkg, closeErr)
}
if err := converter.Close(); err != nil {
return fmt.Errorf("close converter for test log %q package %s: %w", entry.logPath, entry.pkg, err)
}
}
return nil
}
111 changes: 111 additions & 0 deletions bazel/tools/testlogs_to_json/testlogs_to_json_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
// Unless explicitly stated otherwise all files in this repository are licensed
// under the Apache License Version 2.0.
// This product includes software developed at Datadog (https://www.datadoghq.com/).
// Copyright 2026-present Datadog, Inc.

package main

import (
"bytes"
"encoding/json"
"os"
"path/filepath"
"strings"
"testing"
)

func TestReadManifest(t *testing.T) {
dir := t.TempDir()
manifestPath := filepath.Join(dir, "manifest.tsv")
if err := os.WriteFile(manifestPath, []byte(strings.Join([]string{
"",
"github.com/DataDog/datadog-agent/pkg/foo\t/path/to/foo.log",
"github.com/DataDog/datadog-agent/pkg/bar\t/path with spaces/bar.log",
}, "\n")), 0o644); err != nil {
t.Fatal(err)
}

entries, err := readManifest(manifestPath)
if err != nil {
t.Fatal(err)
}

want := []manifestEntry{
{pkg: "github.com/DataDog/datadog-agent/pkg/foo", logPath: "/path/to/foo.log"},
{pkg: "github.com/DataDog/datadog-agent/pkg/bar", logPath: "/path with spaces/bar.log"},
}
if len(entries) != len(want) {
t.Fatalf("got %d entries, want %d: %#v", len(entries), len(want), entries)
}
for i := range want {
if entries[i] != want[i] {
t.Fatalf("entry %d = %#v, want %#v", i, entries[i], want[i])
}
}
}

func TestReadManifestRejectsInvalidLine(t *testing.T) {
dir := t.TempDir()
manifestPath := filepath.Join(dir, "manifest.tsv")
if err := os.WriteFile(manifestPath, []byte("github.com/DataDog/datadog-agent/pkg/foo /path/to/foo.log\n"), 0o644); err != nil {
t.Fatal(err)
}

_, err := readManifest(manifestPath)
if err == nil || !strings.Contains(err.Error(), "invalid manifest line") {
t.Fatalf("expected invalid manifest error, got %v", err)
}
}

func TestConvertMultipleLogs(t *testing.T) {
dir := t.TempDir()
fooLog := filepath.Join(dir, "foo.log")
barLog := filepath.Join(dir, "bar.log")
if err := os.WriteFile(fooLog, []byte("=== RUN TestFoo\n--- PASS: TestFoo (0.01s)\nPASS\n"), 0o644); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(barLog, []byte("=== RUN TestBar\n bar_test.go:12: boom\n--- FAIL: TestBar (0.02s)\nFAIL\n"), 0o644); err != nil {
t.Fatal(err)
}

var out bytes.Buffer
err := convert([]manifestEntry{
{pkg: "github.com/DataDog/datadog-agent/pkg/foo", logPath: fooLog},
{pkg: "github.com/DataDog/datadog-agent/pkg/bar", logPath: barLog},
}, &out)
if err != nil {
t.Fatal(err)
}

var events []map[string]any
for _, line := range strings.Split(strings.TrimSpace(out.String()), "\n") {
var event map[string]any
if err := json.Unmarshal([]byte(line), &event); err != nil {
t.Fatalf("invalid JSON line %q: %v", line, err)
}
events = append(events, event)
}

assertEvent(t, events, "github.com/DataDog/datadog-agent/pkg/foo", "TestFoo", "pass")
assertEvent(t, events, "github.com/DataDog/datadog-agent/pkg/foo", "", "pass")
assertEvent(t, events, "github.com/DataDog/datadog-agent/pkg/bar", "TestBar", "fail")
assertEvent(t, events, "github.com/DataDog/datadog-agent/pkg/bar", "", "fail")
}

func assertEvent(t *testing.T, events []map[string]any, pkg, testName, action string) {
t.Helper()
for _, event := range events {
if event["Package"] == pkg && event["Action"] == action {
if testName == "" {
if _, ok := event["Test"]; !ok {
return
}
continue
}
if event["Test"] == testName {
return
}
}
}
t.Fatalf("did not find event package=%q test=%q action=%q in %#v", pkg, testName, action, events)
}
Loading
Loading