enhanced_errors 2.1.0 → 2.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -82,7 +82,7 @@
82
82
  <p class="children">
83
83
 
84
84
 
85
- <strong class="modules">Modules:</strong> <span class='object_link'><a href="Enhanced.html" title="Enhanced (module)">Enhanced</a></span>
85
+ <strong class="modules">Modules:</strong> <span class='object_link'><a href="Enhanced.html" title="Enhanced (module)">Enhanced</a></span>, <span class='object_link'><a href="Minitest.html" title="Minitest (module)">Minitest</a></span>
86
86
 
87
87
 
88
88
 
@@ -122,9 +122,9 @@
122
122
  </div>
123
123
 
124
124
  <div id="footer">
125
- Generated on Mon Dec 9 19:51:26 2024 by
125
+ Generated on Mon Dec 16 10:41:42 2024 by
126
126
  <a href="https://yardoc.org" title="Yay! A Ruby Documentation Tool" target="_parent">yard</a>
127
- 0.9.37 (ruby-3.1.3).
127
+ 0.9.37 (ruby-3.3.6).
128
128
  </div>
129
129
 
130
130
  </div>
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |spec|
2
2
  spec.name = "enhanced_errors"
3
- spec.version = "2.1.0"
3
+ spec.version = "2.1.2"
4
4
  spec.authors = ["Eric Beland"]
5
5
 
6
6
  spec.summary = "Automatically enhance your errors with messages containing variable values from the moment they were raised."
@@ -14,8 +14,12 @@ RSpec.configure do |config|
14
14
  EnhancedErrors.start_rspec_binding_capture
15
15
  end
16
16
 
17
+ config.before(:example) do |_example|
18
+ EnhancedErrors.start_rspec_binding_capture
19
+ end
20
+
17
21
  config.after(:example) do |example|
18
- EnhancedErrors.override_exception_message(example.exception, EnhancedErrors.stop_rspec_binding_capture)
22
+ EnhancedErrors.override_rspec_message(example, EnhancedErrors.stop_rspec_binding_capture)
19
23
  end
20
24
  # -- End EnhancedErrors config
21
25
 
@@ -36,6 +40,17 @@ RSpec.describe 'Neo' do
36
40
  stop = 'bullets'
37
41
  raise 'No!'
38
42
  end
43
+
44
+ it "dodges multiple exception-bullets at once" do
45
+ foo = 'bar'
46
+ expect(1).to eq(2)
47
+ expect(true).to eq(false)
48
+ end
49
+
50
+ after(:each) do
51
+ raise "This is another error"
52
+ end
53
+
39
54
  end
40
55
  end
41
56
 
@@ -21,70 +21,71 @@ class EnhancedErrors
21
21
  @monitor ||= Monitor.new
22
22
  end
23
23
 
24
- attr_accessor :enabled, :config_block, :on_capture_hook, :eligible_for_capture, :trace, :override_messages
24
+ attr_accessor :enabled, :config_block, :on_capture_hook, :eligible_for_capture, :exception_trace, :override_messages
25
25
 
26
26
  GEMS_REGEX = %r{[\/\\]gems[\/\\]}
27
- RSPEC_EXAMPLE_REGEXP = /RSpec::ExampleGroups::[A-Z0-9]+.*/
28
27
  DEFAULT_MAX_LENGTH = 2000
29
28
  MAX_BINDING_INFOS = 3
30
29
 
