better_errors 0.0.2 → 0.3.0

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.
@@ -0,0 +1,69 @@
1
+ <header class="trace_info">
2
+ <div class="title">
3
+ <h2 class="name"><%= @frame.name %></h2>
4
+ <div class="location"><span class="filename"><a href="<%= editor_url(@frame) %>"><%= @frame.pretty_path %></a></span></div>
5
+ </div>
6
+
7
+ <%== highlighted_code_block @frame %>
8
+
9
+ <% if BetterErrors.binding_of_caller_available? && @frame.frame_binding %>
10
+ <div class="repl">
11
+ <div class="console">
12
+ <pre></pre>
13
+ <div class="prompt"><span>&gt;&gt;</span> <input/></div>
14
+ </div>
15
+ </div>
16
+ <% end %>
17
+ </header>
18
+
19
+ <% if BetterErrors.binding_of_caller_available? && @frame.frame_binding %>
20
+ <div class="hint">
21
+ This a live shell. Type in here.
22
+ </div>
23
+
24
+ <div class="variable_info"></div>
25
+ <% end %>
26
+
27
+ <% unless BetterErrors.binding_of_caller_available? %>
28
+ <div class="hint">
29
+ <strong>Tip:</strong> add <code>gem "binding_of_caller"</code> to your Gemfile to enable the REPL and local/instance variable inspection.
30
+ </div>
31
+ <% end %>
32
+
33
+ <div class="sub">
34
+ <h3>Request info</h3>
35
+ <div class='inset variables'>
36
+ <table class="var_table">
37
+ <% if rails_params %>
38
+ <tr><td class="name">Request parameters</td><td><pre><%== inspect_value rails_params %></pre></td></tr>
39
+ <% end %>
40
+ <% if rack_session %>
41
+ <tr><td class="name">Rack session</td><td><pre><%== inspect_value rack_session %></pre></td></tr>
42
+ <% end %>
43
+ </table>
44
+ </div>
45
+ </div>
46
+
47
+ <div class="sub">
48
+ <h3>Local Variables</h3>
49
+ <div class='inset variables'>
50
+ <table class="var_table">
51
+ <% @frame.local_variables.each do |name, value| %>
52
+ <tr><td class="name"><%= name %></td><td><pre><%== inspect_value value %></pre></td></tr>
53
+ <% end %>
54
+ </table>
55
+ </div>
56
+ </div>
57
+
58
+ <div class="sub">
59
+ <h3>Instance Variables</h3>
60
+ <div class="inset variables">
61
+ <table class="var_table">
62
+ <% @frame.instance_variables.each do |name, value| %>
63
+ <tr><td class="name"><%= name %></td><td><pre><%== inspect_value value %></pre></td></tr>
64
+ <% end %>
65
+ </table>
66
+ </div>
67
+ </div>
68
+
69
+ <!-- <%= Time.now.to_f - @var_start_time %> seconds -->
@@ -1,3 +1,3 @@
1
1
  module BetterErrors
2
- VERSION = "0.0.2"
2
+ VERSION = "0.3.0"
3
3
  end
data/lib/better_errors.rb CHANGED
@@ -1,31 +1,119 @@
1
1
  require "pp"
2
2
  require "erubis"
3
3
  require "coderay"
4
+ require "uri"
4
5
 
5
6
  require "better_errors/version"
6
7
  require "better_errors/error_page"
7
- require "better_errors/error_frame"
8
+ require "better_errors/stack_frame"
8
9
  require "better_errors/middleware"
10
+ require "better_errors/disable_logging_middleware"
11
+ require "better_errors/code_formatter"
12
+ require "better_errors/repl"
9
13
 
10
- class << BetterErrors
11
- attr_accessor :application_root, :binding_of_caller_available
14
+ module BetterErrors
15
+ class << self
16
+ # The path to the root of the application. Better Errors uses this property
17
+ # to determine if a file in a backtrace should be considered an application
18
+ # frame. If you are using Better Errors with Rails, you do not need to set
19
+ # this attribute manually.
20
+ #
21
+ # @return [String]
22
+ attr_accessor :application_root
23
+
24
+ # The logger to use when logging exception details and backtraces. If you
25
+ # are using Better Errors with Rails, you do not need to set this attribute
26
+ # manually. If this attribute is `nil`, nothing will be logged.
27
+ #
28
+ # @return [Logger, nil]
29
+ attr_accessor :logger
30
+
31
+ # @private
32
+ attr_accessor :binding_of_caller_available
33
+
34
+ # @private
35
+ alias_method :binding_of_caller_available?, :binding_of_caller_available
36
+ end
37
+
38
+ # Returns a proc, which when called with a filename and line number argument,
39
+ # returns a URL to open the filename and line in the selected editor.
40
+ #
41
+ # Generates TextMate URLs by default.
42
+ #
43
+ # BetterErrors.editor["/some/file", 123]
44
+ # # => txmt://open?url=file:///some/file&line=123
45
+ #
46
+ # @return [Proc]
47
+ def self.editor
48
+ @editor
49
+ end
12
50
 
