github.com/newrelic/go-agent@v3.26.0+incompatible/internal/expect.go (about)

     1  // Copyright 2020 New Relic Corporation. All rights reserved.
     2  // SPDX-License-Identifier: Apache-2.0
     3  
     4  package internal
     5  
     6  import (
     7  	"encoding/json"
     8  	"fmt"
     9  	"runtime"
    10  
    11  	"time"
    12  )
    13  
    14  var (
    15  	// Unfortunately, the resolution of time.Now() on Windows is coarse: Two
    16  	// sequential calls to time.Now() may return the same value, and tests
    17  	// which expect non-zero durations may fail.  To avoid adding sleep
    18  	// statements or mocking time.Now(), those tests are skipped on Windows.
    19  	doDurationTests = runtime.GOOS != `windows`
    20  )
    21  
    22  // Validator is used for testing.
    23  type Validator interface {
    24  	Error(...interface{})
    25  }
    26  
    27  func validateStringField(v Validator, fieldName, expect, actual string) {
    28  	// If an expected value is not set, we assume the user does not want to validate it
    29  	if expect == "" {
    30  		return
    31  	}
    32  	if expect != actual {
    33  		v.Error(fieldName, "incorrect: Expected:", expect, " Got:", actual)
    34  	}
    35  }
    36  
    37  type addValidatorField struct {
    38  	field    interface{}
    39  	original Validator
    40  }
    41  
    42  func (a addValidatorField) Error(fields ...interface{}) {
    43  	fields = append([]interface{}{a.field}, fields...)
    44  	a.original.Error(fields...)
    45  }
    46  
    47  // ExtendValidator is used to add more context to a validator.
    48  func ExtendValidator(v Validator, field interface{}) Validator {
    49  	return addValidatorField{
    50  		field:    field,
    51  		original: v,
    52  	}
    53  }
    54  
    55  // WantMetric is a metric expectation.  If Data is nil, then any data values are
    56  // acceptable.  If Data has len 1, then only the metric count is validated.
    57  type WantMetric struct {
    58  	Name   string
    59  	Scope  string
    60  	Forced interface{} // true, false, or nil
    61  	Data   []float64
    62  }
    63  
    64  // WantError is a traced error expectation.
    65  type WantError struct {
    66  	TxnName         string
    67  	Msg             string
    68  	Klass           string
    69  	UserAttributes  map[string]interface{}
    70  	AgentAttributes map[string]interface{}
    71  }
    72  
    73  func uniquePointer() *struct{} {
    74  	s := struct{}{}
    75  	return &s
    76  }
    77  
    78  var (
    79  	// MatchAnything is for use when matching attributes.
    80  	MatchAnything = uniquePointer()
    81  )
    82  
    83  // WantEvent is a transaction or error event expectation.
    84  type WantEvent struct {
    85  	Intrinsics      map[string]interface{}
    86  	UserAttributes  map[string]interface{}
    87  	AgentAttributes map[string]interface{}
    88  }
    89  
    90  // WantTxnTrace is a transaction trace expectation.
    91  type WantTxnTrace struct {
    92  	MetricName      string
    93  	NumSegments     int
    94  	UserAttributes  map[string]interface{}
    95  	AgentAttributes map[string]interface{}
    96  	Intrinsics      map[string]interface{}
    97  	// If the Root's SegmentName is populated then the segments will be
    98  	// tested, otherwise NumSegments will be tested.
    99  	Root WantTraceSegment
   100  }
   101  
   102  // WantTraceSegment is a transaction trace segment expectation.
   103  type WantTraceSegment struct {
   104  	SegmentName string
   105  	// RelativeStartMillis and RelativeStopMillis will be tested if they are
   106  	// provided:  This makes it easy for top level tests which cannot
   107  	// control duration.
   108  	RelativeStartMillis interface{}
   109  	RelativeStopMillis  interface{}
   110  	Attributes          map[string]interface{}
   111  	Children            []WantTraceSegment
   112  }
   113  
   114  // WantSlowQuery is a slowQuery expectation.
   115  type WantSlowQuery struct {
   116  	Count        int32
   117  	MetricName   string
   118  	Query        string
   119  	TxnName      string
   120  	TxnURL       string
   121  	DatabaseName string
   122  	Host         string
   123  	PortPathOrID string
   124  	Params       map[string]interface{}
   125  }
   126  
   127  // HarvestTestinger is implemented by the app.  It sets an empty test harvest
   128  // and modifies the connect reply if a callback is provided.
   129  type HarvestTestinger interface {
   130  	HarvestTesting(replyfn func(*ConnectReply))
   131  }
   132  
   133  // HarvestTesting allows integration packages to test instrumentation.
   134  func HarvestTesting(app interface{}, replyfn func(*ConnectReply)) {
   135  	ta, ok := app.(HarvestTestinger)
   136  	if !ok {
   137  		panic("HarvestTesting type assertion failure")
   138  	}
   139  	ta.HarvestTesting(replyfn)
   140  }
   141  
   142  // WantTxn provides the expectation parameters to ExpectTxnMetrics.
   143  type WantTxn struct {
   144  	Name      string
   145  	IsWeb     bool
   146  	NumErrors int
   147  }
   148  
   149  // ExpectTxnMetrics tests that the app contains metrics for a transaction.
   150  func ExpectTxnMetrics(t Validator, mt *metricTable, want WantTxn) {
   151  	var metrics []WantMetric
   152  	var scope string
   153  	var allWebOther string
   154  	if want.IsWeb {
   155  		scope = "WebTransaction/Go/" + want.Name
   156  		allWebOther = "allWeb"
   157  		metrics = []WantMetric{
   158  			{Name: "WebTransaction/Go/" + want.Name, Scope: "", Forced: true, Data: nil},
   159  			{Name: "WebTransaction", Scope: "", Forced: true, Data: nil},
   160  			{Name: "WebTransactionTotalTime/Go/" + want.Name, Scope: "", Forced: false, Data: nil},
   161  			{Name: "WebTransactionTotalTime", Scope: "", Forced: true, Data: nil},
   162  			{Name: "HttpDispatcher", Scope: "", Forced: true, Data: nil},
   163  			{Name: "Apdex", Scope: "", Forced: true, Data: nil},
   164  			{Name: "Apdex/Go/" + want.Name, Scope: "", Forced: false, Data: nil},
   165  		}
   166  	} else {
   167  		scope = "OtherTransaction/Go/" + want.Name
   168  		allWebOther = "allOther"
   169  		metrics = []WantMetric{
   170  			{Name: "OtherTransaction/Go/" + want.Name, Scope: "", Forced: true, Data: nil},
   171  			{Name: "OtherTransaction/all", Scope: "", Forced: true, Data: nil},
   172  			{Name: "OtherTransactionTotalTime/Go/" + want.Name, Scope: "", Forced: false, Data: nil},
   173  			{Name: "OtherTransactionTotalTime", Scope: "", Forced: true, Data: nil},
   174  		}
   175  	}
   176  	if want.NumErrors > 0 {
   177  		data := []float64{float64(want.NumErrors), 0, 0, 0, 0, 0}
   178  		metrics = append(metrics, []WantMetric{
   179  			{Name: "Errors/all", Scope: "", Forced: true, Data: data},
   180  			{Name: "Errors/" + allWebOther, Scope: "", Forced: true, Data: data},
   181  			{Name: "Errors/" + scope, Scope: "", Forced: true, Data: data},
   182  		}...)
   183  	}
   184  	ExpectMetrics(t, mt, metrics)
   185  }
   186  
   187  // Expect exposes methods that allow for testing whether the correct data was
   188  // captured.
   189  type Expect interface {
   190  	ExpectCustomEvents(t Validator, want []WantEvent)
   191  	ExpectErrors(t Validator, want []WantError)
   192  	ExpectErrorEvents(t Validator, want []WantEvent)
   193  
   194  	ExpectTxnEvents(t Validator, want []WantEvent)
   195  
   196  	ExpectMetrics(t Validator, want []WantMetric)
   197  	ExpectMetricsPresent(t Validator, want []WantMetric)
   198  	ExpectTxnMetrics(t Validator, want WantTxn)
   199  
   200  	ExpectTxnTraces(t Validator, want []WantTxnTrace)
   201  	ExpectSlowQueries(t Validator, want []WantSlowQuery)
   202  
   203  	ExpectSpanEvents(t Validator, want []WantEvent)
   204  }
   205  
   206  func expectMetricField(t Validator, id metricID, v1, v2 float64, fieldName string) {
   207  	if v1 != v2 {
   208  		t.Error("metric fields do not match", id, v1, v2, fieldName)
   209  	}
   210  }
   211  
   212  // ExpectMetricsPresent allows testing of metrics without requiring an exact match
   213  func ExpectMetricsPresent(t Validator, mt *metricTable, expect []WantMetric) {
   214  	expectMetrics(t, mt, expect, false)
   215  }
   216  
   217  // ExpectMetrics allows testing of metrics.  It passes if mt exactly matches expect.
   218  func ExpectMetrics(t Validator, mt *metricTable, expect []WantMetric) {
   219  	expectMetrics(t, mt, expect, true)
   220  }
   221  
   222  func expectMetrics(t Validator, mt *metricTable, expect []WantMetric, exactMatch bool) {
   223  	if exactMatch {
   224  		if len(mt.metrics) != len(expect) {
   225  			t.Error("metric counts do not match expectations", len(mt.metrics), len(expect))
   226  		}
   227  	}
   228  	expectedIds := make(map[metricID]struct{})
   229  	for _, e := range expect {
   230  		id := metricID{Name: e.Name, Scope: e.Scope}
   231  		expectedIds[id] = struct{}{}
   232  		m := mt.metrics[id]
   233  		if nil == m {
   234  			t.Error("unable to find metric", id)
   235  			continue
   236  		}
   237  
   238  		if b, ok := e.Forced.(bool); ok {
   239  			if b != (forced == m.forced) {
   240  				t.Error("metric forced incorrect", b, m.forced, id)
   241  			}
   242  		}
   243  
   244  		if nil != e.Data {
   245  			expectMetricField(t, id, e.Data[0], m.data.countSatisfied, "countSatisfied")
   246  
   247  			if len(e.Data) > 1 {
   248  				expectMetricField(t, id, e.Data[1], m.data.totalTolerated, "totalTolerated")
   249  				expectMetricField(t, id, e.Data[2], m.data.exclusiveFailed, "exclusiveFailed")
   250  				expectMetricField(t, id, e.Data[3], m.data.min, "min")
   251  				expectMetricField(t, id, e.Data[4], m.data.max, "max")
   252  				expectMetricField(t, id, e.Data[5], m.data.sumSquares, "sumSquares")
   253  			}
   254  		}
   255  	}
   256  	if exactMatch {
   257  		for id := range mt.metrics {
   258  			if _, ok := expectedIds[id]; !ok {
   259  				t.Error("expected metrics does not contain", id.Name, id.Scope)
   260  			}
   261  		}
   262  	}
   263  }
   264  
   265  func expectAttributes(v Validator, exists map[string]interface{}, expect map[string]interface{}) {
   266  	// TODO: This params comparison can be made smarter: Alert differences
   267  	// based on sub/super set behavior.
   268  	if len(exists) != len(expect) {
   269  		v.Error("attributes length difference", len(exists), len(expect))
   270  	}
   271  	for key, val := range expect {
   272  		found, ok := exists[key]
   273  		if !ok {
   274  			v.Error("expected attribute not found: ", key)
   275  			continue
   276  		}
   277  		if val == MatchAnything {
   278  			continue
   279  		}
   280  		v1 := fmt.Sprint(found)
   281  		v2 := fmt.Sprint(val)
   282  		if v1 != v2 {
   283  			v.Error("value difference", fmt.Sprintf("key=%s", key), v1, v2)
   284  		}
   285  	}
   286  	for key, val := range exists {
   287  		_, ok := expect[key]
   288  		if !ok {
   289  			v.Error("unexpected attribute present: ", key, val)
   290  			continue
   291  		}
   292  	}
   293  }
   294  
   295  // ExpectCustomEvents allows testing of custom events.  It passes if cs exactly matches expect.
   296  func ExpectCustomEvents(v Validator, cs *customEvents, expect []WantEvent) {
   297  	expectEvents(v, cs.analyticsEvents, expect, nil)
   298  }
   299  
   300  func expectEvent(v Validator, e json.Marshaler, expect WantEvent) {
   301  	js, err := e.MarshalJSON()
   302  	if nil != err {
   303  		v.Error("unable to marshal event", err)
   304  		return
   305  	}
   306  	var event []map[string]interface{}
   307  	err = json.Unmarshal(js, &event)
   308  	if nil != err {
   309  		v.Error("unable to parse event json", err)
   310  		return
   311  	}
   312  	intrinsics := event[0]
   313  	userAttributes := event[1]
   314  	agentAttributes := event[2]
   315  
   316  	if nil != expect.Intrinsics {
   317  		expectAttributes(v, intrinsics, expect.Intrinsics)
   318  	}
   319  	if nil != expect.UserAttributes {
   320  		expectAttributes(v, userAttributes, expect.UserAttributes)
   321  	}
   322  	if nil != expect.AgentAttributes {
   323  		expectAttributes(v, agentAttributes, expect.AgentAttributes)
   324  	}
   325  }
   326  
   327  func expectEvents(v Validator, events *analyticsEvents, expect []WantEvent, extraAttributes map[string]interface{}) {
   328  	if len(events.events) != len(expect) {
   329  		v.Error("number of events does not match", len(events.events), len(expect))
   330  		return
   331  	}
   332  	for i, e := range expect {
   333  		event, ok := events.events[i].jsonWriter.(json.Marshaler)
   334  		if !ok {
   335  			v.Error("event does not implement json.Marshaler")
   336  			continue
   337  		}
   338  		if nil != e.Intrinsics {
   339  			e.Intrinsics = mergeAttributes(extraAttributes, e.Intrinsics)
   340  		}
   341  		expectEvent(v, event, e)
   342  	}
   343  }
   344  
   345  // Second attributes have priority.
   346  func mergeAttributes(a1, a2 map[string]interface{}) map[string]interface{} {
   347  	a := make(map[string]interface{})
   348  	for k, v := range a1 {
   349  		a[k] = v
   350  	}
   351  	for k, v := range a2 {
   352  		a[k] = v
   353  	}
   354  	return a
   355  }
   356  
   357  // ExpectErrorEvents allows testing of error events.  It passes if events exactly matches expect.
   358  func ExpectErrorEvents(v Validator, events *errorEvents, expect []WantEvent) {
   359  	expectEvents(v, events.analyticsEvents, expect, map[string]interface{}{
   360  		// The following intrinsics should always be present in
   361  		// error events:
   362  		"type":      "TransactionError",
   363  		"timestamp": MatchAnything,
   364  		"duration":  MatchAnything,
   365  	})
   366  }
   367  
   368  // ExpectSpanEvents allows testing of span events.  It passes if events exactly matches expect.
   369  func ExpectSpanEvents(v Validator, events *spanEvents, expect []WantEvent) {
   370  	expectEvents(v, events.analyticsEvents, expect, map[string]interface{}{
   371  		// The following intrinsics should always be present in
   372  		// span events:
   373  		"type":          "Span",
   374  		"timestamp":     MatchAnything,
   375  		"duration":      MatchAnything,
   376  		"traceId":       MatchAnything,
   377  		"guid":          MatchAnything,
   378  		"transactionId": MatchAnything,
   379  		// All span events are currently sampled.
   380  		"sampled":  true,
   381  		"priority": MatchAnything,
   382  	})
   383  }
   384  
   385  // ExpectTxnEvents allows testing of txn events.
   386  func ExpectTxnEvents(v Validator, events *txnEvents, expect []WantEvent) {
   387  	expectEvents(v, events.analyticsEvents, expect, map[string]interface{}{
   388  		// The following intrinsics should always be present in
   389  		// txn events:
   390  		"type":      "Transaction",
   391  		"timestamp": MatchAnything,
   392  		"duration":  MatchAnything,
   393  		"totalTime": MatchAnything,
   394  		"error":     MatchAnything,
   395  	})
   396  }
   397  
   398  func expectError(v Validator, err *tracedError, expect WantError) {
   399  	validateStringField(v, "txnName", expect.TxnName, err.FinalName)
   400  	validateStringField(v, "klass", expect.Klass, err.Klass)
   401  	validateStringField(v, "msg", expect.Msg, err.Msg)
   402  	js, errr := err.MarshalJSON()
   403  	if nil != errr {
   404  		v.Error("unable to marshal error json", errr)
   405  		return
   406  	}
   407  	var unmarshalled []interface{}
   408  	errr = json.Unmarshal(js, &unmarshalled)
   409  	if nil != errr {
   410  		v.Error("unable to unmarshal error json", errr)
   411  		return
   412  	}
   413  	attributes := unmarshalled[4].(map[string]interface{})
   414  	agentAttributes := attributes["agentAttributes"].(map[string]interface{})
   415  	userAttributes := attributes["userAttributes"].(map[string]interface{})
   416  
   417  	if nil != expect.UserAttributes {
   418  		expectAttributes(v, userAttributes, expect.UserAttributes)
   419  	}
   420  	if nil != expect.AgentAttributes {
   421  		expectAttributes(v, agentAttributes, expect.AgentAttributes)
   422  	}
   423  	if stack := attributes["stack_trace"]; nil == stack {
   424  		v.Error("missing error stack trace")
   425  	}
   426  }
   427  
   428  // ExpectErrors allows testing of errors.
   429  func ExpectErrors(v Validator, errors harvestErrors, expect []WantError) {
   430  	if len(errors) != len(expect) {
   431  		v.Error("number of errors mismatch", len(errors), len(expect))
   432  		return
   433  	}
   434  	for i, e := range expect {
   435  		expectError(v, errors[i], e)
   436  	}
   437  }
   438  
   439  func countSegments(node []interface{}) int {
   440  	count := 1
   441  	children := node[4].([]interface{})
   442  	for _, c := range children {
   443  		node := c.([]interface{})
   444  		count += countSegments(node)
   445  	}
   446  	return count
   447  }
   448  
   449  func expectTraceSegment(v Validator, nodeObj interface{}, expect WantTraceSegment) {
   450  	node := nodeObj.([]interface{})
   451  	start := int(node[0].(float64))
   452  	stop := int(node[1].(float64))
   453  	name := node[2].(string)
   454  	attributes := node[3].(map[string]interface{})
   455  	children := node[4].([]interface{})
   456  
   457  	validateStringField(v, "segmentName", expect.SegmentName, name)
   458  	if nil != expect.RelativeStartMillis {
   459  		expectStart, ok := expect.RelativeStartMillis.(int)
   460  		if !ok {
   461  			v.Error("invalid expect.RelativeStartMillis", expect.RelativeStartMillis)
   462  		} else if expectStart != start {
   463  			v.Error("segmentStartTime", expect.SegmentName, start, expectStart)
   464  		}
   465  	}
   466  	if nil != expect.RelativeStopMillis {
   467  		expectStop, ok := expect.RelativeStopMillis.(int)
   468  		if !ok {
   469  			v.Error("invalid expect.RelativeStopMillis", expect.RelativeStopMillis)
   470  		} else if expectStop != stop {
   471  			v.Error("segmentStopTime", expect.SegmentName, stop, expectStop)
   472  		}
   473  	}
   474  	if nil != expect.Attributes {
   475  		expectAttributes(v, attributes, expect.Attributes)
   476  	}
   477  	if len(children) != len(expect.Children) {
   478  		v.Error("segmentChildrenCount", expect.SegmentName, len(children), len(expect.Children))
   479  	} else {
   480  		for idx, child := range children {
   481  			expectTraceSegment(v, child, expect.Children[idx])
   482  		}
   483  	}
   484  }
   485  
   486  func expectTxnTrace(v Validator, got interface{}, expect WantTxnTrace) {
   487  	unmarshalled := got.([]interface{})
   488  	duration := unmarshalled[1].(float64)
   489  	name := unmarshalled[2].(string)
   490  	var arrayURL string
   491  	if nil != unmarshalled[3] {
   492  		arrayURL = unmarshalled[3].(string)
   493  	}
   494  	traceData := unmarshalled[4].([]interface{})
   495  
   496  	rootNode := traceData[3].([]interface{})
   497  	attributes := traceData[4].(map[string]interface{})
   498  	userAttributes := attributes["userAttributes"].(map[string]interface{})
   499  	agentAttributes := attributes["agentAttributes"].(map[string]interface{})
   500  	intrinsics := attributes["intrinsics"].(map[string]interface{})
   501  
   502  	validateStringField(v, "metric name", expect.MetricName, name)
   503  
   504  	if doDurationTests && 0 == duration {
   505  		v.Error("zero trace duration")
   506  	}
   507  
   508  	if nil != expect.UserAttributes {
   509  		expectAttributes(v, userAttributes, expect.UserAttributes)
   510  	}
   511  	if nil != expect.AgentAttributes {
   512  		expectAttributes(v, agentAttributes, expect.AgentAttributes)
   513  		expectURL, _ := expect.AgentAttributes["request.uri"].(string)
   514  		if "" != expectURL {
   515  			validateStringField(v, "request url in array", expectURL, arrayURL)
   516  		}
   517  	}
   518  	if nil != expect.Intrinsics {
   519  		expectAttributes(v, intrinsics, expect.Intrinsics)
   520  	}
   521  	if expect.Root.SegmentName != "" {
   522  		expectTraceSegment(v, rootNode, expect.Root)
   523  	} else {
   524  		numSegments := countSegments(rootNode)
   525  		// The expectation segment count does not include the two root nodes.
   526  		numSegments -= 2
   527  		if expect.NumSegments != numSegments {
   528  			v.Error("wrong number of segments", expect.NumSegments, numSegments)
   529  		}
   530  	}
   531  }
   532  
   533  // ExpectTxnTraces allows testing of transaction traces.
   534  func ExpectTxnTraces(v Validator, traces *harvestTraces, want []WantTxnTrace) {
   535  	if len(want) != traces.Len() {
   536  		v.Error("number of traces do not match", len(want), traces.Len())
   537  		return
   538  	}
   539  	if len(want) == 0 {
   540  		return
   541  	}
   542  	js, err := traces.Data("agentRunID", time.Now())
   543  	if nil != err {
   544  		v.Error("error creasing harvest traces data", err)
   545  		return
   546  	}
   547  
   548  	var unmarshalled []interface{}
   549  	err = json.Unmarshal(js, &unmarshalled)
   550  	if nil != err {
   551  		v.Error("unable to unmarshal error json", err)
   552  		return
   553  	}
   554  	if "agentRunID" != unmarshalled[0].(string) {
   555  		v.Error("traces agent run id wrong", unmarshalled[0])
   556  		return
   557  	}
   558  	gotTraces := unmarshalled[1].([]interface{})
   559  	if len(gotTraces) != len(want) {
   560  		v.Error("number of traces in json does not match", len(gotTraces), len(want))
   561  		return
   562  	}
   563  	for i, expected := range want {
   564  		expectTxnTrace(v, gotTraces[i], expected)
   565  	}
   566  }
   567  
   568  func expectSlowQuery(t Validator, slowQuery *slowQuery, want WantSlowQuery) {
   569  	if slowQuery.Count != want.Count {
   570  		t.Error("wrong Count field", slowQuery.Count, want.Count)
   571  	}
   572  	uri, _ := slowQuery.TxnEvent.Attrs.GetAgentValue(attributeRequestURI, destTxnTrace)
   573  	validateStringField(t, "MetricName", slowQuery.DatastoreMetric, want.MetricName)
   574  	validateStringField(t, "Query", slowQuery.ParameterizedQuery, want.Query)
   575  	validateStringField(t, "TxnEvent.FinalName", slowQuery.TxnEvent.FinalName, want.TxnName)
   576  	validateStringField(t, "request.uri", uri, want.TxnURL)
   577  	validateStringField(t, "DatabaseName", slowQuery.DatabaseName, want.DatabaseName)
   578  	validateStringField(t, "Host", slowQuery.Host, want.Host)
   579  	validateStringField(t, "PortPathOrID", slowQuery.PortPathOrID, want.PortPathOrID)
   580  	expectAttributes(t, map[string]interface{}(slowQuery.QueryParameters), want.Params)
   581  }
   582  
   583  // ExpectSlowQueries allows testing of slow queries.
   584  func ExpectSlowQueries(t Validator, slowQueries *slowQueries, want []WantSlowQuery) {
   585  	if len(want) != len(slowQueries.priorityQueue) {
   586  		t.Error("wrong number of slow queries",
   587  			"expected", len(want), "got", len(slowQueries.priorityQueue))
   588  		return
   589  	}
   590  	for _, s := range want {
   591  		idx, ok := slowQueries.lookup[s.Query]
   592  		if !ok {
   593  			t.Error("unable to find slow query", s.Query)
   594  			continue
   595  		}
   596  		expectSlowQuery(t, slowQueries.priorityQueue[idx], s)
   597  	}
   598  }