Validating Stack Frame Processing in BetterErrors
This test suite thoroughly validates the StackFrame class functionality in BetterErrors, focusing on stack trace parsing, file path handling, and error state management. It ensures proper handling of application paths, gem paths, and various error scenarios.
Test Coverage Overview
Implementation Analysis
Technical Details
Best Practices Demonstrated
bettererrors/better_errors
spec/better_errors/stack_frame_spec.rb
require "spec_helper"
module BetterErrors
describe StackFrame do
context "#application?" do
it "is true for application filenames" do
allow(BetterErrors).to receive(:application_root).and_return("/abc/xyz")
frame = StackFrame.new("/abc/xyz/app/controllers/crap_controller.rb", 123, "index")
expect(frame).to be_application
end
it "is false for everything else" do
allow(BetterErrors).to receive(:application_root).and_return("/abc/xyz")
frame = StackFrame.new("/abc/nope", 123, "foo")
expect(frame).not_to be_application
end
it "doesn't care if no application_root is set" do
frame = StackFrame.new("/abc/xyz/app/controllers/crap_controller.rb", 123, "index")
expect(frame).not_to be_application
end
end
context "#gem?" do
it "is true for gem filenames" do
allow(Gem).to receive(:path).and_return(["/abc/xyz"])
frame = StackFrame.new("/abc/xyz/gems/whatever-1.2.3/lib/whatever.rb", 123, "foo")
expect(frame).to be_gem
end
it "is false for everything else" do
allow(Gem).to receive(:path).and_return(["/abc/xyz"])
frame = StackFrame.new("/abc/nope", 123, "foo")
expect(frame).not_to be_gem
end
end
context "#application_path" do
it "chops off the application root" do
allow(BetterErrors).to receive(:application_root).and_return("/abc/xyz")
frame = StackFrame.new("/abc/xyz/app/controllers/crap_controller.rb", 123, "index")
expect(frame.application_path).to eq("app/controllers/crap_controller.rb")
end
end
context "#gem_path" do
it "chops of the gem path and stick (gem) there" do
allow(Gem).to receive(:path).and_return(["/abc/xyz"])
frame = StackFrame.new("/abc/xyz/gems/whatever-1.2.3/lib/whatever.rb", 123, "foo")
expect(frame.gem_path).to eq("whatever (1.2.3) lib/whatever.rb")
end
it "prioritizes gem path over application path" do
allow(BetterErrors).to receive(:application_root).and_return("/abc/xyz")
allow(Gem).to receive(:path).and_return(["/abc/xyz/vendor"])
frame = StackFrame.new("/abc/xyz/vendor/gems/whatever-1.2.3/lib/whatever.rb", 123, "foo")
expect(frame.gem_path).to eq("whatever (1.2.3) lib/whatever.rb")
end
end
context "#pretty_path" do
it "returns #application_path for application paths" do
allow(BetterErrors).to receive(:application_root).and_return("/abc/xyz")
frame = StackFrame.new("/abc/xyz/app/controllers/crap_controller.rb", 123, "index")
expect(frame.pretty_path).to eq(frame.application_path)
end
it "returns #gem_path for gem paths" do
allow(Gem).to receive(:path).and_return(["/abc/xyz"])
frame = StackFrame.new("/abc/xyz/gems/whatever-1.2.3/lib/whatever.rb", 123, "foo")
expect(frame.pretty_path).to eq(frame.gem_path)
end
end
context "#local_variable" do
it "returns exception details when #get_local_variable raises NameError" do
frame = StackFrame.new("/abc/xyz/app/controllers/crap_controller.rb", 123, "index")
allow(frame).to receive(:get_local_variable).and_raise(NameError.new("details"))
expect(frame.local_variable("foo")).to eq("NameError: details")
end
it "returns exception details when #eval_local_variable raises NameError" do
frame = StackFrame.new("/abc/xyz/app/controllers/crap_controller.rb", 123, "index")
allow(frame).to receive(:eval_local_variable).and_raise(NameError.new("details"))
expect(frame.local_variable("foo")).to eq("NameError: details")
end
it "raises on non-NameErrors" do
frame = StackFrame.new("/abc/xyz/app/controllers/crap_controller.rb", 123, "index")
allow(frame).to receive(:get_local_variable).and_raise(ArgumentError)
expect { frame.local_variable("foo") }.to raise_error(ArgumentError)
end
end
it "special cases SyntaxErrors" do
begin
eval(%{ raise SyntaxError, "you wrote bad ruby!" }, nil, "my_file.rb", 123)
rescue SyntaxError => syntax_error
end
frames = StackFrame.from_exception(syntax_error)
expect(frames.first.filename).to eq("my_file.rb")
expect(frames.first.line).to eq(123)
end
it "doesn't blow up if no method name is given" do
error = StandardError.allocate
allow(error).to receive(:backtrace).and_return(["foo.rb:123"])
frames = StackFrame.from_exception(error)
expect(frames.first.filename).to eq("foo.rb")
expect(frames.first.line).to eq(123)
allow(error).to receive(:backtrace).and_return(["foo.rb:123: this is an error message"])
frames = StackFrame.from_exception(error)
expect(frames.first.filename).to eq("foo.rb")
expect(frames.first.line).to eq(123)
end
it "ignores a backtrace line if its format doesn't make any sense at all" do
error = StandardError.allocate
allow(error).to receive(:backtrace).and_return(["foo.rb:123:in `foo'", "C:in `find'", "bar.rb:123:in `bar'"])
frames = StackFrame.from_exception(error)
expect(frames.count).to eq(2)
end
it "doesn't blow up if a filename contains a colon" do
error = StandardError.allocate
allow(error).to receive(:backtrace).and_return(["crap:filename.rb:123"])
frames = StackFrame.from_exception(error)
expect(frames.first.filename).to eq("crap:filename.rb")
end
it "doesn't blow up with a BasicObject as frame binding" do
obj = BasicObject.new
def obj.my_binding
::Kernel.binding
end
frame = StackFrame.new("/abc/xyz/app/controllers/crap_controller.rb", 123, "index", obj.my_binding)
expect(frame.class_name).to eq('BasicObject')
end
it "sets method names properly" do
obj = "string"
def obj.my_method
begin
raise "foo"
rescue => err
err
end
end
frame = StackFrame.from_exception(obj.my_method).first
if BetterErrors.binding_of_caller_available?
expect(frame.method_name).to eq("#my_method")
expect(frame.class_name).to eq("String")
else
expect(frame.method_name).to eq("my_method")
expect(frame.class_name).to eq(nil)
end
end
if RUBY_ENGINE == "java"
it "doesn't blow up on a native Java exception" do
expect { StackFrame.from_exception(java.lang.Exception.new) }.to_not raise_error
end
end
end
end