blob: c2b5420deed968705b47ecb2f3bf18dc3013d0a0 [file] [log] [blame]
// Copyright 2017, OpenCensus 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 zipkin
import (
"encoding/json"
"io/ioutil"
"net/http"
"reflect"
"strings"
"testing"
"time"
"github.com/openzipkin/zipkin-go/model"
httpreporter "github.com/openzipkin/zipkin-go/reporter/http"
"go.opencensus.io/trace"
)
type roundTripper func(*http.Request) (*http.Response, error)
func (r roundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
return r(req)
}
func TestExport(t *testing.T) {
// Since Zipkin reports in microsecond resolution let's round our Timestamp,
// so when deserializing Zipkin data in this test we can properly compare.
now := time.Now().Round(time.Microsecond)
tests := []struct {
span *trace.SpanData
want model.SpanModel
}{
{
span: &trace.SpanData{
SpanContext: trace.SpanContext{
TraceID: trace.TraceID{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16},
SpanID: trace.SpanID{17, 18, 19, 20, 21, 22, 23, 24},
TraceOptions: 1,
},
Name: "name",
SpanKind: trace.SpanKindClient,
StartTime: now,
EndTime: now.Add(24 * time.Hour),
Attributes: map[string]interface{}{
"stringkey": "value",
"intkey": int64(42),
"boolkey1": true,
"boolkey2": false,
"doublekey": float64(123.456),
},
MessageEvents: []trace.MessageEvent{
{
Time: now,
EventType: trace.MessageEventTypeSent,
MessageID: 12,
UncompressedByteSize: 99,
CompressedByteSize: 98,
},
},
Annotations: []trace.Annotation{
{
Time: now,
Message: "Annotation",
Attributes: map[string]interface{}{
"stringkey": "value",
"intkey": int64(42),
"boolkey1": true,
"boolkey2": false,
"doublekey": float64(123.456),
},
},
},
Status: trace.Status{
Code: 3,
Message: "error",
},
},
want: model.SpanModel{
SpanContext: model.SpanContext{
TraceID: model.TraceID{
High: 0x0102030405060708,
Low: 0x090a0b0c0d0e0f10,
},
ID: 0x1112131415161718,
Sampled: &sampledTrue,
},
Name: "name",
Kind: model.Client,
Timestamp: now,
Duration: 24 * time.Hour,
Shared: false,
Annotations: []model.Annotation{
{
Timestamp: now,
Value: "Annotation",
},
{
Timestamp: now,
Value: "SENT",
},
},
Tags: map[string]string{
"stringkey": "value",
"intkey": "42",
"boolkey1": "true",
"boolkey2": "false",
"doublekey": "123.456",
"error": "INVALID_ARGUMENT",
"opencensus.status_description": "error",
},
},
},
{
span: &trace.SpanData{
SpanContext: trace.SpanContext{
TraceID: trace.TraceID{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16},
SpanID: trace.SpanID{17, 18, 19, 20, 21, 22, 23, 24},
TraceOptions: 1,
},
Name: "name",
StartTime: now,
EndTime: now.Add(24 * time.Hour),
},
want: model.SpanModel{
SpanContext: model.SpanContext{
TraceID: model.TraceID{
High: 0x0102030405060708,
Low: 0x090a0b0c0d0e0f10,
},
ID: 0x1112131415161718,
Sampled: &sampledTrue,
},
Name: "name",
Timestamp: now,
Duration: 24 * time.Hour,
Shared: false,
},
},
{
span: &trace.SpanData{
SpanContext: trace.SpanContext{
TraceID: trace.TraceID{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16},
SpanID: trace.SpanID{17, 18, 19, 20, 21, 22, 23, 24},
TraceOptions: 1,
},
Name: "name",
StartTime: now,
EndTime: now.Add(24 * time.Hour),
Status: trace.Status{
Code: 0,
Message: "there is no cause for alarm",
},
},
want: model.SpanModel{
SpanContext: model.SpanContext{
TraceID: model.TraceID{
High: 0x0102030405060708,
Low: 0x090a0b0c0d0e0f10,
},
ID: 0x1112131415161718,
Sampled: &sampledTrue,
},
Name: "name",
Timestamp: now,
Duration: 24 * time.Hour,
Shared: false,
Tags: map[string]string{
"opencensus.status_description": "there is no cause for alarm",
},
},
},
{
span: &trace.SpanData{
SpanContext: trace.SpanContext{
TraceID: trace.TraceID{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16},
SpanID: trace.SpanID{17, 18, 19, 20, 21, 22, 23, 24},
TraceOptions: 1,
},
Name: "name",
StartTime: now,
EndTime: now.Add(24 * time.Hour),
Status: trace.Status{
Code: 1234,
},
},
want: model.SpanModel{
SpanContext: model.SpanContext{
TraceID: model.TraceID{
High: 0x0102030405060708,
Low: 0x090a0b0c0d0e0f10,
},
ID: 0x1112131415161718,
Sampled: &sampledTrue,
},
Name: "name",
Timestamp: now,
Duration: 24 * time.Hour,
Shared: false,
Tags: map[string]string{
"error": "error code 1234",
},
},
},
}
for _, tt := range tests {
got := zipkinSpan(tt.span, nil)
if len(got.Annotations) != len(tt.want.Annotations) {
t.Fatalf("zipkinSpan: got %d annotations in span, want %d", len(got.Annotations), len(tt.want.Annotations))
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("zipkinSpan:\n\tgot %#v\n\twant %#v", got, tt.want)
}
}
for _, tt := range tests {
ch := make(chan []byte)
client := http.Client{
Transport: roundTripper(func(req *http.Request) (*http.Response, error) {
body, _ := ioutil.ReadAll(req.Body)
ch <- body
return &http.Response{StatusCode: 200, Body: ioutil.NopCloser(strings.NewReader(""))}, nil
}),
}
reporter := httpreporter.NewReporter("foo", httpreporter.Client(&client), httpreporter.BatchInterval(time.Millisecond))
exporter := NewExporter(reporter, nil)
exporter.ExportSpan(tt.span)
var data []byte
select {
case data = <-ch:
case <-time.After(2 * time.Second):
t.Fatalf("span was not exported")
}
var spans []model.SpanModel
json.Unmarshal(data, &spans)
if len(spans) != 1 {
t.Fatalf("Export: got %d spans, want 1", len(spans))
}
got := spans[0]
got.SpanContext.Sampled = &sampledTrue // Sampled is not set when the span is reported.
if len(got.Annotations) != len(tt.want.Annotations) {
t.Fatalf("Export: got %d annotations in span, want %d", len(got.Annotations), len(tt.want.Annotations))
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("Export:\n\tgot %#v\n\twant %#v", got, tt.want)
}
}
}