better_errors-creditkudos 2.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +8 -0
  3. data/.travis.yml +8 -0
  4. data/.yardopts +1 -0
  5. data/CHANGELOG.md +3 -0
  6. data/Gemfile +11 -0
  7. data/LICENSE.txt +22 -0
  8. data/README.md +128 -0
  9. data/Rakefile +13 -0
  10. data/better_errors-creditkudos.gemspec +28 -0
  11. data/lib/better_errors.rb +152 -0
  12. data/lib/better_errors/code_formatter.rb +63 -0
  13. data/lib/better_errors/code_formatter/html.rb +26 -0
  14. data/lib/better_errors/code_formatter/text.rb +14 -0
  15. data/lib/better_errors/error_page.rb +129 -0
  16. data/lib/better_errors/exception_extension.rb +17 -0
  17. data/lib/better_errors/middleware.rb +141 -0
  18. data/lib/better_errors/rails.rb +28 -0
  19. data/lib/better_errors/raised_exception.rb +68 -0
  20. data/lib/better_errors/repl.rb +30 -0
  21. data/lib/better_errors/repl/basic.rb +20 -0
  22. data/lib/better_errors/repl/pry.rb +78 -0
  23. data/lib/better_errors/stack_frame.rb +111 -0
  24. data/lib/better_errors/templates/main.erb +1032 -0
  25. data/lib/better_errors/templates/text.erb +21 -0
  26. data/lib/better_errors/templates/variable_info.erb +72 -0
  27. data/lib/better_errors/version.rb +3 -0
  28. data/spec/better_errors/code_formatter_spec.rb +92 -0
  29. data/spec/better_errors/error_page_spec.rb +122 -0
  30. data/spec/better_errors/middleware_spec.rb +180 -0
  31. data/spec/better_errors/raised_exception_spec.rb +72 -0
  32. data/spec/better_errors/repl/basic_spec.rb +18 -0
  33. data/spec/better_errors/repl/pry_spec.rb +40 -0
  34. data/spec/better_errors/repl/shared_examples.rb +18 -0
  35. data/spec/better_errors/stack_frame_spec.rb +157 -0
  36. data/spec/better_errors/support/my_source.rb +20 -0
  37. data/spec/better_errors_spec.rb +73 -0
  38. data/spec/spec_helper.rb +5 -0
  39. data/spec/without_binding_of_caller.rb +9 -0
  40. metadata +136 -0
