newrelic_rpm 9.2.1 → 9.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (57) hide show
  1. checksums.yaml +4 -4
  2. data/.build_ignore +21 -0
  3. data/CHANGELOG.md +65 -2
  4. data/README.md +4 -4
  5. data/lib/new_relic/agent/configuration/default_source.rb +77 -29
  6. data/lib/new_relic/agent/configuration/manager.rb +3 -2
  7. data/lib/new_relic/agent/configuration/yaml_source.rb +13 -0
  8. data/lib/new_relic/agent/instrumentation/active_record.rb +1 -1
  9. data/lib/new_relic/agent/instrumentation/active_record_notifications.rb +2 -1
  10. data/lib/new_relic/agent/instrumentation/concurrent_ruby/chain.rb +1 -1
  11. data/lib/new_relic/agent/instrumentation/concurrent_ruby/instrumentation.rb +1 -2
  12. data/lib/new_relic/agent/instrumentation/concurrent_ruby/prepend.rb +1 -1
  13. data/lib/new_relic/agent/instrumentation/fiber/chain.rb +10 -3
  14. data/lib/new_relic/agent/instrumentation/fiber/instrumentation.rb +1 -2
  15. data/lib/new_relic/agent/instrumentation/fiber/prepend.rb +10 -3
  16. data/lib/new_relic/agent/instrumentation/memcache/instrumentation.rb +3 -3
  17. data/lib/new_relic/agent/instrumentation/rails_notifications/action_cable.rb +1 -1
  18. data/lib/new_relic/agent/instrumentation/thread/chain.rb +1 -1
  19. data/lib/new_relic/agent/instrumentation/thread/instrumentation.rb +0 -1
  20. data/lib/new_relic/agent/instrumentation/thread/prepend.rb +1 -1
  21. data/lib/new_relic/agent/log_event_aggregator.rb +49 -2
  22. data/lib/new_relic/agent/log_event_attributes.rb +115 -0
  23. data/lib/new_relic/agent/logging.rb +4 -4
  24. data/lib/new_relic/agent/method_tracer_helpers.rb +26 -5
  25. data/lib/new_relic/agent/tracer.rb +2 -2
  26. data/lib/new_relic/agent/transaction.rb +1 -1
  27. data/lib/new_relic/agent.rb +37 -0
  28. data/lib/new_relic/dependency_detection.rb +6 -0
  29. data/lib/new_relic/latest_changes.rb +1 -1
  30. data/lib/new_relic/supportability_helper.rb +1 -0
  31. data/lib/new_relic/traced_thread.rb +2 -3
  32. data/lib/new_relic/version.rb +2 -2
  33. data/lib/sequel/extensions/new_relic_instrumentation.rb +1 -1
  34. data/lib/tasks/bump_version.rake +21 -0
  35. data/lib/tasks/helpers/newrelicyml.rb +144 -0
  36. data/lib/tasks/helpers/version_bump.rb +62 -0
  37. data/lib/tasks/multiverse.rb +0 -8
  38. data/lib/tasks/newrelicyml.rake +13 -0
  39. data/newrelic.yml +307 -266
  40. data/newrelic_rpm.gemspec +5 -4
  41. metadata +12 -22
  42. data/.gitignore +0 -43
  43. data/.project +0 -23
  44. data/.rubocop.yml +0 -1845
  45. data/.rubocop_todo.yml +0 -61
  46. data/.simplecov +0 -16
  47. data/.snyk +0 -11
  48. data/.yardopts +0 -27
  49. data/Brewfile +0 -13
  50. data/DOCKER.md +0 -167
  51. data/Dockerfile +0 -10
  52. data/Guardfile +0 -27
  53. data/config/database.yml +0 -5
  54. data/config.dot +0 -278
  55. data/docker-compose.yml +0 -107
  56. data/lefthook.yml +0 -9
  57. data/test/agent_helper.rb +0 -1027
