newrelic_rpm 3.9.0.229 → 3.9.1.236

Sign up to get free protection for your applications and to get access to all the features.
Files changed (82) hide show
  1. data.tar.gz.sig +4 -1
  2. data/CHANGELOG +73 -0
  3. data/install.rb +2 -2
  4. data/lib/new_relic/agent/agent.rb +8 -1
  5. data/lib/new_relic/agent/browser_token.rb +10 -7
  6. data/lib/new_relic/agent/configuration/default_source.rb +8 -1
  7. data/lib/new_relic/agent/configuration/high_security_source.rb +56 -0
  8. data/lib/new_relic/agent/configuration/manager.rb +35 -28
  9. data/lib/new_relic/agent/cross_app_monitor.rb +23 -0
  10. data/lib/new_relic/agent/cross_app_tracing.rb +34 -26
  11. data/lib/new_relic/agent/database.rb +7 -6
  12. data/lib/new_relic/agent/instrumentation/active_record.rb +18 -18
  13. data/lib/new_relic/agent/instrumentation/controller_instrumentation.rb +1 -1
  14. data/lib/new_relic/agent/instrumentation/curb.rb +18 -15
  15. data/lib/new_relic/agent/instrumentation/excon/connection.rb +1 -1
  16. data/lib/new_relic/agent/instrumentation/excon/middleware.rb +7 -4
  17. data/lib/new_relic/agent/instrumentation/httpclient.rb +1 -1
  18. data/lib/new_relic/agent/instrumentation/memcache.rb +9 -2
  19. data/lib/new_relic/agent/instrumentation/middleware_tracing.rb +1 -1
  20. data/lib/new_relic/agent/instrumentation/net.rb +1 -1
  21. data/lib/new_relic/agent/instrumentation/rails3/errors.rb +1 -1
  22. data/lib/new_relic/agent/instrumentation/rails4/errors.rb +1 -1
  23. data/lib/new_relic/agent/instrumentation/typhoeus.rb +6 -4
  24. data/lib/new_relic/agent/javascript_instrumentor.rb +9 -1
  25. data/lib/new_relic/agent/new_relic_service.rb +7 -0
  26. data/lib/new_relic/agent/obfuscator.rb +3 -2
  27. data/lib/new_relic/agent/request_sampler.rb +17 -1
  28. data/lib/new_relic/agent/sql_sampler.rb +10 -6
  29. data/lib/new_relic/agent/traced_method_stack.rb +0 -12
  30. data/lib/new_relic/agent/transaction.rb +98 -10
  31. data/lib/new_relic/agent/transaction_sampler.rb +10 -3
  32. data/lib/new_relic/agent/transaction_state.rb +2 -12
  33. data/lib/new_relic/control/frameworks/sinatra.rb +0 -3
  34. data/lib/new_relic/control/instance_methods.rb +5 -0
  35. data/lib/new_relic/json_wrapper.rb +7 -1
  36. data/lib/new_relic/rack/browser_monitoring.rb +25 -4
  37. data/lib/new_relic/version.rb +1 -1
  38. data/newrelic_rpm.gemspec +0 -1
  39. data/test/agent_helper.rb +76 -0
  40. data/test/fixtures/cross_agent_tests/cat_map.json +299 -0
  41. data/test/multiverse/suites/agent_only/collector_exception_handling_test.rb +16 -0
  42. data/test/multiverse/suites/agent_only/cross_application_tracing_test.rb +30 -0
  43. data/test/multiverse/suites/agent_only/marshaling_test.rb +17 -17
  44. data/test/multiverse/suites/agent_only/testing_app.rb +10 -1
  45. data/test/multiverse/suites/deferred_instrumentation/config/newrelic.yml +0 -1
  46. data/test/multiverse/suites/high_security/Envfile +3 -0
  47. data/test/multiverse/suites/high_security/config/newrelic.yml +27 -0
  48. data/test/multiverse/suites/high_security/high_security_test.rb +64 -0
  49. data/test/multiverse/suites/rails/action_controller_live_rum_test.rb +39 -0
  50. data/test/multiverse/suites/rails/bad_instrumentation_test.rb +3 -1
  51. data/test/multiverse/suites/rails/parameter_capture_test.rb +0 -20
  52. data/test/new_relic/agent/agent/connect_test.rb +5 -10
  53. data/test/new_relic/agent/agent_test.rb +11 -0
  54. data/test/new_relic/agent/browser_token_test.rb +10 -6
  55. data/test/new_relic/agent/configuration/default_source_test.rb +6 -0
  56. data/test/new_relic/agent/configuration/high_security_source_test.rb +83 -0
  57. data/test/new_relic/agent/configuration/manager_test.rb +7 -2
  58. data/test/new_relic/agent/cross_app_monitor_test.rb +17 -1
  59. data/test/new_relic/agent/cross_app_tracing_test.rb +11 -10
  60. data/test/new_relic/agent/hostname_test.rb +1 -1
  61. data/test/new_relic/agent/instrumentation/controller_instrumentation_test.rb +22 -0
  62. data/test/new_relic/agent/instrumentation/middleware_tracing_test.rb +37 -0
  63. data/test/new_relic/agent/instrumentation/net_instrumentation_test.rb +4 -2
  64. data/test/new_relic/agent/javascript_instrumentor_test.rb +42 -0
  65. data/test/new_relic/agent/memcache_instrumentation_test.rb +11 -5
  66. data/test/new_relic/agent/request_sampler_test.rb +9 -0
  67. data/test/new_relic/agent/sql_sampler_test.rb +46 -0
  68. data/test/new_relic/agent/stats_engine/gc_profiler_test.rb +1 -0
  69. data/test/new_relic/agent/transaction_sampler_test.rb +64 -4
  70. data/test/new_relic/agent/transaction_state_test.rb +0 -75
  71. data/test/new_relic/agent/transaction_test.rb +142 -0
  72. data/test/new_relic/control/instance_methods_test.rb +24 -4
  73. data/test/new_relic/fake_collector.rb +6 -13
  74. data/test/new_relic/fake_external_server.rb +14 -1
  75. data/test/new_relic/http_client_test_cases.rb +69 -21
  76. data/test/new_relic/json_wrapper_test.rb +10 -5
  77. data/test/performance/suites/rack_middleware.rb +2 -1
  78. data/test/performance/suites/rum_autoinsertion.rb +17 -3
  79. data/test/script/path_hash.rb +49 -0
  80. data/test/test_helper.rb +0 -10
  81. metadata +12 -52
  82. metadata.gz.sig +0 -0
