newrelic_rpm 6.9.0.363 → 6.10.0.364

Sign up to get free protection for your applications and to get access to all the features.
Files changed (58) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +15 -0
  3. data/CHANGELOG.md +59 -5
  4. data/lib/new_relic/agent.rb +3 -2
  5. data/lib/new_relic/agent/agent.rb +2 -2
  6. data/lib/new_relic/agent/datastores/mongo.rb +1 -1
  7. data/lib/new_relic/agent/distributed_tracing/trace_context_payload.rb +1 -1
  8. data/lib/new_relic/agent/error_collector.rb +30 -11
  9. data/lib/new_relic/agent/error_event_aggregator.rb +4 -4
  10. data/lib/new_relic/agent/hostname.rb +7 -1
  11. data/lib/new_relic/agent/http_clients/abstract.rb +82 -0
  12. data/lib/new_relic/agent/http_clients/curb_wrappers.rb +24 -19
  13. data/lib/new_relic/agent/http_clients/excon_wrappers.rb +28 -13
  14. data/lib/new_relic/agent/http_clients/http_rb_wrappers.rb +17 -21
  15. data/lib/new_relic/agent/http_clients/httpclient_wrappers.rb +10 -11
  16. data/lib/new_relic/agent/http_clients/net_http_wrappers.rb +16 -4
  17. data/lib/new_relic/agent/http_clients/typhoeus_wrappers.rb +4 -6
  18. data/lib/new_relic/agent/http_clients/uri_util.rb +3 -2
  19. data/lib/new_relic/agent/instrumentation/action_cable_subscriber.rb +4 -5
  20. data/lib/new_relic/agent/instrumentation/action_controller_subscriber.rb +4 -0
  21. data/lib/new_relic/agent/instrumentation/action_view_subscriber.rb +11 -2
  22. data/lib/new_relic/agent/instrumentation/active_record.rb +4 -2
  23. data/lib/new_relic/agent/instrumentation/active_record_subscriber.rb +7 -2
  24. data/lib/new_relic/agent/instrumentation/active_storage_subscriber.rb +8 -4
  25. data/lib/new_relic/agent/instrumentation/bunny.rb +44 -27
  26. data/lib/new_relic/agent/instrumentation/curb.rb +58 -17
  27. data/lib/new_relic/agent/instrumentation/data_mapper.rb +3 -1
  28. data/lib/new_relic/agent/instrumentation/excon/connection.rb +6 -3
  29. data/lib/new_relic/agent/instrumentation/excon/middleware.rb +2 -1
  30. data/lib/new_relic/agent/instrumentation/http.rb +5 -2
  31. data/lib/new_relic/agent/instrumentation/httpclient.rb +4 -2
  32. data/lib/new_relic/agent/instrumentation/memcache.rb +3 -1
  33. data/lib/new_relic/agent/instrumentation/memcache/dalli.rb +6 -2
  34. data/lib/new_relic/agent/instrumentation/mongo.rb +9 -3
  35. data/lib/new_relic/agent/instrumentation/mongodb_command_subscriber.rb +13 -0
  36. data/lib/new_relic/agent/instrumentation/net.rb +5 -2
  37. data/lib/new_relic/agent/instrumentation/notifications_subscriber.rb +25 -1
  38. data/lib/new_relic/agent/instrumentation/redis.rb +9 -3
  39. data/lib/new_relic/agent/instrumentation/sidekiq.rb +0 -1
  40. data/lib/new_relic/agent/instrumentation/typhoeus.rb +22 -5
  41. data/lib/new_relic/agent/logging.rb +1 -1
  42. data/lib/new_relic/agent/method_tracer_helpers.rb +1 -1
  43. data/lib/new_relic/agent/noticible_error.rb +22 -0
  44. data/lib/new_relic/agent/span_event_primitive.rb +43 -32
  45. data/lib/new_relic/agent/tracer.rb +30 -15
  46. data/lib/new_relic/agent/transaction.rb +47 -43
  47. data/lib/new_relic/agent/transaction/abstract_segment.rb +26 -0
  48. data/lib/new_relic/agent/transaction/external_request_segment.rb +25 -9
  49. data/lib/new_relic/agent/transaction_error_primitive.rb +5 -3
  50. data/lib/new_relic/cli/commands/install.rb +3 -2
  51. data/lib/new_relic/noticed_error.rb +28 -9
  52. data/lib/new_relic/rack/browser_monitoring.rb +2 -1
  53. data/lib/new_relic/version.rb +1 -1
  54. data/lib/tasks/multiverse.rb +25 -0
  55. data/lib/tasks/tests.rake +1 -1
  56. data/test/agent_helper.rb +217 -64
  57. metadata +4 -3
  58. data/lib/new_relic/agent/http_clients/abstract_request.rb +0 -31
@@ -1,6 +1,7 @@
1
1
  # encoding: utf-8
2
2
  # This file is distributed under New Relic's license terms.
3
3
  # See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
4
+ # frozen_string_literal: true
4
5
 
5
6
  require 'new_relic/agent/transaction/segment'
6
7
  require 'new_relic/agent/http_clients/uri_util'
@@ -14,12 +15,16 @@ module NewRelic
14
15
  #
15
16
  # @api public
16
17
  class ExternalRequestSegment < Segment