13
- alias_method :binding_of_caller_available?, :binding_of_caller_available
51
+ # Configures how Better Errors generates open-in-editor URLs.
52
+ #
53
+ # @overload BetterErrors.editor=(sym)
54
+ # Uses one of the preset editor configurations. Valid symbols are:
55
+ #
56
+ # * `:textmate`, `:txmt`, `:tm`
57
+ # * `:sublime`, `:subl`, `:st`
58
+ # * `:macvim`
59
+ #
60
+ # @param [Symbol] sym
61
+ #
62
+ # @overload BetterErrors.editor=(str)
63
+ # Uses `str` as the format string for generating open-in-editor URLs.
64
+ #
65
+ # Use `%{file}` and `%{line}` as placeholders for the actual values.
66
+ #
67
+ # @example
68
+ # BetterErrors.editor = "my-editor://open?url=%{file}&line=%{line}"
69
+ #
70
+ # @param [String] str
71
+ #
72
+ # @overload BetterErrors.editor=(proc)
73
+ # Uses `proc` to generate open-in-editor URLs. The proc will be called
74
+ # with `file` and `line` parameters when a URL needs to be generated.
75
+ #
76
+ # Your proc should take care to escape `file` appropriately with
77
+ # `URI.encode_www_form_component` (please note that `URI.escape` is **not**
78
+ # a suitable substitute.)
79
+ #
80
+ # @example
81
+ # BetterErrors.editor = proc { |file, line|
82
+ # "my-editor://open?url=#{URI.encode_www_form_component file}&line=#{line}"
83
+ # }
84
+ #
85
+ # @param [Proc] proc
86
+ #
87
+ def self.editor=(editor)
88
+ case editor
89
+ when :textmate, :txmt, :tm
90
+ self.editor = "txmt://open?url=file://%{file}&line=%{line}"
91
+ when :sublime, :subl, :st
92
+ self.editor = "subl://open?url=file://%{file}&line=%{line}"
93
+ when :macvim, :mvim
94
+ self.editor = "mvim://open?url=file://%{file}&line=%{line}"
95
+ when String
96
+ self.editor = proc { |file, line| editor % { file: URI.encode_www_form_component(file), line: line } }
97
+ else
98
+ if editor.respond_to? :call
99
+ @editor = editor
100
+ else
101
+ raise TypeError, "Expected editor to be a valid editor key, a format string or a callable."
102
+ end
103
+ end
104
+ end
105
+
106
+ BetterErrors.editor = :textmate
14
107
  end
15
108
 
16
109
  begin
110
+ $:.unshift "/Users/charlie/code/binding_of_caller/lib"
17
111
  require "binding_of_caller"
18
112
  BetterErrors.binding_of_caller_available = true
19
113
  rescue LoadError => e
20
114
  BetterErrors.binding_of_caller_available = false
21
115
  end
22
116
 
23
- if BetterErrors.binding_of_caller_available?
24
- require "better_errors/core_ext/exception"
25
- else
26
- warn "BetterErrors: binding_of_caller gem unavailable, cannot display local variables on error pages."
27
- warn "Add 'binding_of_caller' to your Gemfile to make this warning go away."
28
- warn ""
29
- end
117
+ require "better_errors/core_ext/exception"
30
118
 
31
119
  require "better_errors/rails" if defined? Rails::Railtie
