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 }