Skip to content

Commit 81a1bcf

Browse files
committed
Fix NoMethodError when a caller location has no path
`Thread::Backtrace::Location#path` returns nil for the Enumerator frames (e.g. "Enumerator::Generator#each") that appear in `caller_locations` during an external iteration of Enumerator (e.g. `Enumerator#next`). When a `TracePoint` callback fires inside such an iteration, `PowerAssert.internal_file?` raises `NoMethodError` and the callback reports: ``` power_assert: [BUG] Failed to trace: NoMethodError: undefined method 'start_with?' for nil ``` This happens in practice when a block-style assertion drives Capybara, whose `Capybara::Result` iterates matched elements with `Enumerator#next`. While #31 originally suspected a CRuby issue, the type signature of `Thread::Backtrace::Location#path` in ruby/rbs allows nil (`() -> String?`), and this nil case is observable at least on Ruby 2.6.10, 2.7.8, 3.1.5, 3.3.10, 3.4.10, and 4.0.6, so it seems reasonable for power_assert to tolerate nil either way. Since `internal_file?` answers whether the given file belongs to the power_assert library, treat locations without a path as non-internal ones. Also guard `app_context?`, which walks caller locations the same way. Closes #31.
1 parent 583705a commit 81a1bcf

2 files changed

Lines changed: 24 additions & 1 deletion

File tree

lib/power_assert.rb

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,13 +37,15 @@ def app_caller_locations
3737
end
3838

3939
def app_context?
40-
top_frame = caller_locations.drop_while {|i| i.path.start_with?(POWER_ASSERT_LIB_DIR) }.first
40+
top_frame = caller_locations.drop_while {|i| i.path&.start_with?(POWER_ASSERT_LIB_DIR) }.first
4141
top_frame and ! internal_file?(top_frame.path)
4242
end
4343

4444
private
4545

4646
def internal_file?(file)
47+
return false unless file
48+
4749
INTERNAL_LIB_DIRS.find do |_, dir|
4850
file.start_with?(dir)
4951
end

test/nil_path_location_test.rb

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
require_relative 'test_helper'
2+
3+
class TestNilPathLocation < Test::Unit::TestCase
4+
include PowerAssertTestHelper
5+
6+
t do
7+
obj = []
8+
def obj.foo; self; end
9+
enum = Enumerator.new {|y| y << obj.foo }
10+
def enum.inspect; '#<Enumerator>'; end
11+
assert_equal <<END.chomp, assertion_message {
12+
enum.next.foo
13+
| | |
14+
| | []
15+
| []
16+
#<Enumerator>
17+
END
18+
enum.next.foo
19+
}
20+
end
21+
end

0 commit comments

Comments
 (0)