@@ -23,18 +23,6 @@ module NewRelic
23
23
  @stack = []
24
24
  end
25
25
 
26
- def self.tl_push_frame(tag, time = Time.now.to_f)
27
- state = NewRelic::Agent::TransactionState.tl_get
28
- stack = state.traced_method_stack
29
- stack.push_frame(state, tag, time)
30
- end
31
-
32
- def self.tl_pop_frame(expected_frame, name, time, deduct_call_time_from_parent=true)
33
- state = NewRelic::Agent::TransactionState.tl_get
34
- stack = state.traced_method_stack
35
- stack.pop_frame(state, expected_frame, name, time, deduct_call_time_from_parent)
36
- end
37
-
38
26
  # Pushes a frame onto the transaction stack - this generates a
39
27
  # TransactionSample::Segment at the end of transaction execution.
40
28
  #
@@ -126,11 +126,21 @@ module NewRelic
126
126
  end
127
127
 
128
128
  txn
129
+ rescue => e
130
+ NewRelic::Agent.logger.error("Exception during Transaction.start", e)
131
+ nil
129
132
  end
130
133
 
134
+ FAILED_TO_STOP_MESSAGE = "Failed during Transaction.stop because there is no current transaction"
135
+
131
136
  def self.stop(state, end_time=Time.now)
132
137
  txn = state.current_transaction
133
138
 
139
+ if txn.nil?
140
+ NewRelic::Agent.logger.error(FAILED_TO_STOP_MESSAGE)
141
+ return
142
+ end
143
+
134
144
  if txn.frame_stack.empty?
135
145
  txn.stop(state, end_time)
136
146
  state.reset
@@ -166,6 +176,9 @@ module NewRelic
166
176
  end
167
177
 
168
178
  :transaction_stopped
179
+ rescue => e
180
+ NewRelic::Agent.logger.error("Exception during Transaction.stop", e)
181
+ nil
169
182
  end
170
183
 
171
184
  def self.nested_transaction_name(name)
@@ -188,7 +201,7 @@ module NewRelic
188
201
  end
189
202
  end