data/test/agent_helper.rb DELETED
@@ -1,1027 +0,0 @@
1
- # This file is distributed under New Relic's license terms.
2
- # See https://github.com/newrelic/newrelic-ruby-agent/blob/main/LICENSE for complete details.
3
- # frozen_string_literal: true
4
-
5
- # These helpers should not have any gem dependencies except on newrelic_rpm
6
- # itself, and should be usable from within any multiverse suite.
7
-
8
- require 'json'
9
- require 'net/http'
10
- begin
11
- require 'net/http/status'
12
- rescue LoadError
13
- # NOP -- Net::HTTP::STATUS_CODES was introduced in Ruby 2.5
14
- end
15
-
16
- class ArrayLogDevice
17
- def initialize(array = [])
18
- @array = array
19
- end
20
- attr_reader :array
21
-
22
- def write(message)
23
- @array << message
24
- end
25
-
26
- def close; end
27
- end
28
-
29
- def fake_guid(length = 16)
30
- NewRelic::Agent::GuidGenerator.generate_guid(length)
31
- end
32
-
33
- def assert_between(floor, ceiling, value, message = "expected #{floor} <= #{value} <= #{ceiling}")
34
- assert((floor <= value && value <= ceiling), message)
35
- end
36
-
37
- def assert_in_delta(expected, actual, delta)
38
- assert_between((expected - delta), (expected + delta), actual)
39
- end
40
-
41
- def harvest_error_traces!
42
- NewRelic::Agent.instance.error_collector.error_trace_aggregator.harvest!
43
- end
44
-
45
- def reset_error_traces!
46
- NewRelic::Agent.instance.error_collector.error_trace_aggregator.reset!
47
- end
48
-
49
- def assert_has_traced_error(error_class)
50
- errors = harvest_error_traces!
51
-
52
- refute_nil errors.find { |e| e.exception_class_name == error_class.name }, \
53
- "Didn't find error of class #{error_class}"
54
- end
55
-
56
- def last_traced_error
57
- harvest_error_traces!.last
58
- end
59
-
60
- def harvest_transaction_events!
61
- NewRelic::Agent.instance.transaction_event_aggregator.harvest!
62
- end
63
-
64
- def last_transaction_event
65
- harvest_transaction_events!.last.last
66
- end
67
-
68
- def harvest_span_events!
69
- NewRelic::Agent.instance.span_event_aggregator.harvest!
70
- end
71
-
72
- def last_span_event
73
- harvest_span_events!.last.last
74
- end
75
-
76
- def harvest_error_events!
77
- NewRelic::Agent.instance.error_collector.error_event_aggregator.harvest!
78
- end
79
-
80
- def last_error_event
81
- harvest_error_events!.last.last
82
- end
83
-
84
- unless defined? assert_includes
85
- def assert_includes(collection, member, msg = nil)
86
- msg = "Expected #{collection.inspect} to include #{member.inspect}"
87
-
88
- assert_includes collection, member, msg
89
- end
90
- end
91
-
92
- unless defined? assert_not_includes
93
- def assert_not_includes(collection, member, msg = nil)
94
- msg = "Expected #{collection.inspect} not to include #{member.inspect}"
95
-
96
- refute_includes collection, member, msg
97
- end
98
- end
99
-
100
- unless defined? assert_empty
101
- def assert_empty(collection, msg = nil)
102
- assert_empty collection, msg
103
- end
104
- end
105
-
106
- def assert_equal_unordered(left, right)
107
- assert_equal(left.length, right.length, "Lengths don't match. #{left.length} != #{right.length}")
108
- left.each { |element| assert_includes(right, element) }
109
- end
110
-
111
- def assert_log_contains(log, message)
112
- lines = log.array
113
-
114
- assert (lines.any? { |line| line.match(message) }),
115
- "Could not find message. Log contained: #{lines.join("\n")}"
116
- end
117
-
118
- def assert_audit_log_contains(audit_log_contents, needle)
119
- # Original request bodies dumped to the log have symbol keys, but once
120
- # they go through a dump/load, they're strings again, so we strip
121
- # double-quotes and colons from the log, and the strings we searching for.
122
- regex = /[:"]/
123
- needle = needle.gsub(regex, '')
124
- haystack = audit_log_contents.gsub(regex, '')
125
-
126
- assert_includes(haystack, needle, "Expected log to contain '#{needle}'")
127
- end
128
-
129
- # Because we don't generate a strictly machine-readable representation of
130
- # request bodies for the audit log, the transformation into strings is
131
- # effectively one-way. This, combined with the fact that Hash traversal order
132
- # is arbitrary in Ruby 1.8.x means that it's difficult to directly assert that
133
- # some object graph made it into the audit log (due to different possible
134
- # orderings of the key/value pairs in Hashes that were embedded in the request
135
- # body). So, this method traverses an object graph and only makes assertions
136
- # about the terminal (non-Array-or-Hash) nodes therein.
137
- def assert_audit_log_contains_object(audit_log_contents, o, format = :json)
138
- case o
139
- when Hash
140
- o.each do |k, v|
141
- assert_audit_log_contains_object(audit_log_contents, v, format)
142
- assert_audit_log_contains_object(audit_log_contents, k, format)
143
- end
144
- when Array
145
-
146
- o.each do |el|
147
- assert_audit_log_contains_object(audit_log_contents, el, format)
148
- end
149
- when NilClass
150
-
151
- assert_audit_log_contains(audit_log_contents, format == :json ? 'null' : 'nil')
152
- else
153
- assert_audit_log_contains(audit_log_contents, o.inspect)
154
- end
155
- end
156
-
157
- def compare_metrics(expected, actual)
158
- actual.delete_if { |a| a.include?('GC/Transaction/') }
159
-
160
- assert_equal(expected.to_a.sort, actual.to_a.sort, "extra: #{(actual - expected).to_a.inspect}; missing: #{(expected - actual).to_a.inspect}")
161
- end
162
-
163
- def metric_spec_from_specish(specish)
164
- spec = case specish
165
- when String then NewRelic::MetricSpec.new(specish)
166
- when Array then NewRelic::MetricSpec.new(*specish)
167
- end
168
- spec
169
- end
170
-
171
- def _normalize_metric_expectations(expectations)
172
- case expectations
173
- when Array
174
- hash = {}
175
- # Just assert that the metric is present, nothing about the attributes
176
- expectations.each { |k| hash[k] = {} }
177
- hash
178
- when String
179
- {expectations => {}}
180
- else
181
- expectations
182
- end
183
- end
184
-
185
- def dump_stats(stats)
186
- str = +" Call count: #{stats.call_count}\n"
187
- str << " Total call time: #{stats.total_call_time}\n"
188
- str << " Total exclusive time: #{stats.total_exclusive_time}\n"
189
- str << " Min call time: #{stats.min_call_time}\n"
190
- str << " Max call time: #{stats.max_call_time}\n"
191
- str << " Sum of squares: #{stats.sum_of_squares}\n"
192
- str << " Apdex S: #{stats.apdex_s}\n"
193
- str << " Apdex T: #{stats.apdex_t}\n"
194
- str << " Apdex F: #{stats.apdex_f}\n"
195
- str
196
- end
197
-
198
- def assert_stats_has_values(stats, expected_spec, expected_attrs)
199
- expected_attrs.each do |attr, expected_value|
200
- actual_value = stats.send(attr)
201
-
202
- msg = "Expected #{attr} for #{expected_spec} to be #{'~' unless attr == :call_count}#{expected_value}, " \
203
- "got #{actual_value}.\nActual stats:\n#{dump_stats(stats)}"
204
-
205
- if attr == :call_count
206
- assert_stats_has_values_with_call_count(expected_value, actual_value, msg)
207
- else
208
- assert_in_delta(expected_value, actual_value, 0.0001, msg)
209
- end
210
- end
211
- end
212
-
213
- def assert_stats_has_values_with_call_count(expected_value, actual_value, msg)
214
- # >, <, >=, <= comparisons
215
- if expected_value.to_s =~ /([<>]=?)\s*(\d+)/
216
- operator = Regexp.last_match(1).to_sym
217
- count = Regexp.last_match(2).to_i
218
-
219
- assert_operator(actual_value, operator, count, msg)
220
- # == comparison
221
- else
222
- assert_equal(expected_value, actual_value, msg)
223
- end
224
- end
225
-
226
- def assert_metrics_recorded(expected)
227
- expected = _normalize_metric_expectations(expected)
228
- expected.each do |specish, expected_attrs|
229
- expected_spec = metric_spec_from_specish(specish)
230
- actual_stats = NewRelic::Agent.instance.stats_engine.to_h[expected_spec]
231
- if !actual_stats
232
- all_specs = NewRelic::Agent.instance.stats_engine.to_h.keys.sort
233
- matches = all_specs.select { |spec| spec.name == expected_spec.name }
234
- matches.map! { |m| " #{m.inspect}" }
235
-
236
- msg = "Did not find stats for spec #{expected_spec.inspect}."
237
- msg += "\nDid find specs: [\n#{matches.join(",\n")}\n]" unless matches.empty?
238
- msg += "\nAll specs in there were: #{format_metric_spec_list(all_specs)}"
239
-
240
- assert(actual_stats, msg)
241
- end
242
-
243
- assert_stats_has_values(actual_stats, expected_spec, expected_attrs)
244
- end
245
- end
246
-
247
- # Use this to assert that *only* the given set of metrics has been recorded.
248
- #
249
- # If you want to scope the search for unexpected metrics to a particular
250
- # namespace (e.g. metrics matching 'Controller/'), pass a Regex for the
251
- # :filter option. Only metrics matching the regex will be searched when looking
252
- # for unexpected metrics.
253
- #
254
- # If you want to *allow* unexpected metrics matching certain patterns, use
255
- # the :ignore_filter option. This will allow you to specify a Regex that
256
- # allowlists broad swathes of metric territory (e.g. 'Supportability/').
257
- #
258
- def assert_metrics_recorded_exclusive(expected, options = {})
259
- expected = _normalize_metric_expectations(expected)
260
-
261
- assert_metrics_recorded(expected)
262
-
263
- recorded_metrics = NewRelic::Agent.instance.stats_engine.to_h.keys
264
-
265
- if options[:filter]
266
- recorded_metrics = recorded_metrics.select { |m| m.name.match(options[:filter]) }
267
- end
268
- if options[:ignore_filter]
269
- recorded_metrics.reject! { |m| m.name.match(options[:ignore_filter]) }
270
- end
271
-
272
- expected_metrics = expected.keys.map { |s| metric_spec_from_specish(s) }
273
-
274
- unexpected_metrics = recorded_metrics - expected_metrics
275
- unexpected_metrics.reject! { |m| m.name.include?('GC/Transaction') }
276
-
277
- assert_equal(0, unexpected_metrics.size, "Found unexpected metrics: #{format_metric_spec_list(unexpected_metrics)}")
278
- end
279
-
280
- def assert_newrelic_metadata_present(metadata)
281
- assert metadata.key?('newrelic')
282
- refute_nil metadata['newrelic']
283
- end
284
-
285
- def assert_distributed_tracing_payload_created_for_transaction(transaction)
286
- assert transaction.distributed_tracer.instance_variable_get(:@distributed_trace_payload_created)
287
- end
288
-
289
- # The clear_metrics! method prevents metrics from "leaking" between tests by resetting
290
- # the @stats_hash instance variable in the current instance of NewRelic::Agent::StatsEngine.
291
-
292
- module NewRelic
293
- module Agent
294
- class StatsEngine
295
- def reset_for_test!
296
- @stats_hash = StatsHash.new
297
- end
298
- end
299
- end
300
- end
301
-
302
- def clear_metrics!
303
- NewRelic::Agent.instance.stats_engine.reset_for_test!
304
- end
305
-
306
- def assert_metrics_not_recorded(not_expected)
307
- not_expected = _normalize_metric_expectations(not_expected)
308
- found_but_not_expected = []
309
- not_expected.each do |specish, _|
310
- spec = metric_spec_from_specish(specish)
311
- if NewRelic::Agent.instance.stats_engine.to_h[spec]
312
- found_but_not_expected << spec
313
- end
314
- end
315
-
316
- assert_empty(found_but_not_expected, "Found unexpected metrics: #{format_metric_spec_list(found_but_not_expected)}")
317
- end
318
-
319
- alias :refute_metrics_recorded :assert_metrics_not_recorded
320
-
321
- def assert_no_metrics_match(regex)
322
- matching_metrics = []
323
- NewRelic::Agent.instance.stats_engine.to_h.keys.map(&:to_s).each do |metric|
324
- matching_metrics << metric if metric.match(regex)
325
- end
326
-
327
- assert_empty(
328
- matching_metrics,
329
- "Found unexpected metrics:\n" + matching_metrics.map { |m| " '#{m}'" }.join("\n") + "\n\n"
330
- )
331
- end
332
-
333
- alias :refute_metrics_match :assert_no_metrics_match
334
-
335
- def format_metric_spec_list(specs)
336
- spec_strings = specs.map do |spec|
337
- "#{spec.name} (#{spec.scope.empty? ? '<unscoped>' : spec.scope})"
338
- end
339
- "[\n #{spec_strings.join(",\n ")}\n]"
340
- end
341
-
342
- def assert_truthy(expected, msg = nil)
343
- msg ||= "Expected #{expected.inspect} to be truthy"
344
-
345
- refute !expected, msg
346
- end
347
-
348
- def assert_falsy(expected, msg = nil)
349
- msg ||= "Expected #{expected.inspect} to be falsy"
350
-
351
- refute expected, msg
352
- end
353
-
354
- unless defined? assert_false
355
- def assert_false(expected)
356
- refute expected
357
- end
358
- end
359
-
360
- unless defined? refute
361
- alias refute assert_false
362
- end
363
-
364
- # Mock up a transaction for testing purposes, optionally specifying a name and
365
- # transaction category. The given block will be executed within the context of the
366
- # dummy transaction.
367
- #
368
- # Examples:
369
- #
370
- # With default name ('dummy') and category (:other):
371
- # in_transaction { ... }
372
- #
373
- # With an explicit transaction name and default category:
374
- # in_transaction('foobar') { ... }
375
- #
376
- # With default name and explicit category:
377
- # in_transaction(:category => :controller) { ... }
378
- #
379
- # With a transaction name plus category:
380
- # in_transaction('foobar', :category => :controller) { ... }
381
- #
382
- def in_transaction(*args, &blk)
383
- opts = args.last&.is_a?(Hash) ? args.pop : {}
384
- category = (opts&.delete(:category)) || :other
385
-
386
- # At least one test passes `:transaction_name => nil`, so handle it gently
387
- name = opts.key?(:transaction_name) ? opts.delete(:transaction_name) : args.first || 'dummy'
388
-
389
- state = NewRelic::Agent::Tracer.state
390
- txn = nil
391
-
392
- NewRelic::Agent::Tracer.in_transaction(name: name, category: category, options: opts) do
393
- txn = state.current_transaction
394
- yield(state.current_transaction)
395
- end
396
-
397
- txn
398
- end
399
-
400
- # Temporarily disables default transformer so tests with invalid inputs can be tried
401
- def with_disabled_defaults_transformer(key)
402
- begin
403
- transformer = NewRelic::Agent::Configuration::DEFAULTS[key][:transform]
404
- NewRelic::Agent::Configuration::DEFAULTS[key][:transform] = nil
405
- yield
406
- ensure
407
- NewRelic::Agent::Configuration::DEFAULTS[key][:transform] = transformer
408
- end
409
- end
410
-
411
- # Convenience wrapper to stand up a transaction and provide a segment within
412
- # that transaction to work with. The same arguments as provided to in_transaction
413
- # may be supplied.
414
- def with_segment(*args, &blk)
415
- segment = nil
416
- txn = in_transaction(*args) do |t|
417
- segment = t.current_segment
418
- yield(segment, t)
419
- end
420
- [segment, txn]
421
- end
422
-
423
- # building error attributes on segments are deferred until it's time
424
- # to publish/harvest them as spans, so for testing, we'll explicitly
425
- # build 'em as appropriate so we can test 'em
426
- def build_deferred_error_attributes(segment)
427
- return unless segment.noticed_error
428
- return if segment.noticed_error_attributes.frozen?
429
-
430
- segment.noticed_error.build_error_attributes
431
- end
432
-
433
- def capture_segment_with_error
434
- begin
435
- segment_with_error = nil
436
- with_segment do |segment|
437
- segment_with_error = segment
438
- raise 'oops!'
439
- end
440
- rescue Exception => exception
441
- assert segment_with_error, 'expected to have a segment_with_error'
442
- build_deferred_error_attributes(segment_with_error)
443
- return segment_with_error, exception
444
- end
445
- end
446
-
447
- def stub_transaction_guid(guid)
448
- NewRelic::Agent::Transaction.tl_current.instance_variable_set(:@guid, guid)
449
- end
450
-
451
- # Convenience wrapper around in_transaction that sets the category so that it
452
- # looks like we are in a web transaction
453
- def in_web_transaction(name = 'dummy')
454
- in_transaction(name, :category => :controller, :request => stub(:path => '/')) do |txn|
455
- yield(txn)
456
- end
457
- end
458
-
459
- def in_background_transaction(name = 'silly')
460
- in_transaction(name, :category => :task) do |txn|
461
- yield(txn)
462
- end
463
- end
464
-
465
- def refute_contains_request_params(attributes)
466
- attributes.keys.each do |key|
467
- refute_match(/^request\.parameters\./, key.to_s)
468
- end
469
- end
470
-
471
- def last_transaction_trace
472
- return unless last_sample = NewRelic::Agent.agent.transaction_sampler.last_sample
473
-
474
- NewRelic::Agent::Transaction::TraceBuilder.build_trace(last_sample)
475
- end
476
-
477
- def last_transaction_trace_request_params
478
- agent_attributes = attributes_for(last_transaction_trace, :agent)
479
- agent_attributes.inject({}) do |memo, (key, value)|
480
- memo[key] = value if key.to_s.start_with?('request.parameters.')
481
- memo
482
- end
483
- end
484
-
485
- def find_sql_trace(metric_name)
486
- NewRelic::Agent.agent.sql_sampler.sql_traces.values.detect do |trace|
487
- trace.database_metric_name == metric_name
488
- end
489
- end
490
-
491
- def last_sql_trace
492
- NewRelic::Agent.agent.sql_sampler.sql_traces.values.last
493
- end
494
-
495
- def find_last_transaction_node(transaction_sample = nil)
496
- if transaction_sample
497
- root_node = transaction_sample.root_node
498
- else
499
- root_node = last_transaction_trace.root_node
500
- end
501
-
502
- last_node = nil
503
- root_node.each_node { |s| last_node = s }
504
-
505
- return last_node
506
- end
507
-
508
- def find_node_with_name(transaction_sample, name)
509
- transaction_sample.root_node.each_node do |node|
510
- if node.metric_name == name
511
- return node
512
- end
513
- end
514
-
515
- nil
516
- end
517
-
518
- def find_node_with_name_matching(transaction_sample, regex)
519
- transaction_sample.root_node.each_node do |node|
520
- if node.metric_name.match(regex)
521
- return node
522
- end
523
- end
524
-
525
- nil
526
- end
527
-
528
- def find_all_nodes_with_name_matching(transaction_sample, regexes)
529
- regexes = [regexes].flatten
530
- matching_nodes = []
531
-
532
- transaction_sample.root_node.each_node do |node|
533
- regexes.each do |regex|
534
- if node.metric_name.match(regex)
535
- matching_nodes << node
536
- end
537
- end
538
- end
539
-
540
- matching_nodes
541
- end
542
-
543
- def with_config(config_hash, at_start = true)
544
- config = NewRelic::Agent::Configuration::DottedHash.new(config_hash, true)
545
- NewRelic::Agent.config.add_config_for_testing(config, at_start)
546
- NewRelic::Agent.instance.refresh_attribute_filter
547
- begin
548
- yield
549
- ensure
550
- NewRelic::Agent.config.remove_config(config)
551
- NewRelic::Agent.instance.refresh_attribute_filter
552
- end
553
- end
554
-
555
- def with_server_source(config_hash, at_start = true)
556
- with_config(config_hash, at_start) do
557
- NewRelic::Agent.config.notify_server_source_added
558
- yield
559
- end
560
- end
561
-
562
- def with_config_low_priority(config_hash)
563
- with_config(config_hash, false) do
564
- yield
565
- end
566
- end
567
-
568
- def with_transaction_renaming_rules(rule_specs)
569
- original_engine = NewRelic::Agent.agent.instance_variable_get(:@transaction_rules)
570
- begin
571
- new_engine = NewRelic::Agent::RulesEngine.create_transaction_rules('transaction_name_rules' => rule_specs)
572
- NewRelic::Agent.agent.instance_variable_set(:@transaction_rules, new_engine)
573
- yield
574
- ensure
575
- NewRelic::Agent.agent.instance_variable_set(:@transaction_rules, original_engine)
576
- end
577
- end
578
-
579
- # Need to guard against double-installing this patch because in 1.8.x the same
580
- # file can be required multiple times under different non-canonicalized paths.
581
- unless Time.respond_to?(:__original_now)
582
- Time.instance_eval do
583
- class << self
584
- attr_accessor :__frozen_now
585
- alias_method :__original_now, :now
586
-
587
- def now
588
- __frozen_now || __original_now
589
- end
590
- end
591
- end
592
- end
593
-
594
- def nr_freeze_time(now = Time.now)
595
- Time.__frozen_now = now
596
- end
597
-
598
- def nr_unfreeze_time
599
- Time.__frozen_now = nil
600
- end
601
-
602
- def advance_time(seconds)
603
- Time.__frozen_now = Time.now + seconds
604
- end
605
-
606
- unless Process.respond_to?(:__original_clock_gettime)
607
- Process.instance_eval do
608
- class << self
609
- attr_accessor :__frozen_clock_gettime
610
- alias_method :__original_clock_gettime, :clock_gettime
611
-
612
- def clock_gettime(clock_id, unit = :float_second)
613
- __frozen_clock_gettime || __original_clock_gettime(clock_id, unit)
614
- end
615
- end
616
- end
617
- end
618
-
619
- def advance_process_time(seconds, clock_id = Process::CLOCK_REALTIME)
620
- Process.__frozen_clock_gettime = Process.clock_gettime(clock_id) + seconds
621
- end
622
-
623
- def nr_freeze_process_time(now = Process.clock_gettime(Process::CLOCK_REALTIME))
624
- Process.__frozen_clock_gettime = now
625
- end
626
-
627
- def nr_unfreeze_process_time
628
- Process.__frozen_clock_gettime = nil
629
- end
630
-
631
- def with_constant_defined(constant_symbol, implementation = Module.new)
632
- const_path = constant_path(constant_symbol.to_s)
633
-
634
- if const_path
635
- # Constant is already defined, nothing to do
636
- return yield
637
- else
638
- const_path = constant_path(constant_symbol.to_s, :allow_partial => true)
639
- parent = const_path[-1]
640
- constant_symbol = constant_symbol.to_s.split('::').last.to_sym
641
- end
642
-
643
- begin
644
- parent.const_set(constant_symbol, implementation)
645
- yield
646
- ensure
647
- parent.send(:remove_const, constant_symbol)
648
- end
649
- end
650
-
651
- def constant_path(name, opts = {})
652
- allow_partial = opts[:allow_partial]
653
- path = [Object]
654
- parts = name.gsub(/^::/, '').split('::')
655
- parts.each do |part|
656
- if !path.last.constants.include?(part.to_sym)
657
- return allow_partial ? path : nil
658
- end
659
-
660
- path << path.last.const_get(part)
661
- end
662
- path
663
- end
664
-
665
- def get_parent(constant_name)
666
- parent_name = constant_name.gsub(/::[^:]*$/, '')
667
- const_path = constant_path(parent_name)
668
- const_path ? const_path[-1] : nil
669
- end
670
-
671
- def undefine_constant(constant_symbol)
672
- const_str = constant_symbol.to_s
673
- parent = get_parent(const_str)
674
- const_name = const_str.gsub(/.*::/, '')
675
- return yield unless parent&.constants&.include?(const_name.to_sym)
676
-
677
- removed_constant = parent.send(:remove_const, const_name)
678
- yield
679
- ensure
680
- parent.const_set(const_name, removed_constant) if removed_constant
681
- end
682
-
683
- def with_debug_logging
684
- orig_logger = NewRelic::Agent.logger
685
- $stderr.puts '', '---', ''
686
- NewRelic::Agent.logger =
687
- NewRelic::Agent::AgentLogger.new('', Logger.new($stderr))
688
-
689
- with_config(:log_level => 'debug') do
690
- yield
691
- end
692
- ensure
693
- NewRelic::Agent.logger = orig_logger
694
- end
695
-
696
- def create_agent_command(args = {})
697
- NewRelic::Agent::Commands::AgentCommand.new([-1, {'name' => 'command_name', 'arguments' => args}])
698
- end
699
-
700
- def wait_for_backtrace_service_poll(opts = {})
701
- defaults = {
702
- :timeout => 10.0,
703
- :service => NewRelic::Agent.agent.instance_variable_get(:@agent_command_router).backtrace_service,
704
- :iterations => 1
705
- }
706
- opts = defaults.merge(opts)
707
- deadline = Process.clock_gettime(Process::CLOCK_REALTIME) + opts[:timeout]
708
-
709
- service = opts[:service]
710
- worker_loop = service.worker_loop
711
- worker_loop.setup(0, service.method(:poll))
712
-
713
- until worker_loop.iterations > opts[:iterations]
714
- sleep(0.01)
715
- if Process.clock_gettime(Process::CLOCK_REALTIME) > deadline
716
- raise "Timed out waiting #{opts[:timeout]} s for backtrace service poll\n" +
717
- "Worker loop ran for #{opts[:service].worker_loop.iterations} iterations\n\n" +
718
- Thread.list.map { |t|
719
- "#{t.to_s}: newrelic_label: #{t[:newrelic_label].inspect}\n\n" +
720
- (t.backtrace || []).join("\n\t")
721
- }.join("\n\n")
722
- end
723
- end
724
- end
725
-
726
- def with_array_logger(level = :info)
727
- orig_logger = NewRelic::Agent.logger
728
- config = {:log_level => level}
729
- logdev = ArrayLogDevice.new
730
- override_logger = Logger.new(logdev)
731
-
732
- with_config(config) do
733
- NewRelic::Agent.logger = NewRelic::Agent::AgentLogger.new('', override_logger)
734
- yield
735
- end
736
-
737
- return logdev
738
- ensure
739
- NewRelic::Agent.logger = orig_logger
740
- end
741
-
742
- # The EnvUpdater was introduced due to random fails in JRuby environment
743
- # whereby attempting to set ENV[key] = some_value randomly failed.
744
- # It is conjectured that this is thread related, but may also be
745
- # a core bug in the JVM implementation of Ruby. Root cause was not
746
- # discovered, but it was found that a combination of retrying and using
747
- # mutex lock around the update operation was the only consistently working
748
- # solution as the error continued to surface without the mutex and
749
- # retry alone wasn't enough, either.
750
- #
751
- # JRUBY: oraclejdk8 + jruby-9.2.6.0
752
- #
753
- # NOTE: Singleton pattern to ensure one mutex lock for all threads
754
- class EnvUpdater
755
- MAX_RETRIES = 5
756
-
757
- def initialize
758
- @mutex = Mutex.new
759
- end
760
-
761
- # Will attempt the given block up to MAX_RETRIES before
762
- # surfacing the exception down the chain.
763
- def with_retry(retry_limit = MAX_RETRIES)
764
- retries ||= 0
765
- sleep(retries)
766
- yield
767
- rescue
768
- (retries += 1) < retry_limit ? retry : raise
769
- end
770
-
771
- # Locks and updates the ENV
772
- def safe_update(env)
773
- with_retry do
774
- @mutex.synchronize do
775
- env.each { |key, val| ENV[key] = val.to_s }
776
- end
777
- end
778
- end
779
-
780
- # Locks and restores the ENV
781
- def safe_restore(old_env)
782
- with_retry do
783
- @mutex.synchronize do
784
- old_env.each { |key, val| val ? ENV[key] = val : ENV.delete(key) }
785
- end
786
- end
787
- end
788
-
789
- # Singleton pattern implemented via @@instance
790
- def self.instance
791
- @@instance ||= EnvUpdater.new
792
- end
793
-
794
- def self.safe_update(env)
795
- instance.safe_update(env)
796
- end
797
-
798
- def self.safe_restore(old_env)
799
- instance.safe_restore(old_env)
800
- end
801
-
802
- # Effectively saves current ENV settings for given env's key/values,
803
- # runs given block, then restores ENV to original state before returning.
804
- def self.inject(env, &block)
805
- old_env = {}
806
- env.each { |key, val| old_env[key] = ENV[key] }
807
- begin
808
- safe_update(env)
809
- yield
810
- ensure
811
- safe_restore(old_env)
812
- end
813
- end
814
-
815
- # must call instance here to ensure only one @mutex for all threads.
816
- instance
817
- end
818
-
819
- # Changes ENV settings to given and runs given block and restores ENV
820
- # to original values before returning.
821
- def with_environment(env, &block)
822
- EnvUpdater.inject(env) { yield }
823
- end
824
-
825
- def with_argv(argv)
826
- old_argv = ARGV.dup
827
- ARGV.clear
828
- ARGV.concat(argv)
829
-
830
- begin
831
- yield
832
- ensure
833
- ARGV.clear
834
- ARGV.concat(old_argv)
835
- end
836
- end
837
-
838
- def with_ignore_error_filter(filter, &blk)
839
- original_filter = NewRelic::Agent.ignore_error_filter
840
- NewRelic::Agent.ignore_error_filter(&filter)
841
-
842
- yield
843
- ensure
844
- NewRelic::Agent::ErrorCollector.ignore_error_filter = original_filter
845
- end
846
-
847
- def json_dump_and_encode(object)
848
- Base64.encode64(JSON.dump(object))
849
- end
850
-
851
- def get_last_analytics_event
852
- NewRelic::Agent.agent.transaction_event_aggregator.harvest![1].last
853
- end
854
-
855
- def swap_instance_method(target, method_name, new_method_implementation, &blk)
856
- old_method_implementation = target.instance_method(method_name)
857
- target.send(:define_method, method_name, new_method_implementation)
858
- yield
859
- rescue NameError => e
860
- puts "Your target does not have the instance method #{method_name}"
861
- puts e.inspect
862
- ensure
863
- target.send(:define_method, method_name, old_method_implementation)
864
- end
865
-
866
- def cross_agent_tests_dir
867
- File.expand_path(File.join(File.dirname(__FILE__), 'fixtures', 'cross_agent_tests'))
868
- end
869
-
870
- def load_cross_agent_test(name)
871
- test_file_path = File.join(cross_agent_tests_dir, "#{name}.json")
872
- data = File.read(test_file_path)
873
- data.gsub!('callCount', 'call_count')
874
- data = JSON.load(data)
875
- data.each { |testcase| testcase['testname'].tr!(' ', '_') if String === testcase['testname'] }
876
- data
877
- end
878
-
879
- def each_cross_agent_test(options)
880
- options = {:dir => nil, :pattern => '*'}.update(options)
881
- path = File.join([cross_agent_tests_dir, options[:dir], options[:pattern]].compact)
882
- Dir.glob(path).each { |file| yield(file) }
883
- end
884
-
885
- def assert_event_attributes(event, test_name, expected_attributes, non_expected_attributes)
886
- incorrect_attributes = []
887
-
888
- event_attrs = event[0]
889
-
890
- expected_attributes.each do |name, expected_value|
891
- actual_value = event_attrs[name]
892
- incorrect_attributes << name unless actual_value == expected_value
893
- end
894
-
895
- msg = +"Found missing or incorrect attribute values in #{test_name}:\n"
896
-
897
- incorrect_attributes.each do |name|
898
- msg << " #{name}: expected = #{expected_attributes[name].inspect}, actual = #{event_attrs[name].inspect}\n"
899
- end
900
- msg << "\n"
901
-
902
- msg << "All event values:\n"
903
- event_attrs.each do |name, actual_value|
904
- msg << " #{name}: #{actual_value.inspect}\n"
905
- end
906
-
907
- assert_empty(incorrect_attributes, msg)
908
-
909
- non_expected_attributes.each do |name|
910
- refute event_attrs[name], "Found value '#{event_attrs[name]}' for attribute '#{name}', but expected nothing in #{test_name}"
911
- end
912
- end
913
-
914
- def attributes_for(sample, type)
915
- sample.attributes.instance_variable_get("@#{type}_attributes")
916
- end
917
-
918
- def uncache_trusted_account_key
919
- NewRelic::Agent::Transaction::TraceContext::AccountHelpers.instance_variable_set(:@trace_state_entry_key, nil)
920
- end
921
-
922
- def reset_buffers_and_caches
923
- NewRelic::Agent.drop_buffered_data
924
- uncache_trusted_account_key
925
- end
926
-
927
- def message_for_status_code(code)
928
- # Net::HTTP::STATUS_CODES was introduced in Ruby 2.5
929
- if defined?(Net::HTTP::STATUS_CODES)
930
- return Net::HTTP::STATUS_CODES[code]
931
- end
932
-
933
- case code
934
- when 200 then 'OK'
935
- when 404 then 'Not Found'
936
- when 403 then 'Forbidden'
937
- else 'Unknown'
938
- end
939
- end
940
-
941
- # wraps the given headers in a Net::HTTPResponse which has accompanying
942
- # http status code associated with it.
943
- # a "status_code" may be passed in the headers to alter the HTTP Status Code
944
- # that is wrapped in the response.
945
- def mock_http_response(headers, wrap_it = true)
946
- status_code = (headers.delete('status_code') || 200).to_i
947
- net_http_resp = Net::HTTPResponse.new(1.0, status_code, message_for_status_code(status_code))
948
- headers.each do |key, value|
949
- net_http_resp.add_field(key.to_s, value)
950
- end
951
- return net_http_resp unless wrap_it
952
-
953
- NewRelic::Agent::HTTPClients::NetHTTPResponse.new(net_http_resp)
954
- end
955
-
956
- # +expected+ can be a string or regular expression
957
- def assert_match_or_equal(expected, value)
958
- if expected.is_a?(Regexp)
959
- assert_match expected, value
960
- else
961
- assert_equal expected, value
962
- end
963
- end
964
-
965
- # selects the last segment with a noticed_error and checks
966
- # the expectations against it.
967
- def assert_segment_noticed_error(txn, segment_name, error_classes, error_message)
968
- error_segment = txn.segments.reverse.detect { |s| s.noticed_error }
969
-
970
- assert error_segment, 'Expected at least one segment with a noticed_error'
971
-
972
- assert_match_or_equal segment_name, error_segment.name
973
-
974
- noticed_error = error_segment.noticed_error
975
-
976
- assert_match_or_equal error_classes, noticed_error.exception_class_name
977
- assert_match_or_equal error_message, noticed_error.message
978
- end
979
-
980
- def assert_transaction_noticed_error(txn, error_classes)
981
- refute_empty txn.exceptions, 'Expected transaction to notice the error'
982
- assert_match_or_equal error_classes, txn.exceptions.keys.first.class.name
983
- end
984
-
985
- def refute_transaction_noticed_error(txn, error_class)
986
- error_segment = txn.segments.reverse.detect { |s| s.noticed_error }
987
-
988
- assert error_segment, 'Expected at least one segment with a noticed_error'
989
- assert_empty txn.exceptions, 'Expected transaction to NOT notice any segment errors'
990
- end
991
-
992
- def refute_raises(*exp)
993
- msg = "#{exp.pop}.\n" if String === exp.last
994
-
995
- begin
996
- yield
997
- rescue MiniTest::Skip => e
998
- puts "SKIP REPORTS: #{e.inspect}"
999
- return e if exp.include?(MiniTest::Skip)
1000
-
1001
- raise e
1002
- rescue Exception => e
1003
- puts "EXCEPTION RAISED: #{e.inspect}\n#{e.backtrace}"
1004
- exp = exp.first if exp.size == 1
1005
-
1006
- flunk(msg || "unexpected exception raised: #{e}")
1007
- end
1008
- end
1009
-
1010
- def assert_implements(instance, method, *args)
1011
- fail_message = "expected #{instance.class}##{method} method to be implemented"
1012
- refute_raises NotImplementedError, fail_message do
1013
- instance.send(method, *args)
1014
- end
1015
- end
1016
-
1017
- def defer_testing_to_min_supported_rails(test_file, min_rails_version, supports_jruby = true)
1018
- if defined?(Rails) &&
1019
- defined?(Rails::VERSION::STRING) &&
1020
- (Rails::VERSION::STRING.to_f >= min_rails_version) &&
1021
- (supports_jruby || !NewRelic::LanguageSupport.jruby?)
1022
-
1023
- yield
1024
- else
1025
- puts "Skipping tests in #{File.basename(test_file)} because Rails >= #{min_rails_version} is unavailable" if ENV['VERBOSE_TEST_OUTPUT']
1026
- end
1027
- end