31
30
  RSPEC_SKIP_LIST = [
32
- :@__inspect_output,
33
- :@__memoized,
34
- :@assertion_delegator,
35
- :@assertion_instance,
36
- :@assertions,
37
- :@connection_subscriber,
38
- :@example,
39
- :@fixture_cache,
40
- :@fixture_cache_key,
41
- :@fixture_connection_pools,
42
- :@fixture_connections,
43
- :@integration_session,
44
- :@legacy_saved_pool_configs,
45
- :@loaded_fixtures,
46
- :@matcher_definitions,
47
- :@saved_pool_configs
48
- ].freeze
31
+ :@__inspect_output,
32
+ :@__memoized,
33
+ :@assertion_delegator,
34
+ :@assertion_instance,
35
+ :@assertions,
36
+ :@connection_subscriber,
37
+ :@example,
38
+ :@fixture_cache,
39
+ :@fixture_cache_key,
40
+ :@fixture_connection_pools,
41
+ :@fixture_connections,
42
+ :@integration_session,
43
+ :@legacy_saved_pool_configs,
44
+ :@loaded_fixtures,
45
+ :@matcher_definitions,
46
+ :@saved_pool_configs
47
+ ].freeze
49
48
 
50
49
  RAILS_SKIP_LIST = [
51
- :@new_record,
52
- :@attributes,
53
- :@association_cache,
54
- :@readonly,
55
- :@previously_new_record,
56
- :@_routes,
57
- :@routes,
58
- :@app,
59
- :@arel_table,
60
- :@assertion_instance,
61
- :@association_cache,
62
- :@attributes,
63
- :@destroyed,
64
- :@destroyed_by_association,
65
- :@find_by_statement_cache,
66
- :@generated_relation_method,
67
- :@integration_session,
68
- :@marked_for_destruction,
69
- :@mutations_before_last_save,
70
- :@mutations_from_database,
71
- :@new_record,
72
- :@predicate_builder,
73
- :@previously_new_record,
74
- :@primary_key,
75
- :@readonly,
76
- :@relation_delegate_cache,
77
- :@response,
78
- :@response_klass,
79
- :@routes,
80
- :@strict_loading,
81
- :@strict_loading_mode
82
- ].freeze
50
+ :@new_record,
51
+ :@attributes,
52
+ :@association_cache,
53
+ :@readonly,
54
+ :@previously_new_record,
55
+ :@_routes,
56
+ :@routes,
57
+ :@app,
58
+ :@arel_table,
59
+ :@assertion_instance,
60
+ :@association_cache,
61
+ :@attributes,
62
+ :@destroyed,
63
+ :@destroyed_by_association,
64
+ :@find_by_statement_cache,
65
+ :@generated_relation_method,
66
+ :@integration_session,
67
+ :@marked_for_destruction,
68
+ :@mutations_before_last_save,
69
+ :@mutations_from_database,
70
+ :@new_record,
71
+ :@predicate_builder,
72
+ :@previously_new_record,
73
+ :@primary_key,
74
+ :@readonly,
75
+ :@relation_delegate_cache,
76
+ :@response,
77
+ :@response_klass,
78
+ :@routes,
79
+ :@strict_loading,
80
+ :@strict_loading_mode
81
+ ].freeze
83
82
 
84
83
  MINITEST_SKIP_LIST = [:@NAME, :@failures, :@time].freeze
85
84
 
86
85
  DEFAULT_SKIP_LIST = (RAILS_SKIP_LIST + RSPEC_SKIP_LIST + MINITEST_SKIP_LIST)
87
86
 
87
+ RSPEC_HANDLER_NAMES = ['RSpec::Expectations::PositiveExpectationHandler', 'RSpec::Expectations::NegativeExpectationHandler']
88
+
88
89
  @enabled = false
89
90
  @max_length = nil
90
91
  @capture_rescue = nil
@@ -94,12 +95,11 @@ class EnhancedErrors
94
95
  @output_format = nil
95
96
  @eligible_for_capture = nil
96
97
  @original_global_variables = nil
97
- @trace = nil
98
+ @exception_trace = nil
98
99
  @override_messages = nil
99
- @rspec_failure_message_loaded = nil
100
100
 
101
101
  # Default values
102
- @max_capture_events = -1 # -1 means no limit
102
+ @max_capture_events = -1 # -1 means no limit
103
103
  @capture_events_count = 0
104
104
 
105
105
  # Thread-safe getters and setters
