-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathhelpers.go
177 lines (153 loc) · 4.82 KB
/
helpers.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
//go:build go1.21
package testutil // import "github.com/docker/docker/testutil"
import (
"context"
"io"
"os"
"reflect"
"strings"
"sync"
"testing"
"github.com/containerd/log"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/codes"
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp"
"go.opentelemetry.io/otel/propagation"
"go.opentelemetry.io/otel/sdk/resource"
"go.opentelemetry.io/otel/sdk/trace"
semconv "go.opentelemetry.io/otel/semconv/v1.21.0"
"gotest.tools/v3/icmd"
)
// DevZero acts like /dev/zero but in an OS-independent fashion.
var DevZero io.Reader = devZero{}
type devZero struct{}
func (d devZero) Read(p []byte) (n int, err error) {
for i := range p {
p[i] = 0
}
return len(p), nil
}
var tracingOnce sync.Once
// ConfigureTracing sets up an OTLP tracing exporter for use in tests.
func ConfigureTracing() func(context.Context) {
if os.Getenv("OTEL_EXPORTER_OTLP_ENDPOINT") == "" {
// No OTLP endpoint configured, so don't bother setting up tracing.
// Since we are not using a batch exporter we don't want tracing to block up tests.
return func(context.Context) {}
}
var tp *trace.TracerProvider
tracingOnce.Do(func() {
ctx := context.Background()
exp := otlptracehttp.NewUnstarted()
sp := trace.NewBatchSpanProcessor(exp)
props := propagation.NewCompositeTextMapPropagator(propagation.TraceContext{}, propagation.Baggage{})
otel.SetTextMapPropagator(props)
tp = trace.NewTracerProvider(
trace.WithSpanProcessor(sp),
trace.WithSampler(trace.AlwaysSample()),
trace.WithResource(resource.NewSchemaless(semconv.ServiceName("integration-test-client"))),
)
otel.SetTracerProvider(tp)
if err := exp.Start(ctx); err != nil {
log.G(ctx).WithError(err).Warn("Failed to start tracing exporter")
}
})
// if ConfigureTracing was called multiple times we'd have a nil `tp` here
// Get the already configured tracer provider
if tp == nil {
tp = otel.GetTracerProvider().(*trace.TracerProvider)
}
return func(ctx context.Context) {
if err := tp.Shutdown(ctx); err != nil {
log.G(ctx).WithError(err).Warn("Failed to shutdown tracer")
}
}
}
// TestingT is an interface wrapper around *testing.T and *testing.B.
type TestingT interface {
Name() string
Cleanup(func())
Log(...any)
Failed() bool
}
// StartSpan starts a span for the given test.
func StartSpan(ctx context.Context, t TestingT) context.Context {
ConfigureTracing()
ctx, span := otel.Tracer("").Start(ctx, t.Name())
t.Cleanup(func() {
if t.Failed() {
span.SetStatus(codes.Error, "test failed")
}
span.End()
})
return ctx
}
func RunCommand(ctx context.Context, cmd string, args ...string) *icmd.Result {
_, span := otel.Tracer("").Start(ctx, "RunCommand "+cmd+" "+strings.Join(args, " "))
res := icmd.RunCommand(cmd, args...)
if res.Error != nil {
span.SetStatus(codes.Error, res.Error.Error())
}
span.SetAttributes(attribute.String("cmd", cmd), attribute.String("args", strings.Join(args, " ")))
span.SetAttributes(attribute.Int("exit", res.ExitCode))
span.SetAttributes(attribute.String("stdout", res.Stdout()), attribute.String("stderr", res.Stderr()))
span.End()
return res
}
type testContextStore struct {
mu sync.Mutex
idx map[TestingT]context.Context
}
var testContexts = &testContextStore{idx: make(map[TestingT]context.Context)}
func (s *testContextStore) Get(t TestingT) context.Context {
s.mu.Lock()
defer s.mu.Unlock()
ctx, ok := s.idx[t]
if ok {
return ctx
}
ctx = context.Background()
s.idx[t] = ctx
return ctx
}
func (s *testContextStore) Set(ctx context.Context, t TestingT) {
s.mu.Lock()
if _, ok := s.idx[t]; ok {
panic("test context already set")
}
s.idx[t] = ctx
s.mu.Unlock()
}
func (s *testContextStore) Delete(t *testing.T) {
s.mu.Lock()
defer s.mu.Unlock()
delete(s.idx, t)
}
func GetContext(t TestingT) context.Context {
return testContexts.Get(t)
}
func SetContext(t TestingT, ctx context.Context) {
testContexts.Set(ctx, t)
}
func CleanupContext(t *testing.T) {
testContexts.Delete(t)
}
// CheckNotParallel checks if t.Parallel() was not called on the current test.
// There's no public method to check this, so we use reflection to check the
// internal field set by t.Parallel()
// https://github.com/golang/go/blob/8e658eee9c7a67a8a79a8308695920ac9917566c/src/testing/testing.go#L1449
//
// Since this is not a public API, it might change at any time.
func CheckNotParallel(t testing.TB) {
t.Helper()
field := reflect.ValueOf(t).Elem().FieldByName("isParallel")
if field.IsValid() {
if field.Bool() {
t.Fatal("t.Parallel() was called before")
}
} else {
t.Logf("FIXME: CheckParallel could not determine if test %s is parallel - did the t.Parallel() implementation change?", t.Name())
}
}