@@ -0,0 +1,51 @@
1
+ require "spec_helper"
2
+
3
+ module BetterErrors
4
+ describe CodeFormatter do
5
+ let(:filename) { File.expand_path("../support/my_source.rb", __FILE__) }
6
+
7
+ let(:formatter) { CodeFormatter.new(filename, 8) }
8
+
9
+ it "should pick an appropriate scanner" do
10
+ formatter.coderay_scanner.should == :ruby
11
+ end
12
+
13
+ it "should show 5 lines of context" do
14
+ formatter.line_range.should == (3..13)
15
+
16
+ formatter.context_lines.should == [
17
+ "three\n",
18
+ "four\n",
19
+ "five\n",
20
+ "six\n",
21
+ "seven\n",
22
+ "eight\n",
23
+ "nine\n",
24
+ "ten\n",
25
+ "eleven\n",
26
+ "twelve\n",
27
+ "thirteen\n"
28
+ ]
29
+ end
30
+
31
+ it "should highlight the erroring line" do
32
+ formatter.html.should =~ /highlight.*eight/
33
+ end
34
+
35
+ it "should work when the line is right on the edge" do
36
+ formatter = CodeFormatter.new(filename, 20)
37
+ formatter.line_range.should == (15..20)
38
+ formatter.html.should_not == formatter.source_unavailable
39
+ end
40
+
41
+ it "should not barf when the lines don't make any sense" do
42
+ formatter = CodeFormatter.new(filename, 999)
43
+ formatter.html.should == formatter.source_unavailable
44
+ end
45
+
46
+ it "should not barf when the file doesn't exist" do
47
+ formatter = CodeFormatter.new("fkdguhskd7e l", 1)
48
+ formatter.html.should == formatter.source_unavailable
49
+ end
50
+ end
51
+ end
@@ -2,11 +2,21 @@ require "spec_helper"
2
2
 
3
3
  module BetterErrors
4
4
  describe ErrorPage do
5
- let(:exception) { raise ZeroDivisionError, "you divided by zero you silly goose!" rescue $! }
5
+ let!(:exception) { raise ZeroDivisionError, "you divided by zero you silly goose!" rescue $! }
6
6
 
7
- let(:error_page) { ErrorPage.new exception, { "REQUEST_PATH" => "/some/path" } }
7
+ let(:error_page) { ErrorPage.new exception, { "PATH_INFO" => "/some/path" } }
8
8
 
9
9
  let(:response) { error_page.render }
10
+
11
+ let(:empty_binding) {
12
+ local_a = :value_for_local_a
13
+ local_b = :value_for_local_b
14
+
15
+ @inst_c = :value_for_inst_c
16
+ @inst_d = :value_for_inst_d
17
+
18
+ binding
19
+ }
10
20
 
11
21
  it "should include the error message" do
12
22
  response.should include("you divided by zero you silly goose!")
@@ -20,26 +30,31 @@ module BetterErrors
20
30
  response.should include("ZeroDivisionError")
21
31
  end
22
32
 
23
- context "when showing source code" do
24
- before do
25
- exception.stub!(:backtrace).and_return([
26
- "#{File.expand_path("../support/my_source.rb", __FILE__)}:8:in `some_method'"
27
- ])
28
- end
33
+ context "variable inspection" do
34
+ let(:exception) { empty_binding.eval("raise") rescue $! }
29
35
 
30
- it "should show the line where the exception was raised" do
31
- response.should include("8 eight")
36
+ it "should show local variables" do
37
+ html = error_page.do_variables("index" => 0)[:html]
38
+ html.should include("local_a")
39
+ html.should include(":value_for_local_a")
40
+ html.should include("local_b")
41
+ html.should include(":value_for_local_b")
32
42
  end
33
43
 
34
- it "should show five lines of context" do
35
- response.should include("3 three")
36
- response.should include("13 thirteen")
37
- end
38
-
39
- it "should not show more than five lines of context" do
40
- response.should_not include("2 two")
41
- response.should_not include("14 fourteen")
44
+ it "should show instance variables" do
45
+ html = error_page.do_variables("index" => 0)[:html]
46
+ html.should include("inst_c")
47
+ html.should include(":value_for_inst_c")
48
+ html.should include("inst_d")
49
+ html.should include(":value_for_inst_d")
42
50
  end
43
51
  end
52
+
53
+ it "should not die if the source file is not a real filename" do
54
+ exception.stub!(:backtrace).and_return([
55
+ "<internal:prelude>:10:in `spawn_rack_application'"
56
+ ])
57
+ response.should include("Source unavailable")
58
+ end
44
59
  end
45
60
  end
@@ -2,10 +2,30 @@ require "spec_helper"
2
2
 
3
3
  module BetterErrors
4
4
  describe Middleware do
