helpers.go 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155
  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.21.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(semconv.ServiceName("integration-test-client"))),
  48. )
  49. otel.SetTracerProvider(tp)
  50. if err := exp.Start(ctx); err != nil {
  51. log.G(ctx).WithError(err).Warn("Failed to start tracing exporter")
  52. }
  53. })
  54. // if ConfigureTracing was called multiple times we'd have a nil `tp` here
  55. // Get the already configured tracer provider
  56. if tp == nil {
  57. tp = otel.GetTracerProvider().(*trace.TracerProvider)
  58. }
  59. return func(ctx context.Context) {
  60. if err := tp.Shutdown(ctx); err != nil {
  61. log.G(ctx).WithError(err).Warn("Failed to shutdown tracer")
  62. }
  63. }
  64. }
  65. // TestingT is an interface wrapper around *testing.T and *testing.B.
  66. type TestingT interface {
  67. Name() string
  68. Cleanup(func())
  69. Log(...any)
  70. Failed() bool
  71. }
  72. // StartSpan starts a span for the given test.
  73. func StartSpan(ctx context.Context, t TestingT) context.Context {
  74. ConfigureTracing()
  75. ctx, span := otel.Tracer("").Start(ctx, t.Name())
  76. t.Cleanup(func() {
  77. if t.Failed() {
  78. span.SetStatus(codes.Error, "test failed")
  79. }
  80. span.End()
  81. })
  82. return ctx
  83. }
  84. func RunCommand(ctx context.Context, cmd string, args ...string) *icmd.Result {
  85. _, span := otel.Tracer("").Start(ctx, "RunCommand "+cmd+" "+strings.Join(args, " "))
  86. res := icmd.RunCommand(cmd, args...)
  87. if res.Error != nil {
  88. span.SetStatus(codes.Error, res.Error.Error())
  89. }
  90. span.SetAttributes(attribute.String("cmd", cmd), attribute.String("args", strings.Join(args, " ")))
  91. span.SetAttributes(attribute.Int("exit", res.ExitCode))
  92. span.SetAttributes(attribute.String("stdout", res.Stdout()), attribute.String("stderr", res.Stderr()))
  93. span.End()
  94. return res
  95. }
  96. type testContextStore struct {
  97. mu sync.Mutex
  98. idx map[TestingT]context.Context
  99. }
  100. var testContexts = &testContextStore{idx: make(map[TestingT]context.Context)}
  101. func (s *testContextStore) Get(t TestingT) context.Context {
  102. s.mu.Lock()
  103. defer s.mu.Unlock()
  104. ctx, ok := s.idx[t]
  105. if ok {
  106. return ctx
  107. }
  108. ctx = context.Background()
  109. s.idx[t] = ctx
  110. return ctx
  111. }
  112. func (s *testContextStore) Set(ctx context.Context, t TestingT) {
  113. s.mu.Lock()
  114. if _, ok := s.idx[t]; ok {
  115. panic("test context already set")
  116. }
  117. s.idx[t] = ctx
  118. s.mu.Unlock()
  119. }
  120. func (s *testContextStore) Delete(t *testing.T) {
  121. s.mu.Lock()
  122. defer s.mu.Unlock()
  123. delete(s.idx, t)
  124. }
  125. func GetContext(t TestingT) context.Context {
  126. return testContexts.Get(t)
  127. }
  128. func SetContext(t TestingT, ctx context.Context) {
  129. testContexts.Set(ctx, t)
  130. }
  131. func CleanupContext(t *testing.T) {
  132. testContexts.Delete(t)
  133. }