123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664 |
- package internal
- import (
- "encoding/json"
- "fmt"
- "runtime"
- )
- var (
- // Unfortunately, the resolution of time.Now() on Windows is coarse: Two
- // sequential calls to time.Now() may return the same value, and tests
- // which expect non-zero durations may fail. To avoid adding sleep
- // statements or mocking time.Now(), those tests are skipped on Windows.
- doDurationTests = runtime.GOOS != `windows`
- )
- // Validator is used for testing.
- type Validator interface {
- Error(...interface{})
- }
- func validateStringField(v Validator, fieldName, v1, v2 string) {
- if v1 != v2 {
- v.Error(fieldName, v1, v2)
- }
- }
- type addValidatorField struct {
- field interface{}
- original Validator
- }
- func (a addValidatorField) Error(fields ...interface{}) {
- fields = append([]interface{}{a.field}, fields...)
- a.original.Error(fields...)
- }
- // ExtendValidator is used to add more context to a validator.
- func ExtendValidator(v Validator, field interface{}) Validator {
- return addValidatorField{
- field: field,
- original: v,
- }
- }
- // WantMetric is a metric expectation. If Data is nil, then any data values are
- // acceptable.
- type WantMetric struct {
- Name string
- Scope string
- Forced interface{} // true, false, or nil
- Data []float64
- }
- // WantError is a traced error expectation.
- type WantError struct {
- TxnName string
- Msg string
- Klass string
- Caller string
- URL string
- UserAttributes map[string]interface{}
- AgentAttributes map[string]interface{}
- }
- func uniquePointer() *struct{} {
- s := struct{}{}
- return &s
- }
- var (
- // MatchAnything is for use when matching attributes.
- MatchAnything = uniquePointer()
- )
- // WantEvent is a transaction or error event expectation.
- type WantEvent struct {
- Intrinsics map[string]interface{}
- UserAttributes map[string]interface{}
- AgentAttributes map[string]interface{}
- }
- // WantTxnTrace is a transaction trace expectation.
- type WantTxnTrace struct {
- MetricName string
- CleanURL string
- NumSegments int
- UserAttributes map[string]interface{}
- AgentAttributes map[string]interface{}
- }
- // WantSlowQuery is a slowQuery expectation.
- type WantSlowQuery struct {
- Count int32
- MetricName string
- Query string
- TxnName string
- TxnURL string
- DatabaseName string
- Host string
- PortPathOrID string
- Params map[string]interface{}
- }
- // Expect exposes methods that allow for testing whether the correct data was
- // captured.
- type Expect interface {
- ExpectCustomEvents(t Validator, want []WantEvent)
- ExpectErrors(t Validator, want []WantError)
- ExpectErrorEvents(t Validator, want []WantEvent)
- ExpectErrorEventsPresent(t Validator, want []WantEvent)
- ExpectErrorEventsAbsent(t Validator, names []string)
- ExpectTxnEvents(t Validator, want []WantEvent)
- ExpectTxnEventsPresent(t Validator, want []WantEvent)
- ExpectTxnEventsAbsent(t Validator, names []string)
- ExpectMetrics(t Validator, want []WantMetric)
- ExpectMetricsPresent(t Validator, want []WantMetric)
- ExpectTxnTraces(t Validator, want []WantTxnTrace)
- ExpectSlowQueries(t Validator, want []WantSlowQuery)
- ExpectSpanEvents(t Validator, want []WantEvent)
- ExpectSpanEventsPresent(t Validator, want []WantEvent)
- ExpectSpanEventsAbsent(t Validator, names []string)
- ExpectSpanEventsCount(t Validator, c int)
- }
- func expectMetricField(t Validator, id metricID, v1, v2 float64, fieldName string) {
- if v1 != v2 {
- t.Error("metric fields do not match", id, v1, v2, fieldName)
- }
- }
- // ExpectMetricsPresent allows testing of metrics with requiring an exact match
- func ExpectMetricsPresent(t Validator, mt *metricTable, expect []WantMetric) {
- expectedIds := make(map[metricID]struct{})
- for _, e := range expect {
- id := metricID{Name: e.Name, Scope: e.Scope}
- expectedIds[id] = struct{}{}
- m := mt.metrics[id]
- if nil == m {
- t.Error("unable to find metric", id)
- continue
- }
- if b, ok := e.Forced.(bool); ok {
- if b != (forced == m.forced) {
- t.Error("metric forced incorrect", b, m.forced, id)
- }
- }
- if nil != e.Data {
- expectMetricField(t, id, e.Data[0], m.data.countSatisfied, "countSatisfied")
- expectMetricField(t, id, e.Data[1], m.data.totalTolerated, "totalTolerated")
- expectMetricField(t, id, e.Data[2], m.data.exclusiveFailed, "exclusiveFailed")
- expectMetricField(t, id, e.Data[3], m.data.min, "min")
- expectMetricField(t, id, e.Data[4], m.data.max, "max")
- expectMetricField(t, id, e.Data[5], m.data.sumSquares, "sumSquares")
- }
- }
- }
- // ExpectMetrics allows testing of metrics. It passes if mt exactly matches expect.
- func ExpectMetrics(t Validator, mt *metricTable, expect []WantMetric) {
- if len(mt.metrics) != len(expect) {
- t.Error("metric counts do not match expectations", len(mt.metrics), len(expect))
- }
- expectedIds := make(map[metricID]struct{})
- for _, e := range expect {
- id := metricID{Name: e.Name, Scope: e.Scope}
- expectedIds[id] = struct{}{}
- m := mt.metrics[id]
- if nil == m {
- t.Error("unable to find metric", id)
- continue
- }
- if b, ok := e.Forced.(bool); ok {
- if b != (forced == m.forced) {
- t.Error("metric forced incorrect", b, m.forced, id)
- }
- }
- if nil != e.Data {
- expectMetricField(t, id, e.Data[0], m.data.countSatisfied, "countSatisfied")
- expectMetricField(t, id, e.Data[1], m.data.totalTolerated, "totalTolerated")
- expectMetricField(t, id, e.Data[2], m.data.exclusiveFailed, "exclusiveFailed")
- expectMetricField(t, id, e.Data[3], m.data.min, "min")
- expectMetricField(t, id, e.Data[4], m.data.max, "max")
- expectMetricField(t, id, e.Data[5], m.data.sumSquares, "sumSquares")
- }
- }
- for id := range mt.metrics {
- if _, ok := expectedIds[id]; !ok {
- t.Error("expected metrics does not contain", id.Name, id.Scope)
- }
- }
- }
- func expectAttributesPresent(v Validator, exists map[string]interface{}, expect map[string]interface{}) {
- for key, val := range expect {
- found, ok := exists[key]
- if !ok {
- v.Error("expected attribute not found: ", key)
- continue
- }
- if val == MatchAnything {
- continue
- }
- v1 := fmt.Sprint(found)
- v2 := fmt.Sprint(val)
- if v1 != v2 {
- v.Error("value difference", fmt.Sprintf("key=%s", key), v1, v2)
- }
- }
- }
- func expectAttributes(v Validator, exists map[string]interface{}, expect map[string]interface{}) {
- // TODO: This params comparison can be made smarter: Alert differences
- // based on sub/super set behavior.
- if len(exists) != len(expect) {
- v.Error("attributes length difference", len(exists), len(expect))
- }
- for key, val := range expect {
- found, ok := exists[key]
- if !ok {
- v.Error("expected attribute not found: ", key)
- continue
- }
- if val == MatchAnything {
- continue
- }
- v1 := fmt.Sprint(found)
- v2 := fmt.Sprint(val)
- if v1 != v2 {
- v.Error("value difference", fmt.Sprintf("key=%s", key), v1, v2)
- }
- }
- for key, val := range exists {
- _, ok := expect[key]
- if !ok {
- v.Error("unexpected attribute present: ", key, val)
- continue
- }
- }
- }
- // ExpectCustomEvents allows testing of custom events. It passes if cs exactly matches expect.
- func ExpectCustomEvents(v Validator, cs *customEvents, expect []WantEvent) {
- if len(cs.events.events) != len(expect) {
- v.Error("number of custom events does not match", len(cs.events.events),
- len(expect))
- return
- }
- for i, e := range expect {
- event, ok := cs.events.events[i].jsonWriter.(*CustomEvent)
- if !ok {
- v.Error("wrong custom event")
- } else {
- expectEvent(v, event, e)
- }
- }
- }
- func expectEventAbsent(v Validator, e json.Marshaler, names []string) {
- js, err := e.MarshalJSON()
- if nil != err {
- v.Error("unable to marshal event", err)
- return
- }
- var event []map[string]interface{}
- err = json.Unmarshal(js, &event)
- if nil != err {
- v.Error("unable to parse event json", err)
- return
- }
- intrinsics := event[0]
- userAttributes := event[1]
- agentAttributes := event[2]
- for _, name := range names {
- if _, ok := intrinsics[name]; ok {
- v.Error("unexpected key found", name)
- }
- if _, ok := userAttributes[name]; ok {
- v.Error("unexpected key found", name)
- }
- if _, ok := agentAttributes[name]; ok {
- v.Error("unexpected key found", name)
- }
- }
- }
- func expectEventPresent(v Validator, e json.Marshaler, expect WantEvent) {
- js, err := e.MarshalJSON()
- if nil != err {
- v.Error("unable to marshal event", err)
- return
- }
- var event []map[string]interface{}
- err = json.Unmarshal(js, &event)
- if nil != err {
- v.Error("unable to parse event json", err)
- return
- }
- intrinsics := event[0]
- userAttributes := event[1]
- agentAttributes := event[2]
- if nil != expect.Intrinsics {
- expectAttributesPresent(v, intrinsics, expect.Intrinsics)
- }
- if nil != expect.UserAttributes {
- expectAttributesPresent(v, userAttributes, expect.UserAttributes)
- }
- if nil != expect.AgentAttributes {
- expectAttributesPresent(v, agentAttributes, expect.AgentAttributes)
- }
- }
- func expectEvent(v Validator, e json.Marshaler, expect WantEvent) {
- js, err := e.MarshalJSON()
- if nil != err {
- v.Error("unable to marshal event", err)
- return
- }
- var event []map[string]interface{}
- err = json.Unmarshal(js, &event)
- if nil != err {
- v.Error("unable to parse event json", err)
- return
- }
- intrinsics := event[0]
- userAttributes := event[1]
- agentAttributes := event[2]
- if nil != expect.Intrinsics {
- expectAttributes(v, intrinsics, expect.Intrinsics)
- }
- if nil != expect.UserAttributes {
- expectAttributes(v, userAttributes, expect.UserAttributes)
- }
- if nil != expect.AgentAttributes {
- expectAttributes(v, agentAttributes, expect.AgentAttributes)
- }
- }
- // Second attributes have priority.
- func mergeAttributes(a1, a2 map[string]interface{}) map[string]interface{} {
- a := make(map[string]interface{})
- for k, v := range a1 {
- a[k] = v
- }
- for k, v := range a2 {
- a[k] = v
- }
- return a
- }
- // ExpectErrorEventsPresent allows testing of events with requiring an exact match
- func ExpectErrorEventsPresent(v Validator, events *errorEvents, expect []WantEvent) {
- for i, e := range expect {
- event, ok := events.events.events[i].jsonWriter.(*ErrorEvent)
- if !ok {
- v.Error("wrong span event in ExpectErrorEventsPresent")
- } else {
- expectEventPresent(v, event, e)
- }
- }
- }
- // ExpectErrorEventsAbsent allows testing that a set of attribute names are absent from the event data
- func ExpectErrorEventsAbsent(v Validator, events *errorEvents, names []string) {
- for _, eventHarvested := range events.events.events {
- event, ok := eventHarvested.jsonWriter.(*ErrorEvent)
- if !ok {
- v.Error("wrong span event in ExpectErrorEventsAbsent")
- } else {
- expectEventAbsent(v, event, names)
- }
- }
- }
- // ExpectErrorEvents allows testing of error events. It passes if events exactly matches expect.
- func ExpectErrorEvents(v Validator, events *errorEvents, expect []WantEvent) {
- if len(events.events.events) != len(expect) {
- v.Error("number of custom events does not match",
- len(events.events.events), len(expect))
- return
- }
- for i, e := range expect {
- event, ok := events.events.events[i].jsonWriter.(*ErrorEvent)
- if !ok {
- v.Error("wrong error event")
- } else {
- if nil != e.Intrinsics {
- e.Intrinsics = mergeAttributes(map[string]interface{}{
- // The following intrinsics should always be present in
- // error events:
- "type": "TransactionError",
- "timestamp": MatchAnything,
- "duration": MatchAnything,
- }, e.Intrinsics)
- }
- expectEvent(v, event, e)
- }
- }
- }
- // ExpectSpanEventsCount allows us to count how many events the system generated
- func ExpectSpanEventsCount(v Validator, events *spanEvents, c int) {
- len := len(events.events.events)
- if len != c {
- v.Error(fmt.Sprintf("expected %d span events, found %d", c, len))
- }
- }
- // ExpectSpanEventsPresent allows us to test for the presence and value of events
- // without also requiring an exact match
- func ExpectSpanEventsPresent(v Validator, events *spanEvents, expect []WantEvent) {
- for i, e := range expect {
- event, ok := events.events.events[i].jsonWriter.(*SpanEvent)
- if !ok {
- v.Error("wrong span event in ExpectSpanEventsPresent")
- } else {
- expectEventPresent(v, event, e)
- }
- }
- }
- // ExpectSpanEventsAbsent allows us to ensure that a set of attribute names are absent
- // from the event data
- func ExpectSpanEventsAbsent(v Validator, events *spanEvents, names []string) {
- for _, eventHarvested := range events.events.events {
- event, ok := eventHarvested.jsonWriter.(*SpanEvent)
- if !ok {
- v.Error("wrong span event in ExpectSpanEventsAbsent")
- } else {
- expectEventAbsent(v, event, names)
- }
- }
- }
- // ExpectSpanEvents allows testing of span events. It passes if events exactly matches expect.
- func ExpectSpanEvents(v Validator, events *spanEvents, expect []WantEvent) {
- if len(events.events.events) != len(expect) {
- v.Error("number of txn events does not match",
- len(events.events.events), len(expect))
- return
- }
- for i, e := range expect {
- event, ok := events.events.events[i].jsonWriter.(*SpanEvent)
- if !ok {
- v.Error("wrong span event")
- } else {
- if nil != e.Intrinsics {
- e.Intrinsics = mergeAttributes(map[string]interface{}{
- // The following intrinsics should always be present in
- // span events:
- "type": "Transaction",
- "timestamp": MatchAnything,
- "duration": MatchAnything,
- }, e.Intrinsics)
- }
- expectEvent(v, event, e)
- }
- }
- }
- // ExpectTxnEventsPresent allows us to test for the presence and value of events
- // without also requiring an exact match
- func ExpectTxnEventsPresent(v Validator, events *txnEvents, expect []WantEvent) {
- for i, e := range expect {
- event, ok := events.events.events[i].jsonWriter.(*TxnEvent)
- if !ok {
- v.Error("wrong txn event in ExpectTxnEventsPresent")
- } else {
- expectEventPresent(v, event, e)
- }
- }
- }
- // ExpectTxnEventsAbsent allows us to ensure that a set of attribute names are absent
- // from the event data
- func ExpectTxnEventsAbsent(v Validator, events *txnEvents, names []string) {
- for _, eventHarvested := range events.events.events {
- event, ok := eventHarvested.jsonWriter.(*TxnEvent)
- if !ok {
- v.Error("wrong txn event in ExpectTxnEventsAbsent")
- } else {
- expectEventAbsent(v, event, names)
- }
- }
- }
- // ExpectTxnEvents allows testing of txn events.
- func ExpectTxnEvents(v Validator, events *txnEvents, expect []WantEvent) {
- if len(events.events.events) != len(expect) {
- v.Error("number of txn events does not match",
- len(events.events.events), len(expect))
- return
- }
- for i, e := range expect {
- event, ok := events.events.events[i].jsonWriter.(*TxnEvent)
- if !ok {
- v.Error("wrong txn event")
- } else {
- if nil != e.Intrinsics {
- e.Intrinsics = mergeAttributes(map[string]interface{}{
- // The following intrinsics should always be present in
- // txn events:
- "type": "Transaction",
- "timestamp": MatchAnything,
- "duration": MatchAnything,
- "error": MatchAnything,
- }, e.Intrinsics)
- }
- expectEvent(v, event, e)
- }
- }
- }
- func expectError(v Validator, err *tracedError, expect WantError) {
- caller := topCallerNameBase(err.ErrorData.Stack)
- validateStringField(v, "caller", expect.Caller, caller)
- validateStringField(v, "txnName", expect.TxnName, err.FinalName)
- validateStringField(v, "klass", expect.Klass, err.Klass)
- validateStringField(v, "msg", expect.Msg, err.Msg)
- validateStringField(v, "URL", expect.URL, err.CleanURL)
- js, errr := err.MarshalJSON()
- if nil != errr {
- v.Error("unable to marshal error json", errr)
- return
- }
- var unmarshalled []interface{}
- errr = json.Unmarshal(js, &unmarshalled)
- if nil != errr {
- v.Error("unable to unmarshal error json", errr)
- return
- }
- attributes := unmarshalled[4].(map[string]interface{})
- agentAttributes := attributes["agentAttributes"].(map[string]interface{})
- userAttributes := attributes["userAttributes"].(map[string]interface{})
- if nil != expect.UserAttributes {
- expectAttributes(v, userAttributes, expect.UserAttributes)
- }
- if nil != expect.AgentAttributes {
- expectAttributes(v, agentAttributes, expect.AgentAttributes)
- }
- }
- // ExpectErrors allows testing of errors.
- func ExpectErrors(v Validator, errors harvestErrors, expect []WantError) {
- if len(errors) != len(expect) {
- v.Error("number of errors mismatch", len(errors), len(expect))
- return
- }
- for i, e := range expect {
- expectError(v, errors[i], e)
- }
- }
- func countSegments(node []interface{}) int {
- count := 1
- children := node[4].([]interface{})
- for _, c := range children {
- node := c.([]interface{})
- count += countSegments(node)
- }
- return count
- }
- func expectTxnTrace(v Validator, got json.Marshaler, expect WantTxnTrace) {
- js, err := got.MarshalJSON()
- if nil != err {
- v.Error("unable to marshal txn trace json", err)
- return
- }
- var unmarshalled []interface{}
- err = json.Unmarshal(js, &unmarshalled)
- if nil != err {
- v.Error("unable to unmarshal error json", err)
- return
- }
- duration := unmarshalled[1].(float64)
- name := unmarshalled[2].(string)
- cleanURL := unmarshalled[3].(string)
- traceData := unmarshalled[4].([]interface{})
- rootNode := traceData[3].([]interface{})
- attributes := traceData[4].(map[string]interface{})
- userAttributes := attributes["userAttributes"].(map[string]interface{})
- agentAttributes := attributes["agentAttributes"].(map[string]interface{})
- validateStringField(v, "metric name", expect.MetricName, name)
- validateStringField(v, "request url", expect.CleanURL, cleanURL)
- if doDurationTests && 0 == duration {
- v.Error("zero trace duration")
- }
- if nil != expect.UserAttributes {
- expectAttributes(v, userAttributes, expect.UserAttributes)
- }
- if nil != expect.AgentAttributes {
- expectAttributes(v, agentAttributes, expect.AgentAttributes)
- }
- numSegments := countSegments(rootNode)
- // The expectation segment count does not include the two root nodes.
- numSegments -= 2
- if expect.NumSegments != numSegments {
- v.Error("wrong number of segments", expect.NumSegments, numSegments)
- }
- }
- // ExpectTxnTraces allows testing of transaction traces.
- func ExpectTxnTraces(v Validator, traces *harvestTraces, want []WantTxnTrace) {
- if len(want) != traces.Len() {
- v.Error("number of traces do not match", len(want), traces.Len())
- }
- actual := traces.slice()
- for i, expected := range want {
- expectTxnTrace(v, actual[i], expected)
- }
- }
- func expectSlowQuery(t Validator, slowQuery *slowQuery, want WantSlowQuery) {
- if slowQuery.Count != want.Count {
- t.Error("wrong Count field", slowQuery.Count, want.Count)
- }
- validateStringField(t, "MetricName", slowQuery.DatastoreMetric, want.MetricName)
- validateStringField(t, "Query", slowQuery.ParameterizedQuery, want.Query)
- validateStringField(t, "TxnEvent.FinalName", slowQuery.TxnEvent.FinalName, want.TxnName)
- validateStringField(t, "TxnEvent.CleanURL", slowQuery.TxnEvent.CleanURL, want.TxnURL)
- validateStringField(t, "DatabaseName", slowQuery.DatabaseName, want.DatabaseName)
- validateStringField(t, "Host", slowQuery.Host, want.Host)
- validateStringField(t, "PortPathOrID", slowQuery.PortPathOrID, want.PortPathOrID)
- expectAttributes(t, map[string]interface{}(slowQuery.QueryParameters), want.Params)
- }
- // ExpectSlowQueries allows testing of slow queries.
- func ExpectSlowQueries(t Validator, slowQueries *slowQueries, want []WantSlowQuery) {
- if len(want) != len(slowQueries.priorityQueue) {
- t.Error("wrong number of slow queries",
- "expected", len(want), "got", len(slowQueries.priorityQueue))
- return
- }
- for _, s := range want {
- idx, ok := slowQueries.lookup[s.Query]
- if !ok {
- t.Error("unable to find slow query", s.Query)
- continue
- }
- expectSlowQuery(t, slowQueries.priorityQueue[idx], s)
- }
- }
|