From 89a9489c570c3d7d724c73d11d2f228dc5f6f8cc Mon Sep 17 00:00:00 2001 From: Tyler Yahn Date: Mon, 9 Aug 2021 08:56:24 -0700 Subject: [PATCH] Add OC bridge internal unit tests (#2164) --- .../internal/oc2otel/attributes_test.go | 56 ++++ .../oc2otel/tracer_start_options_test.go | 52 ++++ bridge/opencensus/internal/span_test.go | 264 ++++++++++++++++++ bridge/opencensus/internal/tracer_test.go | 161 +++++++++++ 4 files changed, 533 insertions(+) create mode 100644 bridge/opencensus/internal/oc2otel/attributes_test.go create mode 100644 bridge/opencensus/internal/oc2otel/tracer_start_options_test.go create mode 100644 bridge/opencensus/internal/span_test.go create mode 100644 bridge/opencensus/internal/tracer_test.go diff --git a/bridge/opencensus/internal/oc2otel/attributes_test.go b/bridge/opencensus/internal/oc2otel/attributes_test.go new file mode 100644 index 000000000..44d40ec89 --- /dev/null +++ b/bridge/opencensus/internal/oc2otel/attributes_test.go @@ -0,0 +1,56 @@ +// Copyright The 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 oc2otel + +import ( + "testing" + + octrace "go.opencensus.io/trace" + + "go.opentelemetry.io/otel/attribute" +) + +func TestAttributes(t *testing.T) { + in := []octrace.Attribute{ + octrace.BoolAttribute("bool", true), + octrace.Int64Attribute("int64", 49), + octrace.Float64Attribute("float64", 1.618), + octrace.StringAttribute("key", "val"), + } + + want := []attribute.KeyValue{ + attribute.Bool("bool", true), + attribute.Int64("int64", 49), + attribute.Float64("float64", 1.618), + attribute.String("key", "val"), + } + got := Attributes(in) + + if len(got) != len(want) { + t.Errorf("Attributes conversion failed: want %#v, got %#v", want, got) + } + for i := range got { + if g, w := got[i], want[i]; g != w { + t.Errorf("Attributes conversion: want %#v, got %#v", w, g) + } + } +} + +func TestAttributeValueUnknown(t *testing.T) { + got := AttributeValue([]byte{}) + if got != attribute.StringValue("unknown") { + t.Errorf("AttributeValue of unknown wrong: %#v", got) + } +} diff --git a/bridge/opencensus/internal/oc2otel/tracer_start_options_test.go b/bridge/opencensus/internal/oc2otel/tracer_start_options_test.go new file mode 100644 index 000000000..6a525424c --- /dev/null +++ b/bridge/opencensus/internal/oc2otel/tracer_start_options_test.go @@ -0,0 +1,52 @@ +// Copyright The 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 oc2otel + +import ( + "testing" + + octrace "go.opencensus.io/trace" + + "go.opentelemetry.io/otel/trace" +) + +func TestStartOptionsSpanKind(t *testing.T) { + conv := map[int]trace.SpanKind{ + octrace.SpanKindClient: trace.SpanKindClient, + octrace.SpanKindServer: trace.SpanKindServer, + octrace.SpanKindUnspecified: trace.SpanKindUnspecified, + } + + for oc, otel := range conv { + ocOpts := []octrace.StartOption{octrace.WithSpanKind(oc)} + otelOpts, err := StartOptions(ocOpts) + if err != nil { + t.Errorf("StartOptions errored: %v", err) + continue + } + c := trace.NewSpanStartConfig(otelOpts...) + if c.SpanKind() != otel { + t.Errorf("conversion of SpanKind start option: got %v, want %v", c.SpanKind(), otel) + } + } +} + +func TestStartOptionsSamplerErrors(t *testing.T) { + ocOpts := []octrace.StartOption{octrace.WithSampler(octrace.AlwaysSample())} + _, err := StartOptions(ocOpts) + if err == nil { + t.Error("StartOptions should error Sampler option") + } +} diff --git a/bridge/opencensus/internal/span_test.go b/bridge/opencensus/internal/span_test.go new file mode 100644 index 000000000..b0771aad6 --- /dev/null +++ b/bridge/opencensus/internal/span_test.go @@ -0,0 +1,264 @@ +// Copyright The 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 internal_test + +import ( + "testing" + + octrace "go.opencensus.io/trace" + + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/bridge/opencensus/internal" + "go.opentelemetry.io/otel/bridge/opencensus/internal/oc2otel" + "go.opentelemetry.io/otel/bridge/opencensus/internal/otel2oc" + "go.opentelemetry.io/otel/codes" + "go.opentelemetry.io/otel/trace" +) + +type span struct { + trace.Span + + recording bool + ended bool + sc trace.SpanContext + name string + sCode codes.Code + sMsg string + attrs []attribute.KeyValue + eName string + eOpts []trace.EventOption +} + +func (s *span) IsRecording() bool { return s.recording } +func (s *span) End(...trace.SpanEndOption) { s.ended = true } +func (s *span) SpanContext() trace.SpanContext { return s.sc } +func (s *span) SetName(n string) { s.name = n } +func (s *span) SetStatus(c codes.Code, d string) { s.sCode, s.sMsg = c, d } +func (s *span) SetAttributes(a ...attribute.KeyValue) { s.attrs = a } +func (s *span) AddEvent(n string, o ...trace.EventOption) { s.eName, s.eOpts = n, o } + +func TestSpanIsRecordingEvents(t *testing.T) { + s := &span{recording: true} + ocS := internal.NewSpan(s) + if !ocS.IsRecordingEvents() { + t.Errorf("span.IsRecordingEvents() = false, want true") + } + s.recording = false + if ocS.IsRecordingEvents() { + t.Errorf("span.IsRecordingEvents() = true, want false") + } +} + +func TestSpanEnd(t *testing.T) { + s := new(span) + ocS := internal.NewSpan(s) + if s.ended { + t.Fatal("new span already ended") + } + + ocS.End() + if !s.ended { + t.Error("span.End() did not end OpenTelemetry span") + } +} + +func TestSpanSpanContext(t *testing.T) { + sc := trace.NewSpanContext(trace.SpanContextConfig{ + TraceID: [16]byte{1}, + SpanID: [8]byte{1}, + }) + // Do not test the conversion, only that the method is called. + converted := otel2oc.SpanContext(sc) + + s := &span{sc: sc} + ocS := internal.NewSpan(s) + if ocS.SpanContext() != converted { + t.Error("span.SpanContext did not use OpenTelemetry SpanContext") + } +} + +func TestSpanSetName(t *testing.T) { + // OpenCensus does not set a name if not recording. + s := &span{recording: true} + ocS := internal.NewSpan(s) + name := "test name" + ocS.SetName(name) + if s.name != name { + t.Error("span.SetName did not set OpenTelemetry span name") + } +} + +func TestSpanSetStatus(t *testing.T) { + // OpenCensus does not set a status if not recording. + s := &span{recording: true} + ocS := internal.NewSpan(s) + + c, d := codes.Error, "error" + status := octrace.Status{Code: int32(c), Message: d} + ocS.SetStatus(status) + + if s.sCode != c { + t.Error("span.SetStatus failed to set OpenTelemetry status code") + } + if s.sMsg != d { + t.Error("span.SetStatus failed to set OpenTelemetry status description") + } +} + +func TestSpanAddAttributes(t *testing.T) { + attrs := []octrace.Attribute{ + octrace.BoolAttribute("a", true), + } + // Do not test the conversion, only that the method is called. + converted := oc2otel.Attributes(attrs) + + // OpenCensus does not set attributes if not recording. + s := &span{recording: true} + ocS := internal.NewSpan(s) + ocS.AddAttributes(attrs...) + + if len(s.attrs) != len(converted) || s.attrs[0] != converted[0] { + t.Error("span.AddAttributes failed to set OpenTelemetry attributes") + } +} + +func TestSpanAnnotate(t *testing.T) { + name := "annotation" + attrs := []octrace.Attribute{ + octrace.BoolAttribute("a", true), + } + // Do not test the conversion, only that the method is called. + want := oc2otel.Attributes(attrs) + + // OpenCensus does not set events if not recording. + s := &span{recording: true} + ocS := internal.NewSpan(s) + ocS.Annotate(attrs, name) + + if s.eName != name { + t.Error("span.Annotate did not set event name") + } + + got := trace.NewEventConfig(s.eOpts...).Attributes() + if len(want) != len(got) || want[0] != got[0] { + t.Error("span.Annotate did not set event options") + } +} + +func TestSpanAnnotatef(t *testing.T) { + format := "annotation %s" + attrs := []octrace.Attribute{ + octrace.BoolAttribute("a", true), + } + // Do not test the conversion, only that the method is called. + want := oc2otel.Attributes(attrs) + + // OpenCensus does not set events if not recording. + s := &span{recording: true} + ocS := internal.NewSpan(s) + ocS.Annotatef(attrs, format, "a") + + if s.eName != "annotation a" { + t.Error("span.Annotatef did not set event name") + } + + got := trace.NewEventConfig(s.eOpts...).Attributes() + if len(want) != len(got) || want[0] != got[0] { + t.Error("span.Annotatef did not set event options") + } +} + +func TestSpanAddMessageSendEvent(t *testing.T) { + var u, c int64 = 1, 2 + + // OpenCensus does not set events if not recording. + s := &span{recording: true} + ocS := internal.NewSpan(s) + ocS.AddMessageSendEvent(0, u, c) + + if s.eName != internal.MessageSendEvent { + t.Error("span.AddMessageSendEvent did not set event name") + } + + got := trace.NewEventConfig(s.eOpts...).Attributes() + if len(got) != 2 { + t.Fatalf("span.AddMessageSendEvent set %d attributes, want 2", len(got)) + } + + want := attribute.KeyValue{Key: internal.UncompressedKey, Value: attribute.Int64Value(u)} + if got[0] != want { + t.Errorf("span.AddMessageSendEvent wrong uncompressed attribute: %v", got[0]) + } + + want = attribute.KeyValue{Key: internal.CompressedKey, Value: attribute.Int64Value(c)} + if got[1] != want { + t.Errorf("span.AddMessageSendEvent wrong compressed attribute: %v", got[1]) + } +} + +func TestSpanAddMessageReceiveEvent(t *testing.T) { + var u, c int64 = 3, 4 + + // OpenCensus does not set events if not recording. + s := &span{recording: true} + ocS := internal.NewSpan(s) + ocS.AddMessageReceiveEvent(0, u, c) + + if s.eName != internal.MessageReceiveEvent { + t.Error("span.AddMessageReceiveEvent did not set event name") + } + + got := trace.NewEventConfig(s.eOpts...).Attributes() + if len(got) != 2 { + t.Fatalf("span.AddMessageReceiveEvent set %d attributes, want 2", len(got)) + } + + want := attribute.KeyValue{Key: internal.UncompressedKey, Value: attribute.Int64Value(u)} + if got[0] != want { + t.Errorf("span.AddMessageReceiveEvent wrong uncompressed attribute: %v", got[0]) + } + + want = attribute.KeyValue{Key: internal.CompressedKey, Value: attribute.Int64Value(c)} + if got[1] != want { + t.Errorf("span.AddMessageReceiveEvent wrong compressed attribute: %v", got[1]) + } +} + +func TestSpanAddLinkFails(t *testing.T) { + h, restore := withHandler() + defer restore() + + // OpenCensus does not try to set links if not recording. + s := &span{recording: true} + ocS := internal.NewSpan(s) + ocS.AddLink(octrace.Link{}) + + if h.err == nil { + t.Error("span.AddLink failed to raise an error") + } +} + +func TestSpanString(t *testing.T) { + sc := trace.NewSpanContext(trace.SpanContextConfig{ + TraceID: [16]byte{1}, + SpanID: [8]byte{1}, + }) + + s := &span{sc: sc} + ocS := internal.NewSpan(s) + if expected := "span 0100000000000000"; ocS.String() != expected { + t.Errorf("span.String = %q, not %q", ocS.String(), expected) + } +} diff --git a/bridge/opencensus/internal/tracer_test.go b/bridge/opencensus/internal/tracer_test.go new file mode 100644 index 000000000..1e3518a7e --- /dev/null +++ b/bridge/opencensus/internal/tracer_test.go @@ -0,0 +1,161 @@ +// Copyright The 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 internal_test + +import ( + "context" + "testing" + + octrace "go.opencensus.io/trace" + + "go.opentelemetry.io/otel/bridge/opencensus/internal" + "go.opentelemetry.io/otel/bridge/opencensus/internal/oc2otel" + "go.opentelemetry.io/otel/bridge/opencensus/internal/otel2oc" + "go.opentelemetry.io/otel/trace" +) + +type handler struct{ err error } + +func (h *handler) Handle(e error) { h.err = e } + +func withHandler() (*handler, func()) { + h := new(handler) + original := internal.Handle + internal.Handle = h.Handle + return h, func() { internal.Handle = original } +} + +type tracer struct { + ctx context.Context + name string + opts []trace.SpanStartOption +} + +func (t *tracer) Start(ctx context.Context, name string, opts ...trace.SpanStartOption) (context.Context, trace.Span) { + t.ctx, t.name, t.opts = ctx, name, opts + noop := trace.NewNoopTracerProvider().Tracer("testing") + return noop.Start(ctx, name, opts...) +} + +type ctxKey string + +func TestTracerStartSpan(t *testing.T) { + h, restore := withHandler() + defer restore() + + otelTracer := &tracer{} + ocTracer := internal.NewTracer(otelTracer) + + ctx := context.WithValue(context.Background(), ctxKey("key"), "value") + name := "testing span" + ocTracer.StartSpan(ctx, name, octrace.WithSpanKind(octrace.SpanKindClient)) + if h.err != nil { + t.Fatalf("OC tracer.StartSpan errored: %v", h.err) + } + + if otelTracer.ctx != ctx { + t.Error("OTel tracer.Start called with wrong context") + } + if otelTracer.name != name { + t.Error("OTel tracer.Start called with wrong name") + } + sk := trace.SpanKindClient + c := trace.NewSpanStartConfig(otelTracer.opts...) + if c.SpanKind() != sk { + t.Errorf("OTel tracer.Start called with wrong options: %#v", c) + } +} + +func TestTracerStartSpanReportsErrors(t *testing.T) { + h, restore := withHandler() + defer restore() + + ocTracer := internal.NewTracer(&tracer{}) + ocTracer.StartSpan(context.Background(), "", octrace.WithSampler(octrace.AlwaysSample())) + if h.err == nil { + t.Error("OC tracer.StartSpan no error when converting Sampler") + } +} + +func TestTracerStartSpanWithRemoteParent(t *testing.T) { + otelTracer := new(tracer) + ocTracer := internal.NewTracer(otelTracer) + sc := octrace.SpanContext{TraceID: [16]byte{1}, SpanID: [8]byte{1}} + converted := oc2otel.SpanContext(sc).WithRemote(true) + + ocTracer.StartSpanWithRemoteParent(context.Background(), "", sc) + + got := trace.SpanContextFromContext(otelTracer.ctx) + if !got.Equal(converted) { + t.Error("tracer.StartSpanWithRemoteParent failed to set remote parent") + } +} + +func TestTracerFromContext(t *testing.T) { + sc := trace.NewSpanContext(trace.SpanContextConfig{ + TraceID: [16]byte{1}, + SpanID: [8]byte{1}, + }) + ctx := trace.ContextWithSpanContext(context.Background(), sc) + + noop := trace.NewNoopTracerProvider().Tracer("TestTracerFromContext") + // Test using the fact that the No-Op span will propagate a span context . + ctx, _ = noop.Start(ctx, "test") + + got := internal.NewTracer(noop).FromContext(ctx).SpanContext() + // Do not test the convedsion, only that the propagtion. + want := otel2oc.SpanContext(sc) + if got != want { + t.Errorf("tracer.FromContext returned wrong context: %#v", got) + } +} + +func TestTracerNewContext(t *testing.T) { + sc := trace.NewSpanContext(trace.SpanContextConfig{ + TraceID: [16]byte{1}, + SpanID: [8]byte{1}, + }) + ctx := trace.ContextWithSpanContext(context.Background(), sc) + + noop := trace.NewNoopTracerProvider().Tracer("TestTracerNewContext") + // Test using the fact that the No-Op span will propagate a span context . + _, s := noop.Start(ctx, "test") + + ocTracer := internal.NewTracer(noop) + ctx = ocTracer.NewContext(context.Background(), internal.NewSpan(s)) + got := trace.SpanContextFromContext(ctx) + + if !got.Equal(sc) { + t.Error("tracer.NewContext did not attach Span to context") + } +} + +type differentSpan struct { + octrace.SpanInterface +} + +func (s *differentSpan) String() string { return "testing span" } + +func TestTracerNewContextErrors(t *testing.T) { + h, restore := withHandler() + defer restore() + + ocTracer := internal.NewTracer(&tracer{}) + ocSpan := octrace.NewSpan(&differentSpan{}) + ocTracer.NewContext(context.Background(), ocSpan) + if h.err == nil { + t.Error("tracer.NewContext did not error for unrecognized span") + } +}