1// Copyright The OpenTelemetry Authors 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15package oteltest // import "go.opentelemetry.io/otel/oteltest" 16 17import ( 18 "context" 19 "fmt" 20 "sync" 21 "testing" 22 "time" 23 24 "go.opentelemetry.io/otel/attribute" 25 "go.opentelemetry.io/otel/codes" 26 "go.opentelemetry.io/otel/internal/matchers" 27 "go.opentelemetry.io/otel/trace" 28) 29 30// Harness is a testing harness used to test implementations of the 31// OpenTelemetry API. 32type Harness struct { 33 t *testing.T 34} 35 36// NewHarness returns an instantiated *Harness using t. 37func NewHarness(t *testing.T) *Harness { 38 return &Harness{ 39 t: t, 40 } 41} 42 43// TestTracer runs validation tests for an implementation of the OpenTelemetry 44// TracerProvider API. 45func (h *Harness) TestTracerProvider(subjectFactory func() trace.TracerProvider) { 46 h.t.Run("#Start", func(t *testing.T) { 47 t.Run("allow creating an arbitrary number of TracerProvider instances", func(t *testing.T) { 48 t.Parallel() 49 50 e := matchers.NewExpecter(t) 51 52 tp1 := subjectFactory() 53 tp2 := subjectFactory() 54 55 e.Expect(tp1).NotToEqual(tp2) 56 }) 57 t.Run("all methods are safe to be called concurrently", func(t *testing.T) { 58 t.Parallel() 59 60 runner := func(tp trace.TracerProvider) <-chan struct{} { 61 done := make(chan struct{}) 62 go func(tp trace.TracerProvider) { 63 var wg sync.WaitGroup 64 for i := 0; i < 20; i++ { 65 wg.Add(1) 66 go func(name, version string) { 67 _ = tp.Tracer(name, trace.WithInstrumentationVersion(version)) 68 wg.Done() 69 }(fmt.Sprintf("tracer %d", i%5), fmt.Sprintf("%d", i)) 70 } 71 wg.Wait() 72 done <- struct{}{} 73 }(tp) 74 return done 75 } 76 77 matchers.NewExpecter(t).Expect(func() { 78 // Run with multiple TracerProvider to ensure they encapsulate 79 // their own Tracers. 80 tp1 := subjectFactory() 81 tp2 := subjectFactory() 82 83 done1 := runner(tp1) 84 done2 := runner(tp2) 85 86 <-done1 87 <-done2 88 }).NotToPanic() 89 }) 90 }) 91} 92 93// TestTracer runs validation tests for an implementation of the OpenTelemetry 94// Tracer API. 95func (h *Harness) TestTracer(subjectFactory func() trace.Tracer) { 96 h.t.Run("#Start", func(t *testing.T) { 97 t.Run("propagates the original context", func(t *testing.T) { 98 t.Parallel() 99 100 e := matchers.NewExpecter(t) 101 subject := subjectFactory() 102 103 ctxKey := testCtxKey{} 104 ctxValue := "ctx value" 105 ctx := context.WithValue(context.Background(), ctxKey, ctxValue) 106 107 ctx, _ = subject.Start(ctx, "test") 108 109 e.Expect(ctx.Value(ctxKey)).ToEqual(ctxValue) 110 }) 111 112 t.Run("returns a span containing the expected properties", func(t *testing.T) { 113 t.Parallel() 114 115 e := matchers.NewExpecter(t) 116 subject := subjectFactory() 117 118 _, span := subject.Start(context.Background(), "test") 119 120 e.Expect(span).NotToBeNil() 121 122 e.Expect(span.Tracer()).ToEqual(subject) 123 e.Expect(span.SpanContext().IsValid()).ToBeTrue() 124 }) 125 126 t.Run("stores the span on the provided context", func(t *testing.T) { 127 t.Parallel() 128 129 e := matchers.NewExpecter(t) 130 subject := subjectFactory() 131 132 ctx, span := subject.Start(context.Background(), "test") 133 134 e.Expect(span).NotToBeNil() 135 e.Expect(span.SpanContext()).NotToEqual(trace.SpanContext{}) 136 e.Expect(trace.SpanFromContext(ctx)).ToEqual(span) 137 }) 138 139 t.Run("starts spans with unique trace and span IDs", func(t *testing.T) { 140 t.Parallel() 141 142 e := matchers.NewExpecter(t) 143 subject := subjectFactory() 144 145 _, span1 := subject.Start(context.Background(), "span1") 146 _, span2 := subject.Start(context.Background(), "span2") 147 148 sc1 := span1.SpanContext() 149 sc2 := span2.SpanContext() 150 151 e.Expect(sc1.TraceID()).NotToEqual(sc2.TraceID()) 152 e.Expect(sc1.SpanID()).NotToEqual(sc2.SpanID()) 153 }) 154 155 t.Run("propagates a parent's trace ID through the context", func(t *testing.T) { 156 t.Parallel() 157 158 e := matchers.NewExpecter(t) 159 subject := subjectFactory() 160 161 ctx, parent := subject.Start(context.Background(), "parent") 162 _, child := subject.Start(ctx, "child") 163 164 psc := parent.SpanContext() 165 csc := child.SpanContext() 166 167 e.Expect(csc.TraceID()).ToEqual(psc.TraceID()) 168 e.Expect(csc.SpanID()).NotToEqual(psc.SpanID()) 169 }) 170 171 t.Run("ignores parent's trace ID when new root is requested", func(t *testing.T) { 172 t.Parallel() 173 174 e := matchers.NewExpecter(t) 175 subject := subjectFactory() 176 177 ctx, parent := subject.Start(context.Background(), "parent") 178 _, child := subject.Start(ctx, "child", trace.WithNewRoot()) 179 180 psc := parent.SpanContext() 181 csc := child.SpanContext() 182 183 e.Expect(csc.TraceID()).NotToEqual(psc.TraceID()) 184 e.Expect(csc.SpanID()).NotToEqual(psc.SpanID()) 185 }) 186 187 t.Run("propagates remote parent's trace ID through the context", func(t *testing.T) { 188 t.Parallel() 189 190 e := matchers.NewExpecter(t) 191 subject := subjectFactory() 192 193 _, remoteParent := subject.Start(context.Background(), "remote parent") 194 parentCtx := trace.ContextWithRemoteSpanContext(context.Background(), remoteParent.SpanContext()) 195 _, child := subject.Start(parentCtx, "child") 196 197 psc := remoteParent.SpanContext() 198 csc := child.SpanContext() 199 200 e.Expect(csc.TraceID()).ToEqual(psc.TraceID()) 201 e.Expect(csc.SpanID()).NotToEqual(psc.SpanID()) 202 }) 203 204 t.Run("ignores remote parent's trace ID when new root is requested", func(t *testing.T) { 205 t.Parallel() 206 207 e := matchers.NewExpecter(t) 208 subject := subjectFactory() 209 210 _, remoteParent := subject.Start(context.Background(), "remote parent") 211 parentCtx := trace.ContextWithRemoteSpanContext(context.Background(), remoteParent.SpanContext()) 212 _, child := subject.Start(parentCtx, "child", trace.WithNewRoot()) 213 214 psc := remoteParent.SpanContext() 215 csc := child.SpanContext() 216 217 e.Expect(csc.TraceID()).NotToEqual(psc.TraceID()) 218 e.Expect(csc.SpanID()).NotToEqual(psc.SpanID()) 219 }) 220 221 t.Run("all methods are safe to be called concurrently", func(t *testing.T) { 222 t.Parallel() 223 224 e := matchers.NewExpecter(t) 225 tracer := subjectFactory() 226 227 ctx, parent := tracer.Start(context.Background(), "span") 228 229 runner := func(tp trace.Tracer) <-chan struct{} { 230 done := make(chan struct{}) 231 go func(tp trace.Tracer) { 232 var wg sync.WaitGroup 233 for i := 0; i < 20; i++ { 234 wg.Add(1) 235 go func(name string) { 236 defer wg.Done() 237 _, child := tp.Start(ctx, name) 238 239 psc := parent.SpanContext() 240 csc := child.SpanContext() 241 242 e.Expect(csc.TraceID()).ToEqual(psc.TraceID()) 243 e.Expect(csc.SpanID()).NotToEqual(psc.SpanID()) 244 }(fmt.Sprintf("span %d", i)) 245 } 246 wg.Wait() 247 done <- struct{}{} 248 }(tp) 249 return done 250 } 251 252 e.Expect(func() { 253 done := runner(tracer) 254 255 <-done 256 }).NotToPanic() 257 }) 258 }) 259 260 h.testSpan(subjectFactory) 261} 262 263func (h *Harness) testSpan(tracerFactory func() trace.Tracer) { 264 var methods = map[string]func(span trace.Span){ 265 "#End": func(span trace.Span) { 266 span.End() 267 }, 268 "#AddEvent": func(span trace.Span) { 269 span.AddEvent("test event") 270 }, 271 "#AddEventWithTimestamp": func(span trace.Span) { 272 span.AddEvent("test event", trace.WithTimestamp(time.Now().Add(1*time.Second))) 273 }, 274 "#SetStatus": func(span trace.Span) { 275 span.SetStatus(codes.Error, "internal") 276 }, 277 "#SetName": func(span trace.Span) { 278 span.SetName("new name") 279 }, 280 "#SetAttributes": func(span trace.Span) { 281 span.SetAttributes(attribute.String("key1", "value"), attribute.Int("key2", 123)) 282 }, 283 } 284 var mechanisms = map[string]func() trace.Span{ 285 "Span created via Tracer#Start": func() trace.Span { 286 tracer := tracerFactory() 287 _, subject := tracer.Start(context.Background(), "test") 288 289 return subject 290 }, 291 } 292 293 for mechanismName, mechanism := range mechanisms { 294 h.t.Run(mechanismName, func(t *testing.T) { 295 for methodName, method := range methods { 296 t.Run(methodName, func(t *testing.T) { 297 t.Run("is thread-safe", func(t *testing.T) { 298 t.Parallel() 299 300 span := mechanism() 301 302 wg := &sync.WaitGroup{} 303 wg.Add(2) 304 305 go func() { 306 defer wg.Done() 307 308 method(span) 309 }() 310 311 go func() { 312 defer wg.Done() 313 314 method(span) 315 }() 316 317 wg.Wait() 318 }) 319 }) 320 } 321 322 t.Run("#End", func(t *testing.T) { 323 t.Run("can be called multiple times", func(t *testing.T) { 324 t.Parallel() 325 326 span := mechanism() 327 328 span.End() 329 span.End() 330 }) 331 }) 332 }) 333 } 334} 335 336type testCtxKey struct{} 337