17
- NR_SYNTHETICS_HEADER = 'X-NewRelic-Synthetics'.freeze
18
- EXTERNAL_TRANSACTION_PREFIX = 'ExternalTransaction/'.freeze
19
- SLASH = '/'.freeze
20
- APP_DATA_KEY = 'NewRelicAppData'.freeze
18
+ NR_SYNTHETICS_HEADER = 'X-NewRelic-Synthetics'
19
+ APP_DATA_KEY = 'NewRelicAppData'
20
+
21
+ EXTERNAL_ALL = "External/all"
22
+ EXTERNAL_ALL_WEB = "External/allWeb"
23
+ EXTERNAL_ALL_OTHER = "External/allOther"
24
+ MISSING_STATUS_CODE = "MissingHTTPStatusCode"
21
25
 
22
26
  attr_reader :library, :uri, :procedure
27
+ attr_reader :http_status_code
23
28
 
24
29
  def initialize library, uri, procedure, start_time = nil # :nodoc:
25
30
  @library = library
@@ -27,6 +32,7 @@ module NewRelic
27
32
  @procedure = procedure
28
33
  @host_header = nil
29
34
  @app_data = nil
35
+ @http_status_code = nil
30
36
  super(nil, nil, start_time)
31
37
  end
32
38
 
@@ -176,8 +182,23 @@ module NewRelic
176
182
  super
177
183
  end
178
184
 
185
+ def process_response_headers response # :nodoc:
186
+ set_http_status_code response
187
+ read_response_headers response
188
+ end
189
+
179
190
  private
180
191
 
192
+ # Only sets the http_status_code if response.status_code is non-empty value
193
+ def set_http_status_code response
194
+ if response.respond_to? :status_code
195
+ @http_status_code = response.status_code if response.has_status_code?
196
+ else
197
+ NewRelic::Agent.logger.warn "Cannot extract HTTP Status Code from response #{response.class.to_s}"
198
+ NewRelic::Agent.record_metric "#{name}/#{MISSING_STATUS_CODE}", 1
199
+ end
200
+ end
201
+
181
202
  def insert_synthetics_header request, header
182
203
  request[NR_SYNTHETICS_HEADER] = header
183
204
  end
@@ -197,8 +218,6 @@ module NewRelic
197
218
  end
198
219
  end
199
220
 
200
- EXTERNAL_ALL = "External/all".freeze
201
-
202
221
  def add_unscoped_metrics
