helpers.go 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157
  1. package testutil // import "github.com/docker/docker/testutil"
  2. import (
  3. "context"
  4. "io"
  5. "os"
  6. "strings"
  7. "sync"
  8. "testing"
  9. "github.com/containerd/log"
  10. "go.opentelemetry.io/otel"
  11. "go.opentelemetry.io/otel/attribute"
  12. "go.opentelemetry.io/otel/codes"
  13. "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp"
  14. "go.opentelemetry.io/otel/propagation"
  15. "go.opentelemetry.io/otel/sdk/resource"
  16. "go.opentelemetry.io/otel/sdk/trace"
  17. semconv "go.opentelemetry.io/otel/semconv/v1.17.0"
  18. "gotest.tools/v3/icmd"
  19. )
  20. // DevZero acts like /dev/zero but in an OS-independent fashion.
  21. var DevZero io.Reader = devZero{}
  22. type devZero struct{}
  23. func (d devZero) Read(p []byte) (n int, err error) {
  24. for i := range p {
  25. p[i] = 0
  26. }
  27. return len(p), nil
  28. }
  29. var tracingOnce sync.Once
  30. // configureTracing sets up an OTLP tracing exporter for use in tests.
  31. func ConfigureTracing() func(context.Context) {
  32. if os.Getenv("OTEL_EXPORTER_OTLP_ENDPOINT") == "" {
  33. // No OTLP endpoint configured, so don't bother setting up tracing.
  34. // Since we are not using a batch exporter we don't want tracing to block up tests.
  35. return func(context.Context) {}
  36. }
  37. var tp *trace.TracerProvider
  38. tracingOnce.Do(func() {
  39. ctx := context.Background()
  40. exp := otlptracehttp.NewUnstarted()
  41. sp := trace.NewBatchSpanProcessor(exp)
  42. props := propagation.NewCompositeTextMapPropagator(propagation.TraceContext{}, propagation.Baggage{})
  43. otel.SetTextMapPropagator(props)
  44. tp = trace.NewTracerProvider(
  45. trace.WithSpanProcessor(sp),
  46. trace.WithSampler(trace.AlwaysSample()),
  47. trace.WithResource(resource.NewSchemaless(
  48. attribute.KeyValue{Key: semconv.ServiceNameKey, Value: attribute.StringValue("integration-test-client")},
  49. )),
  50. )
  51. otel.SetTracerProvider(tp)
  52. if err := exp.Start(ctx); err != nil {
  53. log.G(ctx).WithError(err).Warn("Failed to start tracing exporter")
  54. }
  55. })
  56. // if ConfigureTracing was called multiple times we'd have a nil `tp` here
  57. // Get the already configured tracer provider
  58. if tp == nil {
  59. tp = otel.GetTracerProvider().(*trace.TracerProvider)
  60. }
  61. return func(ctx context.Context) {
  62. if err := tp.Shutdown(ctx); err != nil {
  63. log.G(ctx).WithError(err).Warn("Failed to shutdown tracer")
  64. }
  65. }
  66. }
  67. // TestingT is an interface wrapper around *testing.T and *testing.B.
  68. type TestingT interface {
  69. Name() string
  70. Cleanup(func())
  71. Log(...any)
  72. Failed() bool
  73. }
  74. // StartSpan starts a span for the given test.
  75. func StartSpan(ctx context.Context, t TestingT) context.Context {
  76. ConfigureTracing()
  77. ctx, span := otel.Tracer("").Start(ctx, t.Name())
  78. t.Cleanup(func() {
  79. if t.Failed() {
  80. span.SetStatus(codes.Error, "test failed")
  81. }
  82. span.End()
  83. })
  84. return ctx
  85. }
  86. func RunCommand(ctx context.Context, cmd string, args ...string) *icmd.Result {
  87. _, span := otel.Tracer("").Start(ctx, "RunCommand "+cmd+" "+strings.Join(args, " "))
  88. res := icmd.RunCommand(cmd, args...)
  89. if res.Error != nil {
  90. span.SetStatus(codes.Error, res.Error.Error())
  91. }
  92. span.SetAttributes(attribute.String("cmd", cmd), attribute.String("args", strings.Join(args, " ")))
  93. span.SetAttributes(attribute.Int("exit", res.ExitCode))
  94. span.SetAttributes(attribute.String("stdout", res.Stdout()), attribute.String("stderr", res.Stderr()))
  95. span.End()
  96. return res
  97. }
  98. type testContextStore struct {
  99. mu sync.Mutex
  100. idx map[TestingT]context.Context
  101. }
  102. var testContexts = &testContextStore{idx: make(map[TestingT]context.Context)}
  103. func (s *testContextStore) Get(t TestingT) context.Context {
  104. s.mu.Lock()
  105. defer s.mu.Unlock()
  106. ctx, ok := s.idx[t]
  107. if ok {
  108. return ctx
  109. }
  110. ctx = context.Background()
  111. s.idx[t] = ctx
  112. return ctx
  113. }
  114. func (s *testContextStore) Set(ctx context.Context, t TestingT) {
  115. s.mu.Lock()
  116. if _, ok := s.idx[t]; ok {
  117. panic("test context already set")
  118. }
  119. s.idx[t] = ctx
  120. s.mu.Unlock()
  121. }
  122. func (s *testContextStore) Delete(t *testing.T) {
  123. s.mu.Lock()
  124. defer s.mu.Unlock()
  125. delete(s.idx, t)
  126. }
  127. func GetContext(t TestingT) context.Context {
  128. return testContexts.Get(t)
  129. }
  130. func SetContext(t TestingT, ctx context.Context) {
  131. testContexts.Set(ctx, t)
  132. }
  133. func CleanupContext(t *testing.T) {
  134. testContexts.Delete(t)
  135. }