mirror of
https://github.com/raseels-repos/golang-saas-starter-kit.git
synced 2025-06-06 23:46:29 +02:00
279 lines
9.3 KiB
Go
279 lines
9.3 KiB
Go
|
package trace
|
||
|
|
||
|
import (
|
||
|
"fmt"
|
||
|
"io/ioutil"
|
||
|
"log"
|
||
|
"net/http"
|
||
|
"net/http/httptest"
|
||
|
"reflect"
|
||
|
"testing"
|
||
|
"time"
|
||
|
|
||
|
"go.opencensus.io/trace"
|
||
|
)
|
||
|
|
||
|
// Success and failure markers.
|
||
|
const (
|
||
|
success = "\u2713"
|
||
|
failed = "\u2717"
|
||
|
)
|
||
|
|
||
|
// inputSpans represents spans of data for the tests.
|
||
|
var inputSpans = []*trace.SpanData{
|
||
|
{Name: "span1"},
|
||
|
{Name: "span2"},
|
||
|
{Name: "span3"},
|
||
|
}
|
||
|
|
||
|
// inputSpansJSON represents a JSON representation of the span data.
|
||
|
var inputSpansJSON = `[{"TraceID":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"SpanID":[0,0,0,0,0,0,0,0],"TraceOptions":0,"ParentSpanID":[0,0,0,0,0,0,0,0],"SpanKind":0,"Name":"span1","StartTime":"0001-01-01T00:00:00Z","EndTime":"0001-01-01T00:00:00Z","Attributes":null,"Annotations":null,"MessageEvents":null,"Code":0,"Message":"","Links":null,"HasRemoteParent":false},{"TraceID":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"SpanID":[0,0,0,0,0,0,0,0],"TraceOptions":0,"ParentSpanID":[0,0,0,0,0,0,0,0],"SpanKind":0,"Name":"span2","StartTime":"0001-01-01T00:00:00Z","EndTime":"0001-01-01T00:00:00Z","Attributes":null,"Annotations":null,"MessageEvents":null,"Code":0,"Message":"","Links":null,"HasRemoteParent":false},{"TraceID":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"SpanID":[0,0,0,0,0,0,0,0],"TraceOptions":0,"ParentSpanID":[0,0,0,0,0,0,0,0],"SpanKind":0,"Name":"span3","StartTime":"0001-01-01T00:00:00Z","EndTime":"0001-01-01T00:00:00Z","Attributes":null,"Annotations":null,"MessageEvents":null,"Code":0,"Message":"","Links":null,"HasRemoteParent":false}]`
|
||
|
|
||
|
// =============================================================================
|
||
|
|
||
|
// logger is required to create an Exporter.
|
||
|
var logger = func(format string, v ...interface{}) {
|
||
|
log.Printf(format, v)
|
||
|
}
|
||
|
|
||
|
// MakeExporter abstracts the error handling aspects of creating an Exporter.
|
||
|
func makeExporter(host string, batchSize int, sendInterval, sendTimeout time.Duration) *Exporter {
|
||
|
exporter, err := NewExporter(logger, host, batchSize, sendInterval, sendTimeout)
|
||
|
if err != nil {
|
||
|
log.Fatalln("Unable to create exporter, ", err)
|
||
|
}
|
||
|
return exporter
|
||
|
}
|
||
|
|
||
|
// =============================================================================
|
||
|
|
||
|
var saveTests = []struct {
|
||
|
name string
|
||
|
e *Exporter
|
||
|
input []*trace.SpanData
|
||
|
output []*trace.SpanData
|
||
|
lastSaveDelay time.Duration // The delay before the last save. For testing intervals.
|
||
|
isInputMatchBatch bool // If the input should match the internal exporter collection after the last save.
|
||
|
isSendBatch bool // If the last save should return nil or batch data.
|
||
|
}{
|
||
|
{"NoSend", makeExporter("test", 10, time.Minute, time.Second), inputSpans, nil, time.Nanosecond, true, false},
|
||
|
{"SendOnBatchSize", makeExporter("test", 3, time.Minute, time.Second), inputSpans, inputSpans, time.Nanosecond, false, true},
|
||
|
{"SendOnTime", makeExporter("test", 4, time.Millisecond, time.Second), inputSpans, inputSpans, 2 * time.Millisecond, false, true},
|
||
|
}
|
||
|
|
||
|
// TestSave validates the save batch functionality is working.
|
||
|
func TestSave(t *testing.T) {
|
||
|
t.Log("Given the need to validate saving span data to a batch.")
|
||
|
{
|
||
|
for i, tt := range saveTests {
|
||
|
t.Logf("\tTest: %d\tWhen running test: %s", i, tt.name)
|
||
|
{
|
||
|
// Save the input of span data.
|
||
|
l := len(tt.input) - 1
|
||
|
var batch []*trace.SpanData
|
||
|
for i, span := range tt.input {
|
||
|
|
||
|
// If this is the last save, take the configured delay.
|
||
|
// We might be testing invertal based batching.
|
||
|
if l == i {
|
||
|
time.Sleep(tt.lastSaveDelay)
|
||
|
}
|
||
|
batch = tt.e.saveBatch(span)
|
||
|
}
|
||
|
|
||
|
// Compare the internal collection with what we saved.
|
||
|
if tt.isInputMatchBatch {
|
||
|
if len(tt.e.batch) != len(tt.input) {
|
||
|
t.Log("\t\tGot :", len(tt.e.batch))
|
||
|
t.Log("\t\tWant:", len(tt.input))
|
||
|
t.Errorf("\t%s\tShould have the same number of spans as input.", failed)
|
||
|
} else {
|
||
|
t.Logf("\t%s\tShould have the same number of spans as input.", success)
|
||
|
}
|
||
|
} else {
|
||
|
if len(tt.e.batch) != 0 {
|
||
|
t.Log("\t\tGot :", len(tt.e.batch))
|
||
|
t.Log("\t\tWant:", 0)
|
||
|
t.Errorf("\t%s\tShould have zero spans.", failed)
|
||
|
} else {
|
||
|
t.Logf("\t%s\tShould have zero spans.", success)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Validate the return provided or didn't provide a batch to send.
|
||
|
if !tt.isSendBatch && batch != nil {
|
||
|
t.Errorf("\t%s\tShould not have a batch to send.", failed)
|
||
|
} else if !tt.isSendBatch {
|
||
|
t.Logf("\t%s\tShould not have a batch to send.", success)
|
||
|
}
|
||
|
if tt.isSendBatch && batch == nil {
|
||
|
t.Errorf("\t%s\tShould have a batch to send.", failed)
|
||
|
} else if tt.isSendBatch {
|
||
|
t.Logf("\t%s\tShould have a batch to send.", success)
|
||
|
}
|
||
|
|
||
|
// Compare the batch to send.
|
||
|
if !reflect.DeepEqual(tt.output, batch) {
|
||
|
t.Log("\t\tGot :", batch)
|
||
|
t.Log("\t\tWant:", tt.output)
|
||
|
t.Errorf("\t%s\tShould have an expected match of the batch to send.", failed)
|
||
|
} else {
|
||
|
t.Logf("\t%s\tShould have an expected match of the batch to send.", success)
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// =============================================================================
|
||
|
|
||
|
var sendTests = []struct {
|
||
|
name string
|
||
|
e *Exporter
|
||
|
input []*trace.SpanData
|
||
|
pass bool
|
||
|
}{
|
||
|
{"success", makeExporter("test", 3, time.Minute, time.Hour), inputSpans, true},
|
||
|
{"failure", makeExporter("test", 3, time.Minute, time.Hour), inputSpans[:2], false},
|
||
|
{"timeout", makeExporter("test", 3, time.Minute, time.Nanosecond), inputSpans, false},
|
||
|
}
|
||
|
|
||
|
// mockServer returns a pointer to a server to handle the mock get call.
|
||
|
func mockServer() *httptest.Server {
|
||
|
f := func(w http.ResponseWriter, r *http.Request) {
|
||
|
d, _ := ioutil.ReadAll(r.Body)
|
||
|
data := string(d)
|
||
|
if data != inputSpansJSON {
|
||
|
w.WriteHeader(http.StatusBadRequest)
|
||
|
fmt.Fprint(w, data)
|
||
|
return
|
||
|
}
|
||
|
w.WriteHeader(http.StatusNoContent)
|
||
|
}
|
||
|
return httptest.NewServer(http.HandlerFunc(f))
|
||
|
}
|
||
|
|
||
|
// TestSend validates spans can be sent to the sidecar.
|
||
|
func TestSend(t *testing.T) {
|
||
|
s := mockServer()
|
||
|
defer s.Close()
|
||
|
|
||
|
t.Log("Given the need to validate sending span data to the sidecar.")
|
||
|
{
|
||
|
for i, tt := range sendTests {
|
||
|
t.Logf("\tTest: %d\tWhen running test: %s", i, tt.name)
|
||
|
{
|
||
|
// Set the URL for the call.
|
||
|
tt.e.host = s.URL
|
||
|
|
||
|
// Send the span data.
|
||
|
err := tt.e.send(tt.input)
|
||
|
if tt.pass {
|
||
|
if err != nil {
|
||
|
t.Errorf("\t%s\tShould be able to send the batch successfully: %v", failed, err)
|
||
|
} else {
|
||
|
t.Logf("\t%s\tShould be able to send the batch successfully.", success)
|
||
|
}
|
||
|
} else {
|
||
|
if err == nil {
|
||
|
t.Errorf("\t%s\tShould not be able to send the batch successfully : %v", failed, err)
|
||
|
} else {
|
||
|
t.Logf("\t%s\tShould not be able to send the batch successfully.", success)
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// TestClose validates the flushing of the final batched spans.
|
||
|
func TestClose(t *testing.T) {
|
||
|
s := mockServer()
|
||
|
defer s.Close()
|
||
|
|
||
|
t.Log("Given the need to validate flushing the remaining batched spans.")
|
||
|
{
|
||
|
t.Logf("\tTest: %d\tWhen running test: %s", 0, "FlushWithData")
|
||
|
{
|
||
|
e, err := NewExporter(logger, "test", 10, time.Minute, time.Hour)
|
||
|
if err != nil {
|
||
|
t.Fatalf("\t%s\tShould be able to create an Exporter : %v", failed, err)
|
||
|
}
|
||
|
t.Logf("\t%s\tShould be able to create an Exporter.", success)
|
||
|
|
||
|
// Set the URL for the call.
|
||
|
e.host = s.URL
|
||
|
|
||
|
// Save the input of span data.
|
||
|
for _, span := range inputSpans {
|
||
|
e.saveBatch(span)
|
||
|
}
|
||
|
|
||
|
// Close the Exporter and we should get those spans sent.
|
||
|
sent, err := e.Close()
|
||
|
if err != nil {
|
||
|
t.Fatalf("\t%s\tShould be able to flush the Exporter : %v", failed, err)
|
||
|
}
|
||
|
t.Logf("\t%s\tShould be able to flush the Exporter.", success)
|
||
|
|
||
|
if sent != len(inputSpans) {
|
||
|
t.Log("\t\tGot :", sent)
|
||
|
t.Log("\t\tWant:", len(inputSpans))
|
||
|
t.Fatalf("\t%s\tShould have flushed the expected number of spans.", failed)
|
||
|
}
|
||
|
t.Logf("\t%s\tShould have flushed the expected number of spans.", success)
|
||
|
}
|
||
|
|
||
|
t.Logf("\tTest: %d\tWhen running test: %s", 0, "FlushWithError")
|
||
|
{
|
||
|
e, err := NewExporter(logger, "test", 10, time.Minute, time.Hour)
|
||
|
if err != nil {
|
||
|
t.Fatalf("\t%s\tShould be able to create an Exporter : %v", failed, err)
|
||
|
}
|
||
|
t.Logf("\t%s\tShould be able to create an Exporter.", success)
|
||
|
|
||
|
// Set the URL for the call.
|
||
|
e.host = s.URL
|
||
|
|
||
|
// Save the input of span data.
|
||
|
for _, span := range inputSpans[:2] {
|
||
|
e.saveBatch(span)
|
||
|
}
|
||
|
|
||
|
// Close the Exporter and we should get those spans sent.
|
||
|
if _, err := e.Close(); err == nil {
|
||
|
t.Fatalf("\t%s\tShould not be able to flush the Exporter.", failed)
|
||
|
}
|
||
|
t.Logf("\t%s\tShould not be able to flush the Exporter.", success)
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// =============================================================================
|
||
|
|
||
|
// TestExporterFailure validates misuse cases are covered.
|
||
|
func TestExporterFailure(t *testing.T) {
|
||
|
t.Log("Given the need to validate Exporter initializes properly.")
|
||
|
{
|
||
|
t.Logf("\tTest: %d\tWhen not passing a proper logger.", 0)
|
||
|
{
|
||
|
_, err := NewExporter(nil, "test", 10, time.Minute, time.Hour)
|
||
|
if err == nil {
|
||
|
t.Errorf("\t%s\tShould not be able to create an Exporter.", failed)
|
||
|
} else {
|
||
|
t.Logf("\t%s\tShould not be able to create an Exporter.", success)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
t.Logf("\tTest: %d\tWhen not passing a proper host.", 1)
|
||
|
{
|
||
|
_, err := NewExporter(logger, "", 10, time.Minute, time.Hour)
|
||
|
if err == nil {
|
||
|
t.Errorf("\t%s\tShould not be able to create an Exporter.", failed)
|
||
|
} else {
|
||
|
t.Logf("\t%s\tShould not be able to create an Exporter.", success)
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|