--- /dev/null
+// Copyright 2019 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package ocagent_test
+
+import (
+ "bytes"
+ "context"
+ "encoding/json"
+ "fmt"
+ "io/ioutil"
+ "net/http"
+ "sync"
+ "testing"
+ "time"
+
+ "golang.org/x/tools/internal/event"
+ "golang.org/x/tools/internal/event/core"
+ "golang.org/x/tools/internal/event/export"
+ "golang.org/x/tools/internal/event/export/metric"
+ "golang.org/x/tools/internal/event/export/ocagent"
+ "golang.org/x/tools/internal/event/keys"
+ "golang.org/x/tools/internal/event/label"
+)
+
+const testNodeStr = `{
+ "node":{
+ "identifier":{
+ "host_name":"tester",
+ "pid":1,
+ "start_timestamp":"1970-01-01T00:00:00Z"
+ },
+ "library_info":{
+ "language":4,
+ "exporter_version":"0.0.1",
+ "core_library_version":"x/tools"
+ },
+ "service_info":{
+ "name":"ocagent-tests"
+ }
+ },`
+
+var (
+ keyDB = keys.NewString("db", "the database name")
+ keyMethod = keys.NewString("method", "a metric grouping key")
+ keyRoute = keys.NewString("route", "another metric grouping key")
+
+ key1DB = keys.NewString("1_db", "A test string key")
+
+ key2aAge = keys.NewFloat64("2a_age", "A test float64 key")
+ key2bTTL = keys.NewFloat32("2b_ttl", "A test float32 key")
+ key2cExpiryMS = keys.NewFloat64("2c_expiry_ms", "A test float64 key")
+
+ key3aRetry = keys.NewBoolean("3a_retry", "A test boolean key")
+ key3bStale = keys.NewBoolean("3b_stale", "Another test boolean key")
+
+ key4aMax = keys.NewInt("4a_max", "A test int key")
+ key4bOpcode = keys.NewInt8("4b_opcode", "A test int8 key")
+ key4cBase = keys.NewInt16("4c_base", "A test int16 key")
+ key4eChecksum = keys.NewInt32("4e_checksum", "A test int32 key")
+ key4fMode = keys.NewInt64("4f_mode", "A test int64 key")
+
+ key5aMin = keys.NewUInt("5a_min", "A test uint key")
+ key5bMix = keys.NewUInt8("5b_mix", "A test uint8 key")
+ key5cPort = keys.NewUInt16("5c_port", "A test uint16 key")
+ key5dMinHops = keys.NewUInt32("5d_min_hops", "A test uint32 key")
+ key5eMaxHops = keys.NewUInt64("5e_max_hops", "A test uint64 key")
+
+ recursiveCalls = keys.NewInt64("recursive_calls", "Number of recursive calls")
+ bytesIn = keys.NewInt64("bytes_in", "Number of bytes in") //, unit.Bytes)
+ latencyMs = keys.NewFloat64("latency", "The latency in milliseconds") //, unit.Milliseconds)
+
+ metricLatency = metric.HistogramFloat64{
+ Name: "latency_ms",
+ Description: "The latency of calls in milliseconds",
+ Keys: []label.Key{keyMethod, keyRoute},
+ Buckets: []float64{0, 5, 10, 25, 50},
+ }
+
+ metricBytesIn = metric.HistogramInt64{
+ Name: "latency_ms",
+ Description: "The latency of calls in milliseconds",
+ Keys: []label.Key{keyMethod, keyRoute},
+ Buckets: []int64{0, 10, 50, 100, 500, 1000, 2000},
+ }
+
+ metricRecursiveCalls = metric.Scalar{
+ Name: "latency_ms",
+ Description: "The latency of calls in milliseconds",
+ Keys: []label.Key{keyMethod, keyRoute},
+ }
+)
+
+type testExporter struct {
+ ocagent *ocagent.Exporter
+ sent fakeSender
+}
+
+func registerExporter() *testExporter {
+ exporter := &testExporter{}
+ cfg := ocagent.Config{
+ Host: "tester",
+ Process: 1,
+ Service: "ocagent-tests",
+ Client: &http.Client{Transport: &exporter.sent},
+ }
+ cfg.Start, _ = time.Parse(time.RFC3339Nano, "1970-01-01T00:00:00Z")
+ exporter.ocagent = ocagent.Connect(&cfg)
+
+ metrics := metric.Config{}
+ metricLatency.Record(&metrics, latencyMs)
+ metricBytesIn.Record(&metrics, bytesIn)
+ metricRecursiveCalls.SumInt64(&metrics, recursiveCalls)
+
+ e := exporter.ocagent.ProcessEvent
+ e = metrics.Exporter(e)
+ e = spanFixer(e)
+ e = export.Spans(e)
+ e = export.Labels(e)
+ e = timeFixer(e)
+ event.SetExporter(e)
+ return exporter
+}
+
+func timeFixer(output event.Exporter) event.Exporter {
+ start, _ := time.Parse(time.RFC3339Nano, "1970-01-01T00:00:30Z")
+ at, _ := time.Parse(time.RFC3339Nano, "1970-01-01T00:00:40Z")
+ end, _ := time.Parse(time.RFC3339Nano, "1970-01-01T00:00:50Z")
+ return func(ctx context.Context, ev core.Event, lm label.Map) context.Context {
+ switch {
+ case event.IsStart(ev):
+ ev = core.CloneEvent(ev, start)
+ case event.IsEnd(ev):
+ ev = core.CloneEvent(ev, end)
+ default:
+ ev = core.CloneEvent(ev, at)
+ }
+ return output(ctx, ev, lm)
+ }
+}
+
+func spanFixer(output event.Exporter) event.Exporter {
+ return func(ctx context.Context, ev core.Event, lm label.Map) context.Context {
+ if event.IsStart(ev) {
+ span := export.GetSpan(ctx)
+ span.ID = export.SpanContext{}
+ }
+ return output(ctx, ev, lm)
+ }
+}
+
+func (e *testExporter) Output(route string) []byte {
+ e.ocagent.Flush()
+ return e.sent.get(route)
+}
+
+func checkJSON(t *testing.T, got, want []byte) {
+ // compare the compact form, to allow for formatting differences
+ g := &bytes.Buffer{}
+ if err := json.Compact(g, got); err != nil {
+ t.Fatal(err)
+ }
+ w := &bytes.Buffer{}
+ if err := json.Compact(w, want); err != nil {
+ t.Fatal(err)
+ }
+ if g.String() != w.String() {
+ t.Fatalf("Got:\n%s\nWant:\n%s", g, w)
+ }
+}
+
+type fakeSender struct {
+ mu sync.Mutex
+ data map[string][]byte
+}
+
+func (s *fakeSender) get(route string) []byte {
+ s.mu.Lock()
+ defer s.mu.Unlock()
+ data, found := s.data[route]
+ if found {
+ delete(s.data, route)
+ }
+ return data
+}
+
+func (s *fakeSender) RoundTrip(req *http.Request) (*http.Response, error) {
+ s.mu.Lock()
+ defer s.mu.Unlock()
+ if s.data == nil {
+ s.data = make(map[string][]byte)
+ }
+ data, err := ioutil.ReadAll(req.Body)
+ if err != nil {
+ return nil, err
+ }
+ path := req.URL.EscapedPath()
+ if _, found := s.data[path]; found {
+ return nil, fmt.Errorf("duplicate delivery to %v", path)
+ }
+ s.data[path] = data
+ return &http.Response{
+ Status: "200 OK",
+ StatusCode: 200,
+ Proto: "HTTP/1.0",
+ ProtoMajor: 1,
+ ProtoMinor: 0,
+ }, nil
+}