@@ -0,0 +1,21 @@
1
+ <%== text_heading("=", "%s at %s" % [exception_type, request_path]) %>
2
+
3
+ > <%== exception_message %>
4
+ <% if backtrace_frames.any? %>
5
+
6
+ <%== text_heading("-", "%s, line %i" % [first_frame.pretty_path, first_frame.line]) %>
7
+
8
+ ``` ruby
9
+ <%== text_formatted_code_block(first_frame) %>```
10
+
11
+ App backtrace
12
+ -------------
13
+
14
+ <%== application_frames.map { |s| " - #{s}" }.join("\n") %>
15
+
16
+ Full backtrace
17
+ --------------
18
+
19
+ <%== backtrace_frames.map { |s| " - #{s}" }.join("\n") %>
20
+
21
+ <% end %>
@@ -0,0 +1,72 @@
1
+ <header class="trace_info clearfix">
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
+ <div class="code_block clearfix">
7
+ <%== html_formatted_code_block @frame %>
8
+ </div>
9
+
10
+ <% if BetterErrors.binding_of_caller_available? && @frame.frame_binding %>
11
+ <div class="repl">
12
+ <div class="console">
13
+ <pre></pre>
14
+ <div class="prompt"><span>&gt;&gt;</span> <input/></div>
15
+ </div>
16
+ </div>
17
+ <% end %>
18
+ </header>
19
+
20
+ <% if BetterErrors.binding_of_caller_available? && @frame.frame_binding %>
21
+ <div class="hint">
22
+ This is a live shell. Type in here.
23
+ </div>
24
+
25
+ <div class="variable_info"></div>
26
+ <% end %>
27
+
28
+ <% unless BetterErrors.binding_of_caller_available? %>
29
+ <div class="hint">
30
+ <strong>Tip:</strong> add <code>gem "binding_of_caller"</code> to your Gemfile to enable the REPL and local/instance variable inspection.
31
+ </div>
32
+ <% end %>
33
+
34
+ <div class="sub">
35
+ <h3>Request info</h3>
36
+ <div class='inset variables'>
37
+ <table class="var_table">
38
+ <% if rails_params %>
39
+ <tr><td class="name">Request parameters</td><td><pre><%== inspect_value rails_params %></pre></td></tr>
40
+ <% end %>
41
+ <% if rack_session %>
42
+ <tr><td class="name">Rack session</td><td><pre><%== inspect_value rack_session %></pre></td></tr>
43
+ <% end %>
44
+ </table>
45
+ </div>
46
+ </div>
47
+
48
+ <% if BetterErrors.binding_of_caller_available? && @frame.frame_binding %>
49
+ <div class="sub">
50
+ <h3>Local Variables</h3>
51
+ <div class='inset variables'>
52
+ <table class="var_table">
53
+ <% @frame.local_variables.each do |name, value| %>
54
+ <tr><td class="name"><%= name %></td><td><pre><%== inspect_value value %></pre></td></tr>
55
+ <% end %>
56
+ </table>
57
+ </div>
58
+ </div>
59
+
60
+ <div class="sub">
61
+ <h3>Instance Variables</h3>
62
+ <div class="inset variables">
63
+ <table class="var_table">
64
+ <% @frame.instance_variables.each do |name, value| %>
65
+ <tr><td class="name"><%= name %></td><td><pre><%== inspect_value value %></pre></td></tr>
66
+ <% end %>
67
+ </table>
68
+ </div>
69
+ </div>
70
+
71
+ <!-- <%= Time.now.to_f - @var_start_time %> seconds -->
72
+ <% end %>
@@ -0,0 +1,3 @@
1
+ module BetterErrors
2
+ VERSION = "2.1.1"
3
+ end
@@ -0,0 +1,92 @@
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 "picks an appropriate scanner" do
10
+ formatter.coderay_scanner.should == :ruby
11
+ end
12
+
13
+ it "shows 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 "works when the line is right on the edge" do
32
+ formatter = CodeFormatter.new(filename, 20)
33
+ formatter.line_range.should == (15..20)
34
+ end
35
+
36
+ describe CodeFormatter::HTML do
37
+ it "highlights the erroring line" do
38
+ formatter = CodeFormatter::HTML.new(filename, 8)
39
+ formatter.output.should =~ /highlight.*eight/
40
+ end
41
+
42
+ it "works when the line is right on the edge" do
43
+ formatter = CodeFormatter::HTML.new(filename, 20)
44
+ formatter.output.should_not == formatter.source_unavailable
45
+ end
46
+
47
+ it "doesn't barf when the lines don't make any sense" do
48
+ formatter = CodeFormatter::HTML.new(filename, 999)
49
+ formatter.output.should == formatter.source_unavailable
50
+ end
51
+
52
+ it "doesn't barf when the file doesn't exist" do
53
+ formatter = CodeFormatter::HTML.new("fkdguhskd7e l", 1)
54
+ formatter.output.should == formatter.source_unavailable
55
+ end
56
+ end
57
+
58
+ describe CodeFormatter::Text do
59
+ it "highlights the erroring line" do
60
+ formatter = CodeFormatter::Text.new(filename, 8)
61
+ formatter.output.should == <<-TEXT.gsub(/^ /, "")
62
+ 3 three
63
+ 4 four
64
+ 5 five
65
+ 6 six
66
+ 7 seven
67
+ > 8 eight
68
+ 9 nine
69
+ 10 ten
70
+ 11 eleven
71
+ 12 twelve
72
+ 13 thirteen
73
+ TEXT
74
+ end
75
+
76
+ it "works when the line is right on the edge" do
77
+ formatter = CodeFormatter::Text.new(filename, 20)
78
+ formatter.output.should_not == formatter.source_unavailable
79
+ end
80
+
81
+ it "doesn't barf when the lines don't make any sense" do
82
+ formatter = CodeFormatter::Text.new(filename, 999)
83
+ formatter.output.should == formatter.source_unavailable
84
+ end
85
+
86
+ it "doesn't barf when the file doesn't exist" do
87
+ formatter = CodeFormatter::Text.new("fkdguhskd7e l", 1)
88
+ formatter.output.should == formatter.source_unavailable
89
+ end
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,122 @@
1
+ require "spec_helper"
2
+
3
+ module BetterErrors
4
+ describe ErrorPage do
5
+ let!(:exception) { raise ZeroDivisionError, "you divided by zero you silly goose!" rescue $! }
6
+
7
+ let(:error_page) { ErrorPage.new exception, { "PATH_INFO" => "/some/path" } }
8
+
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
+ }
20
+
21
+ it "includes the error message" do
22
+ response.should include("you divided by zero you silly goose!")
23
+ end
24
+
25
+ it "includes the request path" do
26
+ response.should include("/some/path")
27
+ end
28
+
29
+ it "includes the exception class" do
30
+ response.should include("ZeroDivisionError")
31
+ end
32
+
33
+ context "variable inspection" do
34
+ let(:exception) { empty_binding.eval("raise") rescue $! }
35
+
36
+ if BetterErrors.binding_of_caller_available?
37
+ it "shows local variables" do
38
+ html = error_page.do_variables("index" => 0)[:html]
39
+ html.should include("local_a")
40
+ html.should include(":value_for_local_a")
41
+ html.should include("local_b")
42
+ html.should include(":value_for_local_b")
43
+ end
44
+ else
45
+ it "tells the user to add binding_of_caller to their gemfile to get fancy features" do
46
+ html = error_page.do_variables("index" => 0)[:html]
47
+ html.should include(%{gem "binding_of_caller"})
48
+ end
49
+ end
50
+
51
+ it "shows instance variables" do
52
+ html = error_page.do_variables("index" => 0)[:html]
53
+ html.should include("inst_c")
54
+ html.should include(":value_for_inst_c")
55
+ html.should include("inst_d")
56
+ html.should include(":value_for_inst_d")
57
+ end
58
+
59
+ it "shows filter instance variables" do
60
+ BetterErrors.stub(:ignored_instance_variables).and_return([ :@inst_d ])
61
+ html = error_page.do_variables("index" => 0)[:html]
62
+ html.should include("inst_c")
63
+ html.should include(":value_for_inst_c")
64
+ html.should_not include('<td class="name">@inst_d</td>')
65
+ html.should_not include("<pre>:value_for_inst_d</pre>")
66
+ end
67
+ end
68
+
69
+ it "doesn't die if the source file is not a real filename" do
70
+ exception.stub(:backtrace).and_return([
71
+ "<internal:prelude>:10:in `spawn_rack_application'"
72
+ ])
73
+ response.should include("Source unavailable")
74
+ end
75
+
76
+ context 'with an exception with blank lines' do
77
+ class SpacedError < StandardError
78
+ def initialize(message = nil)
79
+ message = "\n\n#{message}" if message
80
+ super
81
+ end
82
+ end
83
+
84
+ let!(:exception) { raise SpacedError, "Danger Warning!" rescue $! }
85
+
86
+ it 'should not include leading blank lines from exception_message' do
87
+ exception.message.should =~ /\A\n\n/
88
+ error_page.exception_message.should_not =~ /\A\n\n/
89
+ end
90
+ end
91
+
92
+ context 'with an inspect size limit set' do
93
+ before { BetterErrors.maximum_variable_inspect_size = 50_000 }
94
+
95
+ it "shows variables with inspects that are below the inspect size threshold" do
96
+ content = 'AAAAA'
97
+ empty_binding.instance_variable_set('@small', content)
98
+
99
+ html = error_page.do_variables("index" => 0)[:html]
100
+ html.should_not include "object too large"
101
+ end
102
+
103
+
104
+ it "hides variables with inspects that are above the inspect size threshold" do
105
+ content = 'A' * (BetterErrors.maximum_variable_inspect_size)
106
+ empty_binding.instance_variable_set('@big', content)
107
+
108
+ html = error_page.do_variables("index" => 0)[:html]
109
+ html.should include "object too large"
110
+ end
111
+
112
+ it "shows variables with large inspects if max inspect size is disabled" do
113
+ content = 'A' * (BetterErrors.maximum_variable_inspect_size)
114
+ BetterErrors.maximum_variable_inspect_size = nil
115
+ empty_binding.instance_variable_set('@big', content)
116
+
117
+ html = error_page.do_variables("index" => 0)[:html]
118
+ html.should_not include "object too large"
119
+ end
120
+ end
121
+ end
122
+ end
@@ -0,0 +1,180 @@
1
+ require "spec_helper"
2
+
3
+ module BetterErrors
4
+ describe Middleware do
5
+ let(:app) { Middleware.new(->env { ":)" }) }
6
+ let(:exception) { RuntimeError.new("oh no :(") }
7
+
8
+ it "passes non-error responses through" do
9
+ app.call({}).should == ":)"
10
+ end
11
+
12
+ it "calls the internal methods" do
13
+ app.should_receive :internal_call
14
+ app.call("PATH_INFO" => "/__better_errors/1/preform_awesomness")
15
+ end
16
+
17
+ it "calls the internal methods on any subfolder path" do
18
+ app.should_receive :internal_call
19
+ app.call("PATH_INFO" => "/any_sub/folder/path/__better_errors/1/preform_awesomness")
20
+ end
21
+
22
+ it "shows the error page" do
23
+ app.should_receive :show_error_page
24
+ app.call("PATH_INFO" => "/__better_errors/")
25
+ end
26
+
27
+ it "shows the error page on any subfolder path" do
28
+ app.should_receive :show_error_page
29
+ app.call("PATH_INFO" => "/any_sub/folder/path/__better_errors/")
30
+ end
31
+
32
+ it "doesn't show the error page to a non-local address" do
33
+ app.should_not_receive :better_errors_call
34
+ app.call("REMOTE_ADDR" => "1.2.3.4")
35
+ end
36
+
37
+ it "shows to a whitelisted IP" do
38
+ BetterErrors::Middleware.allow_ip! '77.55.33.11'
39
+ app.should_receive :better_errors_call
40
+ app.call("REMOTE_ADDR" => "77.55.33.11")
41
+ end
42
+
43
+ it "respects the X-Forwarded-For header" do
44
+ app.should_not_receive :better_errors_call
45
+ app.call(
46
+ "REMOTE_ADDR" => "127.0.0.1",
47
+ "HTTP_X_FORWARDED_FOR" => "1.2.3.4",
48
+ )
49
+ end
50
+
51
+ it "doesn't blow up when given a blank REMOTE_ADDR" do
52
+ expect { app.call("REMOTE_ADDR" => " ") }.to_not raise_error
53
+ end
54
+
55
+ it "doesn't blow up when given an IP address with a zone index" do
56
+ expect { app.call("REMOTE_ADDR" => "0:0:0:0:0:0:0:1%0" ) }.to_not raise_error
57
+ end
58
+
59
+ context "when requesting the /__better_errors manually" do
60
+ let(:app) { Middleware.new(->env { ":)" }) }
61
+
62
+ it "shows that no errors have been recorded" do
63
+ status, headers, body = app.call("PATH_INFO" => "/__better_errors")
64
+ body.join.should match /No errors have been recorded yet./
65
+ end
66
+
67
+ it "shows that no errors have been recorded on any subfolder path" do
68
+ status, headers, body = app.call("PATH_INFO" => "/any_sub/folder/path/__better_errors")
69
+ body.join.should match /No errors have been recorded yet./
70
+ end
71
+ end
72
+
73
+ context "when handling an error" do
74
+ let(:app) { Middleware.new(->env { raise exception }) }
75
+
76
+ it "returns status 500" do
77
+ status, headers, body = app.call({})
78
+
79
+ status.should == 500
80
+ end
81
+
82
+ if Exception.new.respond_to?(:cause)
83
+ context "cause" do
84
+ class OtherException < Exception
85
+ def initialize(message)
86
+ super(message)
87
+ end
88
+ end
89
+
90
+ it "shows Original Exception if it responds_to and has an cause" do
91
+ app = Middleware.new(->env {
92
+ begin
93
+ raise "Original Exception"
94
+ rescue
95
+ raise OtherException.new("Other Exception")
96
+ end
97
+ })
98
+
99
+ status, _, body = app.call({})
100
+
101
+ status.should == 500
102
+ body.join.should_not match(/\n> Other Exception\n/)
103
+ body.join.should match(/\n> Original Exception\n/)
104
+ end
105
+ end
106
+ else
107
+ context "original_exception" do
108
+ class OriginalExceptionException < Exception
109
+ attr_reader :original_exception
110
+
111
+ def initialize(message, original_exception = nil)
112
+ super(message)
113
+ @original_exception = original_exception
114
+ end
115
+ end
116
+
117
+ it "shows Original Exception if it responds_to and has an original_exception" do
118
+ app = Middleware.new(->env {
119
+ raise OriginalExceptionException.new("Other Exception", Exception.new("Original Exception"))
120
+ })
121
+
122
+ status, _, body = app.call({})
123
+
124
+ status.should == 500
125
+ body.join.should_not match(/Other Exception/)
126
+ body.join.should match(/Original Exception/)
127
+ end
128
+
129
+ it "won't crash if the exception responds_to but doesn't have an original_exception" do
130
+ app = Middleware.new(->env {
131
+ raise OriginalExceptionException.new("Other Exception")
132
+ })
133
+
134
+ status, _, body = app.call({})
135
+
136
+ status.should == 500
137
+ body.join.should match(/Other Exception/)
138
+ end
139
+ end
140
+ end
141
+
142
+ it "returns ExceptionWrapper's status_code" do
143
+ ad_ew = double("ActionDispatch::ExceptionWrapper")
144
+ ad_ew.stub('new').with({}, exception ){ double("ExceptionWrapper", status_code: 404) }
145
+ stub_const('ActionDispatch::ExceptionWrapper', ad_ew)
146
+
147
+ status, headers, body = app.call({})
148
+
149
+ status.should == 404
150
+ end
151
+
152
+ it "returns UTF-8 error pages" do
153
+ status, headers, body = app.call({})
154
+
155
+ headers["Content-Type"].should match /charset=utf-8/
156
+ end
157
+
158
+ it "returns text pages by default" do
159
+ status, headers, body = app.call({})
160
+
161
+ headers["Content-Type"].should match /text\/plain/
162
+ end
163
+
164
+ it "returns HTML pages by default" do
165
+ # Chrome's 'Accept' header looks similar this.
166
+ status, headers, body = app.call("HTTP_ACCEPT" => "text/html,application/xhtml+xml;q=0.9,*/*")
167
+
168
+ headers["Content-Type"].should match /text\/html/
169
+ end
170
+
171
+ it "logs the exception" do
172
+ logger = Object.new
173
+ logger.should_receive :fatal
174
+ BetterErrors.stub(:logger).and_return(logger)
175
+
176
+ app.call({})
177
+ end
178
+ end
179
+ end
180
+ end