190
203
 
191
- attr_reader :frame_stack
204
+ attr_reader :frame_stack, :cat_path_hashes
192
205
 
193
206
  def initialize(category, options)
194
207
  @frame_stack = []
@@ -209,6 +222,7 @@ module NewRelic
209
222
  @exceptions = {}
210
223
  @metrics = TransactionMetrics.new
211
224
  @guid = generate_guid
225
+ @cat_path_hashes = nil
212
226
 
213
227
  @ignore_this_transaction = false
214
228
  @ignore_apdex = false
@@ -416,23 +430,85 @@ module NewRelic
416
430
  # This event is fired when the transaction is fully completed. The metric
417
431
  # values and sampler can't be successfully modified from this event.
418
432
  def send_transaction_finished_event(state, start_time, end_time)
433
+ duration = end_time.to_f - start_time.to_f
419
434
  payload = {
420
435
  :name => @frozen_name,
421
436
  :start_timestamp => start_time.to_f,
422
- :duration => end_time.to_f - start_time.to_f,
437
+ :duration => duration,
423
438
  :metrics => @metrics,
424
439
  :custom_params => custom_parameters
425
440
  }
426
- append_guid_to(state, payload)
441
+ append_cat_info(state, duration, payload)
442
+ append_apdex_perf_zone(duration, payload)
427
443
  append_referring_transaction_guid_to(state, payload)
428
444
 
429
445
  agent.events.notify(:transaction_finished, payload)
430
446
  end
431
447
 
432
- def append_guid_to(state, payload)
433
- guid = state.request_guid_for_event
434
- if guid
435
- payload[:guid] = guid
448
+ def include_guid?(state, duration)
449
+ state.is_cross_app? ||
450
+ (state.request_token && duration > apdex_t)
451
+ end
452
+
453
+ def cat_trip_id(state)
454
+ NewRelic::Agent.instance.cross_app_monitor.client_referring_transaction_trip_id(state) || guid
455
+ end
456
+
457
+ def cat_path_hash(state)
458
+ referring_path_hash = cat_referring_path_hash(state) || '0'
459
+ seed = referring_path_hash.to_i(16)
460
+ result = NewRelic::Agent.instance.cross_app_monitor.path_hash(best_name, seed)
461
+ record_cat_path_hash(result)
462
+ result
463
+ end
464
+
465
+ def record_cat_path_hash(hash)
466
+ @cat_path_hashes ||= []
467
+ if @cat_path_hashes.size < 10 && !@cat_path_hashes.include?(hash)
468
+ @cat_path_hashes << hash
469
+ end
470
+ end
471
+
472
+ def cat_referring_path_hash(state)
473
+ NewRelic::Agent.instance.cross_app_monitor.client_referring_transaction_path_hash(state)
474
+ end
475
+
476
+ APDEX_S = 'S'.freeze
477
+ APDEX_T = 'T'.freeze
478
+ APDEX_F = 'F'.freeze
479
+
480
+ def append_apdex_perf_zone(duration, payload)
481
+ return unless recording_web_transaction?
482
+
483
+ bucket = apdex_bucket(duration)
484
+ bucket_str = case bucket
485
+ when :apdex_s then APDEX_S
486
+ when :apdex_t then APDEX_T
487
+ when :apdex_f then APDEX_F
488
+ else nil
489
+ end
490
+ payload[:apdex_perf_zone] = bucket_str if bucket_str
491
+ end
492
+
493
+ def append_cat_info(state, duration, payload)
494
+ return unless include_guid?(state, duration)
495
+ payload[:guid] = guid
496
+
497
+ return unless state.is_cross_app?
498
+ trip_id = cat_trip_id(state)
499
+ path_hash = cat_path_hash(state)
500
+ referring_path_hash = cat_referring_path_hash(state)
501
+
502
+ payload[:cat_trip_id] = trip_id if trip_id
503
+ payload[:cat_referring_path_hash] = referring_path_hash if referring_path_hash
504
+
505
+ if path_hash
506
+ payload[:cat_path_hash] = path_hash
507
+
508
+ alternate_path_hashes = cat_path_hashes - [path_hash]
509
+ unless alternate_path_hashes.empty?
510
+ payload[:cat_alternate_path_hashes] = alternate_path_hashes
511
+ end
436
512
  end
437
513
  end
438
514
 