203
222
  @unscoped_metrics = [ EXTERNAL_ALL,
204
223
  "External/#{host}/all",
@@ -210,9 +229,6 @@ module NewRelic
210
229
  end
211
230
  end
212
231
 
213
- EXTERNAL_ALL_WEB = "External/allWeb".freeze
214
- EXTERNAL_ALL_OTHER = "External/allOther".freeze
215
-
216
232
  def suffixed_rollup_metric
217
233
  if Transaction.recording_web_transaction?
218
234
  EXTERNAL_ALL_WEB
@@ -32,16 +32,17 @@ module NewRelic
32
32
  SYNTHETICS_JOB_ID_KEY = "nr.syntheticsJobId".freeze
33
33
  SYNTHETICS_MONITOR_ID_KEY = "nr.syntheticsMonitorId".freeze
34
34
  PRIORITY_KEY = "priority".freeze
35
+ SPAN_ID_KEY = "spanId".freeze
35
36
 
36
- def create noticed_error, payload
37
+ def create noticed_error, payload, span_id
37
38
  [
38
- intrinsic_attributes_for(noticed_error, payload),
39
+ intrinsic_attributes_for(noticed_error, payload, span_id),
39
40
  noticed_error.custom_attributes,
40
41
  noticed_error.agent_attributes
41
42
  ]
42
43
  end
43
44
 
44
- def intrinsic_attributes_for noticed_error, payload
45
+ def intrinsic_attributes_for noticed_error, payload, span_id
45
46
  attrs = {
46
47
  TYPE_KEY => SAMPLE_TYPE,
47
48
  ERROR_CLASS_KEY => noticed_error.exception_class_name,
@@ -50,6 +51,7 @@ module NewRelic
50
51
  TIMESTAMP_KEY => noticed_error.timestamp.to_f
51
52
  }
52
53
 
54
+ attrs[SPAN_ID_KEY] = span_id if span_id
53
55
  attrs[PORT_KEY] = noticed_error.request_port if noticed_error.request_port
54
56
 
55
57
  if payload
@@ -40,8 +40,8 @@ class NewRelic::Cli::Install < NewRelic::Cli::Command
40
40
 
41
41
  def run
42
42
  dest_file = File.expand_path(@dest_dir + "/newrelic.yml")
43
- if File.exist?(dest_file)
44
- raise NewRelic::Cli::Command::CommandFailure, "newrelic.yml file already exists. Move it out of the way."
43
+ if File.exist?(dest_file) && !@force
44
+ raise NewRelic::Cli::Command::CommandFailure, "newrelic.yml file already exists. Use --force flag to overwrite."
45
45
  end
46
46
  File.open(dest_file, 'w') { | out | out.puts(content) }
47
47
 
@@ -73,6 +73,7 @@ Visit support.newrelic.com if you are experiencing installation issues.
73
73
 
74
74
  def options
75
75
  OptionParser.new "Usage: #{$0} #{self.class.command} [ OPTIONS] 'application name'", 40 do |o|
76
+ o.on("-f", "--force", "Overwrite newrelic.yml if it exists") { | e | @force = true }
76
77
  o.on("-l", "--license_key=NAME", String,
77
78
  "Use the given license key") { | e | @license_key = e }
78
79
  o.on("-d", "--destdir=name", String,
@@ -1,6 +1,7 @@
1
1
  # encoding: utf-8
2
2
  # This file is distributed under New Relic's license terms.
3
3
  # See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
4
+ # frozen_string_literal: true
4
5
 
5
6
  require 'new_relic/helper'
6
7
  require 'new_relic/agent/attribute_filter'
@@ -17,9 +18,20 @@ class NewRelic::NoticedError
17
18
 
18
19
  attr_reader :exception_id, :is_internal
19
20
 
20
- STRIPPED_EXCEPTION_REPLACEMENT_MESSAGE = "Message removed by New Relic 'strip_exception_messages' setting".freeze
21
- UNKNOWN_ERROR_CLASS_NAME = 'Error'.freeze
22
- NIL_ERROR_MESSAGE = '<no message>'.freeze
21
+ STRIPPED_EXCEPTION_REPLACEMENT_MESSAGE = "Message removed by New Relic 'strip_exception_messages' setting"
22
+ UNKNOWN_ERROR_CLASS_NAME = 'Error'
23
+ NIL_ERROR_MESSAGE = '<no message>'
24
+
25
+ USER_ATTRIBUTES = "userAttributes"
26
+ AGENT_ATTRIBUTES = "agentAttributes"
27
+ INTRINSIC_ATTRIBUTES = "intrinsics"
28
+
29
+ DESTINATION = NewRelic::Agent::AttributeFilter::DST_ERROR_COLLECTOR
30
+
31
+ ERROR_PREFIX_KEY = 'error'
32
+ ERROR_MESSAGE_KEY = "#{ERROR_PREFIX_KEY}.message"
33
+ ERROR_CLASS_KEY = "#{ERROR_PREFIX_KEY}.class"
34
+ ERROR_EXPECTED_KEY = "#{ERROR_PREFIX_KEY}.expected"
23
35
 
24
36
  def initialize(path, exception, timestamp = Time.now)
25
37
  @exception_id = exception.object_id
@@ -77,12 +89,6 @@ class NewRelic::NoticedError
77
89
  processed_attributes ]
78
90
  end
79
91
 
80
- USER_ATTRIBUTES = "userAttributes".freeze
81
- AGENT_ATTRIBUTES = "agentAttributes".freeze
82
- INTRINSIC_ATTRIBUTES = "intrinsics".freeze
83
-
84
- DESTINATION = NewRelic::Agent::AttributeFilter::DST_ERROR_COLLECTOR
85
-
86
92
  # Note that we process attributes lazily and store the result. This is because
87
93
  # there is a possibility that a noticed error will be discarded and not sent back
88
94
  # as a traced error or TransactionError.
@@ -131,6 +137,16 @@ class NewRelic::NoticedError
131
137
  end
132
138
  end
133
139
 
140
+ def build_error_attributes
141
+ @attributes_from_notice_error ||= {}
142
+ @attributes_from_notice_error.merge!({
143
+ ERROR_MESSAGE_KEY => string(message),
144
+ ERROR_CLASS_KEY => string(exception_class_name)
145
+ })
146
+
147
+ @attributes_from_notice_error[ERROR_EXPECTED_KEY] = true if expected
148
+ end
149
+
134
150
  def build_agent_attributes(merged_attributes)
135
151
  agent_attributes = if @attributes
136
152
  @attributes.agent_attributes_for(DESTINATION)
@@ -177,6 +193,9 @@ class NewRelic::NoticedError
177
193
  if exception.nil?
178
194
  @exception_class_name = UNKNOWN_ERROR_CLASS_NAME
179
195
  @message = NIL_ERROR_MESSAGE
196
+ elsif exception.is_a? NewRelic::Agent::NoticibleError
197
+ @exception_class_name = exception.class_name
198
+ @message = exception.message
180
199
  else
181
200
  if defined?(Rails::VERSION::MAJOR) && Rails::VERSION::MAJOR < 5 && exception.respond_to?(:original_exception)
182
201
  exception = exception.original_exception || exception
@@ -37,7 +37,8 @@ module NewRelic::Rack
37
37
  if (js_to_inject != "") && should_instrument?(env, status, headers)
38
38
  response_string = autoinstrument_source(response, headers, js_to_inject)
39
39
  if headers.key?(CONTENT_LENGTH)
40
- headers[CONTENT_LENGTH] = response_string.size.to_s
40
+ content_length = response_string ? response_string.bytesize : 0
41
+ headers[CONTENT_LENGTH] = content_length.to_s
41
42
  end
42
43
 
43
44
  env[ALREADY_INSTRUMENTED_KEY] = true
@@ -11,7 +11,7 @@ module NewRelic
11
11
  end
12
12
 
13
13
  MAJOR = 6
14
- MINOR = 9
14
+ MINOR = 10
15
15
  TINY = 0
16
16
 
17
17
  begin
@@ -43,11 +43,36 @@ namespace :test do
43
43
 
44
44
  namespace :multiverse do
45
45
 
46
+ def remove_local_multiverse_databases
47
+ list_databases_command = %{echo "show databases" | mysql -u root}
48
+ databases = `#{list_databases_command}`.chomp!.split("\n").select{|s| s =~ /multiverse/}
49
+ databases.each do |database|
50
+ puts "Dropping #{database}"
51
+ `echo "drop database #{database}" | mysql -u root`
52
+ end
53
+ rescue => error
54
+ puts "ERROR: Cannot get MySQL databases..."
55
+ puts error.message
56
+ end
57
+
58
+ def remove_generated_gemfiles
59
+ file_path = File.expand_path "test/multiverse/suites"
60
+ Dir.glob(File.join file_path, "**", "Gemfile*").each do |fn|
61
+ puts "Removing #{fn.gsub(file_path,'.../suites')}"
62
+ FileUtils.rm fn
63
+ end
64
+ end
65
+
46
66
  task :env do
47
67
  # ENV['SUITES_DIRECTORY'] = File.expand_path('../../test/multiverse/suites', __FILE__)
48
68
  require File.expand_path('../../../test/multiverse/lib/multiverse', __FILE__)
49
69
  end
50
70
 
71
+ task :clobber do
72
+ remove_local_multiverse_databases
73
+ remove_generated_gemfiles
74
+ end
75
+
51
76
  desc "Clean cached gemfiles from Bundler.bundle_path"
52
77
  task :clean_gemfile_cache do
53
78
  glob = File.expand_path 'multiverse-cache/Gemfile.*.lock', Bundler.bundle_path
@@ -33,7 +33,7 @@ if defined? Rake::TestTask
33
33
 
34
34
  t.libs << "#{agent_home}/test"
35
35
  t.libs << "#{agent_home}/lib"
36
- t.pattern = file_pattern
36
+ t.pattern = Array(file_pattern).join(",")
37
37
  t.verbose = true
38
38
  end
39
39
 
@@ -6,25 +6,56 @@
6
6
  # itself, and should be usable from within any multiverse suite.
7
7
 
8
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
+ module MiniTest
17
+ module Assertions
18
+ # The failure message is backwards. This patch reverses the message
19
+ # Note: passing +msg+ caused two failure messages to be shown on failure
20
+ # and was more confusing than patching here.
21
+ def assert_match matcher, obj, msg = nil
22
+ msg = message(msg) { "Expected #{mu_pp obj} to match #{mu_pp matcher}" }
23
+ assert_respond_to matcher, :"=~"
24
+ matcher = Regexp.new Regexp.escape matcher if String === matcher
25
+ assert matcher =~ obj, msg
26
+ end
27
+ end
28
+ end
9
29
 
10
30
  class ArrayLogDevice
11
- def initialize( array=[] )
31
+ def initialize array=[]
12
32
  @array = array
13
33
  end
14
34
  attr_reader :array
15
35
 
16
- def write( message )
36
+ def write message
17
37
  @array << message
18
38
  end
19
39
 
20
40
  def close; end
21
41
  end
22
42
 
23
- def assert_between(floor, ceiling, value, message="expected #{floor} <= #{value} <= #{ceiling}")
43
+ def fake_guid length=16
44
+ NewRelic::Agent::GuidGenerator.generate_guid length
45
+ end
46
+
47
+ def assert_match matcher, obj, msg = nil
48
+ msg = message(msg) { "Expected #{mu_pp matcher} to match #{mu_pp obj}" }
49
+ assert_respond_to matcher, :"=~"
50
+ matcher = Regexp.new Regexp.escape matcher if String === matcher
51
+ assert matcher =~ obj, msg
52
+ end
53
+
54
+ def assert_between floor, ceiling, value, message="expected #{floor} <= #{value} <= #{ceiling}"
24
55
  assert((floor <= value && value <= ceiling), message)
25
56
  end
26
57
 
27
- def assert_in_delta(expected, actual, delta)
58
+ def assert_in_delta expected, actual, delta
28
59
  assert_between((expected - delta), (expected + delta), actual)
29
60
  end
30
61
 
@@ -36,7 +67,7 @@ def reset_error_traces!
36
67
  NewRelic::Agent.instance.error_collector.error_trace_aggregator.reset!
37
68
  end
38
69
 
39
- def assert_has_traced_error(error_class)
70
+ def assert_has_traced_error error_class
40
71
  errors = harvest_error_traces!
41
72
  assert \
42
73
  errors.find {|e| e.exception_class_name == error_class.name} != nil, \
@@ -63,38 +94,38 @@ def last_error_event
63
94
  harvest_error_events!.last.last
64
95
  end
65
96
 
66
- unless defined?( assert_block )
67
- def assert_block(*msgs)
97
+ unless defined? assert_block
98
+ def assert_block *msgs
68
99
  assert yield, *msgs
69
100
  end
70
101
  end
71
102
 
72
- unless defined?( assert_includes )
73
- def assert_includes( collection, member, msg=nil )
103
+ unless defined? assert_includes
104
+ def assert_includes collection, member, msg=nil
74
105
  msg = "Expected #{collection.inspect} to include #{member.inspect}"
75
106
  assert_block( msg ) { collection.include?(member) }
76
107
  end
77
108
  end
78
109
 
79
- unless defined?( assert_not_includes )
80
- def assert_not_includes( collection, member, msg=nil )
110
+ unless defined? assert_not_includes
111
+ def assert_not_includes collection, member, msg=nil
81
112
  msg = "Expected #{collection.inspect} not to include #{member.inspect}"
82
113
  assert !collection.include?(member), msg
83
114
  end
84
115
  end
85
116
 
86
- unless defined?( assert_empty )
87
- def assert_empty(collection, msg=nil)
117
+ unless defined? assert_empty
118
+ def assert_empty collection, msg=nil
88
119
  assert collection.empty?, msg
89
120
  end
90
121
  end
91
122
 
92
- def assert_equal_unordered(left, right)
123
+ def assert_equal_unordered left, right
93
124
  assert_equal(left.length, right.length, "Lengths don't match. #{left.length} != #{right.length}")
94
125
  left.each { |element| assert_includes(right, element) }
95
126
  end
96
127
 
97
- def assert_audit_log_contains(audit_log_contents, needle)
128
+ def assert_audit_log_contains audit_log_contents, needle
98
129
  # Original request bodies dumped to the log have symbol keys, but once
99
130
  # they go through a dump/load, they're strings again, so we strip
100
131
  # double-quotes and colons from the log, and the strings we searching for.
@@ -112,7 +143,7 @@ end
112
143
  # orderings of the key/value pairs in Hashes that were embedded in the request
113
144
  # body). So, this method traverses an object graph and only makes assertions
114
145
  # about the terminal (non-Array-or-Hash) nodes therein.
115
- def assert_audit_log_contains_object(audit_log_contents, o, format = :json)
146
+ def assert_audit_log_contains_object audit_log_contents, o, format = :json
116
147
  case o
117
148
  when Hash
118
149
  o.each do |k,v|
@@ -130,12 +161,12 @@ def assert_audit_log_contains_object(audit_log_contents, o, format = :json)
130
161
  end
131
162
  end
132
163
 
133
- def compare_metrics(expected, actual)
164
+ def compare_metrics expected, actual
134
165
  actual.delete_if {|a| a.include?('GC/Transaction/') }
135
166
  assert_equal(expected.to_a.sort, actual.to_a.sort, "extra: #{(actual - expected).to_a.inspect}; missing: #{(expected - actual).to_a.inspect}")
136
167
  end
137
168
 
138
- def metric_spec_from_specish(specish)
169
+ def metric_spec_from_specish specish
139
170
  spec = case specish
140
171
  when String then NewRelic::MetricSpec.new(specish)
141
172
  when Array then NewRelic::MetricSpec.new(*specish)
@@ -143,7 +174,7 @@ def metric_spec_from_specish(specish)
143
174
  spec
144
175
  end
145
176
 
146
- def _normalize_metric_expectations(expectations)
177
+ def _normalize_metric_expectations expectations
147
178
  case expectations
148
179
  when Array
149
180
  hash = {}
@@ -157,7 +188,7 @@ def _normalize_metric_expectations(expectations)
157
188
  end
158
189
  end
159
190
 
160
- def dump_stats(stats)
191
+ def dump_stats stats
161
192
  str = " Call count: #{stats.call_count}\n"
162
193
  str << " Total call time: #{stats.total_call_time}\n"
163
194
  str << " Total exclusive time: #{stats.total_exclusive_time}\n"
@@ -170,7 +201,7 @@ def dump_stats(stats)
170
201
  str
171
202
  end
172
203
 
173
- def assert_stats_has_values(stats, expected_spec, expected_attrs)
204
+ def assert_stats_has_values stats, expected_spec, expected_attrs
174
205
  expected_attrs.each do |attr, expected_value|
175
206
  actual_value = stats.send(attr)
176
207
  if attr == :call_count
@@ -183,7 +214,7 @@ def assert_stats_has_values(stats, expected_spec, expected_attrs)
183
214
  end
184
215
  end
185
216
 
186
- def assert_metrics_recorded(expected)
217
+ def assert_metrics_recorded expected
187
218
  expected = _normalize_metric_expectations(expected)
188
219
  expected.each do |specish, expected_attrs|
189
220
  expected_spec = metric_spec_from_specish(specish)
@@ -214,7 +245,7 @@ end
214
245
  # the :ignore_filter option. This will allow you to specify a Regex that
215
246
  # allowlists broad swathes of metric territory (e.g. 'Supportability/').
216
247
  #
217
- def assert_metrics_recorded_exclusive(expected, options={})
248
+ def assert_metrics_recorded_exclusive expected, options={}
218
249
  expected = _normalize_metric_expectations(expected)
219
250
  assert_metrics_recorded(expected)
220
251
 
@@ -252,7 +283,7 @@ def clear_metrics!
252
283
  NewRelic::Agent.instance.stats_engine.reset_for_test!
253
284
  end
254
285
 
255
- def assert_metrics_not_recorded(not_expected)
286
+ def assert_metrics_not_recorded not_expected
256
287
  not_expected = _normalize_metric_expectations(not_expected)
257
288
  found_but_not_expected = []
258
289
  not_expected.each do |specish, _|
@@ -266,7 +297,7 @@ end
266
297
 
267
298
  alias :refute_metrics_recorded :assert_metrics_not_recorded
268
299
 
269
- def assert_no_metrics_match(regex)
300
+ def assert_no_metrics_match regex
270
301
  matching_metrics = []
271
302
  NewRelic::Agent.instance.stats_engine.to_h.keys.map(&:to_s).each do |metric|
272
303
  matching_metrics << metric if metric.match regex
@@ -281,30 +312,30 @@ end
281
312
 
282
313
  alias :refute_metrics_match :assert_no_metrics_match
283
314
 
284
- def format_metric_spec_list(specs)
315
+ def format_metric_spec_list specs
285
316
  spec_strings = specs.map do |spec|
286
317
  "#{spec.name} (#{spec.scope.empty? ? '<unscoped>' : spec.scope})"
287
318
  end
288
319
  "[\n #{spec_strings.join(",\n ")}\n]"
289
320
  end
290
321
 
291
- def assert_truthy(expected, msg = nil)
322
+ def assert_truthy expected, msg=nil
292
323
  msg ||= "Expected #{expected.inspect} to be truthy"
293
324
  assert !!expected, msg
294
325
  end
295
326
 
296
- def assert_falsy(expected, msg = nil)
327
+ def assert_falsy expected, msg=nil
297
328
  msg ||= "Expected #{expected.inspect} to be falsy"
298
329
  assert !expected, msg
299
330
  end
300
331
 
301
- unless defined?( assert_false )
302
- def assert_false(expected)
332
+ unless defined? assert_false
333
+ def assert_false expected
303
334
  assert_equal false, expected
304
335
  end
305
336
  end
306
337
 
307
- unless defined?(refute)
338
+ unless defined? refute
308
339
  alias refute assert_false
309
340
  end
310
341
 
@@ -326,7 +357,7 @@ end
326
357
  # With a transaction name plus category:
327
358
  # in_transaction('foobar', :category => :controller) { ... }
328
359
  #
329
- def in_transaction(*args, &blk)
360
+ def in_transaction *args, &blk
330
361
  opts = (args.last && args.last.is_a?(Hash)) ? args.pop : {}
331
362
  category = (opts && opts.delete(:category)) || :other
332
363
 
@@ -360,30 +391,55 @@ end
360
391
  # that transaction to work with. The same arguements as provided to in_transaction
361
392
  # may be supplied.
362
393
  def with_segment *args, &blk
363
- in_transaction(*args) do |txn|
364
- yield txn.current_segment, txn
394
+ segment = nil
395
+ txn = in_transaction(*args) do |txn|
396
+ segment = txn.current_segment
397
+ yield segment, txn
398
+ end
399
+ [segment, txn]
400
+ end
401
+
402
+ # building error attributes on segments are deferred until it's time
403
+ # to publish/harvest them as spans, so for testing, we'll explicitly
404
+ # build 'em as appropriate so we can test 'em
405
+ def build_deferred_error_attributes segment
406
+ return unless segment.noticed_error
407
+ segment.noticed_error.build_error_attributes
408
+ end
409
+
410
+ def capture_segment_with_error
411
+ begin
412
+ segment_with_error = nil
413
+ with_segment do |segment|
414
+ segment_with_error = segment
415
+ raise "oops!"
416
+ end
417
+ rescue Exception => exception
418
+ assert segment_with_error, "expected to have a segment_with_error"
419
+ build_deferred_error_attributes segment_with_error
420
+ return segment_with_error, exception
365
421
  end
366
422
  end
367
423
 
368
- def stub_transaction_guid(guid)
424
+ def stub_transaction_guid guid
369
425
  NewRelic::Agent::Transaction.tl_current.instance_variable_set(:@guid, guid)
370
426
  end
371
427
 
372
428
  # Convenience wrapper around in_transaction that sets the category so that it
373
429
  # looks like we are in a web transaction
374
- def in_web_transaction(name='dummy')
430
+ def in_web_transaction name='dummy'
375
431
  in_transaction(name, :category => :controller, :request => stub(:path => '/')) do |txn|
376
432
  yield txn
377
433
  end
378
434
  end
379
435
 
380
- def in_background_transaction(name='silly')
436
+ def in_background_transaction name='silly'
381
437
  in_transaction(name, :category => :task) do |txn|
382
438
  yield txn
383
439
  end
384
440
  end
385
441
 
386
- def refute_contains_request_params(attributes)
442
+ def refute_contains_request_params attributes
387
443
  attributes.keys.each do |key|
388
444
  refute_match(/^request\.parameters\./, key.to_s)
389
445
  end
@@ -402,7 +458,7 @@ def last_transaction_trace_request_params
402
458
  end
403
459
  end
404
460
 
405
- def find_sql_trace(metric_name)
461
+ def find_sql_trace metric_name
406
462
  NewRelic::Agent.agent.sql_sampler.sql_traces.values.detect do |trace|
407
463
  trace.database_metric_name == metric_name
408
464
  end
@@ -412,7 +468,7 @@ def last_sql_trace
412
468
  NewRelic::Agent.agent.sql_sampler.sql_traces.values.last
413
469
  end
414
470
 
415
- def find_last_transaction_node(transaction_sample=nil)
471
+ def find_last_transaction_node transaction_sample=nil
416
472
  if transaction_sample
417
473
  root_node = transaction_sample.root_node
418
474
  else
@@ -425,7 +481,7 @@ def find_last_transaction_node(transaction_sample=nil)
425
481
  return last_node
426
482
  end
427
483
 
428
- def find_node_with_name(transaction_sample, name)
484
+ def find_node_with_name transaction_sample, name
429
485
  transaction_sample.root_node.each_node do |node|
430
486
  if node.metric_name == name
431
487
  return node
@@ -435,7 +491,7 @@ def find_node_with_name(transaction_sample, name)
435
491
  nil
436
492
  end
437
493
 
438
- def find_node_with_name_matching(transaction_sample, regex)
494
+ def find_node_with_name_matching transaction_sample, regex
439
495
  transaction_sample.root_node.each_node do |node|
440
496
  if node.metric_name.match regex
441
497
  return node
@@ -445,7 +501,7 @@ def find_node_with_name_matching(transaction_sample, regex)
445
501
  nil
446
502
  end
447
503
 
448
- def find_all_nodes_with_name_matching(transaction_sample, regexes)
504
+ def find_all_nodes_with_name_matching transaction_sample, regexes
449
505
  regexes = [regexes].flatten
450
506
  matching_nodes = []
451
507
 
@@ -460,7 +516,7 @@ def find_all_nodes_with_name_matching(transaction_sample, regexes)
460
516
  matching_nodes
461
517
  end
462
518
 
463
- def with_config(config_hash, at_start=true)
519
+ def with_config config_hash, at_start=true
464
520
  config = NewRelic::Agent::Configuration::DottedHash.new(config_hash, true)
465
521
  NewRelic::Agent.config.add_config_for_testing(config, at_start)
466
522
  NewRelic::Agent.instance.refresh_attribute_filter
@@ -479,13 +535,13 @@ def with_server_source config_hash, at_start=true
479
535
  end
480
536
  end
481
537
 
482
- def with_config_low_priority(config_hash)
538
+ def with_config_low_priority config_hash
483
539
  with_config(config_hash, false) do
484
540
  yield
485
541
  end
486
542
  end
487
543
 
488
- def with_transaction_renaming_rules(rule_specs)
544
+ def with_transaction_renaming_rules rule_specs
489
545
  original_engine = NewRelic::Agent.agent.instance_variable_get(:@transaction_rules)
490
546
  begin
491
547
  new_engine = NewRelic::Agent::RulesEngine.create_transaction_rules('transaction_name_rules' => rule_specs)
@@ -511,7 +567,7 @@ unless Time.respond_to?(:__original_now)
511
567
  end
512
568
  end
513
569
 
514
- def nr_freeze_time(now=Time.now)
570
+ def nr_freeze_time now=Time.now
515
571
  Time.__frozen_now = now
516
572
  end
517
573
 
@@ -519,11 +575,11 @@ def nr_unfreeze_time
519
575
  Time.__frozen_now = nil
520
576
  end
521
577
 
522
- def advance_time(seconds)
578
+ def advance_time seconds
523
579
  Time.__frozen_now = Time.now + seconds
524
580
  end
525
581
 
526
- def with_constant_defined(constant_symbol, implementation=Module.new)
582
+ def with_constant_defined constant_symbol, implementation=Module.new
527
583
  const_path = constant_path(constant_symbol.to_s)
528
584
 
529
585
  if const_path
@@ -543,7 +599,7 @@ def with_constant_defined(constant_symbol, implementation=Module.new)
543
599
  end
544
600
  end
545
601
 
546
- def constant_path(name, opts={})
602
+ def constant_path name, opts={}
547
603
  allow_partial = opts[:allow_partial]
548
604
  path = [Object]
549
605
  parts = name.gsub(/^::/, '').split('::')
@@ -556,13 +612,13 @@ def constant_path(name, opts={})
556
612
  path
557
613
  end
558
614
 
559
- def get_parent(constant_name)
615
+ def get_parent constant_name
560
616
  parent_name = constant_name.gsub(/::[^:]*$/, '')
561
617
  const_path = constant_path(parent_name)
562
618
  const_path ? const_path[-1] : nil
563
619
  end
564
620
 
565
- def undefine_constant(constant_symbol)
621
+ def undefine_constant constant_symbol
566
622
  const_str = constant_symbol.to_s
567
623
  parent = get_parent(const_str)
568
624
  const_name = const_str.gsub(/.*::/, '')
@@ -586,11 +642,11 @@ ensure
586
642
  NewRelic::Agent.logger = orig_logger
587
643
  end
588
644
 
589
- def create_agent_command(args = {})
645
+ def create_agent_command args={}
590
646
  NewRelic::Agent::Commands::AgentCommand.new([-1, { "name" => "command_name", "arguments" => args}])
591
647
  end
592
648
 
593
- def wait_for_backtrace_service_poll(opts={})
649
+ def wait_for_backtrace_service_poll opts={}
594
650
  defaults = {
595
651
  :timeout => 10.0,
596
652
  :service => NewRelic::Agent.agent.agent_command_router.backtrace_service,
@@ -616,7 +672,7 @@ def wait_for_backtrace_service_poll(opts={})
616
672
  end
617
673
  end
618
674
 
619
- def with_array_logger(level=:info)
675
+ def with_array_logger level=:info
620
676
  orig_logger = NewRelic::Agent.logger
621
677
  config = { :log_level => level }
622
678
  logdev = ArrayLogDevice.new
@@ -711,11 +767,11 @@ end
711
767
 
712
768
  # Changes ENV settings to given and runs given block and restores ENV
713
769
  # to original values before returning.
714
- def with_environment(env, &block)
770
+ def with_environment env, &block
715
771
  EnvUpdater.inject(env) { yield }
716
772
  end
717
773
 
718
- def with_argv(argv)
774
+ def with_argv argv
719
775
  old_argv = ARGV.dup
720
776
  ARGV.clear
721
777
  ARGV.concat(argv)
@@ -728,7 +784,7 @@ def with_argv(argv)
728
784
  end
729
785
  end
730
786
 
731
- def with_ignore_error_filter(filter, &blk)
787
+ def with_ignore_error_filter filter, &blk
732
788
  original_filter = NewRelic::Agent.ignore_error_filter
733
789
  NewRelic::Agent.ignore_error_filter(&filter)
734
790
 
@@ -737,7 +793,7 @@ ensure
737
793
  NewRelic::Agent::ErrorCollector.ignore_error_filter = original_filter
738
794
  end
739
795
 
740
- def json_dump_and_encode(object)
796
+ def json_dump_and_encode object
741
797
  Base64.encode64(::JSON.dump(object))
742
798
  end
743
799
 
@@ -745,7 +801,7 @@ def get_last_analytics_event
745
801
  NewRelic::Agent.agent.transaction_event_aggregator.harvest![1].last
746
802
  end
747
803
 
748
- def swap_instance_method(target, method_name, new_method_implementation, &blk)
804
+ def swap_instance_method target, method_name, new_method_implementation, &blk
749
805
  old_method_implementation = target.instance_method(method_name)
750
806
  target.send(:define_method, method_name, new_method_implementation)
751
807
  yield
@@ -760,7 +816,7 @@ def cross_agent_tests_dir
760
816
  File.expand_path(File.join(File.dirname(__FILE__), 'fixtures', 'cross_agent_tests'))
761
817
  end
762
818
 
763
- def load_cross_agent_test(name)
819
+ def load_cross_agent_test name
764
820
  test_file_path = File.join(cross_agent_tests_dir, "#{name}.json")
765
821
  data = File.read(test_file_path)
766
822
  data.gsub!('callCount', 'call_count')
@@ -769,13 +825,13 @@ def load_cross_agent_test(name)
769
825
  data
770
826
  end
771
827
 
772
- def each_cross_agent_test(options)
828
+ def each_cross_agent_test options
773
829
  options = {:dir => nil, :pattern => "*"}.update(options)
774
830
  path = File.join [cross_agent_tests_dir, options[:dir], options[:pattern]].compact
775
831
  Dir.glob(path).each { |file| yield file}
776
832
  end
777
833
 
778
- def assert_event_attributes(event, test_name, expected_attributes, non_expected_attributes)
834
+ def assert_event_attributes event, test_name, expected_attributes, non_expected_attributes
779
835
  incorrect_attributes = []
780
836
 
781
837
  event_attrs = event[0]
@@ -803,7 +859,7 @@ def assert_event_attributes(event, test_name, expected_attributes, non_expected_
803
859
  end
804
860
  end
805
861
 
806
- def attributes_for(sample, type)
862
+ def attributes_for sample, type
807
863
  sample.attributes.instance_variable_get("@#{type}_attributes")
808
864
  end
809
865
 
@@ -815,3 +871,100 @@ def reset_buffers_and_caches
815
871
  NewRelic::Agent.drop_buffered_data
816
872
  uncache_trusted_account_key
817
873
  end
874
+
875
+ def message_for_status_code code
876
+ # Net::HTTP::STATUS_CODES was introduced in Ruby 2.5
877
+ if defined?(Net::HTTP::STATUS_CODES)
878
+ return Net::HTTP::STATUS_CODES[code]
879
+ end
880
+
881
+ case code
882
+ when 200 then "OK"
883
+ when 404 then "Not Found"
884
+ when 403 then "Forbidden"
885
+ else "Unknown"
886
+ end
887
+ end
888
+
889
+ # wraps the given headers in a Net::HTTPResponse which has accompanying
890
+ # http status code associated with it.
891
+ # a "status_code" may be passed in the headers to alter the HTTP Status Code
892
+ # that is wrapped in the response.
893
+ def mock_http_response headers, wrap_it=true
894
+ status_code = (headers.delete("status_code") || 200).to_i
895
+ net_http_resp = Net::HTTPResponse.new(1.0, status_code, message_for_status_code(status_code))
896
+ headers.each do |key, value|
897
+ net_http_resp.add_field key.to_s, value
898
+ end
899
+ return net_http_resp unless wrap_it
900
+ NewRelic::Agent::HTTPClients::NetHTTPResponse.new(net_http_resp)
901
+ end
902
+
903
+ # +expected+ can be a string or regular expression
904
+ def assert_match_or_equal expected, value
905
+ if expected.is_a?(Regexp)
906
+ assert_match expected, value
907
+ else
908
+ assert_equal expected, value
909
+ end
910
+ end
911
+
912
+ # selects the last segment with a noticed_error and checks
913
+ # the expectations against it.
914
+ def assert_segment_noticed_error txn, segment_name, error_classes, error_message
915
+ error_segment = txn.segments.reverse.detect{|s| s.noticed_error}
916
+ assert error_segment, "Expected at least one segment with a noticed_error"
917
+
918
+ assert_match_or_equal segment_name, error_segment.name
919
+
920
+ noticed_error = error_segment.noticed_error
921
+
922
+ assert_match_or_equal error_classes, noticed_error.exception_class_name
923
+ assert_match_or_equal error_message, noticed_error.message
924
+ end
925
+
926
+ def assert_transaction_noticed_error txn, error_classes
927
+ refute_empty txn.exceptions, "Expected transaction to notice the error"
928
+ assert_match_or_equal error_classes, txn.exceptions.keys.first.class.name
929
+ end
930
+
931
+ def refute_transaction_noticed_error txn, error_class
932
+ error_segment = txn.segments.reverse.detect{|s| s.noticed_error}
933
+ assert error_segment, "Expected at least one segment with a noticed_error"
934
+ assert_empty txn.exceptions, "Expected transaction to NOT notice any segment errors"
935
+ end
936
+
937
+ def refute_raises *exp
938
+ msg = "#{exp.pop}.\n" if String === exp.last
939
+
940
+ begin
941
+ yield
942
+ rescue MiniTest::Skip => e
943
+ puts "SKIP REPORTS: #{e.inspect}"
944
+ return e if exp.include? MiniTest::Skip
945
+ raise e
946
+ rescue Exception => e
947
+ puts "EXCEPTION RAISED: #{e.inspect}\n#{e.backtrace}"
948
+ exp = exp.first if exp.size == 1
949
+ flunk msg || "unexpected exception raised: #{e}"
950
+ end
951
+ end
952
+
953
+ def assert_implements instance, method, *args
954
+ fail_message = "expected #{instance.class}##{method} method to be implemented"
955
+ refute_raises NotImplementedError, fail_message do
956
+ instance.send(method, *args)
957
+ end
958
+ end
959
+
960
+ def defer_testing_to_min_supported_rails test_file, min_rails_version, supports_jruby=true
961
+ if defined?(::Rails) &&
962
+ defined?(::Rails::VERSION::STRING) &&
963
+ (::Rails::VERSION::STRING.to_f >= min_rails_version) &&
964
+ (supports_jruby || !NewRelic::LanguageSupport.jruby?)
965
+
966
+ yield
967
+ else
968
+ puts "Skipping tests in #{test_file} because Rails >= #{min_rails_version} is unavailable"
969
+ end
970
+ end