Skip to content

Commit 22e7c15

Browse files
[8.19](backport #49583) filebeat: auto-build test binary in Go integration tests via TestMain (#50111)
* filebeat: auto-build test binary in Go integration tests via TestMain (#49583) * filebeat: auto-build test binary in Go integration tests via TestMain Move the filebeat.test binary build step from the mage targets into TestMain so that `go test -tags integration` works without requiring a prior `mage buildSystemTestBinary`. This removes a manual prerequisite that tripped up developers running integration tests locally. - Add BuildSystemTestBinary helper to libbeat/tests/integration/framework.go - Add TestMain in filebeat and x-pack/filebeat integration test packages - Remove BuildSystemTestBinary from mage GoIntegTest/GoFIPSOnlyIntegTest - Python integration tests are unchanged (they still use devtools directly) Assisted by Claude (cherry picked from commit 323f731) # Conflicts: # libbeat/tests/integration/framework.go * fix merge conflict --------- Co-authored-by: Anderson Queiroz <anderson.queiroz@elastic.co>
1 parent 1007c8e commit 22e7c15

File tree

18 files changed

+348
-74
lines changed

18 files changed

+348
-74
lines changed

dev-tools/mage/gotest.go

Lines changed: 6 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ import (
3636
"golang.org/x/sys/execabs"
3737

3838
"github.com/elastic/beats/v7/dev-tools/mage/gotool"
39+
"github.com/elastic/beats/v7/dev-tools/testbin"
3940
)
4041

4142
// GoTestArgs are the arguments used for the "go*Test" targets and they define
@@ -519,29 +520,11 @@ func BuildSystemTestBinary() error {
519520
// testing and measuring code coverage. The binary is only instrumented for
520521
// coverage when TEST_COVERAGE=true (default is false).
521522
func BuildSystemTestGoBinary(binArgs TestBinaryArgs) error {
522-
args := []string{
523-
"test", "-c",
524-
"-o", binArgs.Name + ".test",
525-
}
526-
527-
if DevBuild {
528-
// Disable optimizations (-N) and inlining (-l) for debugging.
529-
args = append(args, `-gcflags=all=-N -l`)
530-
}
531-
532-
if TestCoverage {
533-
args = append(args, "-coverpkg", "./...")
534-
}
535-
args = append(args, binArgs.ExtraFlags...)
536-
if len(binArgs.InputFiles) > 0 {
537-
args = append(args, binArgs.InputFiles...)
538-
}
539-
540-
start := time.Now()
541-
defer func() {
542-
log.Printf("BuildSystemTestGoBinary (go %v) took %v.", strings.Join(args, " "), time.Since(start))
543-
}()
544-
return sh.RunV("go", args...)
523+
_, err := testbin.Build(binArgs.Name, ".",
524+
testbin.WithExtraFlags(binArgs.ExtraFlags...),
525+
testbin.WithInputFiles(binArgs.InputFiles...),
526+
)
527+
return err
545528
}
546529

547530
func DefaultECHTestArgs() GoTestArgs {

dev-tools/testbin/testbin.go

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
// Licensed to Elasticsearch B.V. under one or more contributor
2+
// license agreements. See the NOTICE file distributed with
3+
// this work for additional information regarding copyright
4+
// ownership. Elasticsearch B.V. licenses this file to you under
5+
// the Apache License, Version 2.0 (the "License"); you may
6+
// not use this file except in compliance with the License.
7+
// You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing,
12+
// software distributed under the License is distributed on an
13+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
// KIND, either express or implied. See the License for the
15+
// specific language governing permissions and limitations
16+
// under the License.
17+
18+
// Package testbin builds Go test binaries via "go test -c". It is the single
19+
// source of truth for how beat test binaries are compiled, used by both the
20+
// mage build system and the Go integration test framework.
21+
//
22+
// Environment variables respected:
23+
// - DEV=true: disables optimizations for debugging (-gcflags=all=-N -l)
24+
// - TEST_COVERAGE=true: enables coverage instrumentation (-coverpkg ./...)
25+
//
26+
// Platform-specific flags (e.g. stripping DWARF on Windows 386) should be
27+
// passed by the caller via BuildOptions.ExtraFlags.
28+
package testbin
29+
30+
import (
31+
"fmt"
32+
"log"
33+
"os"
34+
"path/filepath"
35+
"strconv"
36+
"strings"
37+
"time"
38+
39+
"golang.org/x/sys/execabs"
40+
)
41+
42+
// buildOptions holds the resolved build configuration.
43+
type buildOptions struct {
44+
extraFlags []string
45+
inputFiles []string
46+
}
47+
48+
// Option configures a Build invocation. Options are applied in order,
49+
// so later options override earlier ones.
50+
type Option func(*buildOptions)
51+
52+
// WithExtraFlags appends additional flags passed to 'go test'.
53+
func WithExtraFlags(flags ...string) Option {
54+
return func(o *buildOptions) {
55+
o.extraFlags = append(o.extraFlags, flags...)
56+
}
57+
}
58+
59+
// WithInputFiles appends specific files/packages after all flags.
60+
func WithInputFiles(files ...string) Option {
61+
return func(o *buildOptions) {
62+
o.inputFiles = append(o.inputFiles, files...)
63+
}
64+
}
65+
66+
// Build compiles a test binary for the given beat using "go test -c".
67+
// dir is the beat root directory where "go test -c" runs and where the
68+
// resulting binary is written. It returns the absolute path of the built
69+
// binary.
70+
func Build(beatName, dir string, opts ...Option) (string, error) {
71+
if !strings.HasSuffix(beatName, ".test") {
72+
beatName += ".test"
73+
}
74+
outputPath, err := filepath.Abs(filepath.Join(dir, beatName))
75+
if err != nil {
76+
return "", fmt.Errorf("failed to resolve output path: %w", err)
77+
}
78+
79+
args := []string{"test", "-c", "-o", outputPath}
80+
81+
if devBuild, _ := strconv.ParseBool(os.Getenv("DEV")); devBuild {
82+
args = append(args, `-gcflags=all=-N -l`)
83+
}
84+
85+
if testCoverage, _ := strconv.ParseBool(os.Getenv("TEST_COVERAGE")); testCoverage {
86+
args = append(args, "-coverpkg", "./...")
87+
}
88+
89+
var o buildOptions
90+
for _, fn := range opts {
91+
fn(&o)
92+
}
93+
args = append(args, o.extraFlags...)
94+
args = append(args, o.inputFiles...)
95+
96+
cmd := execabs.Command("go", args...)
97+
cmd.Dir = dir
98+
99+
start := time.Now()
100+
defer func() {
101+
log.Printf("testbin.Build (go %s) took %v.",
102+
strings.Join(args, " "), time.Since(start))
103+
}()
104+
105+
output, err := cmd.CombinedOutput()
106+
if err != nil {
107+
log.Printf("testbin.Build failed:\n%s", output)
108+
return "", fmt.Errorf("failed to build test binary %q: %w (see log output for details)", beatName, err)
109+
}
110+
111+
return outputPath, nil
112+
}

filebeat/magefile.go

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,12 @@ func Build() error {
5555
return devtools.Build(devtools.DefaultBuildArgs())
5656
}
5757

58-
// BuildSystemTestBinary builds a binary instrumented for use with Python system tests.
58+
// Deprecated: BuildSystemTestBinary builds a binary instrumented for use with Python system tests.
59+
// Go integration tests now build the binary automatically via TestMain.
5960
func BuildSystemTestBinary() error {
61+
fmt.Println("WARNING: BuildSystemTestBinary is deprecated for Go integration tests. " +
62+
"The test binary is now built automatically via TestMain. " +
63+
"This target is only needed for Python system tests.")
6064
return devtools.BuildSystemTestBinary()
6165
}
6266

@@ -196,14 +200,11 @@ func IntegTest() {
196200

197201
// GoIntegTest starts the docker containers and executes the Go integration tests.
198202
func GoIntegTest(ctx context.Context) error {
199-
mg.Deps(BuildSystemTestBinary)
200203
return devtools.GoIntegTestFromHost(ctx, devtools.DefaultGoTestIntegrationFromHostArgs(ctx))
201204
}
202205

203206
// GoFIPSOnlyIntegTest starts the docker containers and executes the Go integration tests with GODEBUG=fips140=only set.
204207
func GoFIPSOnlyIntegTest(ctx context.Context) error {
205-
mg.Deps(BuildSystemTestBinary)
206-
207208
// We pre-cache go module dependencies before running the unit tests with
208209
// GODEBUG=fips140=only. Otherwise, the command that runs the unit tests
209210
// will try to download the dependencies and could fail because the TLS
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
// Licensed to Elasticsearch B.V. under one or more contributor
2+
// license agreements. See the NOTICE file distributed with
3+
// this work for additional information regarding copyright
4+
// ownership. Elasticsearch B.V. licenses this file to you under
5+
// the Apache License, Version 2.0 (the "License"); you may
6+
// not use this file except in compliance with the License.
7+
// You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing,
12+
// software distributed under the License is distributed on an
13+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
// KIND, either express or implied. See the License for the
15+
// specific language governing permissions and limitations
16+
// under the License.
17+
18+
//go:build integration
19+
20+
package integration
21+
22+
import (
23+
"testing"
24+
25+
"github.com/elastic/beats/v7/libbeat/tests/integration"
26+
)
27+
28+
func TestMain(m *testing.M) {
29+
integration.TestMainWithBuild(m, "filebeat")
30+
}

filebeat/input/filestream/legacy_metrics_integration_test.go renamed to filebeat/tests/integration/legacy_metrics_integration_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717

1818
//go:build integration
1919

20-
package filestream
20+
package integration
2121

2222
import (
2323
"encoding/json"

libbeat/magefile.go

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ package main
2121

2222
import (
2323
"context"
24+
"fmt"
2425

2526
"github.com/magefile/mage/mg"
2627

@@ -46,8 +47,12 @@ func Build() error {
4647
return devtools.Build(devtools.DefaultBuildArgs())
4748
}
4849

49-
// BuildSystemTestBinary builds a binary instrumented for use with Python system tests.
50+
// Deprecated: BuildSystemTestBinary builds a binary instrumented for use with Python system tests.
51+
// Go integration tests now build the binary automatically via TestMain.
5052
func BuildSystemTestBinary() error {
53+
fmt.Println("WARNING: BuildSystemTestBinary is deprecated for Go integration tests. " +
54+
"The test binary is now built automatically via TestMain. " +
55+
"This target is only needed for Python system tests.")
5156
return devtools.BuildSystemTestBinary()
5257
}
5358

@@ -75,7 +80,7 @@ func IntegTest() {
7580

7681
// GoIntegTest starts the docker containers and executes the Go integration tests.
7782
func GoIntegTest(ctx context.Context) error {
78-
mg.Deps(Fields, devtools.BuildSystemTestBinary)
83+
mg.Deps(Fields)
7984
args := devtools.DefaultGoTestIntegrationFromHostArgs(ctx)
8085
// ES_USER must be admin in order for the Go Integration tests to function because they require
8186
// indices:data/read/search

libbeat/tests/integration/framework.go

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ import (
5252
"github.com/stretchr/testify/require"
5353
sdkmetric "go.opentelemetry.io/otel/sdk/metric"
5454

55+
"github.com/elastic/beats/v7/dev-tools/testbin"
5556
"github.com/elastic/beats/v7/libbeat/common/proc"
5657
"github.com/elastic/go-elasticsearch/v8"
5758
"github.com/elastic/mock-es/pkg/api"
@@ -1252,7 +1253,7 @@ func StartMockES(
12521253
if err != nil {
12531254
return false
12541255
}
1255-
//nolint: errcheck // We're just draining the body, we can ignore the error
1256+
//nolint:errcheck // We're just draining the body, we can ignore the error
12561257
io.Copy(io.Discard, resp.Body)
12571258
resp.Body.Close()
12581259
return true
@@ -1277,6 +1278,29 @@ func (b *BeatProc) WaitPublishedEvents(timeout time.Duration, events int) {
12771278
}, timeout, 200*time.Millisecond)
12781279
}
12791280

1281+
// TestMainWithBuild is a TestMain helper that builds the beat test binary,
1282+
// runs all tests, cleans up and exits. It resolves paths relative to the
1283+
// working directory (the test package directory), so "../../" reaches the
1284+
// beat root from <beat>/tests/integration/.
1285+
//
1286+
// func TestMain(m *testing.M) {
1287+
// integration.TestMainWithBuild(m, "filebeat")
1288+
// }
1289+
func TestMainWithBuild(m *testing.M, beatName string, opts ...testbin.Option) {
1290+
beatRoot, err := filepath.Abs("../../")
1291+
if err != nil {
1292+
fmt.Fprintf(os.Stderr, "failed to resolve beat root path: %s\n", err)
1293+
os.Exit(1)
1294+
}
1295+
_, err = testbin.Build(beatName, beatRoot, opts...)
1296+
if err != nil {
1297+
fmt.Fprintf(os.Stderr, "failed to build %s test binary: %s\n", beatName, err)
1298+
os.Exit(1)
1299+
}
1300+
1301+
os.Exit(m.Run())
1302+
}
1303+
12801304
// GetEventsFromFileOutput reads all events from file output. If n > 0,
12811305
// then it reads up to n events. It assumes the filename
12821306
// for the output is 'output' and 'path' is set to the TempDir.
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// Licensed to Elasticsearch B.V. under one or more contributor
2+
// license agreements. See the NOTICE file distributed with
3+
// this work for additional information regarding copyright
4+
// ownership. Elasticsearch B.V. licenses this file to you under
5+
// the Apache License, Version 2.0 (the "License"); you may
6+
// not use this file except in compliance with the License.
7+
// You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing,
12+
// software distributed under the License is distributed on an
13+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
// KIND, either express or implied. See the License for the
15+
// specific language governing permissions and limitations
16+
// under the License.
17+
18+
//go:build integration
19+
20+
package integration
21+
22+
import "testing"
23+
24+
func TestMain(m *testing.M) {
25+
TestMainWithBuild(m, "libbeat")
26+
}

metricbeat/magefile.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,8 +63,12 @@ func init() {
6363
devtools.BeatDescription = "Metricbeat is a lightweight shipper for metrics."
6464
}
6565

66-
// BuildSystemTestBinary builds a binary instrumented for use with Python system tests.
66+
// Deprecated: BuildSystemTestBinary builds a binary instrumented for use with Python system tests.
67+
// Go integration tests now build the binary automatically via TestMain.
6768
func BuildSystemTestBinary() error {
69+
fmt.Println("WARNING: BuildSystemTestBinary is deprecated for Go integration tests. " +
70+
"The test binary is now built automatically via TestMain. " +
71+
"This target is only needed for Python system tests.")
6872
return devtools.BuildSystemTestBinary()
6973
}
7074

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
// Licensed to Elasticsearch B.V. under one or more contributor
2+
// license agreements. See the NOTICE file distributed with
3+
// this work for additional information regarding copyright
4+
// ownership. Elasticsearch B.V. licenses this file to you under
5+
// the Apache License, Version 2.0 (the "License"); you may
6+
// not use this file except in compliance with the License.
7+
// You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing,
12+
// software distributed under the License is distributed on an
13+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
// KIND, either express or implied. See the License for the
15+
// specific language governing permissions and limitations
16+
// under the License.
17+
18+
//go:build integration
19+
20+
package integration
21+
22+
import (
23+
"testing"
24+
25+
"github.com/elastic/beats/v7/libbeat/tests/integration"
26+
)
27+
28+
func TestMain(m *testing.M) {
29+
integration.TestMainWithBuild(m, "metricbeat")
30+
}

0 commit comments

Comments
 (0)