@@ -116,11 +116,11 @@ class EnhancedErrors
116
116
  end
117
117
 
118
118
  def capture_rescue
119
- mutex.synchronize { @capture_rescue }
119
+ mutex.synchronize { @capture_rescue }
120
120
  end
121
121
 
122
122
  def capture_events_count
123
- mutex.synchronize { @capture_events_count }
123
+ mutex.synchronize { @capture_events_count || 0 }
124
124
  end
125
125
 
126
126
  def capture_events_count=(val)
@@ -128,7 +128,7 @@ class EnhancedErrors
128
128
  end
129
129
 
130
130
  def max_capture_events
131
- mutex.synchronize { @max_capture_events }
131
+ mutex.synchronize { @max_capture_events || -1 }
132
132
  end
133
133
 
134
134
  def max_capture_events=(value)
@@ -139,7 +139,7 @@ class EnhancedErrors
139
139
  if @enabled
140
140
  puts "EnhancedErrors: max_capture_events set to 0, disabling capturing."
141
141
  @enabled = false
142
- @trace&.disable
142
+ @exception_trace&.disable
143
143
  @rspec_tracepoint&.disable
144
144
  @minitest_trace&.disable
145
145
  end
@@ -147,23 +147,36 @@ class EnhancedErrors
147
147
  end
148
148
  end
149
149
 
150
+ def enforce_capture_limit!
151
+ disable_capturing! if capture_limit_exceeded?
152
+ end
153
+
154
+ def capture_limit_exceeded?
155
+ mutex.synchronize do
156
+ max_capture_events > 0 && capture_events_count >= max_capture_events
157
+ end
158
+ end
159
+
160
+ def disable_capturing!
161
+ mutex.synchronize do
162
+ @enabled = false
163
+ @rspec_tracepoint&.disable
164
+ @minitest_trace&.disable
165
+ @exception_trace&.disable
166
+ end
167
+ end
168
+
150
169
  def increment_capture_events_count
151
170
  mutex.synchronize do
171
+ @capture_events_count ||= 0
172
+ @max_capture_events ||= -1
152
173
  @capture_events_count += 1
153
- # Check if we've hit the limit
154
- if @max_capture_events > 0 && @capture_events_count >= @max_capture_events
155
- # puts "EnhancedErrors: max_capture_events limit (#{@max_capture_events}) reached, disabling capturing."
156
- @enabled = false
157
- end
158
174
  end
159
175
  end
160
176
 
161
177
  def reset_capture_events_count
162
178
  mutex.synchronize do
163
179
  @capture_events_count = 0
164
- @enabled = true
165
- @rspec_tracepoint.enable if @rspec_tracepoint
166
- @trace.enable if @trace
167
180
  end
168
181
  end
169
182
 
@@ -184,6 +197,19 @@ class EnhancedErrors
184
197
  end
185
198
  end
186
199
 
200
+ def override_rspec_message(example, binding_or_bindings)
201
+ exception_obj = example.exception
202
+ case exception_obj
203
+ when nil
204
+ return nil
205
+ when RSpec::Core::MultipleExceptionError
206
+ override_exception_message(exception_obj.all_exceptions.first, binding_or_bindings)
207
+ else
208
+ override_exception_message(exception_obj, binding_or_bindings)
209
+ end
210
+
211
+ end
212
+
187
213
  def override_exception_message(exception, binding_or_bindings)
188
214
  return nil unless exception
189
215
  rspec_binding = !(binding_or_bindings.nil? || binding_or_bindings.empty?)
@@ -211,8 +237,8 @@ class EnhancedErrors
211
237
 
212
238
  def enhance_exceptions!(enabled: true, debug: false, capture_events: nil, override_messages: false, **options, &block)
213
239
  mutex.synchronize do
214
- @trace&.disable
215
- @trace = nil
240
+ @exception_trace&.disable
241
+ @exception_trace = nil
216
242
 
217
243
  @output_format = nil
