enhanced_errors 3.0.3 → 3.0.4
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.
- checksums.yaml +4 -4
- data/README.md +4 -3
- data/benchmark/benchmark.rb +31 -29
- data/benchmark/memory_bench.rb +1 -1
- data/benchmark/result.txt +13 -0
- data/doc/Enhanced/Colors.html +2 -2
- data/doc/Enhanced/Context.html +283 -0
- data/doc/Enhanced/ExceptionBindingInfos.html +255 -0
- data/doc/Enhanced/ExceptionContext.html +397 -0
- data/doc/Enhanced.html +8 -4
- data/doc/EnhancedErrors.html +385 -269
- data/doc/EnhancedExceptionContext.html +15 -15
- data/doc/Exception.html +5 -5
- data/doc/ExceptionBindingInfos.html +2 -2
- data/doc/Minitest.html +3 -3
- data/doc/_index.html +12 -6
- data/doc/class_list.html +1 -1
- data/doc/file.README.html +9 -4
- data/doc/index.html +9 -4
- data/doc/method_list.html +34 -18
- data/doc/top-level-namespace.html +18 -8
- data/enhanced_errors.gemspec +1 -1
- data/lib/enhanced/context.rb +7 -5
- data/lib/enhanced/exception.rb +35 -36
- data/lib/enhanced/exception_context.rb +49 -0
- data/lib/enhanced/minitest_patch.rb +1 -1
- data/lib/enhanced_errors.rb +147 -98
- metadata +8 -9
- data/.yardoc/checksums +0 -6
- data/.yardoc/complete +0 -0
- data/.yardoc/object_types +0 -0
- data/.yardoc/objects/root.dat +0 -0
- data/.yardoc/proxy_types +0 -0
- data/lib/enhanced/enhanced_exception_context.rb +0 -47
data/lib/enhanced/exception.rb
CHANGED
@@ -1,55 +1,54 @@
|
|
1
1
|
# exception.rb
|
2
|
-
require_relative '
|
3
|
-
|
4
|
-
module
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
ctx
|
9
|
-
|
2
|
+
require_relative 'exception_context'
|
3
|
+
|
4
|
+
module Enhanced
|
5
|
+
module ExceptionBindingInfos
|
6
|
+
def binding_infos
|
7
|
+
ctx = Enhanced::ExceptionContext.context_for(self)
|
8
|
+
unless ctx
|
9
|
+
ctx = Context.new
|
10
|
+
Enhanced::ExceptionContext.store_context(self, ctx)
|
11
|
+
end
|
12
|
+
ctx.binding_infos
|
10
13
|
end
|
11
|
-
ctx.binding_infos
|
12
|
-
end
|
13
14
|
|
14
|
-
|
15
|
-
|
15
|
+
def captured_variables
|
16
|
+
return '' unless binding_infos&.any?
|
16
17
|
bindings_of_interest = select_binding_infos
|
17
18
|
EnhancedErrors.format(bindings_of_interest)
|
18
|
-
|
19
|
+
rescue
|
19
20
|
''
|
20
21
|
end
|
21
|
-
rescue
|
22
|
-
''
|
23
|
-
end
|
24
22
|
|
25
|
-
|
23
|
+
private
|
26
24
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
25
|
+
def select_binding_infos
|
26
|
+
# Preference:
|
27
|
+
# 1. First 'raise' binding that isn't from a library (gem).
|
28
|
+
# 2. If none, the first binding.
|
29
|
+
# 3. The last 'rescue' binding if available.
|
32
30
|
|
33
|
-
|
31
|
+
bindings_of_interest = []
|
34
32
|
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
33
|
+
first_app_raise = binding_infos.find do |info|
|
34
|
+
info[:capture_event] == 'raise' && !info[:library]
|
35
|
+
end
|
36
|
+
bindings_of_interest << first_app_raise if first_app_raise
|
39
37
|
|
40
|
-
|
41
|
-
|
42
|
-
|
38
|
+
if bindings_of_interest.empty? && binding_infos.first
|
39
|
+
bindings_of_interest << binding_infos.first
|
40
|
+
end
|
43
41
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
42
|
+
last_rescue = binding_infos.reverse.find do |info|
|
43
|
+
info[:capture_event] == 'rescue'
|
44
|
+
end
|
45
|
+
bindings_of_interest << last_rescue if last_rescue
|
48
46
|
|
49
|
-
|
47
|
+
bindings_of_interest.compact
|
48
|
+
end
|
50
49
|
end
|
51
50
|
end
|
52
51
|
|
53
52
|
class Exception
|
54
|
-
prepend ExceptionBindingInfos
|
53
|
+
prepend Enhanced::ExceptionBindingInfos
|
55
54
|
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'weakref'
|
2
|
+
|
3
|
+
require_relative 'context'
|
4
|
+
|
5
|
+
require 'weakref'
|
6
|
+
require 'monitor'
|
7
|
+
|
8
|
+
module Enhanced
|
9
|
+
module ExceptionContext
|
10
|
+
extend self
|
11
|
+
|
12
|
+
REGISTRY = {}
|
13
|
+
MUTEX = Monitor.new
|
14
|
+
|
15
|
+
def store_context(exception, context)
|
16
|
+
MUTEX.synchronize do
|
17
|
+
REGISTRY[exception.object_id] = { weak_exc: WeakRef.new(exception), context: context }
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def context_for(exception)
|
22
|
+
MUTEX.synchronize do
|
23
|
+
entry = REGISTRY[exception.object_id]
|
24
|
+
return nil unless entry
|
25
|
+
|
26
|
+
begin
|
27
|
+
_ = entry[:weak_exc].__getobj__ # ensure exception is still alive
|
28
|
+
entry[:context]
|
29
|
+
rescue RefError
|
30
|
+
# Exception no longer alive, clean up
|
31
|
+
REGISTRY.delete(exception.object_id)
|
32
|
+
nil
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def clear_context(exception)
|
38
|
+
MUTEX.synchronize do
|
39
|
+
REGISTRY.delete(exception.object_id)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def clear_all
|
44
|
+
MUTEX.synchronize do
|
45
|
+
REGISTRY.clear
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -9,7 +9,7 @@ module Minitest
|
|
9
9
|
begin
|
10
10
|
binding_infos = EnhancedErrors.stop_minitest_binding_capture
|
11
11
|
EnhancedErrors.override_exception_message(result.failures.last, binding_infos) if result.failures.any?
|
12
|
-
|
12
|
+
Enhanced::ExceptionContext.clear_all
|
13
13
|
rescue => e
|
14
14
|
puts "Ignored error during error enhancement: #{e}"
|
15
15
|
end
|
data/lib/enhanced_errors.rb
CHANGED
@@ -4,12 +4,12 @@ require 'set'
|
|
4
4
|
require 'json'
|
5
5
|
require 'monitor'
|
6
6
|
|
7
|
-
|
8
|
-
require_relative 'enhanced/exception'
|
7
|
+
module Enhanced; end
|
9
8
|
|
10
|
-
|
11
|
-
|
12
|
-
RSpec::Matchers::BuiltIn::RaiseError
|
9
|
+
# Exceptions we could handle but overlook for other reasons. These class constants are not always loaded
|
10
|
+
# and generally are only be available when `required`, so we detect them by strings.
|
11
|
+
IGNORED_EXCEPTIONS = %w[RSpec::Expectations::ExpectationNotMetError RSpec::Matchers::BuiltIn::RaiseError
|
12
|
+
JSON::ParserError Zlib::Error OpenSSL::SSL::SSLError Psych::Exception]
|
13
13
|
|
14
14
|
class EnhancedErrors
|
15
15
|
extend ::Enhanced
|
@@ -45,13 +45,8 @@ class EnhancedErrors
|
|
45
45
|
].freeze
|
46
46
|
|
47
47
|
RAILS_SKIP_LIST = [
|
48
|
-
:@new_record,
|
49
|
-
:@attributes,
|
50
48
|
:@association_cache,
|
51
|
-
:@readonly,
|
52
|
-
:@previously_new_record,
|
53
49
|
:@_routes,
|
54
|
-
:@routes,
|
55
50
|
:@app,
|
56
51
|
:@arel_table,
|
57
52
|
:@assertion_instance,
|
@@ -127,7 +122,7 @@ class EnhancedErrors
|
|
127
122
|
mutex.synchronize { @max_capture_length || DEFAULT_MAX_CAPTURE_LENGTH }
|
128
123
|
end
|
129
124
|
|
130
|
-
def max_capture_length=(
|
125
|
+
def max_capture_length=(value)
|
131
126
|
mutex.synchronize { @max_capture_length = value }
|
132
127
|
end
|
133
128
|
|
@@ -140,7 +135,6 @@ class EnhancedErrors
|
|
140
135
|
end
|
141
136
|
end
|
142
137
|
|
143
|
-
|
144
138
|
def reset!
|
145
139
|
mutex.synchronize do
|
146
140
|
@rspec_tracepoint&.disable
|
@@ -155,7 +149,7 @@ class EnhancedErrors
|
|
155
149
|
|
156
150
|
def skip_list
|
157
151
|
mutex.synchronize do
|
158
|
-
@skip_list ||= DEFAULT_SKIP_LIST
|
152
|
+
@skip_list ||= DEFAULT_SKIP_LIST.to_set
|
159
153
|
end
|
160
154
|
end
|
161
155
|
|
@@ -187,12 +181,13 @@ class EnhancedErrors
|
|
187
181
|
|
188
182
|
def add_to_skip_list(*vars)
|
189
183
|
mutex.synchronize do
|
190
|
-
@skip_list.
|
184
|
+
@skip_list.add(*vars)
|
191
185
|
end
|
192
186
|
end
|
193
187
|
|
194
188
|
def enhance_exceptions!(enabled: true, debug: false, capture_events: nil, override_messages: false, **options, &block)
|
195
189
|
mutex.synchronize do
|
190
|
+
ensure_extensions_are_required
|
196
191
|
@exception_trace&.disable
|
197
192
|
@exception_trace = nil
|
198
193
|
|
@@ -223,10 +218,11 @@ class EnhancedErrors
|
|
223
218
|
|
224
219
|
events = @capture_events ? @capture_events.to_a : default_capture_events
|
225
220
|
@exception_trace = TracePoint.new(*events) do |tp|
|
221
|
+
return unless exception_is_handleable?(tp.raised_exception)
|
226
222
|
handle_tracepoint_event(tp)
|
227
223
|
end
|
228
224
|
|
229
|
-
@exception_trace
|
225
|
+
@exception_trace&.enable if @enabled
|
230
226
|
end
|
231
227
|
end
|
232
228
|
|
@@ -235,7 +231,8 @@ class EnhancedErrors
|
|
235
231
|
end
|
236
232
|
|
237
233
|
def start_minitest_binding_capture
|
238
|
-
|
234
|
+
ensure_extensions_are_required
|
235
|
+
Enhanced::ExceptionContext.clear_all
|
239
236
|
@enabled = true if @enabled.nil?
|
240
237
|
return unless @enabled
|
241
238
|
mutex.synchronize do
|
@@ -243,7 +240,7 @@ class EnhancedErrors
|
|
243
240
|
next unless tp.method_id.to_s.start_with?('test_') && is_a_minitest?(tp.defined_class)
|
244
241
|
@minitest_test_binding = tp.binding
|
245
242
|
end
|
246
|
-
@minitest_trace
|
243
|
+
@minitest_trace&.enable
|
247
244
|
end
|
248
245
|
end
|
249
246
|
|
@@ -269,7 +266,8 @@ class EnhancedErrors
|
|
269
266
|
end
|
270
267
|
|
271
268
|
def start_rspec_binding_capture
|
272
|
-
|
269
|
+
ensure_extensions_are_required
|
270
|
+
Enhanced::ExceptionContext.clear_all
|
273
271
|
@enabled = true if @enabled.nil?
|
274
272
|
return unless @enabled
|
275
273
|
|
@@ -277,21 +275,29 @@ class EnhancedErrors
|
|
277
275
|
@rspec_example_binding = nil
|
278
276
|
@capture_next_binding = false
|
279
277
|
@rspec_tracepoint&.disable
|
280
|
-
|
281
278
|
@rspec_tracepoint = TracePoint.new(:raise) do |tp|
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
279
|
+
return unless exception_is_handleable?(tp.raised_exception)
|
280
|
+
class_name = tp.raised_exception.class.name
|
281
|
+
case class_name
|
282
|
+
when 'RSpec::Expectations::ExpectationNotMetError'
|
283
|
+
start_rspec_binding_trap
|
284
|
+
else
|
285
|
+
handle_tracepoint_event(tp)
|
289
286
|
end
|
290
287
|
end
|
291
|
-
|
288
|
+
end
|
289
|
+
@rspec_tracepoint&.enable
|
292
290
|
end
|
293
291
|
|
294
|
-
#
|
292
|
+
# Behavior: Grabs the next rspec spec binding that goes by, and stops the more-expensive b_return trace.
|
293
|
+
# This part of RSpec has been stable, since 2015, so although this is kluge-y, it is stable.
|
294
|
+
# The optimization does a 2-3x on spec speed vs. opening up the Tracepoint. With it,
|
295
|
+
# things are pretty close in speed to plain rspec.
|
296
|
+
# Should the behavior change this can be updated by using a trace to print out items
|
297
|
+
# and their local variables then, find the exception or call that goes by right
|
298
|
+
# before the spec blocks with the variables, and use that to narrow-down the costly part of
|
299
|
+
# the probe to just this point in time. The good news is that
|
300
|
+
# this part is test-time only, and this optimization and kluge only applies to RSpec.
|
295
301
|
def start_rspec_binding_trap
|
296
302
|
@rspec_binding_trap = TracePoint.new(:b_return) do |tp|
|
297
303
|
# kluge-y hack and will be a pain to maintain
|
@@ -302,13 +308,12 @@ class EnhancedErrors
|
|
302
308
|
next unless @capture_next_binding
|
303
309
|
@capture_next_binding = false
|
304
310
|
@rspec_example_binding = tp.binding
|
305
|
-
@rspec_binding_trap
|
311
|
+
@rspec_binding_trap&.disable
|
306
312
|
@rspec_binding_trap = nil
|
307
313
|
end
|
308
|
-
@rspec_binding_trap
|
314
|
+
@rspec_binding_trap&.enable
|
309
315
|
end
|
310
316
|
|
311
|
-
|
312
317
|
def stop_rspec_binding_capture
|
313
318
|
mutex.synchronize do
|
314
319
|
@rspec_tracepoint&.disable
|
@@ -327,6 +332,8 @@ class EnhancedErrors
|
|
327
332
|
|
328
333
|
locals = b.local_variables.map { |var| [var, safe_local_variable_get(b, var)] }.to_h
|
329
334
|
receiver = b.receiver
|
335
|
+
return unless safe_to_inspect?(receiver)
|
336
|
+
|
330
337
|
instance_vars = receiver.instance_variables
|
331
338
|
instances = instance_vars.map { |var| [var, safe_instance_variable_get(receiver, var)] }.to_h
|
332
339
|
|
@@ -355,7 +362,7 @@ class EnhancedErrors
|
|
355
362
|
globals: {}
|
356
363
|
},
|
357
364
|
exception: 'NoException',
|
358
|
-
capture_event: '
|
365
|
+
capture_event: 'test_context'
|
359
366
|
}
|
360
367
|
|
361
368
|
default_on_capture(binding_info)
|
@@ -437,11 +444,7 @@ class EnhancedErrors
|
|
437
444
|
env = ENV['RAILS_ENV'] || ENV['RACK_ENV'] || 'development'
|
438
445
|
@output_format = case env
|
439
446
|
when 'development', 'test'
|
440
|
-
|
441
|
-
:plaintext
|
442
|
-
else
|
443
|
-
:terminal
|
444
|
-
end
|
447
|
+
running_in_ci? ? :plaintext : :terminal
|
445
448
|
when 'production'
|
446
449
|
:json
|
447
450
|
else
|
@@ -453,28 +456,32 @@ class EnhancedErrors
|
|
453
456
|
def running_in_ci?
|
454
457
|
mutex.synchronize do
|
455
458
|
return @running_in_ci if defined?(@running_in_ci)
|
456
|
-
|
457
459
|
@running_in_ci = CI_ENV_VARS.any? { |_, value| value.to_s.downcase == 'true' }
|
458
460
|
end
|
459
461
|
end
|
460
462
|
|
461
463
|
def apply_skip_list(binding_info)
|
462
|
-
|
463
|
-
|
464
|
-
|
465
|
-
variables[:
|
466
|
-
if @debug
|
467
|
-
variables[:globals]&.reject! { |var, _| skip_list.include?(var) }
|
468
|
-
end
|
464
|
+
binding_info[:variables][:instances]&.reject! { |var, _| skip_list.include?(var) || (var.to_s[0, 2] == '@_' && !@debug) }
|
465
|
+
binding_info[:variables][:locals]&.reject! { |var, _| skip_list.include?(var) }
|
466
|
+
if @debug
|
467
|
+
binding_info[:variables][:globals]&.reject! { |var, _| skip_list.include?(var) }
|
469
468
|
end
|
470
469
|
binding_info
|
471
470
|
end
|
472
471
|
|
473
472
|
def validate_binding_format(binding_info)
|
474
|
-
|
475
|
-
|
473
|
+
binding_info.keys.include?(:capture_event) && binding_info[:variables].is_a?(Hash)
|
474
|
+
end
|
475
|
+
|
476
|
+
# Here, we are detecting BasicObject, which is surprisingly annoying.
|
477
|
+
# We also, importantly, need to detect descendants, as they will also present with a
|
478
|
+
# lack of :respond_to? and any other useful method for us.
|
479
|
+
def safe_to_inspect?(obj)
|
480
|
+
begin
|
481
|
+
obj.class
|
482
|
+
rescue NoMethodError
|
483
|
+
return false
|
476
484
|
end
|
477
|
-
binding_info
|
478
485
|
end
|
479
486
|
|
480
487
|
def binding_info_string(binding_info)
|
@@ -524,7 +531,6 @@ class EnhancedErrors
|
|
524
531
|
|
525
532
|
private
|
526
533
|
|
527
|
-
|
528
534
|
def handle_tracepoint_event(tp)
|
529
535
|
# Check enabled outside the synchronized block for speed, but still safe due to re-check inside.
|
530
536
|
return unless enabled
|
@@ -533,9 +539,13 @@ class EnhancedErrors
|
|
533
539
|
Thread.current[:enhanced_errors_processing] = true
|
534
540
|
exception = tp.raised_exception
|
535
541
|
|
536
|
-
|
537
|
-
|
538
|
-
|
542
|
+
return if exception.frozen?
|
543
|
+
|
544
|
+
capture_me = if @eligible_for_capture
|
545
|
+
@eligible_for_capture.call(exception)
|
546
|
+
else
|
547
|
+
default_eligible_for_capture(exception)
|
548
|
+
end
|
539
549
|
|
540
550
|
unless capture_me
|
541
551
|
Thread.current[:enhanced_errors_processing] = false
|
@@ -544,32 +554,43 @@ class EnhancedErrors
|
|
544
554
|
|
545
555
|
binding_context = tp.binding
|
546
556
|
method_name = tp.method_id
|
557
|
+
|
558
|
+
locals = {}
|
559
|
+
|
560
|
+
binding_context.local_variables.each do |var|
|
561
|
+
locals[var] = safe_local_variable_get(binding_context, var)
|
562
|
+
end
|
563
|
+
|
547
564
|
method_and_args = {
|
548
565
|
object_name: determine_object_name(tp, method_name),
|
549
|
-
args: extract_arguments(tp, method_name)
|
566
|
+
args: extract_arguments(tp, method_name, locals)
|
550
567
|
}
|
551
568
|
|
552
|
-
|
553
|
-
|
554
|
-
}.to_h
|
569
|
+
instances = {}
|
570
|
+
receiver = binding_context.receiver
|
555
571
|
|
556
|
-
|
557
|
-
|
558
|
-
|
559
|
-
|
572
|
+
begin
|
573
|
+
if safe_to_inspect?(receiver)
|
574
|
+
receiver.instance_variables.each { |var|
|
575
|
+
instances[var] = safe_instance_variable_get(receiver, var)
|
576
|
+
}
|
577
|
+
end
|
578
|
+
rescue => e
|
579
|
+
puts "#{e.class.name} #{e.backtrace}"
|
580
|
+
end
|
560
581
|
|
561
582
|
lets = {}
|
562
583
|
|
563
584
|
globals = {}
|
564
585
|
mutex.synchronize do
|
565
586
|
if @debug
|
566
|
-
globals = (global_variables - @original_global_variables.to_a).
|
567
|
-
[var
|
568
|
-
|
587
|
+
globals = (global_variables - @original_global_variables.to_a).each do |var|
|
588
|
+
globals[var] = get_global_variable_value(var)
|
589
|
+
end
|
569
590
|
end
|
570
591
|
end
|
571
592
|
|
572
|
-
capture_event =
|
593
|
+
capture_event = tp.event.to_s
|
573
594
|
location = "#{safe_to_s(tp.path)}:#{safe_to_s(tp.lineno)}"
|
574
595
|
binding_info = {
|
575
596
|
source: location,
|
@@ -583,17 +604,16 @@ class EnhancedErrors
|
|
583
604
|
lets: lets,
|
584
605
|
globals: globals
|
585
606
|
},
|
586
|
-
exception:
|
607
|
+
exception: exception.class.name,
|
587
608
|
capture_event: capture_event
|
588
609
|
}
|
589
610
|
|
590
611
|
binding_info = default_on_capture(binding_info)
|
591
|
-
on_capture_hook_local = mutex.synchronize { @on_capture_hook }
|
592
612
|
|
593
|
-
if
|
613
|
+
if on_capture_hook
|
594
614
|
begin
|
595
615
|
Thread.current[:on_capture] = true
|
596
|
-
binding_info =
|
616
|
+
binding_info = on_capture_hook.call(binding_info)
|
597
617
|
rescue
|
598
618
|
binding_info = nil
|
599
619
|
ensure
|
@@ -601,20 +621,16 @@ class EnhancedErrors
|
|
601
621
|
end
|
602
622
|
end
|
603
623
|
|
604
|
-
|
605
|
-
|
606
|
-
|
607
|
-
exception.binding_infos.delete_at(MAX_BINDING_INFOS / 2.round)
|
608
|
-
end
|
609
|
-
if binding_info
|
610
|
-
exception.binding_infos << binding_info
|
611
|
-
mutex.synchronize do
|
612
|
-
override_exception_message(exception, exception.binding_infos) if @override_messages
|
613
|
-
end
|
614
|
-
end
|
624
|
+
return unless binding_info && validate_binding_format(binding_info)
|
625
|
+
if exception.binding_infos.length >= MAX_BINDING_INFOS
|
626
|
+
exception.binding_infos.delete_at(MAX_BINDING_INFOS / 2.round)
|
615
627
|
end
|
616
|
-
|
617
|
-
|
628
|
+
exception.binding_infos << binding_info
|
629
|
+
mutex.synchronize do
|
630
|
+
override_exception_message(exception, exception.binding_infos) if @override_messages
|
631
|
+
end
|
632
|
+
rescue => e
|
633
|
+
puts "Error: #{e&.class&.name} #{e&.backtrace}"
|
618
634
|
ensure
|
619
635
|
Thread.current[:enhanced_errors_processing] = false
|
620
636
|
end
|
@@ -674,17 +690,15 @@ class EnhancedErrors
|
|
674
690
|
capture_events.is_a?(Array) && capture_events.all? { |ev| [:raise, :rescue].include?(ev) }
|
675
691
|
end
|
676
692
|
|
677
|
-
def extract_arguments(tp, method_name)
|
693
|
+
def extract_arguments(tp, method_name, local_vars_hash)
|
678
694
|
return '' unless method_name
|
679
695
|
begin
|
680
|
-
bind = tp.binding
|
681
696
|
unbound_method = tp.defined_class.instance_method(method_name)
|
682
697
|
method_obj = unbound_method.bind(tp.self)
|
683
698
|
parameters = method_obj.parameters
|
684
|
-
locals = bind.local_variables
|
685
699
|
|
686
700
|
parameters.map do |(_, name)|
|
687
|
-
value =
|
701
|
+
value = local_vars_hash[name]
|
688
702
|
"#{name}=#{safe_inspect(value)}"
|
689
703
|
rescue => e
|
690
704
|
"#{name}=[Error getting argument: #{e.message}]"
|
@@ -696,17 +710,20 @@ class EnhancedErrors
|
|
696
710
|
|
697
711
|
def determine_object_name(tp, method_name = '')
|
698
712
|
begin
|
699
|
-
|
700
|
-
|
701
|
-
|
702
|
-
|
703
|
-
|
704
|
-
|
705
|
-
|
713
|
+
# Check if we're dealing with a singleton method
|
714
|
+
if (
|
715
|
+
class << tp.self
|
716
|
+
self;
|
717
|
+
end) == tp.defined_class
|
718
|
+
# Singleton method call
|
719
|
+
object_str = safe_to_s(tp.self)
|
720
|
+
method_suffix = method_name.to_s.empty? ? '' : ".#{method_name}"
|
721
|
+
"#{object_str}#{method_suffix}"
|
706
722
|
else
|
707
|
-
|
708
|
-
|
709
|
-
"
|
723
|
+
# Instance method call
|
724
|
+
klass_name = safe_to_s(tp.self.class.name || 'UnknownClass')
|
725
|
+
method_suffix = method_name.to_s.empty? ? '' : "##{method_name}"
|
726
|
+
"#{klass_name}#{method_suffix}"
|
710
727
|
end
|
711
728
|
rescue
|
712
729
|
'[ErrorGettingName]'
|
@@ -798,10 +815,42 @@ class EnhancedErrors
|
|
798
815
|
apply_skip_list(binding_info)
|
799
816
|
end
|
800
817
|
|
818
|
+
# By default, we have filtering for safety, but past that, we capture everything by default
|
819
|
+
# at the moment.
|
801
820
|
def default_eligible_for_capture(exception)
|
802
|
-
|
803
|
-
|
804
|
-
|
821
|
+
true
|
822
|
+
end
|
823
|
+
|
824
|
+
def exception_is_handleable?(exception)
|
825
|
+
case exception
|
826
|
+
when SystemExit, SignalException, SystemStackError, NoMemoryError
|
827
|
+
# Non-actionable: Ignore these exceptions
|
828
|
+
false
|
829
|
+
when SyntaxError, LoadError, ScriptError
|
830
|
+
# Non-actionable: Structural issues so there's no useful runtime context
|
831
|
+
false
|
832
|
+
else
|
833
|
+
# Ignore internal fatal errors
|
834
|
+
exception.class.to_s != 'fatal'
|
835
|
+
end
|
836
|
+
end
|
837
|
+
|
838
|
+
# This prevents loading it for say, production, if you don't want to,
|
839
|
+
# and keeps things cleaner. It allows a path to put this behind a feature-flag
|
840
|
+
# or env variable, and dynamically enable some capture instrumentation only
|
841
|
+
# when a Heisenbug is being hunted.
|
842
|
+
def ensure_extensions_are_required
|
843
|
+
mutex.synchronize do
|
844
|
+
return if @loaded_required_extensions
|
845
|
+
require_relative 'enhanced/colors'
|
846
|
+
require_relative 'enhanced/exception'
|
847
|
+
@loaded_required_extensions = true
|
848
|
+
end
|
805
849
|
end
|
850
|
+
|
806
851
|
end
|
807
852
|
end
|
853
|
+
|
854
|
+
module Enhanced
|
855
|
+
|
856
|
+
end
|
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: 3.0.
|
4
|
+
version: 3.0.4
|
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-
|
11
|
+
date: 2024-12-24 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: awesome_print
|
@@ -74,19 +74,18 @@ executables: []
|
|
74
74
|
extensions: []
|
75
75
|
extra_rdoc_files: []
|
76
76
|
files:
|
77
|
-
- ".yardoc/checksums"
|
78
|
-
- ".yardoc/complete"
|
79
|
-
- ".yardoc/object_types"
|
80
|
-
- ".yardoc/objects/root.dat"
|
81
|
-
- ".yardoc/proxy_types"
|
82
77
|
- LICENSE
|
83
78
|
- README.md
|
84
79
|
- benchmark/benchmark.rb
|
85
80
|
- benchmark/memory_bench.rb
|
81
|
+
- benchmark/result.txt
|
86
82
|
- benchmark/stackprofile.rb
|
87
83
|
- doc/Context.html
|
88
84
|
- doc/Enhanced.html
|
89
85
|
- doc/Enhanced/Colors.html
|
86
|
+
- doc/Enhanced/Context.html
|
87
|
+
- doc/Enhanced/ExceptionBindingInfos.html
|
88
|
+
- doc/Enhanced/ExceptionContext.html
|
90
89
|
- doc/Enhanced/Integrations.html
|
91
90
|
- doc/Enhanced/Integrations/RSpecErrorFailureMessage.html
|
92
91
|
- doc/EnhancedErrors.html
|
@@ -117,8 +116,8 @@ files:
|
|
117
116
|
- examples/demo_rspec.rb
|
118
117
|
- lib/enhanced/colors.rb
|
119
118
|
- lib/enhanced/context.rb
|
120
|
-
- lib/enhanced/enhanced_exception_context.rb
|
121
119
|
- lib/enhanced/exception.rb
|
120
|
+
- lib/enhanced/exception_context.rb
|
122
121
|
- lib/enhanced/minitest_patch.rb
|
123
122
|
- lib/enhanced_errors.rb
|
124
123
|
homepage: https://github.com/ericbeland/enhanced_errors
|
@@ -141,7 +140,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
141
140
|
- !ruby/object:Gem::Version
|
142
141
|
version: '0'
|
143
142
|
requirements: []
|
144
|
-
rubygems_version: 3.
|
143
|
+
rubygems_version: 3.3.26
|
145
144
|
signing_key:
|
146
145
|
specification_version: 4
|
147
146
|
summary: Automatically enhance your errors with messages containing variable values
|
data/.yardoc/checksums
DELETED
@@ -1,6 +0,0 @@
|
|
1
|
-
lib/enhanced/colors.rb ed3b11d00ff9ceed089d4a65f0be5b3fca64bbe6
|
2
|
-
lib/enhanced_errors.rb f79331fea888262a0447d2fff4e5067fdf1418a9
|
3
|
-
lib/enhanced/context.rb 24ca2d1f4ee2ff48dd83c913ad1f1e7c1aa367c4
|
4
|
-
lib/enhanced/exception.rb 5572411e9e32bbe9ed01b98787e1a53a4ab61408
|
5
|
-
lib/enhanced/minitest_patch.rb 3e7fb88ddc37a1f966877735a43ae206ee396bbc
|
6
|
-
lib/enhanced/enhanced_exception_context.rb 23423dbdb33b7961a0b8a297e052ebb2fbe1c6ac
|
data/.yardoc/complete
DELETED
File without changes
|