what_weve_got_here_is_an_error_to_communicate 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rspec +1 -0
- data/Readme.md +9 -7
- data/experiments/formatting/other_resources +7 -0
- data/lib/error_to_communicate/at_exit.rb +15 -9
- data/lib/error_to_communicate/config.rb +55 -22
- data/lib/error_to_communicate/exception_info.rb +86 -31
- data/lib/error_to_communicate/format_terminal.rb +146 -0
- data/lib/error_to_communicate/heuristic/exception.rb +22 -0
- data/lib/error_to_communicate/heuristic/load_error.rb +57 -0
- data/lib/error_to_communicate/heuristic/no_method_error.rb +35 -0
- data/lib/error_to_communicate/heuristic/syntax_error.rb +55 -0
- data/lib/error_to_communicate/heuristic/wrong_number_of_arguments.rb +73 -0
- data/lib/error_to_communicate/heuristic.rb +54 -0
- data/lib/error_to_communicate/project.rb +50 -0
- data/lib/error_to_communicate/rspec_formatter.rb +8 -9
- data/lib/error_to_communicate/theme.rb +137 -0
- data/lib/error_to_communicate/version.rb +2 -2
- data/lib/error_to_communicate.rb +4 -2
- data/spec/acceptance/exception_spec.rb +2 -4
- data/spec/acceptance/load_error_spec.rb +23 -0
- data/spec/acceptance/name_error_spec.rb +46 -0
- data/spec/acceptance/no_methood_error_spec.rb +6 -8
- data/spec/acceptance/runtime_error_spec.rb +27 -0
- data/spec/acceptance/short_and_long_require_spec.rb +29 -0
- data/spec/acceptance/spec_helper.rb +4 -3
- data/spec/acceptance/syntax_error_spec.rb +32 -0
- data/spec/acceptance/{argument_error_spec.rb → wrong_number_of_arguments_spec.rb} +1 -1
- data/spec/config_spec.rb +120 -0
- data/spec/heuristic/exception_spec.rb +17 -0
- data/spec/heuristic/load_error_spec.rb +195 -0
- data/spec/heuristic/no_method_error_spec.rb +25 -0
- data/spec/heuristic/spec_helper.rb +33 -0
- data/spec/heuristic/wrong_number_of_arguments_spec.rb +115 -0
- data/spec/heuristic_spec.rb +76 -0
- data/spec/parsing_exception_info_spec.rb +212 -0
- data/spec/rspec_formatter_spec.rb +3 -1
- data/spec/spec_helper.rb +28 -1
- data/what_weve_got_here_is_an_error_to_communicate.gemspec +2 -2
- metadata +29 -19
- data/lib/error_to_communicate/format/terminal_helpers.rb +0 -97
- data/lib/error_to_communicate/format.rb +0 -132
- data/lib/error_to_communicate/parse/backtrace.rb +0 -34
- data/lib/error_to_communicate/parse/exception.rb +0 -21
- data/lib/error_to_communicate/parse/no_method_error.rb +0 -27
- data/lib/error_to_communicate/parse/registry.rb +0 -30
- data/lib/error_to_communicate/parse/wrong_number_of_arguments.rb +0 -35
- data/spec/parse/backtrace_spec.rb +0 -101
- data/spec/parse/exception_spec.rb +0 -14
- data/spec/parse/no_method_error_spec.rb +0 -23
- data/spec/parse/registered_parsers_spec.rb +0 -68
- data/spec/parse/spec_helper.rb +0 -23
- data/spec/parse/wrong_number_of_arguments_spec.rb +0 -77
@@ -0,0 +1,212 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
RSpec.describe 'Parsing exceptions to ExceptionInfo', einfo: true do
|
4
|
+
it 'records the class name, message, and backtrace' do
|
5
|
+
exception = FakeException.new message: 'Some message',
|
6
|
+
backtrace: ["/Users/someone/a/b/c.rb:123:in `some_method_name'"]
|
7
|
+
einfo = einfo_for exception
|
8
|
+
expect(einfo.classname ).to eq 'FakeException'
|
9
|
+
expect(einfo.message ).to eq 'Some message'
|
10
|
+
expect(einfo.backtrace.map &:linenum).to eq [123]
|
11
|
+
end
|
12
|
+
|
13
|
+
describe 'recording the actual exception' do
|
14
|
+
let(:exception) { FakeException.new }
|
15
|
+
let(:einfo) { einfo_for exception }
|
16
|
+
|
17
|
+
it 'records the exception for informational purposes' do
|
18
|
+
trap_warnings do
|
19
|
+
expect(einfo.exception).to equal exception
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'warns the first time you try to use it (ie available for debugging, but not for development)' do
|
24
|
+
warnings = trap_warnings { einfo.exception }
|
25
|
+
expect(warnings).to match /debugging/
|
26
|
+
|
27
|
+
warnings = trap_warnings { einfo.exception }
|
28
|
+
expect(warnings).to be_empty
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
describe 'backtrace' do
|
33
|
+
let :exception do
|
34
|
+
FakeException.new backtrace: [
|
35
|
+
"file.rb:111:in `method1'",
|
36
|
+
"file.rb:222:in `method2'",
|
37
|
+
"file.rb:333:in `method3'",
|
38
|
+
]
|
39
|
+
end
|
40
|
+
|
41
|
+
def backtrace_for(exception)
|
42
|
+
einfo_for(exception).backtrace
|
43
|
+
end
|
44
|
+
|
45
|
+
it 'records the linenum, and label of each backtrace location' do
|
46
|
+
locations = backtrace_for exception
|
47
|
+
expect(locations.map &:linenum).to eq [111, 222, 333]
|
48
|
+
expect(locations.map &:label).to eq %w[method1 method2 method3]
|
49
|
+
end
|
50
|
+
|
51
|
+
specify 'the successor is the parsed location that was called, or nil for the first' do
|
52
|
+
l1, l2, l3 = locations = backtrace_for(exception)
|
53
|
+
expect(locations.map &:succ).to eq [nil, l1, l2]
|
54
|
+
end
|
55
|
+
|
56
|
+
specify 'the predecessor is the parsed location from the caller, or nil for the last' do
|
57
|
+
l1, l2, l3 = locations = backtrace_for(exception)
|
58
|
+
expect(locations.map &:pred).to eq [l2, l3, nil]
|
59
|
+
end
|
60
|
+
|
61
|
+
# it 'records the absolute filepath if it can find the file'
|
62
|
+
# it 'records the relative filepath if it can find the file'
|
63
|
+
# it 'records the relative filepath if it cannot fild the file'
|
64
|
+
|
65
|
+
def assert_parses_line(line, assertions)
|
66
|
+
parsed = ErrorToCommunicate::ExceptionInfo::Location.parse(line)
|
67
|
+
assertions.each do |method_name, expected|
|
68
|
+
expected = Pathname.new expected if method_name == :path
|
69
|
+
actual = parsed.__send__ method_name
|
70
|
+
expect(actual).to eq expected
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
it 'records the path whether its absolute or relative' do
|
75
|
+
assert_parses_line "file.rb:111:in `method1'", path: "file.rb"
|
76
|
+
assert_parses_line "/file.rb:111:in `method1'", path: "/file.rb"
|
77
|
+
end
|
78
|
+
|
79
|
+
it 'does not get confused by numbers in directories, filenames, or method names' do
|
80
|
+
line = "/a1/b2/c3123/file123.rb:111:in `method1'"
|
81
|
+
assert_parses_line line, path: "/a1/b2/c3123/file123.rb"
|
82
|
+
assert_parses_line line, linenum: 111
|
83
|
+
assert_parses_line line, label: "method1"
|
84
|
+
end
|
85
|
+
|
86
|
+
context 'random ass colons in the middle of like files and directories and shit' do
|
87
|
+
# $ mkdir 'a:b'
|
88
|
+
# $ echo 'begin; define_method("a:b") { |arg| }; send "a:b"; rescue Exception; p $!.backtrace; end' > 'a:b/c:d.rb'
|
89
|
+
|
90
|
+
# $ chruby-exec 2.2 -- ruby -v
|
91
|
+
# > ruby 2.2.0p0 (2014-12-25 revision 49005) [x86_64-darwin13]
|
92
|
+
#
|
93
|
+
# $ chruby-exec 2.2 -- ruby 'a:b/c:d.rb'
|
94
|
+
# > ["a:b/c:d.rb:1:in `block in <main>'", "a:b/c:d.rb:1:in `<main>'"]
|
95
|
+
it 'does not get confused with MRI style results' do
|
96
|
+
line = "a:b/c:d.rb:1:in `block in <main>'"
|
97
|
+
assert_parses_line line, path: "a:b/c:d.rb"
|
98
|
+
assert_parses_line line, linenum: 1
|
99
|
+
assert_parses_line line, label: "block in <main>"
|
100
|
+
end
|
101
|
+
|
102
|
+
# $ chruby-exec rbx -- ruby -v
|
103
|
+
# > rubinius 2.5.0 (2.1.0 50777f41 2015-01-17 3.5.0 JI) [x86_64-darwin14.1.0]
|
104
|
+
#
|
105
|
+
# $ chruby-exec rbx -- ruby 'a:b/c:d.rb'
|
106
|
+
# > ["a:b/c:d.rb:1:in `__script__'",
|
107
|
+
# "kernel/delta/code_loader.rb:66:in `load_script'",
|
108
|
+
# "kernel/delta/code_loader.rb:152:in `load_script'",
|
109
|
+
# "kernel/loader.rb:645:in `script'",
|
110
|
+
# "kernel/loader.rb:799:in `main'"]
|
111
|
+
it 'does not get confused with RBX style results' do
|
112
|
+
line = "a:b/c:d.rb:1:in `__script__'"
|
113
|
+
assert_parses_line line, path: "a:b/c:d.rb"
|
114
|
+
assert_parses_line line, linenum: 1
|
115
|
+
assert_parses_line line, label: "__script__"
|
116
|
+
end
|
117
|
+
|
118
|
+
# $ chruby-exec jruby -- ruby -v
|
119
|
+
# > jruby 1.7.16 (1.9.3p392) 2014-09-25 575b395 on Java HotSpot(TM) 64-Bit Server VM 1.7.0_51-b13 +jit [darwin-x86_64]
|
120
|
+
#
|
121
|
+
# $ chruby-exec jruby -- ruby 'a:b/c:d.rb'
|
122
|
+
# > ["a:b/c:d.rb:1:in `(root)'"]
|
123
|
+
it 'does not get confused by Jruby style results' do
|
124
|
+
line = "a:b/c:d.rb:1:in `(root)'"
|
125
|
+
assert_parses_line line, path: "a:b/c:d.rb"
|
126
|
+
assert_parses_line line, linenum: 1
|
127
|
+
assert_parses_line line, label: "(root)"
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
describe 'ExceptionInfo::Location' do
|
133
|
+
def location_for(attrs)
|
134
|
+
ErrorToCommunicate::ExceptionInfo::Location.new attrs
|
135
|
+
end
|
136
|
+
|
137
|
+
it 'hashes take into account its path, linenum, and label' do
|
138
|
+
loc_base = location_for(path: 'p', linenum: 123, label: 'l')
|
139
|
+
loc_eq1 = location_for(path: 'p', linenum: 123, label: 'l')
|
140
|
+
loc_eq2 = location_for(path: 'p', linenum: 123, label: 'l', succ: loc_base, pred: loc_base)
|
141
|
+
loc_diff_path = location_for(path: 'P', linenum: 123, label: 'l')
|
142
|
+
loc_diff_linenum = location_for(path: 'p', linenum: 999, label: 'l')
|
143
|
+
loc_diff_label = location_for(path: 'p', linenum: 123, label: 'L')
|
144
|
+
|
145
|
+
expect(loc_base.hash).to eq loc_eq1.hash # same values gets same hash
|
146
|
+
expect(loc_base.hash).to eq loc_eq2.hash # succ/pred are excluded from hash
|
147
|
+
expect(loc_base.hash).to_not eq loc_diff_path.hash
|
148
|
+
expect(loc_base.hash).to_not eq loc_diff_linenum.hash
|
149
|
+
expect(loc_base.hash).to_not eq loc_diff_label.hash
|
150
|
+
|
151
|
+
# use this, just to show it works
|
152
|
+
h = {loc_base => :found}
|
153
|
+
expect( h[loc_eq1 ] ).to eq :found
|
154
|
+
expect( h[loc_eq2 ] ).to eq :found
|
155
|
+
expect( h[loc_diff_path ] ).to eq nil
|
156
|
+
expect( h[loc_diff_linenum ] ).to eq nil
|
157
|
+
expect( h[loc_diff_label ] ).to eq nil
|
158
|
+
end
|
159
|
+
|
160
|
+
|
161
|
+
# Can't directly check associations for equality or we'll end up in an infinite loop
|
162
|
+
# This was maybe a waste of time, to specify it this precisely, all I want to be able to do is compare two paths -.^
|
163
|
+
it 'is == and eql? to another location, if their paths, linenums, and labels are ==' do
|
164
|
+
# the rest are compared against this
|
165
|
+
loc1 = location_for path: 'p1', linenum: 1, label: 'l1'
|
166
|
+
|
167
|
+
# equivalent
|
168
|
+
loc1_same = location_for path: 'p1', linenum: 1, label: 'l1'
|
169
|
+
expect(loc1).to eq loc1_same
|
170
|
+
expect(loc1).to eql loc1_same
|
171
|
+
|
172
|
+
# different path
|
173
|
+
loc1_diff_path = location_for path: 'p2', linenum: 1, label: 'l1'
|
174
|
+
expect(loc1).to_not eq loc1_diff_path
|
175
|
+
expect(loc1).to_not eql loc1_diff_path
|
176
|
+
|
177
|
+
# different line
|
178
|
+
loc1_diff_line = location_for path: 'p1', linenum: 2, label: 'l1'
|
179
|
+
expect(loc1).to_not eq loc1_diff_line
|
180
|
+
expect(loc1).to_not eql loc1_diff_line
|
181
|
+
|
182
|
+
# different label
|
183
|
+
loc1_diff_label = location_for path: 'p1', linenum: 1, label: 'l2'
|
184
|
+
expect(loc1).to_not eq loc1_diff_label
|
185
|
+
expect(loc1).to_not eql loc1_diff_label
|
186
|
+
|
187
|
+
# are == when different before
|
188
|
+
loc2_before = location_for path: 'p2', linenum: 2, label: 'l2'
|
189
|
+
loc2 = location_for path: 'p1', linenum: 1, label: 'l1'
|
190
|
+
loc2.succ, loc2_before.pred = loc2_before, loc2
|
191
|
+
expect(loc1).to eq loc2
|
192
|
+
expect(loc1).to eql loc2
|
193
|
+
|
194
|
+
# are == when different after
|
195
|
+
loc3 = location_for path: 'p1', linenum: 1, label: 'l1'
|
196
|
+
loc3_after = location_for path: 'p2', linenum: 2, label: 'l2'
|
197
|
+
loc3.succ, loc3_after.pred = loc3_after, loc3
|
198
|
+
expect(loc1).to eq loc3
|
199
|
+
expect(loc1).to eql loc3
|
200
|
+
end
|
201
|
+
|
202
|
+
it 'inspects to something that isn\'t obnoxious to look at' do
|
203
|
+
linked_loc = location_for path: 'somepath', linenum: 123, label: 'somelabel'
|
204
|
+
linked_loc.succ, linked_loc.pred = linked_loc, linked_loc
|
205
|
+
expect(linked_loc.inspect).to eq '#<ExInfo::Loc somepath:123:in `somelabel\' pred:true succ:true>'
|
206
|
+
|
207
|
+
unlinked_loc = location_for path: 'anotherpath', linenum: 12, label: 'anotherlabel'
|
208
|
+
expect(unlinked_loc.inspect).to eq '#<ExInfo::Loc anotherpath:12:in `anotherlabel\' pred:false succ:false>'
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
require 'stringio'
|
2
2
|
require 'spec_helper'
|
3
3
|
|
4
|
-
RSpec.describe ErrorToCommunicate::RSpecFormatter,
|
4
|
+
RSpec.describe ErrorToCommunicate::RSpecFormatter, rspec_formatter: true do
|
5
5
|
let(:substring_that_would_only_be_in_full_backtrace) { 'lib/rspec/core' }
|
6
6
|
|
7
7
|
def formatter_for(attributes)
|
@@ -53,6 +53,7 @@ RSpec.describe ErrorToCommunicate::RSpecFormatter, formatter: true do
|
|
53
53
|
end
|
54
54
|
|
55
55
|
it 'uses our lib to print the details of failing examples.' do
|
56
|
+
# does print
|
56
57
|
formatter = new_formatter
|
57
58
|
context_around_failure = this_line_of_code
|
58
59
|
run_specs_against formatter do
|
@@ -60,6 +61,7 @@ RSpec.describe ErrorToCommunicate::RSpecFormatter, formatter: true do
|
|
60
61
|
end
|
61
62
|
expect(get_printed formatter).to include context_around_failure
|
62
63
|
|
64
|
+
# does not print
|
63
65
|
formatter = new_formatter
|
64
66
|
context_around_success = this_line_of_code
|
65
67
|
run_specs_against formatter do
|
data/spec/spec_helper.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
class FakeException
|
2
2
|
attr_reader :message, :backtrace
|
3
3
|
|
4
|
-
def initialize(attributes)
|
4
|
+
def initialize(attributes={})
|
5
5
|
@message = attributes.fetch :message, 'default message'
|
6
6
|
@backtrace = attributes.fetch(:backtrace, [])
|
7
7
|
end
|
@@ -11,7 +11,34 @@ class FakeException
|
|
11
11
|
end
|
12
12
|
end
|
13
13
|
|
14
|
+
module SpecHelpers
|
15
|
+
def einfo_for(exception)
|
16
|
+
ErrorToCommunicate::ExceptionInfo.parse exception
|
17
|
+
end
|
18
|
+
|
19
|
+
def trap_warnings
|
20
|
+
initial_stderr = $stderr
|
21
|
+
mock_stderr = StringIO.new
|
22
|
+
$stderr = mock_stderr
|
23
|
+
yield
|
24
|
+
ensure
|
25
|
+
$stderr = initial_stderr
|
26
|
+
warnings = mock_stderr.string
|
27
|
+
return warnings unless $! # don't swallow exceptions
|
28
|
+
end
|
29
|
+
|
30
|
+
def capture
|
31
|
+
yield
|
32
|
+
raise 'NO EXCEPTION WAS RAISED!'
|
33
|
+
rescue Exception
|
34
|
+
return $!
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
|
14
39
|
RSpec.configure do |config|
|
40
|
+
config.include SpecHelpers
|
41
|
+
|
15
42
|
# Stop testing after first failure
|
16
43
|
config.fail_fast = true
|
17
44
|
|
@@ -1,7 +1,7 @@
|
|
1
1
|
require_relative 'lib/error_to_communicate/version'
|
2
2
|
Gem::Specification.new do |s|
|
3
3
|
s.name = 'what_weve_got_here_is_an_error_to_communicate'
|
4
|
-
s.version =
|
4
|
+
s.version = ErrorToCommunicate::VERSION
|
5
5
|
s.licenses = ['MIT']
|
6
6
|
s.summary = "Readable, helpful error messages"
|
7
7
|
s.description = "Hooks into program lifecycle to display error messages to you in a helpufl way, with inlined code, colour, and helpful heuristics about what might be the cause."
|
@@ -10,7 +10,7 @@ Gem::Specification.new do |s|
|
|
10
10
|
s.files = `git ls-files`.split("\n")
|
11
11
|
s.homepage = 'https://github.com/JoshCheek/what-we-ve-got-here-is-an-error-to-communicate'
|
12
12
|
|
13
|
-
s.add_runtime_dependency '
|
13
|
+
s.add_runtime_dependency 'rouge', '~> 1.8'
|
14
14
|
|
15
15
|
s.add_development_dependency 'rspec', '~> 3.2'
|
16
16
|
s.add_development_dependency 'haiti', '< 0.3', '>= 0.2.0'
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: what_weve_got_here_is_an_error_to_communicate
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Josh Cheek
|
@@ -9,22 +9,22 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2015-
|
12
|
+
date: 2015-05-29 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
|
-
name:
|
15
|
+
name: rouge
|
16
16
|
requirement: !ruby/object:Gem::Requirement
|
17
17
|
requirements:
|
18
18
|
- - "~>"
|
19
19
|
- !ruby/object:Gem::Version
|
20
|
-
version: '1.
|
20
|
+
version: '1.8'
|
21
21
|
type: :runtime
|
22
22
|
prerelease: false
|
23
23
|
version_requirements: !ruby/object:Gem::Requirement
|
24
24
|
requirements:
|
25
25
|
- - "~>"
|
26
26
|
- !ruby/object:Gem::Version
|
27
|
-
version: '1.
|
27
|
+
version: '1.8'
|
28
28
|
- !ruby/object:Gem::Dependency
|
29
29
|
name: rspec
|
30
30
|
requirement: !ruby/object:Gem::Requirement
|
@@ -110,33 +110,43 @@ files:
|
|
110
110
|
- experiments/formatting/5_potential_structure_dsls.rb
|
111
111
|
- experiments/formatting/half_thoughtout_dsl_for_toplevel_structure_of_argument_error.rb
|
112
112
|
- experiments/formatting/haml_like_structure.rb
|
113
|
+
- experiments/formatting/other_resources
|
113
114
|
- experiments/formatting/other_structures
|
114
115
|
- lib/error_to_communicate.rb
|
115
116
|
- lib/error_to_communicate/at_exit.rb
|
116
117
|
- lib/error_to_communicate/config.rb
|
117
118
|
- lib/error_to_communicate/exception_info.rb
|
118
|
-
- lib/error_to_communicate/
|
119
|
-
- lib/error_to_communicate/
|
120
|
-
- lib/error_to_communicate/
|
121
|
-
- lib/error_to_communicate/
|
122
|
-
- lib/error_to_communicate/
|
123
|
-
- lib/error_to_communicate/
|
124
|
-
- lib/error_to_communicate/
|
119
|
+
- lib/error_to_communicate/format_terminal.rb
|
120
|
+
- lib/error_to_communicate/heuristic.rb
|
121
|
+
- lib/error_to_communicate/heuristic/exception.rb
|
122
|
+
- lib/error_to_communicate/heuristic/load_error.rb
|
123
|
+
- lib/error_to_communicate/heuristic/no_method_error.rb
|
124
|
+
- lib/error_to_communicate/heuristic/syntax_error.rb
|
125
|
+
- lib/error_to_communicate/heuristic/wrong_number_of_arguments.rb
|
126
|
+
- lib/error_to_communicate/project.rb
|
125
127
|
- lib/error_to_communicate/rspec_formatter.rb
|
128
|
+
- lib/error_to_communicate/theme.rb
|
126
129
|
- lib/error_to_communicate/version.rb
|
127
130
|
- lib/what_weve_got_here_is_an_error_to_communicate.rb
|
128
131
|
- screenshot.png
|
129
|
-
- spec/acceptance/argument_error_spec.rb
|
130
132
|
- spec/acceptance/exception_spec.rb
|
133
|
+
- spec/acceptance/load_error_spec.rb
|
134
|
+
- spec/acceptance/name_error_spec.rb
|
131
135
|
- spec/acceptance/no_error_spec.rb
|
132
136
|
- spec/acceptance/no_methood_error_spec.rb
|
137
|
+
- spec/acceptance/runtime_error_spec.rb
|
138
|
+
- spec/acceptance/short_and_long_require_spec.rb
|
133
139
|
- spec/acceptance/spec_helper.rb
|
134
|
-
- spec/
|
135
|
-
- spec/
|
136
|
-
- spec/
|
137
|
-
- spec/
|
138
|
-
- spec/
|
139
|
-
- spec/
|
140
|
+
- spec/acceptance/syntax_error_spec.rb
|
141
|
+
- spec/acceptance/wrong_number_of_arguments_spec.rb
|
142
|
+
- spec/config_spec.rb
|
143
|
+
- spec/heuristic/exception_spec.rb
|
144
|
+
- spec/heuristic/load_error_spec.rb
|
145
|
+
- spec/heuristic/no_method_error_spec.rb
|
146
|
+
- spec/heuristic/spec_helper.rb
|
147
|
+
- spec/heuristic/wrong_number_of_arguments_spec.rb
|
148
|
+
- spec/heuristic_spec.rb
|
149
|
+
- spec/parsing_exception_info_spec.rb
|
140
150
|
- spec/rspec_formatter_spec.rb
|
141
151
|
- spec/spec_helper.rb
|
142
152
|
- what_weve_got_here_is_an_error_to_communicate.gemspec
|
@@ -1,97 +0,0 @@
|
|
1
|
-
module WhatWeveGotHereIsAnErrorToCommunicate
|
2
|
-
module Format
|
3
|
-
module TerminalHelpers
|
4
|
-
def separator
|
5
|
-
("="*70) << "\n"
|
6
|
-
end
|
7
|
-
|
8
|
-
def color_path(str)
|
9
|
-
"\e[38;5;36m#{str}\e[39m" # fg r:0, g:3, b:2 (out of 0..5)
|
10
|
-
end
|
11
|
-
|
12
|
-
def color_linenum(linenum)
|
13
|
-
"\e[34m#{linenum}\e[39m"
|
14
|
-
end
|
15
|
-
|
16
|
-
def path_to_dir(from, to)
|
17
|
-
to.relative_path_from(from).dirname
|
18
|
-
rescue ArgumentError
|
19
|
-
return to # eg rbx's core code
|
20
|
-
end
|
21
|
-
|
22
|
-
def white
|
23
|
-
"\e[38;5;255m"
|
24
|
-
end
|
25
|
-
|
26
|
-
def bri_red
|
27
|
-
"\e[38;5;196m"
|
28
|
-
end
|
29
|
-
|
30
|
-
def dim_red
|
31
|
-
"\e[38;5;124m"
|
32
|
-
end
|
33
|
-
|
34
|
-
def none
|
35
|
-
"\e[39m"
|
36
|
-
end
|
37
|
-
|
38
|
-
def bound_num(attributes)
|
39
|
-
num = attributes.fetch :num
|
40
|
-
min = attributes.fetch :min
|
41
|
-
num < min ? min : num
|
42
|
-
end
|
43
|
-
|
44
|
-
def remove_indentation(code)
|
45
|
-
indentation = code.scan(/^\s*/).min_by(&:length)
|
46
|
-
code.gsub(/^#{indentation}/, "")
|
47
|
-
end
|
48
|
-
|
49
|
-
def prefix_linenos_to(code, start_linenum)
|
50
|
-
lines = code.lines
|
51
|
-
max_linenum = lines.count + start_linenum - 1 # 1 to translate to indexes
|
52
|
-
linenum_width = max_linenum.to_s.length + 1 # 1 for the colon
|
53
|
-
lines.zip(start_linenum..max_linenum)
|
54
|
-
.map { |line, num|
|
55
|
-
formatted_num = "#{num}:".ljust(linenum_width)
|
56
|
-
color_linenum(formatted_num) << " " << line
|
57
|
-
}.join("")
|
58
|
-
end
|
59
|
-
|
60
|
-
def add_message_to(code, offset, message)
|
61
|
-
lines = code.lines
|
62
|
-
lines[offset].chomp! << " " << message << "\n"
|
63
|
-
lines.join("")
|
64
|
-
end
|
65
|
-
|
66
|
-
def highlight_text(code, index, text)
|
67
|
-
lines = code.lines
|
68
|
-
return code unless lines[index]
|
69
|
-
lines[index].gsub!(text, "\e[7m#{text}\e[27m") # invert
|
70
|
-
lines.join("")
|
71
|
-
end
|
72
|
-
|
73
|
-
def indent(str, indentation_str)
|
74
|
-
str.gsub /^/, indentation_str
|
75
|
-
end
|
76
|
-
|
77
|
-
def screaming_red(text)
|
78
|
-
return "" if text.empty?
|
79
|
-
"\e[38;5;255;48;5;88m #{text} \e[39;49m" # bright white on medium red
|
80
|
-
end
|
81
|
-
|
82
|
-
def underline(str)
|
83
|
-
"\e[4m#{str}\e[24m"
|
84
|
-
end
|
85
|
-
|
86
|
-
def color_filename(str)
|
87
|
-
"\e[38;5;49;1m#{str}\e[39m" # fg r:0, g:5, b:3 (out of 0..5)
|
88
|
-
end
|
89
|
-
|
90
|
-
def desaturate(str)
|
91
|
-
nocolor = str.gsub(/\e\[[\d;]+?m/, "")
|
92
|
-
allgray = nocolor.gsub(/^(.*?)\n?$/, "\e[38;5;240m\\1\e[39m\n")
|
93
|
-
allgray
|
94
|
-
end
|
95
|
-
end
|
96
|
-
end
|
97
|
-
end
|
@@ -1,132 +0,0 @@
|
|
1
|
-
require 'coderay'
|
2
|
-
require 'pathname'
|
3
|
-
require 'error_to_communicate/format/terminal_helpers'
|
4
|
-
|
5
|
-
module WhatWeveGotHereIsAnErrorToCommunicate
|
6
|
-
extend Format::TerminalHelpers
|
7
|
-
def self.format(info)
|
8
|
-
cwd = Dir.pwd
|
9
|
-
|
10
|
-
# FIXME:
|
11
|
-
# Something else should set this?
|
12
|
-
# I'd say heuristic, but fact is that it needs formatting info.
|
13
|
-
# Maybe initially, heuristic contains both extracted info and formatting info?
|
14
|
-
# Or maybe we want polymorphism at the formatter level?
|
15
|
-
display_class_and_message = lambda do |info|
|
16
|
-
if info.classname == 'ArgumentError'
|
17
|
-
"#{white}#{info.classname} | "\
|
18
|
-
"#{bri_red}#{info.explanation} "\
|
19
|
-
"#{dim_red}(expected #{white}#{info.num_expected},"\
|
20
|
-
"#{dim_red} sent #{white}#{info.num_received}"\
|
21
|
-
"#{dim_red})"\
|
22
|
-
"#{none}"
|
23
|
-
else
|
24
|
-
"#{white}#{info.classname} | "\
|
25
|
-
"#{bri_red}#{info.explanation} "\
|
26
|
-
"#{none}"
|
27
|
-
end
|
28
|
-
end
|
29
|
-
|
30
|
-
display_location = lambda do |attributes|
|
31
|
-
location = attributes.fetch :location
|
32
|
-
cwd = Pathname.new attributes.fetch(:cwd)
|
33
|
-
path = Pathname.new location.path
|
34
|
-
line_index = location.linenum - 1
|
35
|
-
highlight = attributes.fetch :highlight, location.label
|
36
|
-
end_index = bound_num min: 0, num: line_index+attributes.fetch(:context).end
|
37
|
-
start_index = bound_num min: 0, num: line_index+attributes.fetch(:context).begin
|
38
|
-
message = attributes.fetch :message, ''
|
39
|
-
message_offset = line_index - start_index
|
40
|
-
|
41
|
-
# first line gives the path
|
42
|
-
path_line = ""
|
43
|
-
path_line << color_path("#{path_to_dir cwd, path}/")
|
44
|
-
path_line << color_filename(path.basename)
|
45
|
-
path_line << ":" << color_linenum(location.linenum)
|
46
|
-
|
47
|
-
# then display the code
|
48
|
-
if path.exist?
|
49
|
-
code = File.read(path).lines[start_index..end_index].join("")
|
50
|
-
code = remove_indentation code
|
51
|
-
code = CodeRay.encode code, :ruby, :terminal
|
52
|
-
code = prefix_linenos_to code, start_index.next
|
53
|
-
code = indent code, " "
|
54
|
-
code = add_message_to code, message_offset, screaming_red(message)
|
55
|
-
code = highlight_text code, message_offset, highlight
|
56
|
-
else
|
57
|
-
code = "Can't find code\n"
|
58
|
-
end
|
59
|
-
|
60
|
-
# adjust for emphasization
|
61
|
-
if attributes.fetch(:emphasisis) == :path
|
62
|
-
path_line = underline path_line
|
63
|
-
code = indent code, " "
|
64
|
-
code = desaturate code
|
65
|
-
code = highlight_text code, message_offset, highlight # b/c desaturate really strips color
|
66
|
-
end
|
67
|
-
|
68
|
-
# all together
|
69
|
-
path_line << "\n" << code
|
70
|
-
end
|
71
|
-
|
72
|
-
|
73
|
-
# Display the ArgumentError
|
74
|
-
display = ""
|
75
|
-
display << separator
|
76
|
-
display << display_class_and_message.call(info) << "\n"
|
77
|
-
|
78
|
-
# Display the Heuristic
|
79
|
-
display << separator
|
80
|
-
|
81
|
-
# FIXME: Some sort of polymorphism or normalization would be way better here, too
|
82
|
-
# And, at the very least, not switching on classname, but some more abstract piece of info,
|
83
|
-
# b/c classnames are not completely consistent across the implementations
|
84
|
-
# (eg: https://github.com/JoshCheek/seeing_is_believing/blob/cc93b4ee3a83145509c235f64d9454dc3e12d8c9/lib/seeing_is_believing/event_stream/producer.rb#L54-55)
|
85
|
-
if info.classname == 'ArgumentError'
|
86
|
-
display << display_location.call(location: info.backtrace[0],
|
87
|
-
highlight: info.backtrace[0].label,
|
88
|
-
context: 0..5,
|
89
|
-
message: "EXPECTED #{info.num_expected}",
|
90
|
-
emphasisis: :code,
|
91
|
-
cwd: cwd)
|
92
|
-
display << "\n"
|
93
|
-
display << display_location.call(location: info.backtrace[1],
|
94
|
-
highlight: info.backtrace[0].label,
|
95
|
-
context: -5..5,
|
96
|
-
message: "SENT #{info.num_received}",
|
97
|
-
emphasisis: :code,
|
98
|
-
cwd: cwd)
|
99
|
-
elsif info.classname == 'NoMethodError'
|
100
|
-
display << display_location.call(location: info.backtrace[0],
|
101
|
-
highlight: info.backtrace[0].label,
|
102
|
-
context: -5..5,
|
103
|
-
message: "#{info.undefined_method_name} is undefined",
|
104
|
-
emphasisis: :code,
|
105
|
-
cwd: cwd)
|
106
|
-
else
|
107
|
-
display << display_location.call(location: info.backtrace[0],
|
108
|
-
highlight: info.backtrace[0].label,
|
109
|
-
context: -5..5,
|
110
|
-
emphasisis: :code,
|
111
|
-
cwd: cwd)
|
112
|
-
end
|
113
|
-
|
114
|
-
# display the backtrace
|
115
|
-
display << separator
|
116
|
-
display << display_location.call(location: info.backtrace[0],
|
117
|
-
highlight: info.backtrace[0].label,
|
118
|
-
context: 0..0,
|
119
|
-
emphasisis: :path,
|
120
|
-
cwd: cwd)
|
121
|
-
|
122
|
-
display << info.backtrace.each_cons(2).map { |next_loc, crnt_loc|
|
123
|
-
display_location.call location: crnt_loc,
|
124
|
-
highlight: next_loc.label,
|
125
|
-
context: 0..0,
|
126
|
-
emphasisis: :path,
|
127
|
-
cwd: cwd
|
128
|
-
}.join("")
|
129
|
-
|
130
|
-
display
|
131
|
-
end
|
132
|
-
end
|
@@ -1,34 +0,0 @@
|
|
1
|
-
require 'error_to_communicate/exception_info'
|
2
|
-
|
3
|
-
module WhatWeveGotHereIsAnErrorToCommunicate
|
4
|
-
module Parse
|
5
|
-
module Backtrace
|
6
|
-
def self.parse?(exception)
|
7
|
-
# Really, there are better methods, e.g. backtrace_locations,
|
8
|
-
# but they're unevenly implemented across versions and implementations
|
9
|
-
exception.respond_to? :backtrace
|
10
|
-
end
|
11
|
-
|
12
|
-
def self.parse(exception)
|
13
|
-
locations = exception.backtrace.map &method(:parse_backtrace_line)
|
14
|
-
locations.each_cons(2) do |crnt, succ|
|
15
|
-
succ.pred = crnt
|
16
|
-
crnt.succ = succ
|
17
|
-
end
|
18
|
-
locations
|
19
|
-
end
|
20
|
-
|
21
|
-
# TODO: What if the line doesn't match for some reason?
|
22
|
-
# Raise an exception?
|
23
|
-
# Use some reasonable default? (is there one?)
|
24
|
-
def self.parse_backtrace_line(line)
|
25
|
-
line =~ /^(.*?):(\d+):in `(.*?)'$/ # Are ^ and $ sufficient? Should be \A and (\Z or \z)?
|
26
|
-
ExceptionInfo::Location.new(
|
27
|
-
path: $1,
|
28
|
-
linenum: $2.to_i,
|
29
|
-
label: $3,
|
30
|
-
)
|
31
|
-
end
|
32
|
-
end
|
33
|
-
end
|
34
|
-
end
|
@@ -1,21 +0,0 @@
|
|
1
|
-
require 'error_to_communicate/exception_info'
|
2
|
-
require 'error_to_communicate/parse/backtrace'
|
3
|
-
|
4
|
-
module WhatWeveGotHereIsAnErrorToCommunicate
|
5
|
-
module Parse
|
6
|
-
module Exception
|
7
|
-
def self.parse?(exception)
|
8
|
-
exception.respond_to?(:message) && Backtrace.parse?(exception)
|
9
|
-
end
|
10
|
-
|
11
|
-
def self.parse(exception)
|
12
|
-
ExceptionInfo.new(
|
13
|
-
exception: exception,
|
14
|
-
classname: exception.class.to_s,
|
15
|
-
explanation: exception.message,
|
16
|
-
backtrace: Backtrace.parse(exception),
|
17
|
-
)
|
18
|
-
end
|
19
|
-
end
|
20
|
-
end
|
21
|
-
end
|