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  }