| // Copyright 2021 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| // +build !disable_events |
| |
| package otel |
| |
| import ( |
| "bytes" |
| "context" |
| "fmt" |
| "io" |
| "testing" |
| |
| sdktrace "go.opentelemetry.io/otel/sdk/trace" |
| "go.opentelemetry.io/otel/trace" |
| "golang.org/x/exp/event" |
| ) |
| |
| func TestTrace(t *testing.T) { |
| // Verify that otel and event traces work well together. |
| // This test uses a single, fixed span tree (see makeTraceSpec). |
| // Each test case varies which of the individual spans are |
| // created directly from an otel tracer, and which are created |
| // using the event package. |
| |
| want := "root (f (g h) p (q r))" |
| |
| for i, tfunc := range []func(int) bool{ |
| func(int) bool { return true }, |
| func(int) bool { return false }, |
| func(i int) bool { return i%2 == 0 }, |
| func(i int) bool { return i%2 == 1 }, |
| func(i int) bool { return i%3 == 0 }, |
| func(i int) bool { return i%3 == 1 }, |
| } { |
| ctx, tr, shutdown := setupOtel() |
| // There are 7 spans, so we create a 7-element slice. |
| // tfunc determines, for each index, whether it holds |
| // an otel tracer or nil. |
| tracers := make([]trace.Tracer, 7) |
| for i := 0; i < len(tracers); i++ { |
| if tfunc(i) { |
| tracers[i] = tr |
| } |
| } |
| s := makeTraceSpec(tracers) |
| s.apply(ctx) |
| got := shutdown() |
| if got != want { |
| t.Errorf("#%d: got %v, want %v", i, got, want) |
| } |
| } |
| } |
| |
| func makeTraceSpec(tracers []trace.Tracer) *traceSpec { |
| return &traceSpec{ |
| name: "root", |
| tracer: tracers[0], |
| children: []*traceSpec{ |
| { |
| name: "f", |
| tracer: tracers[1], |
| children: []*traceSpec{ |
| {name: "g", tracer: tracers[2]}, |
| {name: "h", tracer: tracers[3]}, |
| }, |
| }, |
| { |
| name: "p", |
| tracer: tracers[4], |
| children: []*traceSpec{ |
| {name: "q", tracer: tracers[5]}, |
| {name: "r", tracer: tracers[6]}, |
| }, |
| }, |
| }, |
| } |
| } |
| |
| type traceSpec struct { |
| name string |
| tracer trace.Tracer // nil for event |
| children []*traceSpec |
| } |
| |
| // apply builds spans for the traceSpec and all its children, |
| // If the traceSpec has a non-nil tracer, it is used to create the span. |
| // Otherwise, event.Trace.Start is used. |
| func (s *traceSpec) apply(ctx context.Context) { |
| if s.tracer != nil { |
| var span trace.Span |
| ctx, span = s.tracer.Start(ctx, s.name) |
| defer span.End() |
| } else { |
| var end func() |
| ctx, end = event.To(ctx).Start(s.name) |
| defer end() |
| } |
| for _, c := range s.children { |
| c.apply(ctx) |
| } |
| } |
| |
| func setupOtel() (context.Context, trace.Tracer, func() string) { |
| ctx := context.Background() |
| e := newTestExporter() |
| bsp := sdktrace.NewSimpleSpanProcessor(e) |
| stp := sdktrace.NewTracerProvider(sdktrace.WithSpanProcessor(bsp)) |
| tracer := stp.Tracer("") |
| |
| ee := event.NewExporter(NewTraceHandler(tracer)) |
| ctx = event.WithExporter(ctx, ee) |
| return ctx, tracer, func() string { stp.Shutdown(ctx); return e.got } |
| } |
| |
| // testExporter is an otel exporter for traces |
| type testExporter struct { |
| m map[trace.SpanID][]*sdktrace.SpanSnapshot // key is parent SpanID |
| got string |
| } |
| |
| var _ sdktrace.SpanExporter = (*testExporter)(nil) |
| |
| func newTestExporter() *testExporter { |
| return &testExporter{m: map[trace.SpanID][]*sdktrace.SpanSnapshot{}} |
| } |
| |
| func (e *testExporter) ExportSpans(ctx context.Context, ss []*sdktrace.SpanSnapshot) error { |
| for _, s := range ss { |
| sid := s.Parent.SpanID() |
| e.m[sid] = append(e.m[sid], s) |
| } |
| return nil |
| } |
| |
| func (e *testExporter) Shutdown(ctx context.Context) error { |
| root := e.m[trace.SpanID{}][0] |
| var buf bytes.Buffer |
| e.print(&buf, root) |
| e.got = buf.String() |
| return nil |
| } |
| |
| func (e *testExporter) print(w io.Writer, ss *sdktrace.SpanSnapshot) { |
| fmt.Fprintf(w, "%s", ss.Name) |
| children := e.m[ss.SpanContext.SpanID()] |
| if len(children) > 0 { |
| fmt.Fprint(w, " (") |
| for i, ss := range children { |
| if i != 0 { |
| fmt.Fprint(w, " ") |
| } |
| e.print(w, ss) |
| } |
| fmt.Fprint(w, ")") |
| } |
| } |