what_weve_got_here_is_an_error_to_communicate 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +11 -0
  3. data/.rspec +4 -0
  4. data/.travis.yml +7 -0
  5. data/Gemfile +2 -0
  6. data/Rakefile +6 -0
  7. data/Readme.md +34 -0
  8. data/acceptance +29 -0
  9. data/experiments/formatting/5_potential_structure_dsls.rb +88 -0
  10. data/experiments/formatting/half_thoughtout_dsl_for_toplevel_structure_of_argument_error.rb +43 -0
  11. data/experiments/formatting/haml_like_structure.rb +139 -0
  12. data/experiments/formatting/other_structures +156 -0
  13. data/lib/error_to_communicate.rb +3 -0
  14. data/lib/error_to_communicate/at_exit.rb +11 -0
  15. data/lib/error_to_communicate/config.rb +32 -0
  16. data/lib/error_to_communicate/exception_info.rb +41 -0
  17. data/lib/error_to_communicate/format.rb +132 -0
  18. data/lib/error_to_communicate/format/terminal_helpers.rb +97 -0
  19. data/lib/error_to_communicate/parse/backtrace.rb +34 -0
  20. data/lib/error_to_communicate/parse/exception.rb +21 -0
  21. data/lib/error_to_communicate/parse/no_method_error.rb +27 -0
  22. data/lib/error_to_communicate/parse/registry.rb +30 -0
  23. data/lib/error_to_communicate/parse/wrong_number_of_arguments.rb +35 -0
  24. data/lib/error_to_communicate/rspec_formatter.rb +46 -0
  25. data/lib/error_to_communicate/version.rb +3 -0
  26. data/lib/what_weve_got_here_is_an_error_to_communicate.rb +3 -0
  27. data/screenshot.png +0 -0
  28. data/spec/acceptance/argument_error_spec.rb +55 -0
  29. data/spec/acceptance/exception_spec.rb +29 -0
  30. data/spec/acceptance/no_error_spec.rb +44 -0
  31. data/spec/acceptance/no_methood_error_spec.rb +50 -0
  32. data/spec/acceptance/spec_helper.rb +41 -0
  33. data/spec/parse/backtrace_spec.rb +101 -0
  34. data/spec/parse/exception_spec.rb +14 -0
  35. data/spec/parse/no_method_error_spec.rb +23 -0
  36. data/spec/parse/registered_parsers_spec.rb +68 -0
  37. data/spec/parse/spec_helper.rb +23 -0
  38. data/spec/parse/wrong_number_of_arguments_spec.rb +77 -0
  39. data/spec/rspec_formatter_spec.rb +95 -0
  40. data/spec/spec_helper.rb +20 -0
  41. data/what_weve_got_here_is_an_error_to_communicate.gemspec +19 -0
  42. metadata +168 -0
