mirror of
https://github.com/open-telemetry/opentelemetry-go.git
synced 2025-02-07 13:31:42 +02:00
Add inspectable test tracer (#353)
* Add inspectable test tracer
This adds a test Tracer implementation that tracks its active and ended
spans and uses a Span implementation that can be inspected (e.g., to see
what attributes have been set).
* Ensure test tracer can start spans concurrently
* Flip conditional logic to return early
* Remove duplicate test
* Fix file name casing
🤦
* Add comments to testtrace code
* Remove Link and AddLink methods from test Span
* Enable concurrently setting and getting test attrs
* Remove SetAttribute from test tracer
* Fix test
* Fix names post-rebase
This commit is contained in:
parent
afd9d5a0d4
commit
a9756528ba
28
api/trace/testtrace/event.go
Normal file
28
api/trace/testtrace/event.go
Normal file
@ -0,0 +1,28 @@
|
||||
// Copyright 2019, OpenTelemetry Authors
|
||||
//
|
||||
// Licensed 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 testtrace
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"go.opentelemetry.io/otel/api/core"
|
||||
)
|
||||
|
||||
// Event encapsulates the properties of calls to AddEvent or AddEventWithTimestamp.
|
||||
type Event struct {
|
||||
Timestamp time.Time
|
||||
Message string
|
||||
Attributes map[core.Key]core.Value
|
||||
}
|
73
api/trace/testtrace/generator.go
Normal file
73
api/trace/testtrace/generator.go
Normal file
@ -0,0 +1,73 @@
|
||||
// Copyright 2019, OpenTelemetry Authors
|
||||
//
|
||||
// Licensed 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 testtrace
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"sync"
|
||||
|
||||
"go.opentelemetry.io/otel/api/core"
|
||||
)
|
||||
|
||||
type Generator interface {
|
||||
TraceID() core.TraceID
|
||||
SpanID() core.SpanID
|
||||
}
|
||||
|
||||
var _ Generator = (*CountGenerator)(nil)
|
||||
|
||||
// CountGenerator is a simple Generator that can be used to create unique, albeit deterministic,
|
||||
// trace and span IDs.
|
||||
type CountGenerator struct {
|
||||
lock sync.Mutex
|
||||
traceIDHigh uint64
|
||||
traceIDLow uint64
|
||||
spanID uint64
|
||||
}
|
||||
|
||||
func NewCountGenerator() *CountGenerator {
|
||||
return &CountGenerator{}
|
||||
}
|
||||
|
||||
func (g *CountGenerator) TraceID() core.TraceID {
|
||||
g.lock.Lock()
|
||||
defer g.lock.Unlock()
|
||||
|
||||
if g.traceIDHigh == g.traceIDLow {
|
||||
g.traceIDHigh++
|
||||
} else {
|
||||
g.traceIDLow++
|
||||
}
|
||||
|
||||
var traceID core.TraceID
|
||||
|
||||
binary.BigEndian.PutUint64(traceID[0:8], g.traceIDLow)
|
||||
binary.BigEndian.PutUint64(traceID[8:], g.traceIDHigh)
|
||||
|
||||
return traceID
|
||||
}
|
||||
|
||||
func (g *CountGenerator) SpanID() core.SpanID {
|
||||
g.lock.Lock()
|
||||
defer g.lock.Unlock()
|
||||
|
||||
g.spanID++
|
||||
|
||||
var spanID core.SpanID
|
||||
|
||||
binary.BigEndian.PutUint64(spanID[:], g.spanID)
|
||||
|
||||
return spanID
|
||||
}
|
15
api/trace/testtrace/package.go
Normal file
15
api/trace/testtrace/package.go
Normal file
@ -0,0 +1,15 @@
|
||||
// Copyright 2019, OpenTelemetry Authors
|
||||
//
|
||||
// Licensed 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 testtrace // import "go.opentelemetry.io/otel/api/trace/testtrace"
|
212
api/trace/testtrace/span.go
Normal file
212
api/trace/testtrace/span.go
Normal file
@ -0,0 +1,212 @@
|
||||
// Copyright 2019, OpenTelemetry Authors
|
||||
//
|
||||
// Licensed 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 testtrace
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"google.golang.org/grpc/codes"
|
||||
|
||||
"go.opentelemetry.io/otel/api/core"
|
||||
"go.opentelemetry.io/otel/api/trace"
|
||||
)
|
||||
|
||||
var _ trace.Span = (*Span)(nil)
|
||||
|
||||
type Span struct {
|
||||
lock *sync.RWMutex
|
||||
tracer *Tracer
|
||||
spanContext core.SpanContext
|
||||
parentSpanID core.SpanID
|
||||
ended bool
|
||||
name string
|
||||
startTime time.Time
|
||||
endTime time.Time
|
||||
status codes.Code
|
||||
attributes map[core.Key]core.Value
|
||||
events []Event
|
||||
links map[core.SpanContext][]core.KeyValue
|
||||
}
|
||||
|
||||
func (s *Span) Tracer() trace.Tracer {
|
||||
return s.tracer
|
||||
}
|
||||
|
||||
func (s *Span) End(opts ...trace.EndOption) {
|
||||
s.lock.Lock()
|
||||
defer s.lock.Unlock()
|
||||
|
||||
if s.ended {
|
||||
return
|
||||
}
|
||||
|
||||
var c trace.EndConfig
|
||||
|
||||
for _, opt := range opts {
|
||||
opt(&c)
|
||||
}
|
||||
|
||||
s.endTime = time.Now()
|
||||
|
||||
if endTime := c.EndTime; !endTime.IsZero() {
|
||||
s.endTime = endTime
|
||||
}
|
||||
|
||||
s.ended = true
|
||||
}
|
||||
|
||||
func (s *Span) AddEvent(ctx context.Context, msg string, attrs ...core.KeyValue) {
|
||||
s.AddEventWithTimestamp(ctx, time.Now(), msg, attrs...)
|
||||
}
|
||||
|
||||
func (s *Span) AddEventWithTimestamp(ctx context.Context, timestamp time.Time, msg string, attrs ...core.KeyValue) {
|
||||
s.lock.Lock()
|
||||
defer s.lock.Unlock()
|
||||
|
||||
if s.ended {
|
||||
return
|
||||
}
|
||||
|
||||
attributes := make(map[core.Key]core.Value)
|
||||
|
||||
for _, attr := range attrs {
|
||||
attributes[attr.Key] = attr.Value
|
||||
}
|
||||
|
||||
s.events = append(s.events, Event{
|
||||
Timestamp: timestamp,
|
||||
Message: msg,
|
||||
Attributes: attributes,
|
||||
})
|
||||
}
|
||||
|
||||
func (s *Span) IsRecording() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (s *Span) SpanContext() core.SpanContext {
|
||||
return s.spanContext
|
||||
}
|
||||
|
||||
func (s *Span) SetStatus(status codes.Code) {
|
||||
s.lock.Lock()
|
||||
defer s.lock.Unlock()
|
||||
|
||||
if s.ended {
|
||||
return
|
||||
}
|
||||
|
||||
s.status = status
|
||||
}
|
||||
|
||||
func (s *Span) SetName(name string) {
|
||||
s.lock.Lock()
|
||||
defer s.lock.Unlock()
|
||||
|
||||
if s.ended {
|
||||
return
|
||||
}
|
||||
|
||||
s.name = name
|
||||
}
|
||||
|
||||
func (s *Span) SetAttributes(attrs ...core.KeyValue) {
|
||||
s.lock.Lock()
|
||||
defer s.lock.Unlock()
|
||||
|
||||
if s.ended {
|
||||
return
|
||||
}
|
||||
|
||||
for _, attr := range attrs {
|
||||
s.attributes[attr.Key] = attr.Value
|
||||
}
|
||||
}
|
||||
|
||||
// Name returns the name most recently set on the Span, either at or after creation time.
|
||||
// It cannot be change after End has been called on the Span.
|
||||
func (s *Span) Name() string {
|
||||
return s.name
|
||||
}
|
||||
|
||||
// ParentSpanID returns the SpanID of the parent Span.
|
||||
// If the Span is a root Span and therefore does not have a parent, the returned SpanID will be invalid
|
||||
// (i.e., it will contain all zeroes).
|
||||
func (s *Span) ParentSpanID() core.SpanID {
|
||||
return s.parentSpanID
|
||||
}
|
||||
|
||||
// Attributes returns the attributes set on the Span, either at or after creation time.
|
||||
// If the same attribute key was set multiple times, the last call will be used.
|
||||
// Attributes cannot be changed after End has been called on the Span.
|
||||
func (s *Span) Attributes() map[core.Key]core.Value {
|
||||
s.lock.RLock()
|
||||
defer s.lock.RUnlock()
|
||||
|
||||
attributes := make(map[core.Key]core.Value)
|
||||
|
||||
for k, v := range s.attributes {
|
||||
attributes[k] = v
|
||||
}
|
||||
|
||||
return attributes
|
||||
}
|
||||
|
||||
// Events returns the events set on the Span.
|
||||
// Events cannot be changed after End has been called on the Span.
|
||||
func (s *Span) Events() []Event {
|
||||
return s.events
|
||||
}
|
||||
|
||||
// Links returns the links set on the Span at creation time.
|
||||
// If multiple links for the same SpanContext were set, the last link will be used.
|
||||
func (s *Span) Links() map[core.SpanContext][]core.KeyValue {
|
||||
links := make(map[core.SpanContext][]core.KeyValue)
|
||||
|
||||
for sc, attributes := range s.links {
|
||||
links[sc] = append([]core.KeyValue{}, attributes...)
|
||||
}
|
||||
|
||||
return links
|
||||
}
|
||||
|
||||
// StartTime returns the time at which the Span was started.
|
||||
// This will be the wall-clock time unless a specific start time was provided.
|
||||
func (s *Span) StartTime() time.Time {
|
||||
return s.startTime
|
||||
}
|
||||
|
||||
// EndTime returns the time at which the Span was ended if at has been ended,
|
||||
// or false otherwise.
|
||||
// If the span has been ended, the returned time will be the wall-clock time
|
||||
// unless a specific end time was provided.
|
||||
func (s *Span) EndTime() (time.Time, bool) {
|
||||
return s.endTime, s.ended
|
||||
}
|
||||
|
||||
// Ended returns whether the Span has been ended,
|
||||
// i.e., whether End has been called at least once on the Span.
|
||||
func (s *Span) Ended() bool {
|
||||
return s.ended
|
||||
}
|
||||
|
||||
// Status returns the status most recently set on the Span,
|
||||
// or codes.OK if no status has been explicitly set.
|
||||
// It cannot be changed after End has been called on the Span.
|
||||
func (s *Span) Status() codes.Code {
|
||||
return s.status
|
||||
}
|
485
api/trace/testtrace/span_test.go
Normal file
485
api/trace/testtrace/span_test.go
Normal file
@ -0,0 +1,485 @@
|
||||
// Copyright 2019, OpenTelemetry Authors
|
||||
//
|
||||
// Licensed 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 testtrace_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"google.golang.org/grpc/codes"
|
||||
|
||||
"go.opentelemetry.io/otel/api/core"
|
||||
"go.opentelemetry.io/otel/api/trace"
|
||||
"go.opentelemetry.io/otel/api/trace/testtrace"
|
||||
"go.opentelemetry.io/otel/internal/matchers"
|
||||
)
|
||||
|
||||
func TestSpan(t *testing.T) {
|
||||
t.Run("#Tracer", func(t *testing.T) {
|
||||
t.Run("returns the tracer used to start the span", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
e := matchers.NewExpecter(t)
|
||||
|
||||
tracer := testtrace.NewTracer()
|
||||
_, subject := tracer.Start(context.Background(), "test")
|
||||
|
||||
e.Expect(subject.Tracer()).ToEqual(tracer)
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("#End", func(t *testing.T) {
|
||||
t.Run("ends the span", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
e := matchers.NewExpecter(t)
|
||||
|
||||
tracer := testtrace.NewTracer()
|
||||
_, span := tracer.Start(context.Background(), "test")
|
||||
|
||||
subject, ok := span.(*testtrace.Span)
|
||||
e.Expect(ok).ToBeTrue()
|
||||
|
||||
e.Expect(subject.Ended()).ToBeFalse()
|
||||
|
||||
_, ok = subject.EndTime()
|
||||
e.Expect(ok).ToBeFalse()
|
||||
|
||||
start := time.Now()
|
||||
|
||||
subject.End()
|
||||
|
||||
end := time.Now()
|
||||
|
||||
e.Expect(subject.Ended()).ToBeTrue()
|
||||
|
||||
endTime, ok := subject.EndTime()
|
||||
e.Expect(ok).ToBeTrue()
|
||||
|
||||
e.Expect(endTime).ToBeTemporally(matchers.AfterOrSameTime, start)
|
||||
e.Expect(endTime).ToBeTemporally(matchers.BeforeOrSameTime, end)
|
||||
})
|
||||
|
||||
t.Run("only takes effect the first time it is called", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
e := matchers.NewExpecter(t)
|
||||
|
||||
tracer := testtrace.NewTracer()
|
||||
_, span := tracer.Start(context.Background(), "test")
|
||||
|
||||
subject, ok := span.(*testtrace.Span)
|
||||
e.Expect(ok).ToBeTrue()
|
||||
|
||||
subject.End()
|
||||
|
||||
expectedEndTime, ok := subject.EndTime()
|
||||
e.Expect(ok).ToBeTrue()
|
||||
|
||||
subject.End()
|
||||
|
||||
endTime, ok := subject.EndTime()
|
||||
e.Expect(ok).ToBeTrue()
|
||||
e.Expect(endTime).ToEqual(expectedEndTime)
|
||||
})
|
||||
|
||||
t.Run("uses the time from WithEndTime", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
e := matchers.NewExpecter(t)
|
||||
|
||||
tracer := testtrace.NewTracer()
|
||||
_, span := tracer.Start(context.Background(), "test")
|
||||
|
||||
subject, ok := span.(*testtrace.Span)
|
||||
e.Expect(ok).ToBeTrue()
|
||||
|
||||
expectedEndTime := time.Now().AddDate(5, 0, 0)
|
||||
subject.End(trace.WithEndTime(expectedEndTime))
|
||||
|
||||
e.Expect(subject.Ended()).ToBeTrue()
|
||||
|
||||
endTime, ok := subject.EndTime()
|
||||
e.Expect(ok).ToBeTrue()
|
||||
|
||||
e.Expect(endTime).ToEqual(expectedEndTime)
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("#IsRecording", func(t *testing.T) {
|
||||
t.Run("returns true", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
e := matchers.NewExpecter(t)
|
||||
|
||||
tracer := testtrace.NewTracer()
|
||||
_, subject := tracer.Start(context.Background(), "test")
|
||||
|
||||
e.Expect(subject.IsRecording()).ToBeTrue()
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("#SpanContext", func(t *testing.T) {
|
||||
t.Run("returns a valid SpanContext", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
e := matchers.NewExpecter(t)
|
||||
|
||||
tracer := testtrace.NewTracer()
|
||||
_, subject := tracer.Start(context.Background(), "test")
|
||||
|
||||
e.Expect(subject.SpanContext().IsValid()).ToBeTrue()
|
||||
})
|
||||
|
||||
t.Run("returns a consistent value", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
e := matchers.NewExpecter(t)
|
||||
|
||||
tracer := testtrace.NewTracer()
|
||||
_, subject := tracer.Start(context.Background(), "test")
|
||||
|
||||
e.Expect(subject.SpanContext()).ToEqual(subject.SpanContext())
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("#Name", func(t *testing.T) {
|
||||
t.Run("returns the most recently set name on the span", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
e := matchers.NewExpecter(t)
|
||||
|
||||
tracer := testtrace.NewTracer()
|
||||
originalName := "test"
|
||||
_, span := tracer.Start(context.Background(), originalName)
|
||||
|
||||
subject, ok := span.(*testtrace.Span)
|
||||
e.Expect(ok).ToBeTrue()
|
||||
|
||||
e.Expect(subject.Name()).ToEqual(originalName)
|
||||
|
||||
subject.SetName("in-between")
|
||||
|
||||
newName := "new name"
|
||||
|
||||
subject.SetName(newName)
|
||||
|
||||
e.Expect(subject.Name()).ToEqual(newName)
|
||||
})
|
||||
|
||||
t.Run("cannot be changed after the span has been ended", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
e := matchers.NewExpecter(t)
|
||||
|
||||
tracer := testtrace.NewTracer()
|
||||
originalName := "test"
|
||||
_, span := tracer.Start(context.Background(), originalName)
|
||||
|
||||
subject, ok := span.(*testtrace.Span)
|
||||
e.Expect(ok).ToBeTrue()
|
||||
|
||||
subject.End()
|
||||
subject.SetName("new name")
|
||||
|
||||
e.Expect(subject.Name()).ToEqual(originalName)
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("#Attributes", func(t *testing.T) {
|
||||
t.Run("returns an empty map by default", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
e := matchers.NewExpecter(t)
|
||||
|
||||
tracer := testtrace.NewTracer()
|
||||
_, span := tracer.Start(context.Background(), "test")
|
||||
|
||||
subject, ok := span.(*testtrace.Span)
|
||||
e.Expect(ok).ToBeTrue()
|
||||
|
||||
e.Expect(subject.Attributes()).ToEqual(map[core.Key]core.Value{})
|
||||
})
|
||||
|
||||
t.Run("returns the most recently set attributes", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
e := matchers.NewExpecter(t)
|
||||
|
||||
tracer := testtrace.NewTracer()
|
||||
_, span := tracer.Start(context.Background(), "test")
|
||||
|
||||
subject, ok := span.(*testtrace.Span)
|
||||
e.Expect(ok).ToBeTrue()
|
||||
|
||||
attr1 := core.Key("key1").String("value1")
|
||||
attr2 := core.Key("key2").String("value2")
|
||||
attr3 := core.Key("key3").String("value3")
|
||||
unexpectedAttr := attr2.Key.String("unexpected")
|
||||
|
||||
subject.SetAttributes(attr1, unexpectedAttr, attr3)
|
||||
subject.SetAttributes(attr2)
|
||||
|
||||
attributes := subject.Attributes()
|
||||
|
||||
e.Expect(attributes[attr1.Key]).ToEqual(attr1.Value)
|
||||
e.Expect(attributes[attr2.Key]).ToEqual(attr2.Value)
|
||||
e.Expect(attributes[attr3.Key]).ToEqual(attr3.Value)
|
||||
})
|
||||
|
||||
t.Run("cannot be changed after the span has been ended", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
e := matchers.NewExpecter(t)
|
||||
|
||||
tracer := testtrace.NewTracer()
|
||||
_, span := tracer.Start(context.Background(), "test")
|
||||
|
||||
subject, ok := span.(*testtrace.Span)
|
||||
e.Expect(ok).ToBeTrue()
|
||||
|
||||
expectedAttr := core.Key("key").String("value")
|
||||
subject.SetAttributes(expectedAttr)
|
||||
subject.End()
|
||||
|
||||
unexpectedAttr := expectedAttr.Key.String("unexpected")
|
||||
subject.SetAttributes(unexpectedAttr)
|
||||
subject.End()
|
||||
|
||||
attributes := subject.Attributes()
|
||||
e.Expect(attributes[expectedAttr.Key]).ToEqual(expectedAttr.Value)
|
||||
})
|
||||
|
||||
t.Run("can be used concurrently with setter", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
e := matchers.NewExpecter(t)
|
||||
|
||||
tracer := testtrace.NewTracer()
|
||||
_, span := tracer.Start(context.Background(), "test")
|
||||
|
||||
subject, ok := span.(*testtrace.Span)
|
||||
e.Expect(ok).ToBeTrue()
|
||||
|
||||
var wg sync.WaitGroup
|
||||
|
||||
wg.Add(2)
|
||||
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
|
||||
subject.SetAttributes(core.Key("key").String("value"))
|
||||
}()
|
||||
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
|
||||
subject.Attributes()
|
||||
}()
|
||||
|
||||
wg.Wait()
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("#Links", func(t *testing.T) {
|
||||
t.Run("returns an empty map by default", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
e := matchers.NewExpecter(t)
|
||||
|
||||
tracer := testtrace.NewTracer()
|
||||
_, span := tracer.Start(context.Background(), "test")
|
||||
|
||||
subject, ok := span.(*testtrace.Span)
|
||||
e.Expect(ok).ToBeTrue()
|
||||
|
||||
e.Expect(len(subject.Links())).ToEqual(0)
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("#Events", func(t *testing.T) {
|
||||
t.Run("returns an empty slice by default", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
e := matchers.NewExpecter(t)
|
||||
|
||||
tracer := testtrace.NewTracer()
|
||||
_, span := tracer.Start(context.Background(), "test")
|
||||
|
||||
subject, ok := span.(*testtrace.Span)
|
||||
e.Expect(ok).ToBeTrue()
|
||||
|
||||
e.Expect(len(subject.Events())).ToEqual(0)
|
||||
})
|
||||
|
||||
t.Run("returns all of the added events", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
e := matchers.NewExpecter(t)
|
||||
|
||||
tracer := testtrace.NewTracer()
|
||||
_, span := tracer.Start(context.Background(), "test")
|
||||
|
||||
subject, ok := span.(*testtrace.Span)
|
||||
e.Expect(ok).ToBeTrue()
|
||||
|
||||
event1Msg := "event1"
|
||||
event1Attributes := []core.KeyValue{
|
||||
core.Key("event1Attr1").String("foo"),
|
||||
core.Key("event1Attr2").String("bar"),
|
||||
}
|
||||
|
||||
event1Start := time.Now()
|
||||
subject.AddEvent(context.Background(), event1Msg, event1Attributes...)
|
||||
event1End := time.Now()
|
||||
|
||||
event2Timestamp := time.Now().AddDate(5, 0, 0)
|
||||
event2Msg := "event1"
|
||||
event2Attributes := []core.KeyValue{
|
||||
core.Key("event2Attr").String("abc"),
|
||||
}
|
||||
|
||||
subject.AddEventWithTimestamp(context.Background(), event2Timestamp, event2Msg, event2Attributes...)
|
||||
|
||||
events := subject.Events()
|
||||
|
||||
e.Expect(len(events)).ToEqual(2)
|
||||
|
||||
event1 := events[0]
|
||||
|
||||
e.Expect(event1.Timestamp).ToBeTemporally(matchers.AfterOrSameTime, event1Start)
|
||||
e.Expect(event1.Timestamp).ToBeTemporally(matchers.BeforeOrSameTime, event1End)
|
||||
e.Expect(event1.Message).ToEqual(event1Msg)
|
||||
|
||||
for _, attr := range event1Attributes {
|
||||
e.Expect(event1.Attributes[attr.Key]).ToEqual(attr.Value)
|
||||
}
|
||||
|
||||
event2 := events[1]
|
||||
|
||||
e.Expect(event2.Timestamp).ToEqual(event2Timestamp)
|
||||
e.Expect(event2.Message).ToEqual(event2Msg)
|
||||
|
||||
for _, attr := range event2Attributes {
|
||||
e.Expect(event2.Attributes[attr.Key]).ToEqual(attr.Value)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("cannot be changed after the span has been ended", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
e := matchers.NewExpecter(t)
|
||||
|
||||
tracer := testtrace.NewTracer()
|
||||
_, span := tracer.Start(context.Background(), "test")
|
||||
|
||||
subject, ok := span.(*testtrace.Span)
|
||||
e.Expect(ok).ToBeTrue()
|
||||
|
||||
subject.AddEvent(context.Background(), "test")
|
||||
|
||||
e.Expect(len(subject.Events())).ToEqual(1)
|
||||
|
||||
expectedEvent := subject.Events()[0]
|
||||
|
||||
subject.End()
|
||||
subject.AddEvent(context.Background(), "should not occur")
|
||||
|
||||
e.Expect(len(subject.Events())).ToEqual(1)
|
||||
e.Expect(subject.Events()[0]).ToEqual(expectedEvent)
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("#Status", func(t *testing.T) {
|
||||
t.Run("defaults to OK", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
e := matchers.NewExpecter(t)
|
||||
|
||||
tracer := testtrace.NewTracer()
|
||||
_, span := tracer.Start(context.Background(), "test")
|
||||
|
||||
subject, ok := span.(*testtrace.Span)
|
||||
e.Expect(ok).ToBeTrue()
|
||||
|
||||
e.Expect(subject.Status()).ToEqual(codes.OK)
|
||||
|
||||
subject.End()
|
||||
|
||||
e.Expect(subject.Status()).ToEqual(codes.OK)
|
||||
})
|
||||
|
||||
statuses := []codes.Code{
|
||||
codes.OK,
|
||||
codes.Canceled,
|
||||
codes.Unknown,
|
||||
codes.InvalidArgument,
|
||||
codes.DeadlineExceeded,
|
||||
codes.NotFound,
|
||||
codes.AlreadyExists,
|
||||
codes.PermissionDenied,
|
||||
codes.ResourceExhausted,
|
||||
codes.FailedPrecondition,
|
||||
codes.Aborted,
|
||||
codes.OutOfRange,
|
||||
codes.Unimplemented,
|
||||
codes.Internal,
|
||||
codes.Unavailable,
|
||||
codes.DataLoss,
|
||||
codes.Unauthenticated,
|
||||
}
|
||||
|
||||
for _, status := range statuses {
|
||||
t.Run("returns the most recently set status on the span", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
e := matchers.NewExpecter(t)
|
||||
|
||||
tracer := testtrace.NewTracer()
|
||||
_, span := tracer.Start(context.Background(), "test")
|
||||
|
||||
subject, ok := span.(*testtrace.Span)
|
||||
e.Expect(ok).ToBeTrue()
|
||||
|
||||
subject.SetStatus(codes.OK)
|
||||
subject.SetStatus(status)
|
||||
|
||||
e.Expect(subject.Status()).ToEqual(status)
|
||||
})
|
||||
|
||||
t.Run("cannot be changed after the span has been ended", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
e := matchers.NewExpecter(t)
|
||||
|
||||
tracer := testtrace.NewTracer()
|
||||
_, span := tracer.Start(context.Background(), "test")
|
||||
|
||||
subject, ok := span.(*testtrace.Span)
|
||||
e.Expect(ok).ToBeTrue()
|
||||
|
||||
originalStatus := codes.OK
|
||||
|
||||
subject.SetStatus(originalStatus)
|
||||
subject.End()
|
||||
subject.SetStatus(status)
|
||||
|
||||
e.Expect(subject.Status()).ToEqual(originalStatus)
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
143
api/trace/testtrace/tracer.go
Normal file
143
api/trace/testtrace/tracer.go
Normal file
@ -0,0 +1,143 @@
|
||||
// Copyright 2019, OpenTelemetry Authors
|
||||
//
|
||||
// Licensed 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 testtrace
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"go.opentelemetry.io/otel/api/core"
|
||||
"go.opentelemetry.io/otel/api/trace"
|
||||
)
|
||||
|
||||
var _ trace.Tracer = (*Tracer)(nil)
|
||||
|
||||
// Tracer is a type of OpenTelemetry Tracer that tracks both active and ended spans,
|
||||
// and which creates Spans that may be inspected to see what data has been set on them.
|
||||
type Tracer struct {
|
||||
lock *sync.RWMutex
|
||||
generator Generator
|
||||
spans []*Span
|
||||
}
|
||||
|
||||
func NewTracer(opts ...TracerOption) *Tracer {
|
||||
c := newTracerConfig(opts...)
|
||||
|
||||
return &Tracer{
|
||||
lock: &sync.RWMutex{},
|
||||
generator: c.generator,
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Tracer) Start(ctx context.Context, name string, opts ...trace.StartOption) (context.Context, trace.Span) {
|
||||
var c trace.StartConfig
|
||||
|
||||
for _, opt := range opts {
|
||||
opt(&c)
|
||||
}
|
||||
|
||||
var traceID core.TraceID
|
||||
var parentSpanID core.SpanID
|
||||
|
||||
if parentSpanContext := c.Relation.SpanContext; parentSpanContext.IsValid() {
|
||||
traceID = parentSpanContext.TraceID
|
||||
parentSpanID = parentSpanContext.SpanID
|
||||
} else if parentSpanContext := trace.CurrentSpan(ctx).SpanContext(); parentSpanContext.IsValid() {
|
||||
traceID = parentSpanContext.TraceID
|
||||
parentSpanID = parentSpanContext.SpanID
|
||||
} else {
|
||||
traceID = t.generator.TraceID()
|
||||
}
|
||||
|
||||
spanID := t.generator.SpanID()
|
||||
|
||||
startTime := time.Now()
|
||||
|
||||
if st := c.StartTime; !st.IsZero() {
|
||||
startTime = st
|
||||
}
|
||||
|
||||
span := &Span{
|
||||
lock: &sync.RWMutex{},
|
||||
tracer: t,
|
||||
startTime: startTime,
|
||||
spanContext: core.SpanContext{
|
||||
TraceID: traceID,
|
||||
SpanID: spanID,
|
||||
},
|
||||
parentSpanID: parentSpanID,
|
||||
attributes: make(map[core.Key]core.Value),
|
||||
links: make(map[core.SpanContext][]core.KeyValue),
|
||||
}
|
||||
|
||||
span.SetName(name)
|
||||
span.SetAttributes(c.Attributes...)
|
||||
|
||||
for _, link := range c.Links {
|
||||
span.links[link.SpanContext] = link.Attributes
|
||||
}
|
||||
|
||||
t.lock.Lock()
|
||||
|
||||
t.spans = append(t.spans, span)
|
||||
|
||||
t.lock.Unlock()
|
||||
|
||||
return trace.SetCurrentSpan(ctx, span), span
|
||||
}
|
||||
|
||||
func (t *Tracer) WithSpan(ctx context.Context, name string, body func(ctx context.Context) error) error {
|
||||
ctx, _ = t.Start(ctx, name)
|
||||
|
||||
return body(ctx)
|
||||
}
|
||||
|
||||
// Spans returns the list of current and ended Spans started via the Tracer.
|
||||
func (t *Tracer) Spans() []*Span {
|
||||
t.lock.RLock()
|
||||
defer t.lock.RUnlock()
|
||||
|
||||
return append([]*Span{}, t.spans...)
|
||||
}
|
||||
|
||||
// TracerOption enables configuration of a new Tracer.
|
||||
type TracerOption func(*tracerConfig)
|
||||
|
||||
// TracerWithGenerator enables customization of the Generator that the Tracer will use
|
||||
// to create new trace and span IDs.
|
||||
// By default, new Tracers will use the CountGenerator.
|
||||
func TracerWithGenerator(generator Generator) TracerOption {
|
||||
return func(c *tracerConfig) {
|
||||
c.generator = generator
|
||||
}
|
||||
}
|
||||
|
||||
type tracerConfig struct {
|
||||
generator Generator
|
||||
}
|
||||
|
||||
func newTracerConfig(opts ...TracerOption) tracerConfig {
|
||||
var c tracerConfig
|
||||
defaultOpts := []TracerOption{
|
||||
TracerWithGenerator(NewCountGenerator()),
|
||||
}
|
||||
|
||||
for _, opt := range append(defaultOpts, opts...) {
|
||||
opt(&c)
|
||||
}
|
||||
|
||||
return c
|
||||
}
|
296
api/trace/testtrace/tracer_test.go
Normal file
296
api/trace/testtrace/tracer_test.go
Normal file
@ -0,0 +1,296 @@
|
||||
// Copyright 2019, OpenTelemetry Authors
|
||||
//
|
||||
// Licensed 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 testtrace_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"go.opentelemetry.io/otel/api/core"
|
||||
"go.opentelemetry.io/otel/api/testharness"
|
||||
"go.opentelemetry.io/otel/api/trace"
|
||||
"go.opentelemetry.io/otel/api/trace/testtrace"
|
||||
"go.opentelemetry.io/otel/internal/matchers"
|
||||
)
|
||||
|
||||
func TestTracer(t *testing.T) {
|
||||
testharness.NewHarness(t).TestTracer(func() trace.Tracer {
|
||||
return testtrace.NewTracer()
|
||||
})
|
||||
|
||||
t.Run("#Start", func(t *testing.T) {
|
||||
testTracedSpan(t, func(tracer trace.Tracer, name string) (trace.Span, error) {
|
||||
_, span := tracer.Start(context.Background(), name)
|
||||
|
||||
return span, nil
|
||||
})
|
||||
|
||||
t.Run("uses the start time from WithStartTime", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
e := matchers.NewExpecter(t)
|
||||
|
||||
expectedStartTime := time.Now().AddDate(5, 0, 0)
|
||||
|
||||
subject := testtrace.NewTracer()
|
||||
_, span := subject.Start(context.Background(), "test", trace.WithStartTime(expectedStartTime))
|
||||
|
||||
testSpan, ok := span.(*testtrace.Span)
|
||||
e.Expect(ok).ToBeTrue()
|
||||
|
||||
e.Expect(testSpan.StartTime()).ToEqual(expectedStartTime)
|
||||
})
|
||||
|
||||
t.Run("uses the attributes from WithAttributes", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
e := matchers.NewExpecter(t)
|
||||
|
||||
attr1 := core.Key("a").String("1")
|
||||
attr2 := core.Key("b").String("2")
|
||||
|
||||
subject := testtrace.NewTracer()
|
||||
_, span := subject.Start(context.Background(), "test", trace.WithAttributes(attr1, attr2))
|
||||
|
||||
testSpan, ok := span.(*testtrace.Span)
|
||||
e.Expect(ok).ToBeTrue()
|
||||
|
||||
attributes := testSpan.Attributes()
|
||||
e.Expect(attributes[attr1.Key]).ToEqual(attr1.Value)
|
||||
e.Expect(attributes[attr2.Key]).ToEqual(attr2.Value)
|
||||
})
|
||||
|
||||
t.Run("uses the parent's span context from ChildOf", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
e := matchers.NewExpecter(t)
|
||||
|
||||
subject := testtrace.NewTracer()
|
||||
|
||||
_, parent := subject.Start(context.Background(), "parent")
|
||||
parentSpanContext := parent.SpanContext()
|
||||
|
||||
_, span := subject.Start(context.Background(), "child", trace.ChildOf(parentSpanContext))
|
||||
|
||||
testSpan, ok := span.(*testtrace.Span)
|
||||
e.Expect(ok).ToBeTrue()
|
||||
|
||||
childSpanContext := testSpan.SpanContext()
|
||||
e.Expect(childSpanContext.TraceID).ToEqual(parentSpanContext.TraceID)
|
||||
e.Expect(childSpanContext.SpanID).NotToEqual(parentSpanContext.SpanID)
|
||||
e.Expect(testSpan.ParentSpanID()).ToEqual(parentSpanContext.SpanID)
|
||||
})
|
||||
|
||||
t.Run("defers to ChildOf if the provided context also contains a parent span", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
e := matchers.NewExpecter(t)
|
||||
|
||||
subject := testtrace.NewTracer()
|
||||
|
||||
_, parent := subject.Start(context.Background(), "parent")
|
||||
parentSpanContext := parent.SpanContext()
|
||||
|
||||
ctx, _ := subject.Start(context.Background(), "should be ignored")
|
||||
_, span := subject.Start(ctx, "child", trace.ChildOf(parentSpanContext))
|
||||
|
||||
testSpan, ok := span.(*testtrace.Span)
|
||||
e.Expect(ok).ToBeTrue()
|
||||
|
||||
childSpanContext := testSpan.SpanContext()
|
||||
e.Expect(childSpanContext.TraceID).ToEqual(parentSpanContext.TraceID)
|
||||
e.Expect(childSpanContext.SpanID).NotToEqual(parentSpanContext.SpanID)
|
||||
e.Expect(testSpan.ParentSpanID()).ToEqual(parentSpanContext.SpanID)
|
||||
})
|
||||
|
||||
t.Run("uses the parent's span context from FollowsFrom", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
e := matchers.NewExpecter(t)
|
||||
|
||||
subject := testtrace.NewTracer()
|
||||
|
||||
_, parent := subject.Start(context.Background(), "parent")
|
||||
parentSpanContext := parent.SpanContext()
|
||||
|
||||
_, span := subject.Start(context.Background(), "child", trace.FollowsFrom(parentSpanContext))
|
||||
|
||||
testSpan, ok := span.(*testtrace.Span)
|
||||
e.Expect(ok).ToBeTrue()
|
||||
|
||||
childSpanContext := testSpan.SpanContext()
|
||||
e.Expect(childSpanContext.TraceID).ToEqual(parentSpanContext.TraceID)
|
||||
e.Expect(childSpanContext.SpanID).NotToEqual(parentSpanContext.SpanID)
|
||||
e.Expect(testSpan.ParentSpanID()).ToEqual(parentSpanContext.SpanID)
|
||||
})
|
||||
|
||||
t.Run("defers to FollowsFrom if the provided context also contains a parent span", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
e := matchers.NewExpecter(t)
|
||||
|
||||
subject := testtrace.NewTracer()
|
||||
|
||||
_, parent := subject.Start(context.Background(), "parent")
|
||||
parentSpanContext := parent.SpanContext()
|
||||
|
||||
ctx, _ := subject.Start(context.Background(), "should be ignored")
|
||||
_, span := subject.Start(ctx, "child", trace.FollowsFrom(parentSpanContext))
|
||||
|
||||
testSpan, ok := span.(*testtrace.Span)
|
||||
e.Expect(ok).ToBeTrue()
|
||||
|
||||
childSpanContext := testSpan.SpanContext()
|
||||
e.Expect(childSpanContext.TraceID).ToEqual(parentSpanContext.TraceID)
|
||||
e.Expect(childSpanContext.SpanID).NotToEqual(parentSpanContext.SpanID)
|
||||
e.Expect(testSpan.ParentSpanID()).ToEqual(parentSpanContext.SpanID)
|
||||
})
|
||||
|
||||
t.Run("uses the links provided through LinkedTo", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
e := matchers.NewExpecter(t)
|
||||
|
||||
subject := testtrace.NewTracer()
|
||||
|
||||
_, span := subject.Start(context.Background(), "link1")
|
||||
link1 := trace.Link{
|
||||
SpanContext: span.SpanContext(),
|
||||
Attributes: []core.KeyValue{
|
||||
core.Key("a").String("1"),
|
||||
},
|
||||
}
|
||||
|
||||
_, span = subject.Start(context.Background(), "link2")
|
||||
link2 := trace.Link{
|
||||
SpanContext: span.SpanContext(),
|
||||
Attributes: []core.KeyValue{
|
||||
core.Key("b").String("2"),
|
||||
},
|
||||
}
|
||||
|
||||
_, span = subject.Start(context.Background(), "test", trace.LinkedTo(link1.SpanContext, link1.Attributes...), trace.LinkedTo(link2.SpanContext, link2.Attributes...))
|
||||
|
||||
testSpan, ok := span.(*testtrace.Span)
|
||||
e.Expect(ok).ToBeTrue()
|
||||
|
||||
links := testSpan.Links()
|
||||
e.Expect(links[link1.SpanContext]).ToEqual(link1.Attributes)
|
||||
e.Expect(links[link2.SpanContext]).ToEqual(link2.Attributes)
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("#WithSpan", func(t *testing.T) {
|
||||
testTracedSpan(t, func(tracer trace.Tracer, name string) (trace.Span, error) {
|
||||
var span trace.Span
|
||||
|
||||
err := tracer.WithSpan(context.Background(), name, func(ctx context.Context) error {
|
||||
span = trace.CurrentSpan(ctx)
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
return span, err
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func testTracedSpan(t *testing.T, fn func(tracer trace.Tracer, name string) (trace.Span, error)) {
|
||||
t.Run("starts a span with the expected name", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
e := matchers.NewExpecter(t)
|
||||
|
||||
subject := testtrace.NewTracer()
|
||||
|
||||
expectedName := "test name"
|
||||
span, err := fn(subject, expectedName)
|
||||
|
||||
e.Expect(err).ToBeNil()
|
||||
|
||||
testSpan, ok := span.(*testtrace.Span)
|
||||
e.Expect(ok).ToBeTrue()
|
||||
|
||||
e.Expect(testSpan.Name()).ToEqual(expectedName)
|
||||
})
|
||||
|
||||
t.Run("uses the current time as the start time", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
e := matchers.NewExpecter(t)
|
||||
|
||||
subject := testtrace.NewTracer()
|
||||
|
||||
start := time.Now()
|
||||
span, err := fn(subject, "test")
|
||||
end := time.Now()
|
||||
|
||||
e.Expect(err).ToBeNil()
|
||||
|
||||
testSpan, ok := span.(*testtrace.Span)
|
||||
e.Expect(ok).ToBeTrue()
|
||||
|
||||
e.Expect(testSpan.StartTime()).ToBeTemporally(matchers.AfterOrSameTime, start)
|
||||
e.Expect(testSpan.StartTime()).ToBeTemporally(matchers.BeforeOrSameTime, end)
|
||||
})
|
||||
|
||||
t.Run("appends the span to the list of Spans", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
e := matchers.NewExpecter(t)
|
||||
|
||||
subject := testtrace.NewTracer()
|
||||
subject.Start(context.Background(), "span1")
|
||||
|
||||
e.Expect(len(subject.Spans())).ToEqual(1)
|
||||
|
||||
span, err := fn(subject, "span2")
|
||||
e.Expect(err).ToBeNil()
|
||||
|
||||
spans := subject.Spans()
|
||||
|
||||
e.Expect(len(spans)).ToEqual(2)
|
||||
e.Expect(spans[1]).ToEqual(span)
|
||||
})
|
||||
|
||||
t.Run("can be run concurrently with another call", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
e := matchers.NewExpecter(t)
|
||||
|
||||
subject := testtrace.NewTracer()
|
||||
|
||||
numSpans := 2
|
||||
|
||||
var wg sync.WaitGroup
|
||||
|
||||
wg.Add(numSpans)
|
||||
|
||||
for i := 0; i < numSpans; i++ {
|
||||
go func() {
|
||||
_, err := fn(subject, "test")
|
||||
e.Expect(err).ToBeNil()
|
||||
|
||||
wg.Done()
|
||||
}()
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
|
||||
e.Expect(len(subject.Spans())).ToEqual(numSpans)
|
||||
})
|
||||
}
|
@ -15,8 +15,17 @@
|
||||
package matchers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"runtime/debug"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
stackTracePruneRE = regexp.MustCompile(`runtime\/debug|testing|internal\/matchers`)
|
||||
)
|
||||
|
||||
type Expectation struct {
|
||||
@ -28,7 +37,7 @@ func (e *Expectation) ToEqual(expected interface{}) {
|
||||
e.verifyExpectedNotNil(expected)
|
||||
|
||||
if !reflect.DeepEqual(e.actual, expected) {
|
||||
e.t.Fatalf("Expected\n\t%v\nto equal\n\t%v\n", e.actual, expected)
|
||||
e.fail(fmt.Sprintf("Expected\n\t%v\nto equal\n\t%v", e.actual, expected))
|
||||
}
|
||||
}
|
||||
|
||||
@ -36,19 +45,19 @@ func (e *Expectation) NotToEqual(expected interface{}) {
|
||||
e.verifyExpectedNotNil(expected)
|
||||
|
||||
if reflect.DeepEqual(e.actual, expected) {
|
||||
e.t.Fatalf("Expected\n\t%v\nnot to equal\n\t%v\n", e.actual, expected)
|
||||
e.fail(fmt.Sprintf("Expected\n\t%v\nnot to equal\n\t%v", e.actual, expected))
|
||||
}
|
||||
}
|
||||
|
||||
func (e *Expectation) ToBeNil() {
|
||||
if e.actual != nil {
|
||||
e.t.Fatalf("Expected\n\t%v\nto be nil\n", e.actual)
|
||||
e.fail(fmt.Sprintf("Expected\n\t%v\nto be nil", e.actual))
|
||||
}
|
||||
}
|
||||
|
||||
func (e *Expectation) NotToBeNil() {
|
||||
if e.actual == nil {
|
||||
e.t.Fatalf("Expected\n\t%v\nnot to be nil\n", e.actual)
|
||||
e.fail(fmt.Sprintf("Expected\n\t%v\nnot to be nil", e.actual))
|
||||
}
|
||||
}
|
||||
|
||||
@ -56,10 +65,10 @@ func (e *Expectation) ToBeTrue() {
|
||||
switch a := e.actual.(type) {
|
||||
case bool:
|
||||
if e.actual == false {
|
||||
e.t.Fatalf("Expected\n\t%v\nto be true\n", e.actual)
|
||||
e.fail(fmt.Sprintf("Expected\n\t%v\nto be true", e.actual))
|
||||
}
|
||||
default:
|
||||
e.t.Fatalf("Cannot check if non-bool value\n\t%v\nis truthy\n", a)
|
||||
e.fail(fmt.Sprintf("Cannot check if non-bool value\n\t%v\nis truthy", a))
|
||||
}
|
||||
}
|
||||
|
||||
@ -67,10 +76,10 @@ func (e *Expectation) ToBeFalse() {
|
||||
switch a := e.actual.(type) {
|
||||
case bool:
|
||||
if e.actual == true {
|
||||
e.t.Fatalf("Expected\n\t%v\nto be false\n", e.actual)
|
||||
e.fail(fmt.Sprintf("Expected\n\t%v\nto be false", e.actual))
|
||||
}
|
||||
default:
|
||||
e.t.Fatalf("Cannot check if non-bool value\n\t%v\nis truthy\n", a)
|
||||
e.fail(fmt.Sprintf("Cannot check if non-bool value\n\t%v\nis truthy", a))
|
||||
}
|
||||
}
|
||||
|
||||
@ -78,10 +87,10 @@ func (e *Expectation) ToSucceed() {
|
||||
switch actual := e.actual.(type) {
|
||||
case error:
|
||||
if actual != nil {
|
||||
e.t.Fatalf("Expected error\n\t%v\nto have succeeded\n", actual)
|
||||
e.fail(fmt.Sprintf("Expected error\n\t%v\nto have succeeded", actual))
|
||||
}
|
||||
default:
|
||||
e.t.Fatalf("Cannot check if non-error value\n\t%v\nsucceeded\n", actual)
|
||||
e.fail(fmt.Sprintf("Cannot check if non-error value\n\t%v\nsucceeded", actual))
|
||||
}
|
||||
}
|
||||
|
||||
@ -90,25 +99,191 @@ func (e *Expectation) ToMatchError(expected interface{}) {
|
||||
|
||||
actual, ok := e.actual.(error)
|
||||
if !ok {
|
||||
e.t.Fatalf("Cannot check if non-error value\n\t%v\nmatches error\n", e.actual)
|
||||
e.fail(fmt.Sprintf("Cannot check if non-error value\n\t%v\nmatches error", e.actual))
|
||||
}
|
||||
|
||||
switch expected := expected.(type) {
|
||||
case error:
|
||||
if !reflect.DeepEqual(actual, expected) {
|
||||
e.t.Fatalf("Expected\n\t%v\nto match error\n\t%v\n", actual, expected)
|
||||
e.fail(fmt.Sprintf("Expected\n\t%v\nto match error\n\t%v", actual, expected))
|
||||
}
|
||||
case string:
|
||||
if actual.Error() != expected {
|
||||
e.t.Fatalf("Expected\n\t%v\nto match error\n\t%v\n", actual, expected)
|
||||
e.fail(fmt.Sprintf("Expected\n\t%v\nto match error\n\t%v", actual, expected))
|
||||
}
|
||||
default:
|
||||
e.t.Fatalf("Cannot match\n\t%v\nagainst non-error\n\t%v\n", actual, expected)
|
||||
e.fail(fmt.Sprintf("Cannot match\n\t%v\nagainst non-error\n\t%v", actual, expected))
|
||||
}
|
||||
}
|
||||
|
||||
func (e *Expectation) ToContain(expected interface{}) {
|
||||
actualValue := reflect.ValueOf(e.actual)
|
||||
actualKind := actualValue.Kind()
|
||||
|
||||
switch actualKind {
|
||||
case reflect.Array, reflect.Slice:
|
||||
default:
|
||||
e.fail(fmt.Sprintf("Expected\n\t%v\nto be an array", e.actual))
|
||||
return
|
||||
}
|
||||
|
||||
expectedValue := reflect.ValueOf(expected)
|
||||
expectedKind := expectedValue.Kind()
|
||||
|
||||
switch expectedKind {
|
||||
case reflect.Array, reflect.Slice:
|
||||
default:
|
||||
expectedValue = reflect.ValueOf([]interface{}{expected})
|
||||
}
|
||||
|
||||
for i := 0; i < expectedValue.Len(); i++ {
|
||||
var contained bool
|
||||
expectedElem := expectedValue.Index(i).Interface()
|
||||
|
||||
for j := 0; j < actualValue.Len(); j++ {
|
||||
if reflect.DeepEqual(actualValue.Index(j).Interface(), expectedElem) {
|
||||
contained = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !contained {
|
||||
e.fail(fmt.Sprintf("Expected\n\t%v\nto contain\n\t%v", e.actual, expectedElem))
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (e *Expectation) NotToContain(expected interface{}) {
|
||||
actualValue := reflect.ValueOf(e.actual)
|
||||
actualKind := actualValue.Kind()
|
||||
|
||||
switch actualKind {
|
||||
case reflect.Array, reflect.Slice:
|
||||
default:
|
||||
e.fail(fmt.Sprintf("Expected\n\t%v\nto be an array", e.actual))
|
||||
return
|
||||
}
|
||||
|
||||
expectedValue := reflect.ValueOf(expected)
|
||||
expectedKind := expectedValue.Kind()
|
||||
|
||||
switch expectedKind {
|
||||
case reflect.Array, reflect.Slice:
|
||||
default:
|
||||
expectedValue = reflect.ValueOf([]interface{}{expected})
|
||||
}
|
||||
|
||||
for i := 0; i < expectedValue.Len(); i++ {
|
||||
expectedElem := expectedValue.Index(i).Interface()
|
||||
|
||||
for j := 0; j < actualValue.Len(); j++ {
|
||||
if reflect.DeepEqual(actualValue.Index(j).Interface(), expectedElem) {
|
||||
e.fail(fmt.Sprintf("Expected\n\t%v\nnot to contain\n\t%v", e.actual, expectedElem))
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (e *Expectation) ToMatchInAnyOrder(expected interface{}) {
|
||||
expectedValue := reflect.ValueOf(expected)
|
||||
expectedKind := expectedValue.Kind()
|
||||
|
||||
switch expectedKind {
|
||||
case reflect.Array, reflect.Slice:
|
||||
default:
|
||||
e.fail(fmt.Sprintf("Expected\n\t%v\nto be an array", expected))
|
||||
return
|
||||
}
|
||||
|
||||
actualValue := reflect.ValueOf(e.actual)
|
||||
actualKind := actualValue.Kind()
|
||||
|
||||
if actualKind != expectedKind {
|
||||
e.fail(fmt.Sprintf("Expected\n\t%v\nto be the same type as\n\t%v", e.actual, expected))
|
||||
return
|
||||
}
|
||||
|
||||
if actualValue.Len() != expectedValue.Len() {
|
||||
e.fail(fmt.Sprintf("Expected\n\t%v\nto have the same length as\n\t%v", e.actual, expected))
|
||||
return
|
||||
}
|
||||
|
||||
var unmatched []interface{}
|
||||
|
||||
for i := 0; i < expectedValue.Len(); i++ {
|
||||
unmatched = append(unmatched, expectedValue.Index(i).Interface())
|
||||
}
|
||||
|
||||
for i := 0; i < actualValue.Len(); i++ {
|
||||
var found bool
|
||||
|
||||
for j, elem := range unmatched {
|
||||
if reflect.DeepEqual(actualValue.Index(i).Interface(), elem) {
|
||||
found = true
|
||||
unmatched = append(unmatched[:j], unmatched[j+1:]...)
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
e.fail(fmt.Sprintf("Expected\n\t%v\nto contain the same elements as\n\t%v", e.actual, expected))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (e *Expectation) ToBeTemporally(matcher TemporalMatcher, compareTo interface{}) {
|
||||
if actual, ok := e.actual.(time.Time); ok {
|
||||
if ct, ok := compareTo.(time.Time); ok {
|
||||
switch matcher {
|
||||
case Before:
|
||||
if !actual.Before(ct) {
|
||||
e.fail(fmt.Sprintf("Expected\n\t%v\nto be temporally before\n\t%v", e.actual, compareTo))
|
||||
}
|
||||
case BeforeOrSameTime:
|
||||
if actual.After(ct) {
|
||||
e.fail(fmt.Sprintf("Expected\n\t%v\nto be temporally before or at the same time as\n\t%v", e.actual, compareTo))
|
||||
}
|
||||
case After:
|
||||
if !actual.After(ct) {
|
||||
e.fail(fmt.Sprintf("Expected\n\t%v\nto be temporally after\n\t%v", e.actual, compareTo))
|
||||
}
|
||||
case AfterOrSameTime:
|
||||
if actual.Before(ct) {
|
||||
e.fail(fmt.Sprintf("Expected\n\t%v\nto be temporally after or at the same time as\n\t%v", e.actual, compareTo))
|
||||
}
|
||||
default:
|
||||
e.fail("Cannot compare times with unexpected temporal matcher")
|
||||
}
|
||||
} else {
|
||||
e.fail(fmt.Sprintf("Cannot compare to non-temporal value\n\t%v", compareTo))
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
e.fail(fmt.Sprintf("Cannot compare non-temporal value\n\t%v", e.actual))
|
||||
}
|
||||
|
||||
func (e *Expectation) verifyExpectedNotNil(expected interface{}) {
|
||||
if expected == nil {
|
||||
e.t.Fatal("Refusing to compare with <nil>. Use `ToBeNil` or `NotToBeNil` instead.")
|
||||
e.fail("Refusing to compare with <nil>. Use `ToBeNil` or `NotToBeNil` instead.")
|
||||
}
|
||||
}
|
||||
|
||||
func (e *Expectation) fail(msg string) {
|
||||
// Prune the stack trace so that it's easier to see relevant lines
|
||||
stack := strings.Split(string(debug.Stack()), "\n")
|
||||
var prunedStack []string
|
||||
|
||||
for _, line := range stack {
|
||||
if !stackTracePruneRE.MatchString(line) {
|
||||
prunedStack = append(prunedStack, line)
|
||||
}
|
||||
}
|
||||
|
||||
e.t.Fatalf("\n%s\n%s\n", strings.Join(prunedStack, "\n"), msg)
|
||||
}
|
||||
|
24
internal/matchers/temporal_matcher.go
Normal file
24
internal/matchers/temporal_matcher.go
Normal file
@ -0,0 +1,24 @@
|
||||
// Copyright 2019, OpenTelemetry Authors
|
||||
//
|
||||
// Licensed 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 matchers
|
||||
|
||||
type TemporalMatcher byte
|
||||
|
||||
const (
|
||||
Before TemporalMatcher = iota
|
||||
BeforeOrSameTime
|
||||
After
|
||||
AfterOrSameTime
|
||||
)
|
Loading…
x
Reference in New Issue
Block a user