what_weve_got_here_is_an_error_to_communicate 0.0.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 (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