@@ -0,0 +1,46 @@
1
+ require 'error_to_communicate'
2
+ require 'error_to_communicate/format'
3
+ require 'rspec/core/formatters/documentation_formatter'
4
+
5
+ module WhatWeveGotHereIsAnErrorToCommunicate
6
+ class RSpecFormatter < RSpec::Core::Formatters::DocumentationFormatter
7
+ # Register for notifications from our parent classes
8
+ # http://rspec.info/documentation/3.2/rspec-core/RSpec/Core/Formatters.html
9
+ #
10
+ # Our complete set of notifications can be seen with:
11
+ # puts RSpecFormatter.ancestors.flat_map { |ancestor|
12
+ # RSpec::Core::Formatters::Loader.formatters.fetch(ancestor, [])
13
+ # }
14
+ RSpec::Core::Formatters.register self
15
+
16
+ # Use WhatWeveGotHereIsAnErrorToCommunicate to print error info
17
+ # rather than default DocumentationFormatter.
18
+ #
19
+ # How did we figure out how to implement it?
20
+ # See "Down the rabbit hole" section in
21
+ # https://github.com/JoshCheek/what-we-ve-got-here-is-an-error-to-communicate/blob/ede6844/lib/error_to_communicate/rspec_formatter.rb#L68
22
+ #
23
+ # FIXME: Needs to respect RSpec.configuration.color_enabled?
24
+ # but we can't currently turn colour off in our output
25
+ def dump_failures(notification)
26
+ formatted = "\nFailures:\n"
27
+ notification.failure_notifications.each_with_index do |failure, failure_number|
28
+ # get the exception with the modified backtrace
29
+ exception = failure.exception.dup
30
+ metadata = failure.example.metadata
31
+ exception.set_backtrace RSpec.configuration
32
+ .backtrace_formatter
33
+ .format_backtrace(exception.backtrace, metadata)
34
+
35
+ # format it with our lib
36
+ exception_info = ErrorToCommunicate::CONFIG.parse(exception)
37
+ formatted_exception = ErrorToCommunicate.format(exception_info)
38
+
39
+ # fit it into the larger failure summary
40
+ formatted << "\n #{failure_number+1}) #{failure.description}\n"
41
+ formatted << formatted_exception.chomp.gsub(/^/, ' ')
42
+ end
43
+ output.puts formatted
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,3 @@
1
+ module WhatWeveGotHereIsAnErrorToCommunicate
2
+ VERSION = '0.0.1'.freeze
3
+ end
@@ -0,0 +1,3 @@
1
+ # exists so that adding it to bundler will turn it on
2
+ # without having to specify the whole name out 2x
3
+ require 'error_to_communicate/at_exit'
data/screenshot.png ADDED
Binary file
@@ -0,0 +1,55 @@
1
+ require 'acceptance/spec_helper'
2
+
3
+ RSpec.context 'ArgumentError', acceptance: true do
4
+ it 'Prints heuristics' do
5
+ # Given a file with three lines in the backtrace that explodes on the third
6
+ write_file 'argument_error.rb', <<-BODY
7
+ require 'what_weve_got_here_is_an_error_to_communicate'
8
+ module Test
9
+ def self.backtrace1
10
+ end
11
+
12
+ def self.backtrace2
13
+ backtrace1 123
14
+ end
15
+ end
16
+
17
+ Test.backtrace2
18
+ BODY
19
+
20
+ invocation = ruby 'argument_error.rb'
21
+ stderr = strip_color invocation.stderr
22
+
23
+ # sanity
24
+ expect(invocation.stdout).to eq ''
25
+ expect(invocation.exitstatus).to eq 1
26
+
27
+ # error: It prints the exception class and prints the reworded message
28
+ expect(stderr).to include 'ArgumentError'
29
+ expect(stderr).to include 'Wrong number of arguments'
30
+ expect(stderr).to include '(expected 0, sent 1)'
31
+
32
+ # heuristic:
33
+ # It displays name of the file with the line number of the error
34
+ expect(stderr).to include 'argument_error.rb:3'
35
+
36
+ # It displays the most recent line of code with some context
37
+ expect(stderr).to include 'self.backtrace1'
38
+ expect(stderr).to include 'end'
39
+
40
+ # It displays the second most recent line of the backtrace with some context around it
41
+ expect(stderr).to include 'def self.backtrace2'
42
+ expect(stderr).to include ' backtrace1 123'
43
+ expect(stderr).to include 'end'
44
+
45
+ # backtrace: displays each line of the backtrace with the code from that line
46
+ expect(stderr).to include 'argument_error.rb:3'
47
+ expect(stderr).to include 'def self.backtrace1'
48
+
49
+ expect(stderr).to include 'argument_error.rb:7'
50
+ expect(stderr).to include 'def self.backtrace2'
51
+
52
+ expect(stderr).to include 'argument_error.rb:11'
53
+ expect(stderr).to include 'Test.backtrace2'
54
+ end
55
+ end
@@ -0,0 +1,29 @@
1
+ require 'acceptance/spec_helper'
2
+
3
+ RSpec.context 'Exception', acceptance: true do
4
+ it 'Prints heuristics' do
5
+ # Given a file with three lines in the backtrace that explodes on the third
6
+ write_file 'exception.rb', <<-BODY
7
+ require "what_weve_got_here_is_an_error_to_communicate"
8
+ raise Exception, "mah message"
9
+ BODY
10
+
11
+ invocation = ruby 'exception.rb'
12
+ stderr = strip_color invocation.stderr
13
+
14
+ # sanity
15
+ expect(invocation.stdout).to eq ''
16
+ expect(invocation.exitstatus).to eq 1
17
+
18
+ # error: It prints the exception class and prints the message
19
+ expect(stderr).to include 'Exception'
20
+ expect(stderr).to include 'mah message'
21
+
22
+ # heuristic displays the line the exception was raised at, and some context
23
+ expect(stderr).to include 'raise Exception, "mah message"'
24
+ expect(stderr).to include 'require "what_weve_got_here_is_an_error_to_communicate"'
25
+
26
+ # typical backtrace
27
+ expect(stderr).to include 'exception.rb:2'
28
+ end
29
+ end
@@ -0,0 +1,44 @@
1
+ require 'acceptance/spec_helper'
2
+
3
+ RSpec.context 'non errors are not captured/reported', acceptance: true do
4
+ example 'no error is raised' do
5
+ write_file 'no_error.rb', <<-BODY
6
+ require 'what_weve_got_here_is_an_error_to_communicate'
7
+ print "hello, world"
8
+ BODY
9
+
10
+ invocation = ruby 'no_error.rb'
11
+
12
+ # No exception
13
+ expect(invocation.stderr).to eq ''
14
+ expect(invocation.stdout).to eq 'hello, world'
15
+ expect(invocation.exitstatus).to eq 0
16
+ end
17
+
18
+
19
+ example 'successful exit' do
20
+ write_file 'exit_0.rb', <<-BODY
21
+ require 'what_weve_got_here_is_an_error_to_communicate'
22
+ exit 0
23
+ BODY
24
+ invocation = ruby 'exit_0.rb'
25
+
26
+ # No exception
27
+ expect(invocation.stderr).to eq ''
28
+ expect(invocation.stdout).to eq ''
29
+ expect(invocation.exitstatus).to eq 0
30
+ end
31
+
32
+ example 'unsuccessful exit' do
33
+ write_file 'exit_2.rb', <<-BODY
34
+ require 'what_weve_got_here_is_an_error_to_communicate'
35
+ exit 2
36
+ BODY
37
+ invocation = ruby 'exit_2.rb'
38
+
39
+ # No exception
40
+ expect(invocation.stderr).to eq ''
41
+ expect(invocation.stdout).to eq ''
42
+ expect(invocation.exitstatus).to eq 2
43
+ end
44
+ end
@@ -0,0 +1,50 @@
1
+ require 'acceptance/spec_helper'
2
+
3
+ RSpec.describe 'NoMethodError', acceptance: true do
4
+ it 'Prints heuristics' do
5
+ # Given a file that throws an error with two lines in the backtrace
6
+ write_file 'no_method_error.rb', <<-BODY
7
+ require 'error_to_communicate/at_exit'
8
+ module Test
9
+ def self.backtrace1
10
+ end
11
+
12
+ def self.backtrace2
13
+ Test.backtrace4
14
+ end
15
+
16
+ def self.backtrace3
17
+ end
18
+ end
19
+
20
+ Test.backtrace2
21
+ BODY
22
+
23
+ invocation = ruby 'no_method_error.rb'
24
+ stderr = strip_color invocation.stderr
25
+
26
+ # sanity
27
+ expect(invocation.stdout).to eq ''
28
+ expect(invocation.exitstatus).to eq 1
29
+
30
+ # error: It prints the exception class and message
31
+ expect(stderr).to include 'NoMethodError'
32
+ expect(stderr).to include "undefined method `backtrace4'"
33
+
34
+ # heuristic:
35
+ # It displays a hlepful message next to the callsite
36
+ expect(stderr).to include 'backtrace4 is undefined'
37
+
38
+ # It displays the line of the backtrace with the NoMethodError, and some context around it
39
+ expect(stderr).to include 'def self.backtrace2'
40
+ expect(stderr).to include ' Test.backtrace4'
41
+ expect(stderr).to include 'end'
42
+
43
+ # backtrace: It displays each line of the backtrace and includes the code from that line
44
+ expect(stderr).to include 'no_method_error.rb:7'
45
+ expect(stderr).to include 'backtrace4'
46
+
47
+ expect(stderr).to include '14'
48
+ expect(stderr).to include 'backtrace2'
49
+ end
50
+ end
@@ -0,0 +1,41 @@
1
+ require 'spec_helper'
2
+ require 'haiti/command_line_helpers'
3
+
4
+ module AcceptanceSpecHelpers
5
+ extend self
6
+ include Haiti::CommandLineHelpers
7
+
8
+ def root_dir
9
+ File.expand_path '../../..', __FILE__
10
+ end
11
+
12
+ def lib_dir
13
+ File.join root_dir, 'lib'
14
+ end
15
+
16
+ def proving_grounds_dir
17
+ File.join root_dir, 'proving_grounds'
18
+ end
19
+
20
+ def ruby(filename)
21
+ # workaround for JRuby bug (capture3 calls open3 with invalid args, needs to splat an array, but doesn't)
22
+ in_proving_grounds do
23
+ Haiti::CommandLineHelpers::Invocation.new *Open3.capture3(ENV, 'ruby', '-I', lib_dir, filename)
24
+ end
25
+ end
26
+
27
+ def strip_color(string)
28
+ string.gsub(/\e\[\d+(;\d+)*?m/, '')
29
+ end
30
+ end
31
+
32
+ # Commandline invocation
33
+ Haiti.configure do |config|
34
+ config.proving_grounds_dir = AcceptanceSpecHelpers.proving_grounds_dir
35
+ end
36
+
37
+ RSpec.configure do |config|
38
+ # Helpers for acceptance tests
39
+ config.before(acceptance: true) { make_proving_grounds }
40
+ config.include AcceptanceSpecHelpers, acceptance: true
41
+ end
@@ -0,0 +1,101 @@
1
+ require 'spec_helper'
2
+ require 'error_to_communicate/parse/backtrace'
3
+
4
+ RSpec.describe 'parsing an ArgumentError', parse: true do
5
+ let :exception do
6
+ FakeException.new backtrace: [
7
+ "file.rb:111:in `method1'",
8
+ "file.rb:222:in `method2'",
9
+ "file.rb:333:in `method3'"
10
+ ]
11
+ end
12
+
13
+ def parse(exception)
14
+ WhatWeveGotHereIsAnErrorToCommunicate::Parse::Backtrace.parse(exception)
15
+ end
16
+
17
+ it 'records the linenum, and label of each backtrace location' do
18
+ locations = parse exception
19
+ expect(locations.map &:linenum).to eq [111, 222, 333]
20
+ expect(locations.map &:label).to eq %w[method1 method2 method3]
21
+ end
22
+
23
+ specify 'the predecessor is the parsed location from the previous index, or nil for the first' do
24
+ l1, l2, l3 = locations = parse(exception)
25
+ expect(locations.map &:pred).to eq [nil, l1, l2]
26
+ end
27
+
28
+ specify 'the successor is the parsed locations from the next index, or nil for the last' do
29
+ l1, l2, l3 = locations = parse(exception)
30
+ expect(locations.map &:succ).to eq [l2, l3, nil]
31
+ end
32
+
33
+ # it 'records the absolute filepath if it can find the file'
34
+ # it 'records the relative filepath if it can find the file'
35
+ # it 'records the relative filepath if it cannot fild the file'
36
+
37
+ def assert_parses_line(line, assertions)
38
+ parsed = WhatWeveGotHereIsAnErrorToCommunicate::Parse::Backtrace.parse_backtrace_line(line)
39
+ assertions.each do |method_name, expected|
40
+ actual = parsed.__send__ method_name
41
+ expect(actual).to eq expected
42
+ end
43
+ end
44
+
45
+ it 'records the path whether its absolute or relative' do
46
+ assert_parses_line "file.rb:111:in `method1'", path: "file.rb"
47
+ assert_parses_line "/file.rb:111:in `method1'", path: "/file.rb"
48
+ end
49
+
50
+ it 'does not get confused by numbers in directories, filenames, or method names' do
51
+ line = "/a1/b2/c3123/file123.rb:111:in `method1'"
52
+ assert_parses_line line, path: "/a1/b2/c3123/file123.rb"
53
+ assert_parses_line line, linenum: 111
54
+ assert_parses_line line, label: "method1"
55
+ end
56
+
57
+ context 'random ass colons in the middle of like files and directories and shit' do
58
+ # $ mkdir 'a:b'
59
+ # $ echo 'begin; define_method("a:b") { |arg| }; send "a:b"; rescue Exception; p $!.backtrace; end' > 'a:b/c:d.rb'
60
+
61
+ # $ chruby-exec 2.2 -- ruby -v
62
+ # > ruby 2.2.0p0 (2014-12-25 revision 49005) [x86_64-darwin13]
63
+ #
64
+ # $ chruby-exec 2.2 -- ruby 'a:b/c:d.rb'
65
+ # > ["a:b/c:d.rb:1:in `block in <main>'", "a:b/c:d.rb:1:in `<main>'"]
66
+ it 'does not get confused with MRI style results' do
67
+ line = "a:b/c:d.rb:1:in `block in <main>'"
68
+ assert_parses_line line, path: "a:b/c:d.rb"
69
+ assert_parses_line line, linenum: 1
70
+ assert_parses_line line, label: "block in <main>"
71
+ end
72
+
73
+ # $ chruby-exec rbx -- ruby -v
74
+ # > rubinius 2.5.0 (2.1.0 50777f41 2015-01-17 3.5.0 JI) [x86_64-darwin14.1.0]
75
+ #
76
+ # $ chruby-exec rbx -- ruby 'a:b/c:d.rb'
77
+ # > ["a:b/c:d.rb:1:in `__script__'",
78
+ # "kernel/delta/code_loader.rb:66:in `load_script'",
79
+ # "kernel/delta/code_loader.rb:152:in `load_script'",
80
+ # "kernel/loader.rb:645:in `script'",
81
+ # "kernel/loader.rb:799:in `main'"]
82
+ it 'does not get confused with RBX style results' do
83
+ line = "a:b/c:d.rb:1:in `__script__'"
84
+ assert_parses_line line, path: "a:b/c:d.rb"
85
+ assert_parses_line line, linenum: 1
86
+ assert_parses_line line, label: "__script__"
87
+ end
88
+
89
+ # $ chruby-exec jruby -- ruby -v
90
+ # > 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]
91
+ #
92
+ # $ chruby-exec jruby -- ruby 'a:b/c:d.rb'
93
+ # > ["a:b/c:d.rb:1:in `(root)'"]
94
+ it 'does not get confused by Jruby style results' do
95
+ line = "a:b/c:d.rb:1:in `(root)'"
96
+ assert_parses_line line, path: "a:b/c:d.rb"
97
+ assert_parses_line line, linenum: 1
98
+ assert_parses_line line, label: "(root)"
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,14 @@
1
+ require 'parse/spec_helper'
2
+ require 'error_to_communicate/parse/exception'
3
+
4
+ RSpec.describe 'parsing an Exception', parse: true do
5
+ def parse(exception)
6
+ WhatWeveGotHereIsAnErrorToCommunicate::Parse::Exception.parse(exception)
7
+ end
8
+
9
+ it_behaves_like 'an exception parser', sample_message: 'literally anything'
10
+
11
+ # Going to wait on implementing these as they may not be correct,
12
+ # e.g. anyone can raise an argument error for any reason.
13
+ it 'extracts the name of the method that was called'
14
+ end
@@ -0,0 +1,23 @@
1
+ require 'parse/spec_helper'
2
+ require 'error_to_communicate/parse/no_method_error'
3
+
4
+ RSpec.describe 'parsing a NoMethodError', parse: true do
5
+ def error_class
6
+ WhatWeveGotHereIsAnErrorToCommunicate::Parse::NoMethodError
7
+ end
8
+
9
+ def parse(exception)
10
+ error_class.parse(exception)
11
+ end
12
+
13
+ it_behaves_like 'an exception parser', sample_message: "undefined method `<' for nil:NilClass"
14
+
15
+ def extracts_method_name!(expected, message)
16
+ actual = error_class.extract_method_name(message)
17
+ expect(actual).to eq expected
18
+ end
19
+
20
+ it 'extracts the name of the method that was called' do
21
+ extracts_method_name! '<', "undefined method `<' for nil:NilClass"
22
+ end
23
+ end
@@ -0,0 +1,68 @@
1
+ require 'error_to_communicate/config'
2
+
3
+ RSpec.describe 'registered parsers', parse: true do
4
+ p = WhatWeveGotHereIsAnErrorToCommunicate::Parse
5
+
6
+ def capture
7
+ yield
8
+ raise 'NO EXCEPTION WAS RAISED!'
9
+ rescue Exception
10
+ return $!
11
+ end
12
+
13
+ describe 'selected parsers' do
14
+ def parser_for(exception)
15
+ WhatWeveGotHereIsAnErrorToCommunicate::Config
16
+ .new.registry.parser_for(exception)
17
+ end
18
+
19
+ it 'doesn\'t parse nil' do
20
+ expect(parser_for nil).to eq nil
21
+ end
22
+
23
+ it 'doesn\'t parse a SystemExit' do
24
+ err = capture { exit }
25
+ expect(parser_for err).to eq nil
26
+
27
+ err = capture { exit 1 }
28
+ expect(parser_for err).to eq nil
29
+ end
30
+
31
+ it 'parses wrong number of arguments' do
32
+ err = capture { lambda { }.call :arg }
33
+ expect(parser_for err).to eq p::WrongNumberOfArguments
34
+ end
35
+
36
+ it 'parses NoMethodErrors' do
37
+ err = capture { sdfsdfsdf() }
38
+ expect(parser_for err).to eq p::NoMethodError
39
+ end
40
+
41
+ it 'lets ArgumentErrors that are not wrong number of arguments fall through', t:true do
42
+ err = capture { raise ArgumentError, "zomg" }
43
+ expect(parser_for err).to eq p::Exception
44
+ end
45
+
46
+ it 'parses Exception' do
47
+ err = capture { raise Exception, "wat" }
48
+ expect(parser_for err).to eq p::Exception
49
+ end
50
+ end
51
+
52
+ describe 'config.parse' do
53
+ def parse(exception)
54
+ WhatWeveGotHereIsAnErrorToCommunicate::Config
55
+ .new.parse(exception)
56
+ end
57
+
58
+ it 'parses the exception if anything is willing to do it' do
59
+ exception = capture { sdfsdfsdf() }
60
+ exception_info = parse exception
61
+ expect(exception_info.exception).to equal exception
62
+ end
63
+
64
+ it 'raises an ArgumentError if there are no parsers for this exception' do
65
+ expect { parse "not an error" }.to raise_error ArgumentError, /"not an error"/
66
+ end
67
+ end
68
+ end