blob: e5a8d0ecea5e46f986d7be4dcd2d25e5bc1e4eb8 [file] [log] [blame]
// Licensed to Apache Software Foundation (ASF) under one or more contributor
// license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright
// ownership. Apache Software Foundation (ASF) licenses this file to you under
// the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package core
import (
"testing"
"time"
"github.com/stretchr/testify/assert"
agentv3 "skywalking.apache.org/repo/goapi/collect/language/agent/v3"
"github.com/apache/skywalking-go/plugins/core/reporter"
"github.com/apache/skywalking-go/plugins/core/tracing"
)
const (
sample = 1
traceID = "1f2d4bf47bf711eab794acde48001122"
parentSegmentID = "1e7c204a7bf711eab858acde48001122"
parentSpanID = 0
parentService = "service"
parentServiceInstance = "instance"
parentEndpoint = "/foo/bar"
addressUsedAtClient = "foo.svc:8787"
)
var header string
func init() {
scx := SpanContext{
Sample: sample,
TraceID: traceID,
ParentSegmentID: parentSegmentID,
ParentSpanID: parentSpanID,
ParentService: parentService,
ParentServiceInstance: parentServiceInstance,
ParentEndpoint: parentEndpoint,
AddressUsedAtClient: addressUsedAtClient,
}
header = scx.EncodeSW8()
}
type spanOperationTestCase struct {
operations []func(existingSpans []tracing.Span) (tracing.Span, error)
exceptedSpans []struct {
spanType SpanType
operationName string
parentSpanOpName string
peer string
}
}
func TestCreateSpanInSingleGoroutine(t *testing.T) {
defer ResetTracingContext()
validateSpanOperation(t, []spanOperationTestCase{
{
operations: []func(existingSpans []tracing.Span) (tracing.Span, error){
func(existingSpans []tracing.Span) (tracing.Span, error) {
return tracing.CreateEntrySpan("/entry", func(key string) (string, error) { return "", nil })
},
func(existingSpans []tracing.Span) (tracing.Span, error) {
return tracing.CreateLocalSpan("/local1")
},
func(existingSpans []tracing.Span) (tracing.Span, error) {
return tracing.CreateLocalSpan("/local1-1")
},
func(existingSpans []tracing.Span) (tracing.Span, error) {
return tracing.CreateExitSpan("/local1-1-exit", "localhost:8080", func(key, value string) error { return nil })
},
func(existingSpans []tracing.Span) (tracing.Span, error) {
existingSpans[3].End()
return nil, nil
},
func(existingSpans []tracing.Span) (tracing.Span, error) {
existingSpans[2].End()
return nil, nil
},
func(existingSpans []tracing.Span) (tracing.Span, error) {
return tracing.CreateExitSpan("/local1-exit", "localhost:8080", func(key, value string) error { return nil })
},
func(existingSpans []tracing.Span) (tracing.Span, error) {
existingSpans[4].End()
return nil, nil
},
func(existingSpans []tracing.Span) (tracing.Span, error) {
existingSpans[1].End()
return nil, nil
},
func(existingSpans []tracing.Span) (tracing.Span, error) {
return tracing.CreateExitSpan("/entry-exit", "localhost:8080", func(key, value string) error { return nil })
},
func(existingSpans []tracing.Span) (tracing.Span, error) {
existingSpans[5].End()
return nil, nil
},
func(existingSpans []tracing.Span) (tracing.Span, error) {
existingSpans[0].End()
return nil, nil
},
},
exceptedSpans: []struct {
spanType SpanType
operationName string
parentSpanOpName string
peer string
}{
{SpanTypeEntry, "/entry", "", ""},
{SpanTypeLocal, "/local1", "/entry", ""},
{SpanTypeLocal, "/local1-1", "/local1", ""},
{SpanTypeExit, "/local1-1-exit", "/local1-1", "localhost:8080"},
{SpanTypeExit, "/local1-exit", "/local1", "localhost:8080"},
{SpanTypeExit, "/entry-exit", "/entry", "localhost:8080"},
},
},
})
}
func TestCreateSpanInDifferenceGoroutine(t *testing.T) {
defer ResetTracingContext()
validateSpanOperation(t, []spanOperationTestCase{
{
operations: []func(existingSpans []tracing.Span) (tracing.Span, error){
func(existingSpans []tracing.Span) (tracing.Span, error) {
return tracing.CreateEntrySpan("/entry", func(key string) (string, error) { return "", nil })
},
func(existingSpans []tracing.Span) (tracing.Span, error) { // new goroutine
SetAsNewGoroutine()
return nil, nil
},
func(existingSpans []tracing.Span) (tracing.Span, error) {
return tracing.CreateLocalSpan("/local")
},
func(existingSpans []tracing.Span) (tracing.Span, error) { // new goroutine
SetAsNewGoroutine()
return nil, nil
},
func(existingSpans []tracing.Span) (tracing.Span, error) {
return tracing.CreateExitSpan("/local-exit", "localhost:8080", func(key, value string) error { return nil })
},
func(existingSpans []tracing.Span) (tracing.Span, error) {
existingSpans[2].End()
return nil, nil
},
func(existingSpans []tracing.Span) (tracing.Span, error) {
existingSpans[1].End()
return nil, nil
},
func(existingSpans []tracing.Span) (tracing.Span, error) {
existingSpans[0].End()
return nil, nil
},
},
exceptedSpans: []struct {
spanType SpanType
operationName string
parentSpanOpName string
peer string
}{
{SpanTypeEntry, "/entry", "", ""},
{SpanTypeLocal, "/local", "/entry", ""},
{SpanTypeExit, "/local-exit", "/local", "localhost:8080"},
},
},
})
}
func TestSpanContextWriting(t *testing.T) {
defer ResetTracingContext()
s, err := tracing.CreateEntrySpan("/entry", func(key string) (string, error) { return "", nil })
assert.NoError(t, err)
s.End()
s, err = tracing.CreateExitSpan("/exit", "localhost:8080", func(key, value string) error {
ctx := SpanContext{}
if key == Header {
assert.NoError(t, ctx.DecodeSW8(value))
}
if key == HeaderCorrelation {
assert.NoError(t, ctx.DecodeSW8Correlation(value))
}
return nil
})
assert.NoError(t, err)
s.End()
}
func TestSpanContextReading(t *testing.T) {
defer ResetTracingContext()
s, err := tracing.CreateEntrySpan("/entry", func(key string) (string, error) {
if key == Header {
return header, nil
}
return "", nil
})
assert.NoError(t, err)
s.End()
time.Sleep(time.Millisecond * 50)
spans := GetReportedSpans()
assert.Equal(t, 1, len(spans), "span count not correct")
tracingContext := spans[0].Context()
assert.Equal(t, traceID, tracingContext.GetTraceID(), "trace id not correct")
assert.Equal(t, 1, len(spans[0].Refs()), "refs not correct")
refCtx := spans[0].Refs()[0]
assert.Equal(t, traceID, refCtx.GetTraceID(), "ref trace id not correct")
assert.Equal(t, parentSegmentID, refCtx.GetParentSegmentID(), "ref segment id not correct")
assert.Equal(t, parentEndpoint, refCtx.GetParentEndpoint(), "ref endpoint not correct")
assert.Equal(t, int32(parentSpanID), refCtx.GetParentSpanID(), "ref span id not correct")
assert.Equal(t, parentService, refCtx.GetParentService(), "ref service not correct")
assert.Equal(t, parentServiceInstance, refCtx.GetParentServiceInstance(), "ref service instance not correct")
assert.Equal(t, parentEndpoint, refCtx.GetParentEndpoint(), "ref endpoint not correct")
}
func TestReporterDisconnect(t *testing.T) {
defer ResetTracingContext()
ReportConnectionStatus = reporter.ConnectionStatusDisconnect
s, err := tracing.CreateEntrySpan("/entry", func(key string) (string, error) {
return "", nil
})
assert.NoError(t, err)
assert.NotNil(t, s, "span should not be nil")
s.End()
time.Sleep(time.Millisecond * 50)
spans := GetReportedSpans()
assert.Equal(t, 0, len(spans), "should no span been collected")
}
func TestSpanOperation(t *testing.T) {
defer ResetTracingContext()
spanCreations := []func(op tracing.SpanOption) (tracing.Span, error){
func(op tracing.SpanOption) (tracing.Span, error) {
return tracing.CreateEntrySpan("test", func(headerKey string) (string, error) {
return "", nil
}, op)
},
func(op tracing.SpanOption) (tracing.Span, error) {
return tracing.CreateLocalSpan("test", op)
},
func(op tracing.SpanOption) (tracing.Span, error) {
return tracing.CreateExitSpan("test", "localhost:8080", func(headerKey, headerValue string) error {
return nil
}, op)
},
}
spanOptions := []struct {
op tracing.SpanOption
validate func(s *RootSegmentSpan) bool
}{
{tracing.WithLayer(tracing.SpanLayerHTTP), func(s *RootSegmentSpan) bool {
return s.DefaultSpan.Layer == agentv3.SpanLayer_Http
}},
{tracing.WithComponent(1), func(s *RootSegmentSpan) bool {
return s.DefaultSpan.ComponentID == 1
}},
{tracing.WithTag("test", "test1"), func(s *RootSegmentSpan) bool {
for _, k := range s.DefaultSpan.Tags {
if k.Key == "test" {
return k.Value == "test1"
}
}
return false
}},
}
for createInx, spanCreate := range spanCreations {
for _, op := range spanOptions {
create, err := spanCreate(op.op)
if err != nil {
assert.Nil(t, err, "create span error")
}
span := create.(*tracing.SpanWrapper).Span.(*RootSegmentSpan)
assert.Truef(t, op.validate(span), "span validation failed, create index: %d, option index: %d", createInx, op)
create.End()
}
}
}
func TestActiveSpan(t *testing.T) {
defer ResetTracingContext()
// active span in same goroutine
span, err := tracing.CreateEntrySpan("/entry", func(key string) (string, error) { return "", nil })
assert.NoError(t, err)
assert.Equal(t, span, tracing.ActiveSpan(), "active span not correct")
oldGLS := GetGLS()
// change goroutine
SetAsNewGoroutine()
assert.NotNil(t, tracing.ActiveSpan(), "active span should be nil when cross goroutine")
// change back
SetGLS(oldGLS)
span.End()
assert.Nil(t, tracing.ActiveSpan(), "active span not correct")
}
func TestRuntimeContext(t *testing.T) {
defer ResetTracingContext()
assert.Nilf(t, tracing.GetRuntimeContextValue("test"), "runtime context data should be nil")
tracing.SetRuntimeContextValue("test", "test")
assert.Equal(t, "test", tracing.GetRuntimeContextValue("test"), "runtime context data should be \"test\"")
// switch to the new goroutine
oldGLS := GetGLS()
SetAsNewGoroutine()
assert.Equal(t, "test", tracing.GetRuntimeContextValue("test"), "runtime context data should be \"test\"")
assert.Nilf(t, tracing.GetRuntimeContextValue("test1"), "runtime context data should be nil")
tracing.SetRuntimeContextValue("test1", "test1")
assert.Equal(t, "test1", tracing.GetRuntimeContextValue("test1"), "runtime context data should be \"test1\"")
// switch back to the old goroutine
SetGLS(oldGLS)
assert.Nilf(t, tracing.GetRuntimeContextValue("test1"), "runtime context data should be nil")
}
func validateSpanOperation(t *testing.T, cases []spanOperationTestCase) {
for _, tt := range cases {
spans := make([]tracing.Span, 0)
for i, op := range tt.operations {
span, err := op(spans)
assert.Nilf(t, err, "create span error, operation index: %d", i)
time.Sleep(time.Millisecond * 50)
if span != nil {
spans = append(spans, span)
}
}
time.Sleep(time.Millisecond * 100)
assert.Equal(t, len(tt.exceptedSpans), len(GetReportedSpans()), "span count not equal")
for i, exceptedSpan := range tt.exceptedSpans {
var span DefaultSpan
if i == 0 {
tmp, ok := GetReportedSpans()[len(GetReportedSpans())-1-i].(*RootSegmentSpan)
assert.True(t, ok, "first span is not root segment span")
span = tmp.DefaultSpan
} else {
found := false
for _, s := range GetReportedSpans() {
if s.OperationName() != exceptedSpan.operationName {
continue
}
tmp, ok := s.(*SegmentSpanImpl)
assert.Truef(t, ok, "span is not segment span, span index: %d", i)
span = tmp.DefaultSpan
found = true
break
}
assert.Truef(t, found, "span not found, span index: %d, name: %s", i, exceptedSpan.operationName)
}
assert.Equalf(t, exceptedSpan.spanType, span.SpanType, "span type not equal, span index: %d", i)
assert.Equalf(t, exceptedSpan.operationName, span.OperationName, "operation name not equal, span index: %d", i)
if exceptedSpan.parentSpanOpName != "" {
assert.Equalf(t, exceptedSpan.parentSpanOpName, span.Parent.GetOperationName(), "parent operation name not equal, span index: %d", i)
} else {
assert.Nilf(t, span.Parent, "parent span not nil, span index: %d", i)
}
if exceptedSpan.peer != "" {
assert.Equalf(t, exceptedSpan.peer, span.Peer, "span peer not equal, span index: %d", i)
} else {
assert.Truef(t, span.Peer == "", "span peer not empty, span index: %d", i)
}
assert.Greaterf(t, Millisecond(span.StartTime), int64(0), "start time not greater than 0, span index: %d", i)
assert.Greaterf(t, Millisecond(span.EndTime), Millisecond(span.StartTime), "end time not greater than 0, span indeX: %d", i)
}
Tracing.Reporter = NewStoreReporter()
}
}