218
244
  @eligible_for_capture = nil
@@ -220,7 +246,9 @@ class EnhancedErrors
220
246
  @override_messages = override_messages
221
247
 
222
248
  # Ensure these are not nil
223
- @max_capture_events = -1 if @max_capture_events.nil?
249
+ if @max_capture_events.nil?
250
+ @max_capture_events = -1
251
+ end
224
252
  @capture_events_count = 0
225
253
 
226
254
  @rspec_failure_message_loaded = true
@@ -256,13 +284,13 @@ class EnhancedErrors
256
284
  end
257
285
 
258
286
  events = @capture_events ? @capture_events.to_a : default_capture_events
259
- @trace = TracePoint.new(*events) do |tp|
287
+ @exception_trace = TracePoint.new(*events) do |tp|
260
288
  handle_tracepoint_event(tp)
261
289
  end
262
290
 
263
291
  # Only enable trace if still enabled and not limited
264
292
  if @enabled && (@max_capture_events == -1 || @capture_events_count < @max_capture_events)
265
- @trace.enable
293
+ @exception_trace.enable
266
294
  end
267
295
  end
268
296
  end
@@ -278,23 +306,12 @@ class EnhancedErrors
278
306
  end
279
307
  end
280
308
 
281
- def safely_prepend_rspec_custom_failure_message
282
- mutex.synchronize do
283
- return if @rspec_failure_message_loaded
284
- if defined?(RSpec::Core::Example) && !RSpec::Core::Example < Enhanced::Integrations::RSpecErrorFailureMessage
285
- RSpec::Core::Example.prepend(Enhanced::Integrations::RSpecErrorFailureMessage)
286
- @rspec_failure_message_loaded = true
287
- end
288
- end
289
- rescue => e
290
- puts "Failed to prepend RSpec custom failure message: #{e.message}"
291
- end
292
-
293
309
  def is_a_minitest?(klass)
294
310
  klass.ancestors.include?(Minitest::Test) && klass.name != 'Minitest::Test'
295
311
  end
296
312
 
297
313
  def start_minitest_binding_capture
314
+ return if capture_limit_exceeded?
298
315
  mutex.synchronize do
299
316
  @minitest_trace = TracePoint.new(:return) do |tp|
300
317
  next unless tp.method_id.to_s.start_with?('test_') && is_a_minitest?(tp.defined_class)
@@ -305,6 +322,7 @@ class EnhancedErrors
305
322
  end
306
323
 
307
324
  def stop_minitest_binding_capture
325
+ disable_capturing! if capture_limit_exceeded?
308
326
  mutex.synchronize do
309
327
  @minitest_trace&.disable
310
328
  @minitest_trace = nil
@@ -312,26 +330,48 @@ class EnhancedErrors
312
330
  end
313
331
  end
314
332
 