5
+ let(:app) { Middleware.new(->env { ":)" }) }
6
+
5
7
  it "should pass non-error responses through" do
6
- app = Middleware.new(->env { ":)" })
7
8
  app.call({}).should == ":)"
8
9
  end
10
+
11
+ it "should call the internal methods" do
12
+ app.should_receive :internal_call
13
+ app.call("PATH_INFO" => "/__better_errors/1/preform_awesomness")
14
+ end
15
+
16
+ it "should show the error page" do
17
+ app.should_receive :show_error_page
18
+ app.call("PATH_INFO" => "/__better_errors/")
19
+ end
20
+
21
+ context "when requesting the /__better_errors manually" do
22
+ let(:app) { Middleware.new(->env { ":)" }) }
23
+
24
+ it "should show that no errors have been recorded" do
25
+ status, headers, body = app.call("PATH_INFO" => "/__better_errors")
26
+ body.join.should match /No errors have been recorded yet./
27
+ end
28
+ end
9
29
 
10
30
  context "when handling an error" do
11
31
  let(:app) { Middleware.new(->env { raise "oh no :(" }) }
@@ -21,6 +41,14 @@ module BetterErrors
21
41
 
22
42
  headers["Content-Type"].should == "text/html; charset=utf-8"
23
43
  end
44
+
45
+ it "should log the exception" do
46
+ logger = Object.new
47
+ logger.should_receive :fatal
48
+ BetterErrors.stub!(:logger).and_return(logger)
49
+
50
+ app.call({})
51
+ end
24
52
  end
25
53
  end
26
54
  end
