airbrake 3.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (79) hide show
  1. data/.gitignore +18 -0
  2. data/.yardopts +3 -0
  3. data/CHANGELOG +441 -0
  4. data/Gemfile +3 -0
  5. data/INSTALL +25 -0
  6. data/MIT-LICENSE +22 -0
  7. data/README.md +431 -0
  8. data/README_FOR_HEROKU_ADDON.md +93 -0
  9. data/Rakefile +188 -0
  10. data/SUPPORTED_RAILS_VERSIONS +14 -0
  11. data/TESTING.md +26 -0
  12. data/airbrake.gemspec +33 -0
  13. data/features/metal.feature +23 -0
  14. data/features/rack.feature +27 -0
  15. data/features/rails.feature +254 -0
  16. data/features/rails_with_js_notifier.feature +78 -0
  17. data/features/rake.feature +23 -0
  18. data/features/sinatra.feature +33 -0
  19. data/features/step_definitions/airbrake_shim.rb.template +15 -0
  20. data/features/step_definitions/file_steps.rb +10 -0
  21. data/features/step_definitions/metal_steps.rb +23 -0
  22. data/features/step_definitions/rack_steps.rb +20 -0
  23. data/features/step_definitions/rails_application_steps.rb +401 -0
  24. data/features/step_definitions/rake_steps.rb +17 -0
  25. data/features/support/airbrake_shim.rb.template +15 -0
  26. data/features/support/env.rb +18 -0
  27. data/features/support/matchers.rb +35 -0
  28. data/features/support/rails.rb +181 -0
  29. data/features/support/rake/Rakefile +57 -0
  30. data/features/support/terminal.rb +103 -0
  31. data/features/user_informer.feature +63 -0
  32. data/generators/airbrake/airbrake_generator.rb +90 -0
  33. data/generators/airbrake/lib/insert_commands.rb +34 -0
  34. data/generators/airbrake/lib/rake_commands.rb +24 -0
  35. data/generators/airbrake/templates/airbrake_tasks.rake +25 -0
  36. data/generators/airbrake/templates/capistrano_hook.rb +6 -0
  37. data/generators/airbrake/templates/initializer.rb +6 -0
  38. data/install.rb +1 -0
  39. data/lib/airbrake.rb +150 -0
  40. data/lib/airbrake/backtrace.rb +100 -0
  41. data/lib/airbrake/capistrano.rb +21 -0
  42. data/lib/airbrake/configuration.rb +247 -0
  43. data/lib/airbrake/notice.rb +348 -0
  44. data/lib/airbrake/rack.rb +42 -0
  45. data/lib/airbrake/rails.rb +41 -0
  46. data/lib/airbrake/rails/action_controller_catcher.rb +30 -0
  47. data/lib/airbrake/rails/controller_methods.rb +68 -0
  48. data/lib/airbrake/rails/error_lookup.rb +33 -0
  49. data/lib/airbrake/rails/javascript_notifier.rb +42 -0
  50. data/lib/airbrake/rails3_tasks.rb +82 -0
  51. data/lib/airbrake/railtie.rb +33 -0
  52. data/lib/airbrake/rake_handler.rb +65 -0
  53. data/lib/airbrake/sender.rb +83 -0
  54. data/lib/airbrake/shared_tasks.rb +30 -0
  55. data/lib/airbrake/tasks.rb +83 -0
  56. data/lib/airbrake/user_informer.rb +25 -0
  57. data/lib/airbrake/version.rb +3 -0
  58. data/lib/airbrake_tasks.rb +50 -0
  59. data/lib/rails/generators/airbrake/airbrake_generator.rb +96 -0
  60. data/lib/templates/javascript_notifier.erb +13 -0
  61. data/lib/templates/rescue.erb +91 -0
  62. data/rails/init.rb +1 -0
  63. data/script/integration_test.rb +38 -0
  64. data/test/airbrake_2_2.xsd +78 -0
  65. data/test/airbrake_tasks_test.rb +163 -0
  66. data/test/backtrace_test.rb +163 -0
  67. data/test/catcher_test.rb +333 -0
  68. data/test/configuration_test.rb +216 -0
  69. data/test/helper.rb +251 -0
  70. data/test/javascript_notifier_test.rb +52 -0
  71. data/test/logger_test.rb +85 -0
  72. data/test/notice_test.rb +459 -0
  73. data/test/notifier_test.rb +235 -0
  74. data/test/rack_test.rb +58 -0
  75. data/test/rails_initializer_test.rb +36 -0
  76. data/test/recursion_test.rb +10 -0
  77. data/test/sender_test.rb +193 -0
  78. data/test/user_informer_test.rb +29 -0
  79. metadata +365 -0
