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 178 179
|
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
//go:build go1.22
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"
"go.opentelemetry.io/otel/trace/noop"
"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.
otel.SetTracerProvider(noop.NewTracerProvider())
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())
}
}
|