333
+ def class_to_string(klass)
334
+ return '' if klass.nil?
335
+ if klass.singleton_class?
336
+ (match = klass.to_s.match(/#<Class:(.*?)>/)) ? match[1] : klass.to_s
337
+ else
338
+ klass.to_s
339
+ end
340
+ end
341
+
342
+ def is_rspec_example?(tracepoint)
343
+ tracepoint.method_id.nil? && !(tracepoint.path.include?('rspec')) && tracepoint.path.end_with?('_spec.rb')
344
+ end
345
+
315
346
  def start_rspec_binding_capture
347
+ return if capture_limit_exceeded?
316
348
  mutex.synchronize do
317
349
  @rspec_example_binding = nil
318
350
  @capture_next_binding = false
319
351
  @rspec_tracepoint&.disable
320
- @enabled = true if @enabled.nil?
321
352
 
322
353
  @rspec_tracepoint = TracePoint.new(:raise, :b_return) do |tp|
323
- # This trickery is to help us identify the anonymous block return we want to grab
354
+ # puts "name #{tp.raised_exception.class.name rescue ''} method:#{tp.method_id} tp.binding:#{tp.binding.local_variables rescue ''}"
355
+ # puts "event: #{tp.event} defined_class#{class_to_string(tp.defined_class)} #{tp.path}:#{tp.lineno} #{tp.callee_id} "
356
+ # This trickery below is to help us identify the anonymous block return we want to grab
357
+ # Very kluge-y and edge cases have grown it, but it works
324
358
  if tp.event == :b_return
325
- next unless @capture_next_binding && @rspec_example_binding.nil?
326
- if @capture_next_binding && tp.method_id.nil? && !(tp.path.include?('rspec')) && tp.path.end_with?('_spec.rb')
327
- if determine_object_name(tp) =~ RSPEC_EXAMPLE_REGEXP
328
- increment_capture_events_count
329
- @rspec_example_binding = tp.binding
330
- end
359
+ if RSPEC_HANDLER_NAMES.include?(class_to_string(tp.defined_class))
360
+ @capture_next_binding = :next
361
+ next
362
+ end
363
+ next unless @capture_next_binding
364
+
365
+ if @capture_next_binding == :next || @capture_next_binding == :next_matching && is_rspec_example?(tp)
366
+ increment_capture_events_count
367
+ @capture_next_binding = false
368
+ @rspec_example_binding = tp.binding
331
369
  end
332
370
  elsif tp.event == :raise
333
- if tp.raised_exception.class.name == 'RSpec::Expectations::ExpectationNotMetError'
334
- @capture_next_binding ||= true
371
+ class_name = tp.raised_exception.class.name
372
+ case class_name
373
+ when 'RSpec::Expectations::ExpectationNotMetError'
374
+ @capture_next_binding = :next_matching
335
375
  else
336
376
  handle_tracepoint_event(tp)
337
377
  end
@@ -342,6 +382,7 @@ class EnhancedErrors
342
382
  end
343
383
 
344
384
  def stop_rspec_binding_capture
385
+ disable_capturing! if capture_limit_exceeded?
345
386
  mutex.synchronize do
346
387
  @rspec_tracepoint&.disable
347
388
  @rspec_tracepoint = nil
@@ -567,6 +608,7 @@ class EnhancedErrors
567
608
 
568
609
  def handle_tracepoint_event(tp)
569
610
  # Check enabled outside the synchronized block for speed, but still safe due to re-check inside.
611
+ enforce_capture_limit!
570
612
  return unless mutex.synchronize { @enabled }
571
613
  return if Thread.current[:enhanced_errors_processing] || Thread.current[:on_capture] || ignored_exception?(tp.raised_exception)
572
614
 
@@ -712,7 +754,7 @@ class EnhancedErrors
712
754
  end
713
755
 
714
756
  def valid_capture_events?(capture_events)
715
- capture_events.is_a?(Array) && [:raise, :rescue] && capture_events
757
+ capture_events.is_a?(Array) && capture_events.all? { |ev| [:raise, :rescue].include?(ev) }
716
758
  end
717
759
 
718
760
  def extract_arguments(tp, method_name)
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: enhanced_errors
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.1.0
4
+ version: 2.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Eric Beland
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-12-15 00:00:00.000000000 Z
11
+ date: 2024-12-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: awesome_print
@@ -90,6 +90,7 @@ files:
90
90
  - doc/Enhanced/Integrations/RSpecErrorFailureMessage.html
91
91
  - doc/EnhancedErrors.html
92
92
  - doc/Exception.html
93
+ - doc/Minitest.html
93
94
  - doc/_index.html
94
95
  - doc/class_list.html
95
96
  - doc/css/common.css
@@ -135,7 +136,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
135
136
  - !ruby/object:Gem::Version
136
137
  version: '0'
137
138
  requirements: []
138
- rubygems_version: 3.3.26
139
+ rubygems_version: 3.5.22
139
140
  signing_key:
140
141
  specification_version: 4
141
142
  summary: Automatically enhance your errors with messages containing variable values