@@ -0,0 +1,333 @@
1
+ require File.dirname(__FILE__) + '/helper'
2
+
3
+ class ActionControllerCatcherTest < Test::Unit::TestCase
4
+
5
+ include DefinesConstants
6
+
7
+ def setup
8
+ super
9
+ reset_config
10
+ Airbrake.sender = CollectingSender.new
11
+ define_constant('RAILS_ROOT', '/path/to/rails/root')
12
+ end
13
+
14
+ def ignore(exception_class)
15
+ Airbrake.configuration.ignore << exception_class
16
+ end
17
+
18
+ def build_controller_class(&definition)
19
+ Class.new(ActionController::Base).tap do |klass|
20
+ klass.__send__(:include, Airbrake::Rails::ActionControllerCatcher)
21
+ klass.class_eval(&definition) if definition
22
+ define_constant('AirbrakeTestController', klass)
23
+ end
24
+ end
25
+
26
+ def assert_sent_hash(hash, xpath)
27
+ hash.each do |key, value|
28
+ next if key.match(/^airbrake\./) # We added this key.
29
+
30
+ element_xpath = "#{xpath}/var[@key = '#{key}']"
31
+ if value.respond_to?(:to_hash)
32
+ assert_sent_hash value.to_hash, element_xpath
33
+ else
34
+ assert_sent_element value, element_xpath
35
+ end
36
+ end
37
+ end
38
+
39
+ def assert_sent_element(value, xpath)
40
+ assert_valid_node last_sent_notice_document, xpath, stringify_array_elements(value).to_s
41
+ end
42
+
43
+ def stringify_array_elements(data)
44
+ if data.respond_to?(:to_ary)
45
+ data.collect do |value|
46
+ stringify_array_elements(value)
47
+ end
48
+ else
49
+ data.to_s
50
+ end
51
+ end
52
+
53
+ def assert_sent_request_info_for(request)
54
+ params = request.parameters.to_hash
55
+ assert_sent_hash params, '/notice/request/params'
56
+ assert_sent_element params['controller'], '/notice/request/component'
57
+ assert_sent_element params['action'], '/notice/request/action'
58
+ assert_sent_element url_from_request(request), '/notice/request/url'
59
+ assert_sent_hash request.env, '/notice/request/cgi-data'
60
+ end
61
+
62
+ def url_from_request(request)
63
+ url = "#{request.protocol}#{request.host}"
64
+
65
+ unless [80, 443].include?(request.port)
66
+ url << ":#{request.port}"
67
+ end
68
+
69
+ url << request.request_uri
70
+ url
71
+ end
72
+
73
+ def sender
74
+ Airbrake.sender
75
+ end
76
+
77
+ def last_sent_notice_xml
78
+ sender.collected.last
79
+ end
80
+
81
+ def last_sent_notice_document
82
+ assert_not_nil xml = last_sent_notice_xml, "No xml was sent"
83
+ Nokogiri::XML.parse(xml)
84
+ end
85
+
86
+ def process_action(opts = {}, &action)
87
+ opts[:request] ||= ActionController::TestRequest.new
88
+ opts[:response] ||= ActionController::TestResponse.new
89
+ klass = build_controller_class do
90
+ cattr_accessor :local
91
+ define_method(:index, &action)
92
+ def local_request?
93
+ local
94
+ end
95
+ end
96
+ if opts[:filters]
97
+ klass.filter_parameter_logging *opts[:filters]
98
+ end
99
+ if opts[:user_agent]
100
+ if opts[:request].respond_to?(:user_agent=)
101
+ opts[:request].user_agent = opts[:user_agent]
102
+ else
103
+ opts[:request].env["HTTP_USER_AGENT"] = opts[:user_agent]
104
+ end
105
+ end
106
+ if opts[:port]
107
+ opts[:request].port = opts[:port]
108
+ end
109
+ klass.consider_all_requests_local = opts[:all_local]
110
+ klass.local = opts[:local]
111
+ controller = klass.new
112
+ controller.stubs(:rescue_action_in_public_without_airbrake)
113
+ opts[:request].query_parameters = opts[:request].query_parameters.merge(opts[:params] || {})
114
+ opts[:request].session = ActionController::TestSession.new(opts[:session] || {})
115
+ # Prevents request.fullpath from crashing Rails in tests
116
+ opts[:request].env['REQUEST_URI'] = opts[:request].request_uri
117
+ controller.process(opts[:request], opts[:response])
118
+ controller
119
+ end
120
+
121
+ def process_action_with_manual_notification(args = {})
122
+ process_action(args) do
123
+ notify_airbrake(:error_message => 'fail')
124
+ # Rails will raise a template error if we don't render something
125
+ render :nothing => true
126
+ end
127
+ end
128
+
129
+ def process_action_with_automatic_notification(args = {})
130
+ process_action(args) { raise "Hello" }
131
+ end
132
+
133
+ should "deliver notices from exceptions raised in public requests" do
134
+ process_action_with_automatic_notification
135
+ assert_caught_and_sent
136
+ end
137
+
138
+ should "not deliver notices from exceptions in local requests" do
139
+ process_action_with_automatic_notification(:local => true)
140
+ assert_caught_and_not_sent
141
+ end
142
+
143
+ should "not deliver notices from exceptions when all requests are local" do
144
+ process_action_with_automatic_notification(:all_local => true)
145
+ assert_caught_and_not_sent
146
+ end
147
+
148
+ should "not deliver notices from actions that don't raise" do
149
+ controller = process_action { render :text => 'Hello' }
150
+ assert_caught_and_not_sent
151
+ assert_equal 'Hello', controller.response.body
152
+ end
153
+
154
+ should "not deliver ignored exceptions raised by actions" do
155
+ ignore(RuntimeError)
156
+ process_action_with_automatic_notification
157
+ assert_caught_and_not_sent
158
+ end
159
+
160
+ should "deliver ignored exception raised manually" do
161
+ ignore(RuntimeError)
162
+ process_action_with_manual_notification
163
+ assert_caught_and_sent
164
+ end
165
+
166
+ should "deliver manually sent notices in public requests" do
167
+ process_action_with_manual_notification
168
+ assert_caught_and_sent
169
+ end
170
+
171
+ should "not deliver manually sent notices in local requests" do
172
+ process_action_with_manual_notification(:local => true)
173
+ assert_caught_and_not_sent
174
+ end
175
+
176
+ should "not deliver manually sent notices when all requests are local" do
177
+ process_action_with_manual_notification(:all_local => true)
178
+ assert_caught_and_not_sent
179
+ end
180
+
181
+ should "continue with default behavior after delivering an exception" do
182
+ controller = process_action_with_automatic_notification(:public => true)
183
+ # TODO: can we test this without stubbing?
184
+ assert_received(controller, :rescue_action_in_public_without_airbrake)
185
+ end
186
+
187
+ should "not create actions from Airbrake methods" do
188
+ controller = build_controller_class.new
189
+ assert_equal [], Airbrake::Rails::ActionControllerCatcher.instance_methods
190
+ end
191
+
192
+ should "ignore exceptions when user agent is being ignored by regular expression" do
193
+ Airbrake.configuration.ignore_user_agent_only = [/Ignored/]
194
+ process_action_with_automatic_notification(:user_agent => 'ShouldBeIgnored')
195
+ assert_caught_and_not_sent
196
+ end
197
+
198
+ should "ignore exceptions when user agent is being ignored by string" do
199
+ Airbrake.configuration.ignore_user_agent_only = ['IgnoredUserAgent']
200
+ process_action_with_automatic_notification(:user_agent => 'IgnoredUserAgent')
201
+ assert_caught_and_not_sent
202
+ end
203
+
204
+ should "not ignore exceptions when user agent is not being ignored" do
205
+ Airbrake.configuration.ignore_user_agent_only = ['IgnoredUserAgent']
206
+ process_action_with_automatic_notification(:user_agent => 'NonIgnoredAgent')
207
+ assert_caught_and_sent
208
+ end
209
+
210
+ should "send session data for manual notifications" do
211
+ data = { 'one' => 'two' }
212
+ process_action_with_manual_notification(:session => data)
213
+ assert_sent_hash data, "/notice/request/session"
214
+ end
215
+
216
+ should "send session data for automatic notification" do
217
+ data = { 'one' => 'two' }
218
+ process_action_with_automatic_notification(:session => data)
219
+ assert_sent_hash data, "/notice/request/session"
220
+ end
221
+
222
+ should "send request data for manual notification" do
223
+ params = { 'controller' => "airbrake_test", 'action' => "index" }
224
+ controller = process_action_with_manual_notification(:params => params)
225
+ assert_sent_request_info_for controller.request
226
+ end
227
+
228
+ should "send request data for manual notification with non-standard port" do
229
+ params = { 'controller' => "airbrake_test", 'action' => "index" }
230
+ controller = process_action_with_manual_notification(:params => params, :port => 81)
231
+ assert_sent_request_info_for controller.request
232
+ end
233
+
234
+ should "send request data for automatic notification" do
235
+ params = { 'controller' => "airbrake_test", 'action' => "index" }
236
+ controller = process_action_with_automatic_notification(:params => params)
237
+ assert_sent_request_info_for controller.request
238
+ end
239
+
240
+ should "send request data for automatic notification with non-standard port" do
241
+ params = { 'controller' => "airbrake_test", 'action' => "index" }
242
+ controller = process_action_with_automatic_notification(:params => params, :port => 81)
243
+ assert_sent_request_info_for controller.request
244
+ end
245
+
246
+ should "use standard rails logging filters on params and session and env" do
247
+ filtered_params = { "abc" => "123",
248
+ "def" => "456",
249
+ "ghi" => "[FILTERED]" }
250
+ filtered_session = { "abc" => "123",
251
+ "ghi" => "[FILTERED]" }
252
+ ENV['ghi'] = 'abc'
253
+ filtered_env = { 'ghi' => '[FILTERED]' }
254
+ filtered_cgi = { 'REQUEST_METHOD' => '[FILTERED]' }
255
+
256
+ process_action_with_automatic_notification(:filters => [:ghi, :request_method],
257
+ :params => { "abc" => "123",
258
+ "def" => "456",
259
+ "ghi" => "789" },
260
+ :session => { "abc" => "123",
261
+ "ghi" => "789" })
262
+ assert_sent_hash filtered_params, '/notice/request/params'
263
+ assert_sent_hash filtered_cgi, '/notice/request/cgi-data'
264
+ assert_sent_hash filtered_session, '/notice/request/session'
265
+ end
266
+
267
+ context "for a local error with development lookup enabled" do
268
+ setup do
269
+ Airbrake.configuration.development_lookup = true
270
+ Airbrake.stubs(:build_lookup_hash_for).returns({ :awesome => 2 })
271
+
272
+ @controller = process_action_with_automatic_notification(:local => true)
273
+ @response = @controller.response
274
+ end
275
+
276
+ should "append custom CSS and JS to response body for a local error" do
277
+ assert_match /text\/css/, @response.body
278
+ assert_match /text\/javascript/, @response.body
279
+ end
280
+
281
+ should "contain host, API key and notice JSON" do
282
+ assert_match Airbrake.configuration.host.to_json, @response.body
283
+ assert_match Airbrake.configuration.api_key.to_json, @response.body
284
+ assert_match ({ :awesome => 2 }).to_json, @response.body
285
+ end
286
+ end
287
+
288
+ context "for a local error with development lookup disabled" do
289
+ setup do
290
+ Airbrake.configuration.development_lookup = false
291
+
292
+ @controller = process_action_with_automatic_notification(:local => true)
293
+ @response = @controller.response
294
+ end
295
+
296
+ should "not append custom CSS and JS to response for a local error" do
297
+ assert_no_match /text\/css/, @response.body
298
+ assert_no_match /text\/javascript/, @response.body
299
+ end
300
+ end
301
+
302
+ should "call session.to_hash if available" do
303
+ hash_data = {:key => :value}
304
+
305
+ session = ActionController::TestSession.new
306
+ ActionController::TestSession.stubs(:new).returns(session)
307
+ session.stubs(:to_hash).returns(hash_data)
308
+
309
+ process_action_with_automatic_notification
310
+ assert_received(session, :to_hash)
311
+ assert_received(session, :data) { |expect| expect.never }
312
+ assert_caught_and_sent
313
+ end
314
+
315
+ should "call session.data if session.to_hash is undefined" do
316
+ hash_data = {:key => :value}
317
+
318
+ session = ActionController::TestSession.new
319
+ ActionController::TestSession.stubs(:new).returns(session)
320
+ session.stubs(:data).returns(hash_data)
321
+ if session.respond_to?(:to_hash)
322
+ class << session
323
+ undef to_hash
324
+ end
325
+ end
326
+
327
+ process_action_with_automatic_notification
328
+ assert_received(session, :to_hash) { |expect| expect.never }
329
+ assert_received(session, :data) { |expect| expect.at_least_once }
330
+ assert_caught_and_sent
331
+ end
332
+
333
+ end
@@ -0,0 +1,216 @@
1
+ require File.dirname(__FILE__) + '/helper'
2
+
3
+ class ConfigurationTest < Test::Unit::TestCase
4
+
5
+ include DefinesConstants
6
+
7
+ should "provide default values" do
8
+ assert_config_default :proxy_host, nil
9
+ assert_config_default :proxy_port, nil
10
+ assert_config_default :proxy_user, nil
11
+ assert_config_default :proxy_pass, nil
12
+ assert_config_default :project_root, nil
13
+ assert_config_default :environment_name, nil
14
+ assert_config_default :logger, nil
15
+ assert_config_default :notifier_version, Airbrake::VERSION
16
+ assert_config_default :notifier_name, 'Airbrake Notifier'
17
+ assert_config_default :notifier_url, 'http://airbrakeapp.com'
18
+ assert_config_default :secure, false
19
+ assert_config_default :host, 'airbrakeapp.com'
20
+ assert_config_default :http_open_timeout, 2
21
+ assert_config_default :http_read_timeout, 5
22
+ assert_config_default :ignore_by_filters, []
23
+ assert_config_default :ignore_user_agent, []
24
+ assert_config_default :params_filters,
25
+ Airbrake::Configuration::DEFAULT_PARAMS_FILTERS
26
+ assert_config_default :backtrace_filters,
27
+ Airbrake::Configuration::DEFAULT_BACKTRACE_FILTERS
28
+ assert_config_default :ignore,
29
+ Airbrake::Configuration::IGNORE_DEFAULT
30
+ assert_config_default :development_lookup, true
31
+ assert_config_default :framework, 'Standalone'
32
+ end
33
+
34
+ should "provide default values for secure connections" do
35
+ config = Airbrake::Configuration.new
36
+ config.secure = true
37
+ assert_equal 443, config.port
38
+ assert_equal 'https', config.protocol
39
+ end
40
+
41
+ should "provide default values for insecure connections" do
42
+ config = Airbrake::Configuration.new
43
+ config.secure = false
44
+ assert_equal 80, config.port
45
+ assert_equal 'http', config.protocol
46
+ end
47
+
48
+ should "not cache inferred ports" do
49
+ config = Airbrake::Configuration.new
50
+ config.secure = false
51
+ config.port
52
+ config.secure = true
53
+ assert_equal 443, config.port
54
+ end
55
+
56
+ should "allow values to be overwritten" do
57
+ assert_config_overridable :proxy_host
58
+ assert_config_overridable :proxy_port
59
+ assert_config_overridable :proxy_user
60
+ assert_config_overridable :proxy_pass
61
+ assert_config_overridable :secure
62
+ assert_config_overridable :host
63
+ assert_config_overridable :port
64
+ assert_config_overridable :http_open_timeout
65
+ assert_config_overridable :http_read_timeout
66
+ assert_config_overridable :project_root
67
+ assert_config_overridable :notifier_version
68
+ assert_config_overridable :notifier_name
69
+ assert_config_overridable :notifier_url
70
+ assert_config_overridable :environment_name
71
+ assert_config_overridable :development_lookup
72
+ assert_config_overridable :logger
73
+ end
74
+
75
+ should "have an api key" do
76
+ assert_config_overridable :api_key
77
+ end
78
+
79
+ should "act like a hash" do
80
+ config = Airbrake::Configuration.new
81
+ hash = config.to_hash
82
+ [:api_key, :backtrace_filters, :development_environments,
83
+ :environment_name, :host, :http_open_timeout,
84
+ :http_read_timeout, :ignore, :ignore_by_filters, :ignore_user_agent,
85
+ :notifier_name, :notifier_url, :notifier_version, :params_filters,
86
+ :project_root, :port, :protocol, :proxy_host, :proxy_pass, :proxy_port,
87
+ :proxy_user, :secure, :development_lookup].each do |option|
88
+ assert_equal config[option], hash[option], "Wrong value for #{option}"
89
+ end
90
+ end
91
+
92
+ should "be mergable" do
93
+ config = Airbrake::Configuration.new
94
+ hash = config.to_hash
95
+ assert_equal hash.merge(:key => 'value'), config.merge(:key => 'value')
96
+ end
97
+
98
+ should "allow param filters to be appended" do
99
+ assert_appends_value :params_filters
100
+ end
101
+
102
+ should "warn when attempting to read environment filters" do
103
+ config = Airbrake::Configuration.new
104
+ config.
105
+ expects(:warn).
106
+ with(regexp_matches(/deprecated/i))
107
+ assert_equal [], config.environment_filters
108
+ end
109
+
110
+ should "warn when attempting to write js_notifier" do
111
+ config = Airbrake::Configuration.new
112
+ config.
113
+ expects(:warn).
114
+ with(regexp_matches(/deprecated/i))
115
+ config.js_notifier = true
116
+ end
117
+
118
+ should "allow ignored user agents to be appended" do
119
+ assert_appends_value :ignore_user_agent
120
+ end
121
+
122
+ should "allow backtrace filters to be appended" do
123
+ assert_appends_value(:backtrace_filters) do |config|
124
+ new_filter = lambda {}
125
+ config.filter_backtrace(&new_filter)
126
+ new_filter
127
+ end
128
+ end
129
+
130
+ should "allow ignore by filters to be appended" do
131
+ assert_appends_value(:ignore_by_filters) do |config|
132
+ new_filter = lambda {}
133
+ config.ignore_by_filter(&new_filter)
134
+ new_filter
135
+ end
136
+ end
137
+
138
+ should "allow ignored exceptions to be appended" do
139
+ config = Airbrake::Configuration.new
140
+ original_filters = config.ignore.dup
141
+ new_filter = 'hello'
142
+ config.ignore << new_filter
143
+ assert_same_elements original_filters + [new_filter], config.ignore
144
+ end
145
+
146
+ should "allow ignored exceptions to be replaced" do
147
+ assert_replaces(:ignore, :ignore_only=)
148
+ end
149
+
150
+ should "allow ignored user agents to be replaced" do
151
+ assert_replaces(:ignore_user_agent, :ignore_user_agent_only=)
152
+ end
153
+
154
+ should "use development and test as development environments by default" do
155
+ config = Airbrake::Configuration.new
156
+ assert_same_elements %w(development test cucumber), config.development_environments
157
+ end
158
+
159
+ should "be public in a public environment" do
160
+ config = Airbrake::Configuration.new
161
+ config.development_environments = %w(development)
162
+ config.environment_name = 'production'
163
+ assert config.public?
164
+ end
165
+
166
+ should "not be public in a development environment" do
167
+ config = Airbrake::Configuration.new
168
+ config.development_environments = %w(staging)
169
+ config.environment_name = 'staging'
170
+ assert !config.public?
171
+ end
172
+
173
+ should "be public without an environment name" do
174
+ config = Airbrake::Configuration.new
175
+ assert config.public?
176
+ end
177
+
178
+ should "use the assigned logger if set" do
179
+ config = Airbrake::Configuration.new
180
+ config.logger = "CUSTOM LOGGER"
181
+ assert_equal "CUSTOM LOGGER", config.logger
182
+ end
183
+
184
+ def assert_config_default(option, default_value, config = nil)
185
+ config ||= Airbrake::Configuration.new
186
+ assert_equal default_value, config.send(option)
187
+ end
188
+
189
+ def assert_config_overridable(option, value = 'a value')
190
+ config = Airbrake::Configuration.new
191
+ config.send(:"#{option}=", value)
192
+ assert_equal value, config.send(option)
193
+ end
194
+
195
+ def assert_appends_value(option, &block)
196
+ config = Airbrake::Configuration.new
197
+ original_values = config.send(option).dup
198
+ block ||= lambda do |config|
199
+ new_value = 'hello'
200
+ config.send(option) << new_value
201
+ new_value
202
+ end
203
+ new_value = block.call(config)
204
+ assert_same_elements original_values + [new_value], config.send(option)
205
+ end
206
+
207
+ def assert_replaces(option, setter)
208
+ config = Airbrake::Configuration.new
209
+ new_value = 'hello'
210
+ config.send(setter, [new_value])
211
+ assert_equal [new_value], config.send(option)
212
+ config.send(setter, new_value)
213
+ assert_equal [new_value], config.send(option)
214
+ end
215
+
216
+ end