better_errors 0.9.0 → 1.0.0.rc1
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of better_errors might be problematic. Click here for more details.
- checksums.yaml +14 -6
- data/.travis.yml +1 -0
- data/Gemfile +8 -0
- data/LICENSE.txt +2 -2
- data/README.md +7 -16
- data/better_errors.gemspec +0 -8
- data/lib/better_errors.rb +45 -31
- data/lib/better_errors/code_formatter.rb +2 -2
- data/lib/better_errors/code_formatter/html.rb +11 -4
- data/lib/better_errors/code_formatter/text.rb +2 -2
- data/lib/better_errors/error_page.rb +2 -1
- data/lib/better_errors/middleware.rb +10 -5
- data/lib/better_errors/repl.rb +2 -2
- data/lib/better_errors/repl/basic.rb +1 -1
- data/lib/better_errors/repl/pry.rb +11 -6
- data/lib/better_errors/stack_frame.rb +41 -7
- data/lib/better_errors/templates/main.erb +37 -5
- data/lib/better_errors/templates/variable_info.erb +4 -3
- data/lib/better_errors/version.rb +1 -1
- data/spec/better_errors/code_formatter_spec.rb +11 -11
- data/spec/better_errors/error_page_spec.rb +20 -13
- data/spec/better_errors/middleware_spec.rb +35 -16
- data/spec/better_errors/repl/basic_spec.rb +1 -1
- data/spec/better_errors/repl/pry_spec.rb +9 -6
- data/spec/better_errors/repl/shared_examples.rb +16 -20
- data/spec/better_errors/stack_frame_spec.rb +71 -30
- data/spec/better_errors_spec.rb +63 -3
- data/spec/spec_helper.rb +1 -0
- metadata +10 -110
- data/CONTRIBUTING.md +0 -9
- data/TIPS_AND_TRICKS.md +0 -39
@@ -41,6 +41,14 @@
|
|
41
41
|
background: #f0f0f5;
|
42
42
|
}
|
43
43
|
|
44
|
+
.clearfix::after{
|
45
|
+
clear: both;
|
46
|
+
content: ".";
|
47
|
+
display: block;
|
48
|
+
height: 0;
|
49
|
+
visibility: hidden;
|
50
|
+
}
|
51
|
+
|
44
52
|
/* ---------------------------------------------------------------------
|
45
53
|
* Basic layout
|
46
54
|
* --------------------------------------------------------------------- */
|
@@ -379,7 +387,7 @@
|
|
379
387
|
* Monospace
|
380
388
|
* --------------------------------------------------------------------- */
|
381
389
|
|
382
|
-
pre, code, .repl input, .repl .prompt span, textarea {
|
390
|
+
pre, code, .repl input, .repl .prompt span, textarea, .code_linenums {
|
383
391
|
font-family: menlo, lucida console, monospace;
|
384
392
|
font-size: 8pt;
|
385
393
|
}
|
@@ -396,6 +404,11 @@
|
|
396
404
|
box-shadow: 0 0 10px rgba(0, 0, 0, 0.03), 1px 1px 0 rgba(0, 0, 0, 0.05), -1px 1px 0 rgba(0, 0, 0, 0.05), 0 0 0 4px rgba(0, 0, 0, 0.04);
|
397
405
|
}
|
398
406
|
|
407
|
+
.code_block{
|
408
|
+
background: #f1f1f1;
|
409
|
+
border-left: 1px solid #ccc;
|
410
|
+
}
|
411
|
+
|
399
412
|
/* Titlebar */
|
400
413
|
.trace_info .title {
|
401
414
|
background: #f1f1f1;
|
@@ -447,15 +460,30 @@
|
|
447
460
|
box-shadow: inset 3px 3px 3px rgba(0, 0, 0, 0.1), inset 0 0 0 1px rgba(0, 0, 0, 0.1);
|
448
461
|
}
|
449
462
|
|
450
|
-
.
|
451
|
-
|
463
|
+
.code_linenums{
|
464
|
+
background:#f1f1f1;
|
465
|
+
padding-top:10px;
|
466
|
+
padding-bottom:9px;
|
467
|
+
float:left;
|
468
|
+
}
|
469
|
+
|
470
|
+
.code_linenums span{
|
471
|
+
display:block;
|
472
|
+
padding:0 12px;
|
452
473
|
}
|
453
474
|
|
454
475
|
.code {
|
476
|
+
margin-bottom: -1px;
|
477
|
+
border-top-left-radius:2px;
|
455
478
|
padding: 10px 0;
|
456
479
|
overflow: auto;
|
457
480
|
}
|
458
481
|
|
482
|
+
.code pre{
|
483
|
+
padding-left:12px;
|
484
|
+
min-height:16px;
|
485
|
+
}
|
486
|
+
|
459
487
|
/* Source unavailable */
|
460
488
|
p.unavailable {
|
461
489
|
padding: 20px 0 40px 0;
|
@@ -489,7 +517,7 @@
|
|
489
517
|
100% { background: rgba(220, 30, 30, 0.1); }
|
490
518
|
}
|
491
519
|
|
492
|
-
.code .highlight {
|
520
|
+
.code .highlight, .code_linenums .highlight {
|
493
521
|
background: rgba(220, 30, 30, 0.1);
|
494
522
|
-webkit-animation: highlight 400ms linear 1;
|
495
523
|
-moz-animation: highlight 400ms linear 1;
|
@@ -773,7 +801,10 @@
|
|
773
801
|
this.inputElement = this.container.querySelector("input");
|
774
802
|
this.outputElement = this.container.querySelector("pre");
|
775
803
|
|
776
|
-
|
804
|
+
var self = this;
|
805
|
+
this.inputElement.onkeydown = function(ev) {
|
806
|
+
self.onKeyDown(ev);
|
807
|
+
};
|
777
808
|
|
778
809
|
this.setPrompt(">>");
|
779
810
|
|
@@ -821,6 +852,7 @@
|
|
821
852
|
self.writeRawOutput(response.highlighted_input + "\n");
|
822
853
|
self.writeOutput(response.result);
|
823
854
|
self.setPrompt(response.prompt);
|
855
|
+
self.setInput(response.prefilled_input);
|
824
856
|
});
|
825
857
|
};
|
826
858
|
|
@@ -1,10 +1,11 @@
|
|
1
|
-
<header class="trace_info">
|
1
|
+
<header class="trace_info clearfix">
|
2
2
|
<div class="title">
|
3
3
|
<h2 class="name"><%= @frame.name %></h2>
|
4
4
|
<div class="location"><span class="filename"><a href="<%= editor_url(@frame) %>"><%= @frame.pretty_path %></a></span></div>
|
5
5
|
</div>
|
6
|
-
|
7
|
-
|
6
|
+
<div class="code_block clearfix">
|
7
|
+
<%== html_formatted_code_block @frame %>
|
8
|
+
</div>
|
8
9
|
|
9
10
|
<% if BetterErrors.binding_of_caller_available? && @frame.frame_binding %>
|
10
11
|
<div class="repl">
|
@@ -6,11 +6,11 @@ module BetterErrors
|
|
6
6
|
|
7
7
|
let(:formatter) { CodeFormatter.new(filename, 8) }
|
8
8
|
|
9
|
-
it "
|
9
|
+
it "picks an appropriate scanner" do
|
10
10
|
formatter.coderay_scanner.should == :ruby
|
11
11
|
end
|
12
12
|
|
13
|
-
it "
|
13
|
+
it "shows 5 lines of context" do
|
14
14
|
formatter.line_range.should == (3..13)
|
15
15
|
|
16
16
|
formatter.context_lines.should == [
|
@@ -28,35 +28,35 @@ module BetterErrors
|
|
28
28
|
]
|
29
29
|
end
|
30
30
|
|
31
|
-
it "
|
31
|
+
it "works when the line is right on the edge" do
|
32
32
|
formatter = CodeFormatter.new(filename, 20)
|
33
33
|
formatter.line_range.should == (15..20)
|
34
34
|
end
|
35
35
|
|
36
36
|
describe CodeFormatter::HTML do
|
37
|
-
it "
|
37
|
+
it "highlights the erroring line" do
|
38
38
|
formatter = CodeFormatter::HTML.new(filename, 8)
|
39
39
|
formatter.output.should =~ /highlight.*eight/
|
40
40
|
end
|
41
41
|
|
42
|
-
it "
|
42
|
+
it "works when the line is right on the edge" do
|
43
43
|
formatter = CodeFormatter::HTML.new(filename, 20)
|
44
44
|
formatter.output.should_not == formatter.source_unavailable
|
45
45
|
end
|
46
46
|
|
47
|
-
it "
|
47
|
+
it "doesn't barf when the lines don't make any sense" do
|
48
48
|
formatter = CodeFormatter::HTML.new(filename, 999)
|
49
49
|
formatter.output.should == formatter.source_unavailable
|
50
50
|
end
|
51
51
|
|
52
|
-
it "
|
52
|
+
it "doesn't barf when the file doesn't exist" do
|
53
53
|
formatter = CodeFormatter::HTML.new("fkdguhskd7e l", 1)
|
54
54
|
formatter.output.should == formatter.source_unavailable
|
55
55
|
end
|
56
56
|
end
|
57
57
|
|
58
58
|
describe CodeFormatter::Text do
|
59
|
-
it "
|
59
|
+
it "highlights the erroring line" do
|
60
60
|
formatter = CodeFormatter::Text.new(filename, 8)
|
61
61
|
formatter.output.should == <<-TEXT.gsub(/^ /, "")
|
62
62
|
3 three
|
@@ -73,17 +73,17 @@ module BetterErrors
|
|
73
73
|
TEXT
|
74
74
|
end
|
75
75
|
|
76
|
-
it "
|
76
|
+
it "works when the line is right on the edge" do
|
77
77
|
formatter = CodeFormatter::Text.new(filename, 20)
|
78
78
|
formatter.output.should_not == formatter.source_unavailable
|
79
79
|
end
|
80
80
|
|
81
|
-
it "
|
81
|
+
it "doesn't barf when the lines don't make any sense" do
|
82
82
|
formatter = CodeFormatter::Text.new(filename, 999)
|
83
83
|
formatter.output.should == formatter.source_unavailable
|
84
84
|
end
|
85
85
|
|
86
|
-
it "
|
86
|
+
it "doesn't barf when the file doesn't exist" do
|
87
87
|
formatter = CodeFormatter::Text.new("fkdguhskd7e l", 1)
|
88
88
|
formatter.output.should == formatter.source_unavailable
|
89
89
|
end
|
@@ -18,30 +18,37 @@ module BetterErrors
|
|
18
18
|
binding
|
19
19
|
}
|
20
20
|
|
21
|
-
it "
|
21
|
+
it "includes the error message" do
|
22
22
|
response.should include("you divided by zero you silly goose!")
|
23
23
|
end
|
24
24
|
|
25
|
-
it "
|
25
|
+
it "includes the request path" do
|
26
26
|
response.should include("/some/path")
|
27
27
|
end
|
28
28
|
|
29
|
-
it "
|
29
|
+
it "includes the exception class" do
|
30
30
|
response.should include("ZeroDivisionError")
|
31
31
|
end
|
32
32
|
|
33
33
|
context "variable inspection" do
|
34
34
|
let(:exception) { empty_binding.eval("raise") rescue $! }
|
35
35
|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
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
|
42
49
|
end
|
43
50
|
|
44
|
-
it "
|
51
|
+
it "shows instance variables" do
|
45
52
|
html = error_page.do_variables("index" => 0)[:html]
|
46
53
|
html.should include("inst_c")
|
47
54
|
html.should include(":value_for_inst_c")
|
@@ -49,7 +56,7 @@ module BetterErrors
|
|
49
56
|
html.should include(":value_for_inst_d")
|
50
57
|
end
|
51
58
|
|
52
|
-
it "
|
59
|
+
it "shows filter instance variables" do
|
53
60
|
BetterErrors.stub(:ignored_instance_variables).and_return([ :@inst_d ])
|
54
61
|
html = error_page.do_variables("index" => 0)[:html]
|
55
62
|
html.should include("inst_c")
|
@@ -59,8 +66,8 @@ module BetterErrors
|
|
59
66
|
end
|
60
67
|
end
|
61
68
|
|
62
|
-
it "
|
63
|
-
exception.stub
|
69
|
+
it "doesn't die if the source file is not a real filename" do
|
70
|
+
exception.stub(:backtrace).and_return([
|
64
71
|
"<internal:prelude>:10:in `spawn_rack_application'"
|
65
72
|
])
|
66
73
|
response.should include("Source unavailable")
|
@@ -3,88 +3,107 @@ require "spec_helper"
|
|
3
3
|
module BetterErrors
|
4
4
|
describe Middleware do
|
5
5
|
let(:app) { Middleware.new(->env { ":)" }) }
|
6
|
+
let(:exception) { RuntimeError.new("oh no :(") }
|
6
7
|
|
7
|
-
it "
|
8
|
+
it "passes non-error responses through" do
|
8
9
|
app.call({}).should == ":)"
|
9
10
|
end
|
10
11
|
|
11
|
-
it "
|
12
|
+
it "calls the internal methods" do
|
12
13
|
app.should_receive :internal_call
|
13
14
|
app.call("PATH_INFO" => "/__better_errors/1/preform_awesomness")
|
14
15
|
end
|
15
16
|
|
16
|
-
it "
|
17
|
+
it "calls the internal methods on any subfolder path" do
|
17
18
|
app.should_receive :internal_call
|
18
19
|
app.call("PATH_INFO" => "/any_sub/folder/path/__better_errors/1/preform_awesomness")
|
19
20
|
end
|
20
21
|
|
21
|
-
it "
|
22
|
+
it "shows the error page" do
|
22
23
|
app.should_receive :show_error_page
|
23
24
|
app.call("PATH_INFO" => "/__better_errors/")
|
24
25
|
end
|
25
26
|
|
26
|
-
it "
|
27
|
+
it "shows the error page on any subfolder path" do
|
27
28
|
app.should_receive :show_error_page
|
28
29
|
app.call("PATH_INFO" => "/any_sub/folder/path/__better_errors/")
|
29
30
|
end
|
30
31
|
|
31
|
-
it "
|
32
|
+
it "doesn't show the error page to a non-local address" do
|
32
33
|
app.should_not_receive :better_errors_call
|
33
34
|
app.call("REMOTE_ADDR" => "1.2.3.4")
|
34
35
|
end
|
35
36
|
|
36
|
-
it "
|
37
|
+
it "shows to a whitelisted IP" do
|
37
38
|
BetterErrors::Middleware.allow_ip! '77.55.33.11'
|
38
39
|
app.should_receive :better_errors_call
|
39
40
|
app.call("REMOTE_ADDR" => "77.55.33.11")
|
40
41
|
end
|
41
42
|
|
43
|
+
it "doesn't blow up when given a blank REMOTE_ADDR" do
|
44
|
+
expect { app.call("REMOTE_ADDR" => " ") }.to_not raise_error
|
45
|
+
end
|
46
|
+
|
47
|
+
it "doesn't blow up when given an IP address with a zone index" do
|
48
|
+
expect { app.call("REMOTE_ADDR" => "0:0:0:0:0:0:0:1%0" ) }.to_not raise_error
|
49
|
+
end
|
50
|
+
|
42
51
|
context "when requesting the /__better_errors manually" do
|
43
52
|
let(:app) { Middleware.new(->env { ":)" }) }
|
44
53
|
|
45
|
-
it "
|
54
|
+
it "shows that no errors have been recorded" do
|
46
55
|
status, headers, body = app.call("PATH_INFO" => "/__better_errors")
|
47
56
|
body.join.should match /No errors have been recorded yet./
|
48
57
|
end
|
49
58
|
|
50
|
-
it "
|
59
|
+
it "shows that no errors have been recorded on any subfolder path" do
|
51
60
|
status, headers, body = app.call("PATH_INFO" => "/any_sub/folder/path/__better_errors")
|
52
61
|
body.join.should match /No errors have been recorded yet./
|
53
62
|
end
|
54
63
|
end
|
55
64
|
|
56
65
|
context "when handling an error" do
|
57
|
-
let(:app) { Middleware.new(->env { raise
|
66
|
+
let(:app) { Middleware.new(->env { raise exception }) }
|
58
67
|
|
59
|
-
it "
|
68
|
+
it "returns status 500" do
|
60
69
|
status, headers, body = app.call({})
|
61
70
|
|
62
71
|
status.should == 500
|
63
72
|
end
|
64
73
|
|
65
|
-
it "
|
74
|
+
it "returns ExceptionWrapper's status_code" do
|
75
|
+
ad_ew = double("ActionDispatch::ExceptionWrapper")
|
76
|
+
ad_ew.stub('new').with({}, exception ){ double("ExceptionWrapper", status_code: 404) }
|
77
|
+
stub_const('ActionDispatch::ExceptionWrapper', ad_ew)
|
78
|
+
|
79
|
+
status, headers, body = app.call({})
|
80
|
+
|
81
|
+
status.should == 404
|
82
|
+
end
|
83
|
+
|
84
|
+
it "returns UTF-8 error pages" do
|
66
85
|
status, headers, body = app.call({})
|
67
86
|
|
68
87
|
headers["Content-Type"].should match /charset=utf-8/
|
69
88
|
end
|
70
89
|
|
71
|
-
it "
|
90
|
+
it "returns text pages by default" do
|
72
91
|
status, headers, body = app.call({})
|
73
92
|
|
74
93
|
headers["Content-Type"].should match /text\/plain/
|
75
94
|
end
|
76
95
|
|
77
|
-
it "
|
96
|
+
it "returns HTML pages by default" do
|
78
97
|
# Chrome's 'Accept' header looks similar this.
|
79
98
|
status, headers, body = app.call("HTTP_ACCEPT" => "text/html,application/xhtml+xml;q=0.9,*/*")
|
80
99
|
|
81
100
|
headers["Content-Type"].should match /text\/html/
|
82
101
|
end
|
83
102
|
|
84
|
-
it "
|
103
|
+
it "logs the exception" do
|
85
104
|
logger = Object.new
|
86
105
|
logger.should_receive :fatal
|
87
|
-
BetterErrors.stub
|
106
|
+
BetterErrors.stub(:logger).and_return(logger)
|
88
107
|
|
89
108
|
app.call({})
|
90
109
|
end
|
@@ -13,21 +13,24 @@ module BetterErrors
|
|
13
13
|
|
14
14
|
let(:repl) { Pry.new fresh_binding }
|
15
15
|
|
16
|
-
|
17
|
-
|
18
|
-
it "should do line continuation" do
|
19
|
-
output, prompt = repl.send_input ""
|
16
|
+
it "does line continuation" do
|
17
|
+
output, prompt, filled = repl.send_input ""
|
20
18
|
output.should == "=> nil\n"
|
21
19
|
prompt.should == ">>"
|
20
|
+
filled.should == ""
|
22
21
|
|
23
|
-
output, prompt = repl.send_input "def f(x)"
|
22
|
+
output, prompt, filled = repl.send_input "def f(x)"
|
24
23
|
output.should == ""
|
25
24
|
prompt.should == ".."
|
25
|
+
filled.should == " "
|
26
26
|
|
27
|
-
output, prompt = repl.send_input "end"
|
27
|
+
output, prompt, filled = repl.send_input "end"
|
28
28
|
output.should == "=> nil\n"
|
29
29
|
prompt.should == ">>"
|
30
|
+
filled.should == ""
|
30
31
|
end
|
32
|
+
|
33
|
+
it_behaves_like "a REPL provider"
|
31
34
|
end
|
32
35
|
end
|
33
36
|
end
|
@@ -1,22 +1,18 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
output.should include "Exception: Exception"
|
18
|
-
prompt.should == ">>"
|
19
|
-
end
|
20
|
-
end
|
1
|
+
shared_examples_for "a REPL provider" do
|
2
|
+
it "evaluates ruby code in a given context" do
|
3
|
+
repl.send_input("local_a = 456")
|
4
|
+
fresh_binding.eval("local_a").should == 456
|
5
|
+
end
|
6
|
+
|
7
|
+
it "returns a tuple of output and the new prompt" do
|
8
|
+
output, prompt = repl.send_input("1 + 2")
|
9
|
+
output.should == "=> 3\n"
|
10
|
+
prompt.should == ">>"
|
11
|
+
end
|
12
|
+
|
13
|
+
it "doesn't barf if the code throws an exception" do
|
14
|
+
output, prompt = repl.send_input("raise Exception")
|
15
|
+
output.should include "Exception: Exception"
|
16
|
+
prompt.should == ">>"
|
21
17
|
end
|
22
18
|
end
|