@@ -505,16 +581,23 @@ module NewRelic
505
581
 
506
582
  APDEX_METRIC = 'Apdex'.freeze
507
583
 
584
+ def had_error?
585
+ !notable_exceptions.empty?
586
+ end
587
+
588
+ def apdex_bucket(duration)
589
+ self.class.apdex_bucket(duration, had_error?, apdex_t)
590
+ end
591
+
508
592
  def record_apdex(state, end_time=Time.now)
509
593
  return unless recording_web_transaction? && state.is_execution_traced?
510
594
 
511
595
  freeze_name_and_execute_if_not_ignored do
512
596
  action_duration = end_time - start_time
513
597
  total_duration = end_time - apdex_start
514
- is_error = !notable_exceptions.empty?
515
598
 
516
- apdex_bucket_global = self.class.apdex_bucket(total_duration, is_error, apdex_t)
517
- apdex_bucket_txn = self.class.apdex_bucket(action_duration, is_error, apdex_t)
599
+ apdex_bucket_global = apdex_bucket(total_duration)
600
+ apdex_bucket_txn = apdex_bucket(action_duration)
518
601
 
519
602
  @metrics.record_unscoped(APDEX_METRIC, apdex_bucket_global, apdex_t)
520
603
  txn_apdex_metric = @frozen_name.gsub(/^[^\/]+\//, 'Apdex/')
@@ -559,6 +642,11 @@ module NewRelic
559
642
  end
560
643
 
561
644
  def add_custom_parameters(p)
645
+ if NewRelic::Agent.config[:high_security]
646
+ NewRelic::Agent.logger.debug("Unable to add custom attributes #{p.keys.inspect} while in high security mode.")
647
+ return
648
+ end
649
+
562
650
  custom_parameters.merge!(p)
563
651
  end
564
652
 
@@ -126,10 +126,17 @@ module NewRelic
126
126
  last_builder.set_transaction_name(txn.best_name)
127
127
  last_builder.finish_trace(time.to_f, custom_parameters_from_transaction(txn))
128
128
 
129
+ last_sample = last_builder.sample
130
+ last_sample.guid = txn.guid
131
+ last_sample.set_custom_param(:gc_time, gc_time) if gc_time
132
+
133
+ if state.is_cross_app?
134
+ last_sample.set_custom_param(:'nr.trip_id', txn.cat_trip_id(state))
135
+ last_sample.set_custom_param(:'nr.path_hash', txn.cat_path_hash(state))
136
+ end
137
+
129
138
  @samples_lock.synchronize do
130
- @last_sample = last_builder.sample
131
- @last_sample.guid = txn.guid
132
- @last_sample.set_custom_param(:gc_time, gc_time) if gc_time
139
+ @last_sample = last_sample
133
140
  store_sample(@last_sample)
134
141
  @last_sample
135
142
  end
@@ -84,9 +84,8 @@ module NewRelic
84
84
  referring_transaction_info != nil
85
85
  end
86
86
 
87
- def request_guid_for_event
88
- return nil unless is_cross_app_callee? || is_cross_app_caller? || include_guid?
89
- request_guid
87
+ def is_cross_app?
88
+ is_cross_app_caller? || is_cross_app_callee?
90
89
  end
91
90
 
92
91
  # Request data
@@ -98,15 +97,6 @@ module NewRelic
98
97
  current_transaction.guid
99
98
  end
100
99
 
101
- def request_guid_to_include
102
- return "" unless include_guid?
103
- request_guid
104
- end
105
-
106
- def include_guid?
107
- request_token && timings.app_time_in_seconds > current_transaction.apdex_t
108
- end
109
-
110
100
  # Current transaction stack and sample building
111
101
  attr_reader :current_transaction
112
102
  attr_accessor :transaction_sample_builder
@@ -8,9 +8,6 @@ module NewRelic
8
8
  module Frameworks
9
9
  # Contains basic control logic for Sinatra
10
10
  class Sinatra < NewRelic::Control::Frameworks::Ruby
11
- def init_config(options={})
12
- super
13
- end
14
11
  end
15
12
  end
16
13
  end
@@ -85,6 +85,11 @@ module NewRelic
85
85
 
86
86
  config_file_path = @config_file_override || Agent.config[:config_path]
87
87
  Agent.config.replace_or_add_config(Agent::Configuration::YamlSource.new(config_file_path, env))
88
+
89
+ if Agent.config[:high_security]
90
+ Agent.logger.info("Installing high security configuration based on local configuration")
91
+ Agent.config.replace_or_add_config(Agent::Configuration::HighSecuritySource.new(Agent.config))
92
+ end
88
93
  end
89
94
 
90
95
  # Install the real agent into the Agent module, and issue the start command.
@@ -33,6 +33,11 @@ module NewRelic
33
33
  end
34
34
 
35
35
  def self.normalize_string(s)
36
+ # Early return if called on 1.8.x. In normal circumstances 1.8.x
37
+ # shouldn't call this--it does nothing for Ruby-marshalled formats-- but
38
+ # we use it in multiverse to make comparing data more consistent.
39
+ return s unless supports_normalization?
40
+
36
41
  encoding = s.encoding
37
42
  if (encoding == Encoding::UTF_8 || encoding == Encoding::ISO_8859_1) && s.valid_encoding?
38
43
  return s
@@ -69,7 +74,8 @@ module NewRelic
69
74
  return object if object.empty?
70
75
  hash = {}
71
76
  object.each_pair do |k, v|
72
- k = normalize_string(k) if k.is_a?(String)
77
+ k = normalize_string(k) if k.is_a?(String)
78
+ k = normalize_string(k.to_s) if k.is_a?(Symbol)
73
79
  hash[k] = normalize(v)
74
80
  end
75
81
  hash
@@ -15,6 +15,10 @@ module NewRelic::Rack
15
15
  # @api public
16
16
  #
17
17
  class BrowserMonitoring < AgentMiddleware
18
+ # The maximum number of bytes of the response body that we will
19
+ # examine in order to look for a RUM insertion point.
20
+ SCAN_LIMIT = 50_000
21
+
18
22
  def traced_call(env)
19
23
  result = @app.call(env) # [status, headers, response]
20
24
 
@@ -38,8 +42,23 @@ module NewRelic::Rack
38
42
  def should_instrument?(env, status, headers)
39
43
  status == 200 &&
40
44
  !env[ALREADY_INSTRUMENTED_KEY] &&
41
- headers["Content-Type"] && headers["Content-Type"].include?("text/html") &&
42
- !headers['Content-Disposition'].to_s.include?('attachment')
45
+ is_html?(headers) &&
46
+ !is_attachment?(headers) &&
47
+ !is_streaming?(env)
48
+ end
49
+
50
+ def is_html?(headers)
51
+ headers["Content-Type"] && headers["Content-Type"].include?("text/html")
52
+ end
53
+
54
+ def is_attachment?(headers)
55
+ headers['Content-Disposition'].to_s.include?('attachment')
56
+ end
57
+
58
+ def is_streaming?(env)
59
+ return false unless defined?(ActionController::Live)
60
+
61
+ env['action_controller.instance'].class.included_modules.include?(ActionController::Live)
43
62
  end
44
63
 
45
64
  CHARSET_RE = /<\s*meta[^>]+charset\s*=[^>]*>/im.freeze
@@ -51,7 +70,7 @@ module NewRelic::Rack
51
70
  return nil unless source
52
71
 
53
72
  # Only scan the first 50k (roughly) then give up.
54
- beginning_of_source = source[0..50_000]
73
+ beginning_of_source = source[0..SCAN_LIMIT]
55
74
 
56
75
  if body_start = find_body_start(beginning_of_source)
57
76
  meta_tag_positions = [
@@ -73,7 +92,9 @@ module NewRelic::Rack
73
92
  NewRelic::Agent.logger.debug "Skipping RUM instrumentation. Could not properly determine location to inject script."
74
93
  end
75
94
  else
76
- NewRelic::Agent.logger.debug "Skipping RUM instrumentation. Unable to find <body> tag in document."
95
+ msg = "Skipping RUM instrumentation. Unable to find <body> tag in first #{SCAN_LIMIT} bytes of document."
96
+ NewRelic::Agent.logger.log_once(:warn, :rum_insertion_failure, msg)
97
+ NewRelic::Agent.logger.debug(msg)
77
98
  end
78
99
 
79
100
  if headers['Content-Length']
@@ -12,7 +12,7 @@ module NewRelic
12
12
 
13
13
  MAJOR = 3
14
14
  MINOR = 9
15
- TINY = 0
15
+ TINY = 1
16
16
 
17
17
  begin
18
18
  require File.join(File.dirname(__FILE__), 'build')
data/newrelic_rpm.gemspec CHANGED
@@ -39,7 +39,6 @@ EOS
39
39
  s.require_paths = ["lib"]
40
40
  s.rubygems_version = Gem::VERSION
41
41
  s.summary = "New Relic Ruby Agent"
42
- s.post_install_message = NewRelic::LatestChanges.read
43
42
 
44
43
  s.add_development_dependency 'rake', '10.1.0'
45
44
  s.add_development_dependency 'minitest', '~> 4.7.5'
data/test/agent_helper.rb CHANGED
@@ -244,6 +244,10 @@ def in_transaction(*args)
244
244
  val
245
245
  end
246
246
 
247
+ def stub_transaction_guid(guid)
248
+ NewRelic::Agent::Transaction.tl_current.instance_variable_set(:@guid, guid)
249
+ end
250
+
247
251
  # Convenience wrapper around in_transaction that sets the category so that it
248
252
  # looks like we are in a web transaction
249
253
  def in_web_transaction(name='dummy')
@@ -252,6 +256,32 @@ def in_web_transaction(name='dummy')
252
256
  end
253
257
  end
254
258
 
259
+ def in_background_transaction(name='silly')
260
+ in_transaction(name, :category => :task) do
261
+ yield
262
+ end
263
+ end
264
+
265
+ def last_traced_error
266
+ NewRelic::Agent.agent.error_collector.errors.last
267
+ end
268
+
269
+ def last_traced_error_request_params
270
+ last_traced_error.params[:request_params]
271
+ end
272
+
273
+ def last_transaction_trace
274
+ NewRelic::Agent.agent.transaction_sampler.last_sample
275
+ end
276
+
277
+ def last_transaction_trace_request_params
278
+ last_transaction_trace.params[:request_params]
279
+ end
280
+
281
+ def last_sql_trace
282
+ NewRelic::Agent.agent.sql_sampler.sql_traces.values.last
283
+ end
284
+
255
285
  def find_last_transaction_segment(transaction_sample=nil)
256
286
  if transaction_sample
257
287
  root_segment = transaction_sample.root_segment
@@ -457,3 +487,49 @@ ensure
457
487
  NewRelic::Agent.ignore_error_filter(&original_filter)
458
488
  end
459
489
  end
490
+
491
+ def json_dump_and_encode(object)
492
+ Base64.encode64(NewRelic::JSONWrapper.dump(object))
493
+ end
494
+
495
+ def get_last_analytics_event
496
+ NewRelic::Agent.agent.instance_variable_get(:@request_sampler).samples.last
497
+ end
498
+
499
+ def cross_agent_tests_dir
500
+ File.expand_path(File.join(File.dirname(__FILE__), 'fixtures', 'cross_agent_tests'))
501
+ end
502
+
503
+ def load_cross_agent_test(name)
504
+ test_file_path = File.join(cross_agent_tests_dir, "#{name}.json")
505
+ data = File.read(test_file_path)
506
+ NewRelic::JSONWrapper.load(data)
507
+ end
508
+
509
+ def assert_event_attributes(event, test_name, expected_attributes, non_expected_attributes)
510
+ incorrect_attributes = []
511
+
512
+ event_attrs = event[0]
513
+
514
+ expected_attributes.each do |name, expected_value|
515
+ actual_value = event_attrs[name]
516
+ incorrect_attributes << name unless actual_value == expected_value
517
+ end
518
+
519
+ msg = "Found missing or incorrect attribute values in #{test_name}:\n"
520
+
521
+ incorrect_attributes.each do |name|
522
+ msg << " #{name}: expected = #{expected_attributes[name].inspect}, actual = #{event_attrs[name].inspect}\n"
523
+ end
524
+ msg << "\n"
525
+
526
+ msg << "All event values:\n"
527
+ event_attrs.each do |name, actual_value|
528
+ msg << " #{name}: #{actual_value.inspect}\n"
529
+ end
530
+ assert(incorrect_attributes.empty?, msg)
531
+
532
+ non_expected_attributes.each do |name|
533
+ assert_nil(event_attrs[name], "Found value '#{event_attrs[name]}' for attribute '#{name}', but expected nothing in #{test_name}")
534
+ end
535
+ end