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  }