github.com/lulzWill/go-agent@v2.1.2+incompatible/internal/expect.go (about) 1 package internal 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "runtime" 7 ) 8 9 var ( 10 // Unfortunately, the resolution of time.Now() on Windows is coarse: Two 11 // sequential calls to time.Now() may return the same value, and tests 12 // which expect non-zero durations may fail. To avoid adding sleep 13 // statements or mocking time.Now(), those tests are skipped on Windows. 14 doDurationTests = runtime.GOOS != `windows` 15 ) 16 17 // Validator is used for testing. 18 type Validator interface { 19 Error(...interface{}) 20 } 21 22 func validateStringField(v Validator, fieldName, v1, v2 string) { 23 if v1 != v2 { 24 v.Error(fieldName, v1, v2) 25 } 26 } 27 28 type addValidatorField struct { 29 field interface{} 30 original Validator 31 } 32 33 func (a addValidatorField) Error(fields ...interface{}) { 34 fields = append([]interface{}{a.field}, fields...) 35 a.original.Error(fields...) 36 } 37 38 // ExtendValidator is used to add more context to a validator. 39 func ExtendValidator(v Validator, field interface{}) Validator { 40 return addValidatorField{ 41 field: field, 42 original: v, 43 } 44 } 45 46 // WantMetric is a metric expectation. If Data is nil, then any data values are 47 // acceptable. 48 type WantMetric struct { 49 Name string 50 Scope string 51 Forced interface{} // true, false, or nil 52 Data []float64 53 } 54 55 // WantError is a traced error expectation. 56 type WantError struct { 57 TxnName string 58 Msg string 59 Klass string 60 Caller string 61 URL string 62 UserAttributes map[string]interface{} 63 AgentAttributes map[string]interface{} 64 } 65 66 func uniquePointer() *struct{} { 67 s := struct{}{} 68 return &s 69 } 70 71 var ( 72 // MatchAnything is for use when matching attributes. 73 MatchAnything = uniquePointer() 74 ) 75 76 // WantEvent is a transaction or error event expectation. 77 type WantEvent struct { 78 Intrinsics map[string]interface{} 79 UserAttributes map[string]interface{} 80 AgentAttributes map[string]interface{} 81 } 82 83 // WantTxnTrace is a transaction trace expectation. 84 type WantTxnTrace struct { 85 MetricName string 86 CleanURL string 87 NumSegments int 88 UserAttributes map[string]interface{} 89 AgentAttributes map[string]interface{} 90 } 91 92 // WantSlowQuery is a slowQuery expectation. 93 type WantSlowQuery struct { 94 Count int32 95 MetricName string 96 Query string 97 TxnName string 98 TxnURL string 99 DatabaseName string 100 Host string 101 PortPathOrID string 102 Params map[string]interface{} 103 } 104 105 // Expect exposes methods that allow for testing whether the correct data was 106 // captured. 107 type Expect interface { 108 ExpectCustomEvents(t Validator, want []WantEvent) 109 ExpectErrors(t Validator, want []WantError) 110 ExpectErrorEvents(t Validator, want []WantEvent) 111 ExpectTxnEvents(t Validator, want []WantEvent) 112 ExpectMetrics(t Validator, want []WantMetric) 113 ExpectTxnTraces(t Validator, want []WantTxnTrace) 114 ExpectSlowQueries(t Validator, want []WantSlowQuery) 115 } 116 117 func expectMetricField(t Validator, id metricID, v1, v2 float64, fieldName string) { 118 if v1 != v2 { 119 t.Error("metric fields do not match", id, v1, v2, fieldName) 120 } 121 } 122 123 // ExpectMetrics allows testing of metrics. 124 func ExpectMetrics(t Validator, mt *metricTable, expect []WantMetric) { 125 if len(mt.metrics) != len(expect) { 126 t.Error("metric counts do not match expectations", len(mt.metrics), len(expect)) 127 } 128 expectedIds := make(map[metricID]struct{}) 129 for _, e := range expect { 130 id := metricID{Name: e.Name, Scope: e.Scope} 131 expectedIds[id] = struct{}{} 132 m := mt.metrics[id] 133 if nil == m { 134 t.Error("unable to find metric", id) 135 continue 136 } 137 138 if b, ok := e.Forced.(bool); ok { 139 if b != (forced == m.forced) { 140 t.Error("metric forced incorrect", b, m.forced, id) 141 } 142 } 143 144 if nil != e.Data { 145 expectMetricField(t, id, e.Data[0], m.data.countSatisfied, "countSatisfied") 146 expectMetricField(t, id, e.Data[1], m.data.totalTolerated, "totalTolerated") 147 expectMetricField(t, id, e.Data[2], m.data.exclusiveFailed, "exclusiveFailed") 148 expectMetricField(t, id, e.Data[3], m.data.min, "min") 149 expectMetricField(t, id, e.Data[4], m.data.max, "max") 150 expectMetricField(t, id, e.Data[5], m.data.sumSquares, "sumSquares") 151 } 152 } 153 for id := range mt.metrics { 154 if _, ok := expectedIds[id]; !ok { 155 t.Error("expected metrics does not contain", id.Name, id.Scope) 156 } 157 } 158 } 159 160 func expectAttributes(v Validator, exists map[string]interface{}, expect map[string]interface{}) { 161 // TODO: This params comparison can be made smarter: Alert differences 162 // based on sub/super set behavior. 163 if len(exists) != len(expect) { 164 v.Error("attributes length difference", len(exists), len(expect)) 165 } 166 for key, val := range expect { 167 found, ok := exists[key] 168 if !ok { 169 v.Error("expected attribute not found: ", key) 170 continue 171 } 172 if val == MatchAnything { 173 continue 174 } 175 v1 := fmt.Sprint(found) 176 v2 := fmt.Sprint(val) 177 if v1 != v2 { 178 v.Error("value difference", fmt.Sprintf("key=%s", key), v1, v2) 179 } 180 } 181 for key, val := range exists { 182 _, ok := expect[key] 183 if !ok { 184 v.Error("unexpected attribute present: ", key, val) 185 continue 186 } 187 } 188 } 189 190 // ExpectCustomEvents allows testing of custom events. 191 func ExpectCustomEvents(v Validator, cs *customEvents, expect []WantEvent) { 192 if len(cs.events.events) != len(expect) { 193 v.Error("number of custom events does not match", len(cs.events.events), 194 len(expect)) 195 return 196 } 197 for i, e := range expect { 198 event, ok := cs.events.events[i].jsonWriter.(*CustomEvent) 199 if !ok { 200 v.Error("wrong custom event") 201 } else { 202 expectEvent(v, event, e) 203 } 204 } 205 } 206 207 func expectEvent(v Validator, e json.Marshaler, expect WantEvent) { 208 js, err := e.MarshalJSON() 209 if nil != err { 210 v.Error("unable to marshal event", err) 211 return 212 } 213 var event []map[string]interface{} 214 err = json.Unmarshal(js, &event) 215 if nil != err { 216 v.Error("unable to parse event json", err) 217 return 218 } 219 intrinsics := event[0] 220 userAttributes := event[1] 221 agentAttributes := event[2] 222 223 if nil != expect.Intrinsics { 224 expectAttributes(v, intrinsics, expect.Intrinsics) 225 } 226 if nil != expect.UserAttributes { 227 expectAttributes(v, userAttributes, expect.UserAttributes) 228 } 229 if nil != expect.AgentAttributes { 230 expectAttributes(v, agentAttributes, expect.AgentAttributes) 231 } 232 } 233 234 // Second attributes have priority. 235 func mergeAttributes(a1, a2 map[string]interface{}) map[string]interface{} { 236 a := make(map[string]interface{}) 237 for k, v := range a1 { 238 a[k] = v 239 } 240 for k, v := range a2 { 241 a[k] = v 242 } 243 return a 244 } 245 246 // ExpectErrorEvents allows testing of error events. 247 func ExpectErrorEvents(v Validator, events *errorEvents, expect []WantEvent) { 248 if len(events.events.events) != len(expect) { 249 v.Error("number of custom events does not match", 250 len(events.events.events), len(expect)) 251 return 252 } 253 for i, e := range expect { 254 event, ok := events.events.events[i].jsonWriter.(*ErrorEvent) 255 if !ok { 256 v.Error("wrong error event") 257 } else { 258 if nil != e.Intrinsics { 259 e.Intrinsics = mergeAttributes(map[string]interface{}{ 260 // The following intrinsics should always be present in 261 // error events: 262 "type": "TransactionError", 263 "timestamp": MatchAnything, 264 "duration": MatchAnything, 265 }, e.Intrinsics) 266 } 267 expectEvent(v, event, e) 268 } 269 } 270 } 271 272 // ExpectTxnEvents allows testing of txn events. 273 func ExpectTxnEvents(v Validator, events *txnEvents, expect []WantEvent) { 274 if len(events.events.events) != len(expect) { 275 v.Error("number of txn events does not match", 276 len(events.events.events), len(expect)) 277 return 278 } 279 for i, e := range expect { 280 event, ok := events.events.events[i].jsonWriter.(*TxnEvent) 281 if !ok { 282 v.Error("wrong txn event") 283 } else { 284 if nil != e.Intrinsics { 285 e.Intrinsics = mergeAttributes(map[string]interface{}{ 286 // The following intrinsics should always be present in 287 // txn events: 288 "type": "Transaction", 289 "timestamp": MatchAnything, 290 "duration": MatchAnything, 291 }, e.Intrinsics) 292 } 293 expectEvent(v, event, e) 294 } 295 } 296 } 297 298 func expectError(v Validator, err *tracedError, expect WantError) { 299 caller := topCallerNameBase(err.ErrorData.Stack) 300 validateStringField(v, "caller", expect.Caller, caller) 301 validateStringField(v, "txnName", expect.TxnName, err.FinalName) 302 validateStringField(v, "klass", expect.Klass, err.Klass) 303 validateStringField(v, "msg", expect.Msg, err.Msg) 304 validateStringField(v, "URL", expect.URL, err.CleanURL) 305 js, errr := err.MarshalJSON() 306 if nil != errr { 307 v.Error("unable to marshal error json", errr) 308 return 309 } 310 var unmarshalled []interface{} 311 errr = json.Unmarshal(js, &unmarshalled) 312 if nil != errr { 313 v.Error("unable to unmarshal error json", errr) 314 return 315 } 316 attributes := unmarshalled[4].(map[string]interface{}) 317 agentAttributes := attributes["agentAttributes"].(map[string]interface{}) 318 userAttributes := attributes["userAttributes"].(map[string]interface{}) 319 320 if nil != expect.UserAttributes { 321 expectAttributes(v, userAttributes, expect.UserAttributes) 322 } 323 if nil != expect.AgentAttributes { 324 expectAttributes(v, agentAttributes, expect.AgentAttributes) 325 } 326 } 327 328 // ExpectErrors allows testing of errors. 329 func ExpectErrors(v Validator, errors harvestErrors, expect []WantError) { 330 if len(errors) != len(expect) { 331 v.Error("number of errors mismatch", len(errors), len(expect)) 332 return 333 } 334 for i, e := range expect { 335 expectError(v, errors[i], e) 336 } 337 } 338 339 func countSegments(node []interface{}) int { 340 count := 1 341 children := node[4].([]interface{}) 342 for _, c := range children { 343 node := c.([]interface{}) 344 count += countSegments(node) 345 } 346 return count 347 } 348 349 func expectTxnTrace(v Validator, got json.Marshaler, expect WantTxnTrace) { 350 js, err := got.MarshalJSON() 351 if nil != err { 352 v.Error("unable to marshal txn trace json", err) 353 return 354 } 355 var unmarshalled []interface{} 356 err = json.Unmarshal(js, &unmarshalled) 357 if nil != err { 358 v.Error("unable to unmarshal error json", err) 359 return 360 } 361 duration := unmarshalled[1].(float64) 362 name := unmarshalled[2].(string) 363 cleanURL := unmarshalled[3].(string) 364 traceData := unmarshalled[4].([]interface{}) 365 366 rootNode := traceData[3].([]interface{}) 367 attributes := traceData[4].(map[string]interface{}) 368 userAttributes := attributes["userAttributes"].(map[string]interface{}) 369 agentAttributes := attributes["agentAttributes"].(map[string]interface{}) 370 371 validateStringField(v, "metric name", expect.MetricName, name) 372 validateStringField(v, "request url", expect.CleanURL, cleanURL) 373 374 if doDurationTests && 0 == duration { 375 v.Error("zero trace duration") 376 } 377 378 if nil != expect.UserAttributes { 379 expectAttributes(v, userAttributes, expect.UserAttributes) 380 } 381 if nil != expect.AgentAttributes { 382 expectAttributes(v, agentAttributes, expect.AgentAttributes) 383 } 384 numSegments := countSegments(rootNode) 385 // The expectation segment count does not include the two root nodes. 386 numSegments -= 2 387 if expect.NumSegments != numSegments { 388 v.Error("wrong number of segments", expect.NumSegments, numSegments) 389 } 390 } 391 392 // ExpectTxnTraces allows testing of transaction traces. 393 func ExpectTxnTraces(v Validator, traces *harvestTraces, want []WantTxnTrace) { 394 if len(want) != traces.Len() { 395 v.Error("number of traces do not match", len(want), traces.Len()) 396 } 397 398 actual := traces.slice() 399 for i, expected := range want { 400 expectTxnTrace(v, actual[i], expected) 401 } 402 } 403 404 func expectSlowQuery(t Validator, slowQuery *slowQuery, want WantSlowQuery) { 405 if slowQuery.Count != want.Count { 406 t.Error("wrong Count field", slowQuery.Count, want.Count) 407 } 408 validateStringField(t, "MetricName", slowQuery.DatastoreMetric, want.MetricName) 409 validateStringField(t, "Query", slowQuery.ParameterizedQuery, want.Query) 410 validateStringField(t, "TxnName", slowQuery.TxnName, want.TxnName) 411 validateStringField(t, "TxnURL", slowQuery.TxnURL, want.TxnURL) 412 validateStringField(t, "DatabaseName", slowQuery.DatabaseName, want.DatabaseName) 413 validateStringField(t, "Host", slowQuery.Host, want.Host) 414 validateStringField(t, "PortPathOrID", slowQuery.PortPathOrID, want.PortPathOrID) 415 expectAttributes(t, map[string]interface{}(slowQuery.QueryParameters), want.Params) 416 } 417 418 // ExpectSlowQueries allows testing of slow queries. 419 func ExpectSlowQueries(t Validator, slowQueries *slowQueries, want []WantSlowQuery) { 420 if len(want) != len(slowQueries.priorityQueue) { 421 t.Error("wrong number of slow queries", 422 "expected", len(want), "got", len(slowQueries.priorityQueue)) 423 return 424 } 425 for _, s := range want { 426 idx, ok := slowQueries.lookup[s.Query] 427 if !ok { 428 t.Error("unable to find slow query", s.Query) 429 continue 430 } 431 expectSlowQuery(t, slowQueries.priorityQueue[idx], s) 432 } 433 }