better_errors 0.9.0 → 1.0.0.rc1
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.
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
|