better_errors-creditkudos 2.1.1

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.
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