| // 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) |
| } |
| } |
| } |