@@ -0,0 +1,32 @@
1
+ require "spec_helper"
2
+ require "better_errors/repl/basic"
3
+
4
+ module BetterErrors
5
+ module REPL
6
+ describe Basic do
7
+ let(:fresh_binding) {
8
+ local_a = 123
9
+ binding
10
+ }
11
+
12
+ let(:repl) { Basic.new fresh_binding }
13
+
14
+ it "should evaluate ruby code in a given context" do
15
+ repl.send_input("local_a = 456")
16
+ fresh_binding.eval("local_a").should == 456
17
+ end
18
+
19
+ it "should return a tuple of output and the new prompt" do
20
+ output, prompt = repl.send_input("1 + 2")
21
+ output.should == "=> 3\n"
22
+ prompt.should == ">>"
23
+ end
24
+
25
+ it "should not barf if the code throws an exception" do
26
+ output, prompt = repl.send_input("raise Exception")
27
+ output.should == "!! #<Exception: Exception>\n"
28
+ prompt.should == ">>"
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,121 @@
1
+ require "spec_helper"
2
+
3
+ module BetterErrors
4
+ describe StackFrame do
5
+ context "#application?" do
6
+ it "should be true for application filenames" do
7
+ BetterErrors.stub!(:application_root).and_return("/abc/xyz")
8
+ frame = StackFrame.new("/abc/xyz/app/controllers/crap_controller.rb", 123, "index")
9
+
10
+ frame.application?.should be_true
11
+ end
12
+
13
+ it "should be false for everything else" do
14
+ BetterErrors.stub!(:application_root).and_return("/abc/xyz")
15
+ frame = StackFrame.new("/abc/nope", 123, "foo")
16
+
17
+ frame.application?.should be_false
18
+ end
19
+
20
+ it "should not care if no application_root is set" do
21
+ frame = StackFrame.new("/abc/xyz/app/controllers/crap_controller.rb", 123, "index")
22
+
23
+ frame.application?.should be_false
24
+ end
25
+ end
26
+
27
+ context "#gem?" do
28
+ it "should be true for gem filenames" do
29
+ Gem.stub!(:path).and_return(["/abc/xyz"])
30
+ frame = StackFrame.new("/abc/xyz/gems/whatever-1.2.3/lib/whatever.rb", 123, "foo")
31
+
32
+ frame.gem?.should be_true
33
+ end
34
+
35
+ it "should be false for everything else" do
36
+ Gem.stub!(:path).and_return(["/abc/xyz"])
37
+ frame = StackFrame.new("/abc/nope", 123, "foo")
38
+
39
+ frame.gem?.should be_false
40
+ end
41
+ end
42
+
43
+ context "#application_path" do
44
+ it "should chop off the application root" do
45
+ BetterErrors.stub!(:application_root).and_return("/abc/xyz")
46
+ frame = StackFrame.new("/abc/xyz/app/controllers/crap_controller.rb", 123, "index")
47
+
48
+ frame.application_path.should == "app/controllers/crap_controller.rb"
49
+ end
50
+ end
51
+
52
+ context "#gem_path" do
53
+ it "should chop of the gem path and stick (gem) there" do
54
+ Gem.stub!(:path).and_return(["/abc/xyz"])
55
+ frame = StackFrame.new("/abc/xyz/gems/whatever-1.2.3/lib/whatever.rb", 123, "foo")
56
+
57
+ frame.gem_path.should == "(gem) whatever-1.2.3/lib/whatever.rb"
58
+ end
59
+
60
+ it "should prioritize gem path over application path" do
61
+ BetterErrors.stub!(:application_root).and_return("/abc/xyz")
62
+ Gem.stub!(:path).and_return(["/abc/xyz/vendor"])
63
+ frame = StackFrame.new("/abc/xyz/vendor/gems/whatever-1.2.3/lib/whatever.rb", 123, "foo")
64
+
65
+ frame.gem_path.should == "(gem) whatever-1.2.3/lib/whatever.rb"
66
+ end
67
+ end
68
+
69
+ context "#pretty_path" do
70
+ it "should return #application_path for application paths" do
71
+ BetterErrors.stub!(:application_root).and_return("/abc/xyz")
72
+ frame = StackFrame.new("/abc/xyz/app/controllers/crap_controller.rb", 123, "index")
73
+ frame.pretty_path.should == frame.application_path
74
+ end
75
+
76
+ it "should return #gem_path for gem paths" do
77
+ Gem.stub!(:path).and_return(["/abc/xyz"])
78
+ frame = StackFrame.new("/abc/xyz/gems/whatever-1.2.3/lib/whatever.rb", 123, "foo")
79
+
80
+ frame.pretty_path.should == frame.gem_path
81
+ end
82
+ end
83
+
84
+ it "should special case SyntaxErrors" do
85
+ syntax_error = SyntaxError.new "my_file.rb:123: you wrote bad ruby!"
86
+ syntax_error.stub!(:backtrace).and_return([])
87
+ frames = StackFrame.from_exception(syntax_error)
88
+ frames.count.should == 1
89
+ frames.first.filename.should == "my_file.rb"
90
+ frames.first.line.should == 123
91
+ end
92
+
93
+ it "should not blow up if no method name is given" do
94
+ error = StandardError.new
95
+
96
+ error.stub!(:backtrace).and_return(["foo.rb:123"])
97
+ frames = StackFrame.from_exception(error)
98
+ frames.first.filename.should == "foo.rb"
99
+ frames.first.line.should == 123
100
+
101
+ error.stub!(:backtrace).and_return(["foo.rb:123: this is an error message"])
102
+ frames = StackFrame.from_exception(error)
103
+ frames.first.filename.should == "foo.rb"
104
+ frames.first.line.should == 123
105
+ end
106
+
107
+ it "should ignore a backtrace line if its format doesn't make any sense at all" do
108
+ error = StandardError.new
109
+ error.stub!(:backtrace).and_return(["foo.rb:123:in `foo'", "C:in `find'", "bar.rb:123:in `bar'"])
110
+ frames = StackFrame.from_exception(error)
111
+ frames.count.should == 2
112
+ end
113
+
114
+ it "should not blow up if a filename contains a colon" do
115
+ error = StandardError.new
116
+ error.stub!(:backtrace).and_return(["crap:filename.rb:123"])
117
+ frames = StackFrame.from_exception(error)
118
+ frames.first.filename.should == "crap:filename.rb"
119
+ end
120
+ end
121
+ end
@@ -0,0 +1,13 @@
1
+ require "spec_helper"
2
+
3
+ describe BetterErrors do
4
+ context ".editor" do
5
+ it "should default to textmate" do
6
+ subject.editor["foo.rb", 123].should == "txmt://open?url=file://foo.rb&line=123"
7
+ end
8
+
9
+ it "should url escape the filename" do
10
+ subject.editor["&.rb", 0].should == "txmt://open?url=file://%26.rb&line=0"
11
+ end
12
+ end
13
+ end
data/spec/spec_helper.rb CHANGED
@@ -1,3 +1,6 @@
1
+ require "simplecov"
2
+ SimpleCov.start
3
+
1
4
  $: << File.expand_path("../../lib", __FILE__)
2
5
  require "better_errors"
3
6
  require "ostruct"