github.com/waldiirawan/apm-agent-go/v2@v2.2.2/gocontext_test.go (about) 1 // Licensed to Elasticsearch B.V. under one or more contributor 2 // license agreements. See the NOTICE file distributed with 3 // this work for additional information regarding copyright 4 // ownership. Elasticsearch B.V. licenses this file to you under 5 // the Apache License, Version 2.0 (the "License"); you may 6 // not use this file except in compliance with the License. 7 // You may obtain a copy of the License at 8 // 9 // http://www.apache.org/licenses/LICENSE-2.0 10 // 11 // Unless required by applicable law or agreed to in writing, 12 // software distributed under the License is distributed on an 13 // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 // KIND, either express or implied. See the License for the 15 // specific language governing permissions and limitations 16 // under the License. 17 18 package apm_test 19 20 import ( 21 "context" 22 "sync" 23 "testing" 24 "time" 25 26 "github.com/pkg/errors" 27 "github.com/stretchr/testify/assert" 28 "github.com/stretchr/testify/require" 29 30 "github.com/waldiirawan/apm-agent-go/v2" 31 "github.com/waldiirawan/apm-agent-go/v2/apmtest" 32 "github.com/waldiirawan/apm-agent-go/v2/model" 33 ) 34 35 func TestContextStartSpanTransactionEnded(t *testing.T) { 36 tracer := apmtest.DiscardTracer 37 var wg sync.WaitGroup 38 for i := 0; i < 100; i++ { 39 wg.Add(1) 40 go func() { 41 defer wg.Done() 42 for i := 0; i < 1000; i++ { 43 tx := tracer.StartTransaction("name", "type") 44 ctx := apm.ContextWithTransaction(context.Background(), tx) 45 tx.End() 46 apm.CaptureError(ctx, errors.New("boom")).Send() 47 span, _ := apm.StartSpan(ctx, "name", "type") 48 assert.False(t, span.Dropped()) 49 span.End() 50 } 51 }() 52 } 53 tracer.Flush(nil) 54 wg.Wait() 55 } 56 57 func TestContextStartSpanSpanEnded(t *testing.T) { 58 tracer := apmtest.DiscardTracer 59 var wg sync.WaitGroup 60 for i := 0; i < 100; i++ { 61 wg.Add(1) 62 go func() { 63 defer wg.Done() 64 for i := 0; i < 1000; i++ { 65 tx := tracer.StartTransaction("name", "type") 66 ctx := apm.ContextWithTransaction(context.Background(), tx) 67 span1, ctx := apm.StartSpan(ctx, "name", "type") 68 span1.End() 69 apm.CaptureError(ctx, errors.New("boom")).Send() 70 span2, _ := apm.StartSpan(ctx, "name", "type") 71 assert.False(t, span2.Dropped()) 72 span2.End() 73 tx.End() 74 } 75 }() 76 } 77 tracer.Flush(nil) 78 wg.Wait() 79 } 80 81 func TestContextStartSpanOptions(t *testing.T) { 82 txTimestamp := time.Now().Add(-time.Hour) 83 tx, spans, _ := apmtest.WithTransactionOptions(apm.TransactionOptions{ 84 Start: txTimestamp, 85 }, func(ctx context.Context) { 86 span0, ctx := apm.StartSpanOptions(ctx, "span0", "type", apm.SpanOptions{ 87 Start: txTimestamp.Add(time.Minute), 88 }) 89 assert.False(t, span0.Dropped()) 90 defer span0.End() 91 92 // span1 should use span0 as its parent, as span0 has not been ended yet. 93 span1, ctx := apm.StartSpanOptions(ctx, "span1", "type", apm.SpanOptions{}) 94 assert.False(t, span1.Dropped()) 95 span1TraceContext := span1.TraceContext() 96 span1.End() 97 98 // span2 should not be dropped. The parent span in ctx should be 99 // completely disregarded, since Parent is specified in options. 100 span2, ctx := apm.StartSpanOptions(ctx, "span2", "type", apm.SpanOptions{ 101 Parent: span1TraceContext, 102 }) 103 assert.False(t, span2.Dropped()) 104 span2.End() 105 106 // span3 should not be dropped. Even though the parent in ctx has been ended, 107 // we still record the span to allow for fire-and-forget type patterns. 108 span3, ctx := apm.StartSpanOptions(ctx, "span3", "type", apm.SpanOptions{}) 109 assert.False(t, span3.Dropped()) 110 span3.End() 111 }) 112 113 require.Len(t, spans, 4) 114 assert.Equal(t, "span0", spans[3].Name) // span 0 ended last 115 assert.Equal(t, "span1", spans[0].Name) 116 assert.Equal(t, "span2", spans[1].Name) 117 assert.Equal(t, "span3", spans[2].Name) 118 119 assert.Equal(t, tx.ID, spans[3].ParentID) 120 assert.Equal(t, spans[3].ID, spans[0].ParentID) 121 assert.Equal(t, spans[0].ID, spans[1].ParentID) 122 assert.Equal(t, spans[1].ID, spans[2].ParentID) 123 124 span0Start := time.Time(tx.Timestamp).Add(time.Minute) 125 assert.Equal(t, model.Time(span0Start), spans[3].Timestamp) 126 } 127 128 func TestDetachedContext(t *testing.T) { 129 funcB := func(ctx context.Context) chan chan error { 130 chch := make(chan chan error) 131 go func() { 132 ch := <-chch 133 defer close(ch) 134 span, ctx := apm.StartSpan(ctx, "funcB", "custom") 135 defer span.End() 136 ch <- ctx.Err() 137 }() 138 return chch 139 } 140 141 funcA := func(ctx context.Context) chan chan error { 142 span, ctx := apm.StartSpan(ctx, "funcA", "custom") 143 defer span.End() 144 return funcB(apm.DetachedContext(ctx)) 145 } 146 147 tx, spans, _ := apmtest.WithTransaction(func(ctx context.Context) { 148 // Call funcA and immediately cancel its context after it returns. 149 ctx, cancel := context.WithCancel(ctx) 150 chch := funcA(ctx) 151 cancel() 152 153 // Sending a channel to the goroutine spawned by funcB will cause 154 // it to start a new span from the context passed by funcA. The 155 // "funcA" span in the context is ended, and the context of funcA 156 // is canceled; but the context of funcB is not canceled because 157 // it was passed a "detached context". 158 ch := make(chan error) 159 chch <- ch 160 err := <-ch 161 assert.NoError(t, err) 162 163 // wait for ch to be closed, at which point we know that funcB's 164 // span has ended. 165 <-ch 166 }) 167 require.Len(t, spans, 2) 168 169 assert.Equal(t, tx.ID, spans[0].ParentID) 170 assert.Equal(t, spans[0].ID, spans[1].ParentID) 171 for _, span := range spans { 172 assert.Equal(t, tx.ID, span.TransactionID) 173 assert.Equal(t, tx.TraceID, span.TraceID) 174 } 175 }