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