Skip to content

Commit 4ade167

Browse files
authored
Test probe load with mocks (#1991)
* Refactor TestLoadProbes * Remove unneeded testutils pkg * Update probe_load workflow * Fix fakeManager lint
1 parent 6d64357 commit 4ade167

File tree

5 files changed

+143
-143
lines changed

5 files changed

+143
-143
lines changed

.github/workflows/probe_load.yaml

+2-2
Original file line numberDiff line numberDiff line change
@@ -41,9 +41,9 @@ jobs:
4141
sudo chmod 0666 /dev/kvm
4242
- name: Test without verifier logs
4343
id: no_verifier_logs_test
44-
run: OTEL_GO_AUTO_SHOW_VERIFIER_LOG=false vimto -kernel :${{ matrix.tag }} -- go test -v -count=1 -tags=ebpf_test go.opentelemetry.io/auto/internal/pkg/instrumentation
44+
run: OTEL_GO_AUTO_SHOW_VERIFIER_LOG=false vimto -kernel :${{ matrix.tag }} -- go test -v -count=1 go.opentelemetry.io/auto/internal/pkg/instrumentation
4545
- name: Test with verifier logs
46-
run: OTEL_GO_AUTO_SHOW_VERIFIER_LOG=true vimto -kernel :${{ matrix.tag }} -- go test -v -count=1 -tags=ebpf_test go.opentelemetry.io/auto/internal/pkg/instrumentation
46+
run: OTEL_GO_AUTO_SHOW_VERIFIER_LOG=true vimto -kernel :${{ matrix.tag }} -- go test -v -count=1 go.opentelemetry.io/auto/internal/pkg/instrumentation
4747
if: always() && steps.no_verifier_logs_test.outcome == 'failure'
4848
- name: Test eBPF sampling
4949
run: vimto -kernel :${{ matrix.tag }} -- go test -v -count=1 -tags=ebpf_test go.opentelemetry.io/auto/internal/pkg/instrumentation/probe/sampling
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,22 @@
11
// Copyright The OpenTelemetry Authors
22
// SPDX-License-Identifier: Apache-2.0
33

4-
//go:build ebpf_test
5-
64
package instrumentation
75

86
import (
7+
"errors"
98
"log/slog"
9+
"os"
10+
"os/exec"
11+
"path/filepath"
1012
"testing"
1113

1214
"github.com/Masterminds/semver/v3"
15+
"github.com/cilium/ebpf"
16+
"github.com/cilium/ebpf/rlimit"
1317
"github.com/stretchr/testify/assert"
1418
"github.com/stretchr/testify/require"
19+
1520
"go.opentelemetry.io/auto/internal/pkg/inject"
1621
dbSql "go.opentelemetry.io/auto/internal/pkg/instrumentation/bpf/database/sql"
1722
kafkaConsumer "go.opentelemetry.io/auto/internal/pkg/instrumentation/bpf/github.com/segmentio/kafka-go/consumer"
@@ -22,71 +27,161 @@ import (
2227
grpcServer "go.opentelemetry.io/auto/internal/pkg/instrumentation/bpf/google.golang.org/grpc/server"
2328
httpClient "go.opentelemetry.io/auto/internal/pkg/instrumentation/bpf/net/http/client"
2429
httpServer "go.opentelemetry.io/auto/internal/pkg/instrumentation/bpf/net/http/server"
30+
"go.opentelemetry.io/auto/internal/pkg/instrumentation/bpffs"
2531
"go.opentelemetry.io/auto/internal/pkg/instrumentation/probe"
26-
"go.opentelemetry.io/auto/internal/pkg/instrumentation/testutils"
2732
"go.opentelemetry.io/auto/internal/pkg/instrumentation/utils"
2833
"go.opentelemetry.io/auto/internal/pkg/process"
29-
"go.opentelemetry.io/auto/internal/pkg/process/binary"
3034
)
3135

3236
func TestLoadProbes(t *testing.T) {
37+
if err := rlimit.RemoveMemlock(); err != nil {
38+
t.Skip("cannot manage memory, skipping test.")
39+
}
40+
41+
id := setupTestModule(t)
42+
pid := process.ID(id)
43+
44+
info, err := process.NewInfo(pid, make(map[string]interface{}))
45+
if info == nil {
46+
t.Fatalf("failed to create process.Info: %v", err)
47+
}
48+
// Reset Info module information.
49+
info.Modules = make(map[string]*semver.Version)
50+
51+
logger := slog.Default()
52+
info.Allocation, err = process.Allocate(logger, pid)
53+
if err != nil {
54+
t.Fatalf("failed to allocate for process %d: %v", id, err)
55+
}
56+
3357
ver := utils.GetLinuxKernelVersion()
3458
require.NotNil(t, ver)
3559
t.Logf("Running on kernel %s", ver.String())
36-
m := fakeManager(t)
3760

38-
for _, p := range m.probes {
61+
probes := []probe.Probe{
62+
grpcClient.New(logger, ""),
63+
grpcServer.New(logger, ""),
64+
httpServer.New(logger, ""),
65+
httpClient.New(logger, ""),
66+
dbSql.New(logger, ""),
67+
kafkaProducer.New(logger, ""),
68+
kafkaConsumer.New(logger, ""),
69+
autosdk.New(logger),
70+
otelTraceGlobal.New(logger),
71+
}
72+
73+
for _, p := range probes {
3974
manifest := p.Manifest()
4075
fields := manifest.StructFields
41-
offsets := map[string]*semver.Version{}
4276
for _, f := range fields {
4377
_, ver := inject.GetLatestOffset(f)
4478
if ver != nil {
45-
offsets[f.PkgPath] = ver
46-
offsets[f.ModPath] = ver
79+
info.Modules[f.PkgPath] = ver
80+
info.Modules[f.ModPath] = ver
4781
}
4882
}
4983
t.Run(p.Manifest().ID.String(), func(t *testing.T) {
50-
testProbe, ok := p.(testutils.TestProbe)
51-
assert.True(t, ok)
52-
testutils.ProbesLoad(t, testProbe, offsets)
84+
require.Implements(t, (*TestProbe)(nil), p)
85+
ProbesLoad(t, info, p.(TestProbe))
5386
})
5487
}
5588
}
5689

57-
func fakeManager(t *testing.T, fnNames ...string) *Manager {
58-
logger := slog.Default()
59-
probes := []probe.Probe{
60-
grpcClient.New(logger, ""),
61-
grpcServer.New(logger, ""),
62-
httpServer.New(logger, ""),
63-
httpClient.New(logger, ""),
64-
dbSql.New(logger, ""),
65-
kafkaProducer.New(logger, ""),
66-
kafkaConsumer.New(logger, ""),
67-
autosdk.New(logger),
68-
otelTraceGlobal.New(logger),
90+
const mainGoContent = `package main
91+
92+
import (
93+
"time"
94+
)
95+
96+
func main() {
97+
for {
98+
time.Sleep(time.Hour)
99+
}
100+
}`
101+
102+
func setupTestModule(t *testing.T) int {
103+
t.Helper()
104+
105+
tempDir := t.TempDir()
106+
107+
// Initialize a Go module
108+
cmd := exec.Command("go", "mod", "init", "example.com/testmodule")
109+
cmd.Dir = tempDir
110+
if err := cmd.Run(); err != nil {
111+
t.Fatalf("failed to initialize Go module: %v", err)
112+
}
113+
114+
mainGoPath := filepath.Join(tempDir, "main.go")
115+
if err := os.WriteFile(mainGoPath, []byte(mainGoContent), 0o600); err != nil {
116+
t.Fatalf("failed to write main.go: %v", err)
69117
}
70-
ver := semver.New(1, 20, 0, "", "")
71-
var fn []*binary.Func
72-
for _, name := range fnNames {
73-
fn = append(fn, &binary.Func{Name: name})
118+
119+
// Compile the Go program
120+
binaryPath := filepath.Join(tempDir, "testbinary")
121+
cmd = exec.Command("go", "build", "-o", binaryPath, mainGoPath)
122+
cmd.Dir = tempDir
123+
if err := cmd.Run(); err != nil {
124+
t.Fatalf("failed to compile binary: %v", err)
125+
}
126+
127+
// Run the compiled binary
128+
cmd = exec.Command(binaryPath)
129+
cmd.Dir = tempDir
130+
cmd.Stdout = os.Stdout
131+
cmd.Stderr = os.Stderr
132+
if err := cmd.Start(); err != nil {
133+
t.Fatalf("failed to start binary: %v", err)
74134
}
75-
m := &Manager{
76-
logger: slog.Default(),
77-
cp: NewNoopConfigProvider(nil),
78-
probes: make(map[probe.ID]probe.Probe),
79-
proc: &process.Info{
80-
ID: 1,
81-
Functions: fn,
82-
GoVersion: ver,
83-
Modules: map[string]*semver.Version{},
135+
136+
// Ensure the process is killed when the test ends
137+
t.Cleanup(func() {
138+
_ = cmd.Process.Kill()
139+
_, _ = cmd.Process.Wait()
140+
})
141+
142+
// Return the process ID
143+
return cmd.Process.Pid
144+
}
145+
146+
type TestProbe interface {
147+
Spec() (*ebpf.CollectionSpec, error)
148+
InjectConsts(*process.Info, *ebpf.CollectionSpec) error
149+
}
150+
151+
func ProbesLoad(t *testing.T, info *process.Info, p TestProbe) {
152+
t.Helper()
153+
154+
require.NoError(t, bpffs.Mount(info))
155+
t.Cleanup(func() { _ = bpffs.Cleanup(info) })
156+
157+
spec, err := p.Spec()
158+
require.NoError(t, err)
159+
160+
// Inject the same constants as the BPF program. It is important to inject
161+
// the same constants as those that will be used in the actual run, since
162+
// From Linux 5.5 the verifier will use constants to eliminate dead code.
163+
require.NoError(t, p.InjectConsts(info, spec))
164+
165+
opts := ebpf.CollectionOptions{
166+
Maps: ebpf.MapOptions{
167+
PinPath: bpffs.PathForTargetApplication(info),
84168
},
85169
}
86-
for _, p := range probes {
87-
m.probes[p.Manifest().ID] = p
170+
171+
collectVerifierLogs := utils.ShouldShowVerifierLogs()
172+
if collectVerifierLogs {
173+
opts.Programs.LogLevel = ebpf.LogLevelStats | ebpf.LogLevelInstruction
88174
}
89-
m.filterUnusedProbes()
90175

91-
return m
176+
c, err := ebpf.NewCollectionWithOptions(spec, opts)
177+
if !assert.NoError(t, err) {
178+
var ve *ebpf.VerifierError
179+
if errors.As(err, &ve) && collectVerifierLogs {
180+
t.Logf("Verifier log: %-100v\n", ve)
181+
}
182+
}
183+
184+
if c != nil {
185+
t.Cleanup(c.Close)
186+
}
92187
}

internal/pkg/instrumentation/manager_test.go

+4-6
Original file line numberDiff line numberDiff line change
@@ -40,18 +40,17 @@ import (
4040

4141
func TestProbeFiltering(t *testing.T) {
4242
t.Run("empty target details", func(t *testing.T) {
43-
m := fakeManager(t)
43+
m := fakeManager()
4444
assert.Empty(t, m.probes)
4545
})
4646

4747
t.Run("only HTTP client target details", func(t *testing.T) {
48-
m := fakeManager(t, "net/http.(*Transport).roundTrip")
48+
m := fakeManager("net/http.(*Transport).roundTrip")
4949
assert.Len(t, m.probes, 1) // one function, single probe
5050
})
5151

5252
t.Run("HTTP server and client target details", func(t *testing.T) {
5353
m := fakeManager(
54-
t,
5554
"net/http.(*Transport).roundTrip",
5655
"net/http.serverHandler.ServeHTTP",
5756
)
@@ -60,7 +59,6 @@ func TestProbeFiltering(t *testing.T) {
6059

6160
t.Run("HTTP server and client dependent function only target details", func(t *testing.T) {
6261
m := fakeManager(
63-
t,
6462
// writeSubset depends on "net/http.(*Transport).roundTrip", it should be ignored without roundTrip
6563
"net/http.Header.writeSubset",
6664
"net/http.serverHandler.ServeHTTP",
@@ -70,7 +68,7 @@ func TestProbeFiltering(t *testing.T) {
7068
}
7169

7270
func TestDependencyChecks(t *testing.T) {
73-
m := fakeManager(t)
71+
m := fakeManager()
7472

7573
t.Run("Dependent probes match", func(t *testing.T) {
7674
syms := []probe.FunctionSymbol{
@@ -152,7 +150,7 @@ func TestDependencyChecks(t *testing.T) {
152150
})
153151
}
154152

155-
func fakeManager(t *testing.T, fnNames ...string) *Manager {
153+
func fakeManager(fnNames ...string) *Manager {
156154
logger := slog.Default()
157155
probes := []probe.Probe{
158156
grpcClient.New(logger, ""),

internal/pkg/instrumentation/testutils/testutils.go

-93
This file was deleted.

internal/pkg/process/info.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ func NewInfo(id ID, relevantFuncs map[string]interface{}) (*Info, error) {
5252

5353
result.Functions, err = findFunctions(elfF, relevantFuncs)
5454
if err != nil {
55-
return nil, err
55+
return result, err
5656
}
5757

5858
result.Modules, err = findModules(goVersion, bi.Deps)

0 commit comments

Comments
 (0)