Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion lib/power_assert.rb
Original file line number Diff line number Diff line change
Expand Up @@ -37,13 +37,15 @@ def app_caller_locations
end

def app_context?
top_frame = caller_locations.drop_while {|i| i.path.start_with?(POWER_ASSERT_LIB_DIR) }.first
top_frame = caller_locations.drop_while {|i| i.path&.start_with?(POWER_ASSERT_LIB_DIR) }.first

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, I applied both suggestions.

One thing I noticed: the &. in app_context? is not covered by the test suite (removing it still passes all tests). The following test covers it by yielding on a non-target thread, which keeps the :call
TracePoint enabled while the Enumerator fiber runs. It fails with NoMethodError if the &. is removed:

t do
  enum = Enumerator.new {|y| y << 1 }
  message = ::PowerAssert.start(-> { enum.next }) do |pa|
    Thread.new { pa.yield }.join
    pa.message
  end
  assert_equal '', message
end

Should I add this test, or would you prefer to keep the &. as an untested defensive guard?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch, could you also add it?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for your review. I've added the test.

top_frame and ! internal_file?(top_frame.path)
end

private

def internal_file?(file)
return false unless file

INTERNAL_LIB_DIRS.find do |_, dir|
file.start_with?(dir)
end
Expand Down
32 changes: 32 additions & 0 deletions test/nil_path_location_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
require_relative 'test_helper'

class TestNilPathLocation < Test::Unit::TestCase

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Except for parser-related code, this project generally does not test individual methods directly. Instead, tests check that the expected power assert message is generated.

For this issue, I think it would be better to add the following reproduction test:

    t do
      obj = []
      def obj.foo; self; end
      enum = Enumerator.new {|y| y << obj.foo }
      def enum.inspect; '#<Enumerator>'; end
      assert_equal <<END.chomp, assertion_message {
        enum.next.foo
        |    |    |
        |    |    []
        |    []
        #<Enumerator>
END
        enum.next.foo
      }
    end

include PowerAssertTestHelper

t do
obj = []
def obj.foo; self; end
enum = Enumerator.new {|y| y << obj.foo }
def enum.inspect; '#<Enumerator>'; end
assert_equal <<END.chomp, assertion_message {
enum.next.foo
| | |
| | []
| []
#<Enumerator>
END
enum.next.foo
}
end

t do
# Yielding on a non-target thread keeps the `TracePoint` for `:call` and `:c_call` events enabled while
# the `Enumerator`'s fiber runs, so that `app_context?` walks caller locations containing a frame without a path.
enum = Enumerator.new {|y| y << 1 }
message = ::PowerAssert.start(-> { enum.next }) do |pa|
Thread.new { pa.yield }.join
pa.message
end
assert_equal '', message
end
end