seeing_is_believing 2.2.0 → 3.0.0.beta.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (60) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/Changelog.md +33 -0
  4. data/bin/seeing_is_believing +1 -1
  5. data/features/errors.feature +3 -3
  6. data/features/examples.feature +6 -6
  7. data/features/flags.feature +51 -196
  8. data/features/regression.feature +12 -3
  9. data/features/safe.feature +33 -0
  10. data/features/support/env.rb +20 -0
  11. data/features/xmpfilter-style.feature +156 -0
  12. data/lib/seeing_is_believing.rb +17 -35
  13. data/lib/seeing_is_believing/binary.rb +81 -176
  14. data/lib/seeing_is_believing/binary/align_chunk.rb +5 -7
  15. data/lib/seeing_is_believing/binary/align_file.rb +4 -5
  16. data/lib/seeing_is_believing/binary/align_line.rb +4 -4
  17. data/lib/seeing_is_believing/binary/annotate_end_of_file.rb +60 -0
  18. data/lib/seeing_is_believing/binary/annotate_every_line.rb +64 -0
  19. data/lib/seeing_is_believing/binary/annotate_xmpfilter_style.rb +133 -0
  20. data/lib/seeing_is_believing/binary/comment_formatter.rb +19 -5
  21. data/lib/seeing_is_believing/binary/comment_lines.rb +1 -1
  22. data/lib/seeing_is_believing/binary/commentable_lines.rb +1 -1
  23. data/lib/seeing_is_believing/binary/interpret_flags.rb +149 -0
  24. data/lib/seeing_is_believing/binary/parse_args.rb +96 -104
  25. data/lib/seeing_is_believing/binary/remove_annotations.rb +95 -0
  26. data/lib/seeing_is_believing/binary/rewrite_comments.rb +8 -30
  27. data/lib/seeing_is_believing/code.rb +99 -0
  28. data/lib/seeing_is_believing/evaluate_by_moving_files.rb +27 -19
  29. data/lib/seeing_is_believing/evaluate_with_eval_in.rb +27 -0
  30. data/lib/seeing_is_believing/event_stream/consumer.rb +111 -0
  31. data/lib/seeing_is_believing/event_stream/events.rb +16 -0
  32. data/lib/seeing_is_believing/event_stream/producer.rb +106 -0
  33. data/lib/seeing_is_believing/event_stream/update_result.rb +21 -0
  34. data/lib/seeing_is_believing/inspect_expressions.rb +24 -0
  35. data/lib/seeing_is_believing/parser_helpers.rb +1 -11
  36. data/lib/seeing_is_believing/result.rb +14 -56
  37. data/lib/seeing_is_believing/the_matrix.rb +14 -14
  38. data/lib/seeing_is_believing/version.rb +1 -1
  39. data/lib/seeing_is_believing/wrap_expressions.rb +32 -9
  40. data/seeing_is_believing.gemspec +7 -7
  41. data/spec/binary/comment_formatter_spec.rb +169 -18
  42. data/spec/binary/comment_lines_spec.rb +1 -1
  43. data/spec/binary/interpret_flags_spec.rb +307 -0
  44. data/spec/binary/parse_args_spec.rb +93 -91
  45. data/spec/binary/{clean_body_spec.rb → remove_annotations_spec.rb} +29 -22
  46. data/spec/binary/rewrite_comments_spec.rb +13 -13
  47. data/spec/code_spec.rb +49 -0
  48. data/spec/debugger_spec.rb +1 -1
  49. data/spec/evaluate_by_moving_files_spec.rb +7 -3
  50. data/spec/event_stream_spec.rb +390 -0
  51. data/spec/hard_core_ensure_spec.rb +1 -1
  52. data/spec/seeing_is_believing_spec.rb +137 -40
  53. data/spec/spec_helper.rb +3 -3
  54. data/spec/wrap_expressions_spec.rb +48 -35
  55. metadata +58 -35
  56. data/lib/seeing_is_believing/binary/add_annotations.rb +0 -144
  57. data/lib/seeing_is_believing/binary/clean_body.rb +0 -95
  58. data/lib/seeing_is_believing/has_exception.rb +0 -27
  59. data/lib/seeing_is_believing/line.rb +0 -90
  60. data/spec/line_spec.rb +0 -86
@@ -1,40 +1,47 @@
1
1
  require 'spec_helper'
2
- require 'seeing_is_believing/binary/clean_body'
2
+ require 'seeing_is_believing/binary/remove_annotations'
3
3
 
4
- describe SeeingIsBelieving::Binary::CleanBody do
4
+ RSpec.describe SeeingIsBelieving::Binary::RemoveAnnotations do
5
5
  def call(code, should_clean_values=true)
6
6
  indentation = code[/\A */]
7
7
  code = code.gsub /^#{indentation}/, ''
8
- described_class.call(code, should_clean_values).chomp
8
+ described_class.call(code,
9
+ should_clean_values,
10
+ value: '# => ',
11
+ exception: '# ~> ',
12
+ stdout: '# >> ',
13
+ stderr: '# !> ',
14
+ nextline: '# ',
15
+ ).chomp
9
16
  end
10
17
 
11
18
  context 'when told to clean out value annotations' do
12
- example { expect(call "1#=>1", true).to eq "1" }
13
- example { expect(call "1 #=>1", true).to eq "1" }
14
- example { expect(call "1 #=>1", true).to eq "1" }
15
- example { expect(call "1 #=> 1", true).to eq "1" }
16
- example { expect(call "1 #=> 1", true).to eq "1" }
17
- example { expect(call "1 #=> 1", true).to eq "1" }
19
+ example { expect(call "1# => 1", true).to eq "1" }
20
+ example { expect(call "1 # => 1", true).to eq "1" }
21
+ example { expect(call "1 # => 1", true).to eq "1" }
22
+ example { expect(call "1 # => 1", true).to eq "1" }
23
+ example { expect(call "1 # => 1", true).to eq "1" }
24
+ example { expect(call "1 # => 1", true).to eq "1" }
18
25
  example { expect(call "\n1 # => 1", true).to eq "\n1" }
19
26
  end
20
27
 
21
28
  context 'when told not to clean out value annotations' do
22
- example { expect(call "1#=>1", false).to eq "1#=>1" }
23
- example { expect(call "1 #=>1", false).to eq "1 #=>1" }
24
- example { expect(call "1 #=>1", false).to eq "1 #=>1" }
25
- example { expect(call "1 #=> 1", false).to eq "1 #=> 1" }
26
- example { expect(call "1 #=> 1", false).to eq "1 #=> 1" }
27
- example { expect(call "1 #=> 1", false).to eq "1 #=> 1" }
29
+ example { expect(call "1# => 1", false).to eq "1# => 1" }
30
+ example { expect(call "1 # => 1", false).to eq "1 # => 1" }
31
+ example { expect(call "1 # => 1", false).to eq "1 # => 1" }
32
+ example { expect(call "1 # => 1", false).to eq "1 # => 1" }
33
+ example { expect(call "1 # => 1", false).to eq "1 # => 1" }
34
+ example { expect(call "1 # => 1", false).to eq "1 # => 1" }
28
35
  example { expect(call "\n1 # => 1", false).to eq "\n1 # => 1" }
29
36
  end
30
37
 
31
38
  context 'cleaning inline exception annotations' do
32
- example { expect(call "1#~>1" ).to eq "1" }
33
- example { expect(call "1 #~>1" ).to eq "1" }
34
- example { expect(call "1 #~>1" ).to eq "1" }
35
- example { expect(call "1 #~> 1" ).to eq "1" }
36
- example { expect(call "1 #~> 1" ).to eq "1" }
37
- example { expect(call "1 #~> 1" ).to eq "1" }
39
+ example { expect(call "1# ~> 1" ).to eq "1" }
40
+ example { expect(call "1 # ~> 1" ).to eq "1" }
41
+ example { expect(call "1 # ~> 1" ).to eq "1" }
42
+ example { expect(call "1 # ~> 1" ).to eq "1" }
43
+ example { expect(call "1 # ~> 1" ).to eq "1" }
44
+ example { expect(call "1 # ~> 1" ).to eq "1" }
38
45
  example { expect(call "\n1 # ~> 1").to eq "\n1" }
39
46
 
40
47
  example { expect(call "# >> 1").to eq "" }
@@ -112,7 +119,7 @@ describe SeeingIsBelieving::Binary::CleanBody do
112
119
  context 'cleaning end of file exception annotations' do
113
120
  example { expect(call(<<-CODE)).to eq "1" }
114
121
  1
115
- # ~>2
122
+ # ~> 2
116
123
  CODE
117
124
 
118
125
  example { expect(call(<<-CODE)).to eq "1" }
@@ -1,7 +1,7 @@
1
1
  require 'spec_helper'
2
2
  require 'seeing_is_believing/binary/rewrite_comments'
3
3
 
4
- describe SeeingIsBelieving::Binary::RewriteComments do
4
+ RSpec.describe SeeingIsBelieving::Binary::RewriteComments do
5
5
  def call(code, &block)
6
6
  described_class.call code, &block
7
7
  end
@@ -15,7 +15,7 @@ describe SeeingIsBelieving::Binary::RewriteComments do
15
15
  expect(seen).to eq []
16
16
  end
17
17
 
18
- it 'yields the line_number, line upto the whitespace, whitespace, and comment' do
18
+ it 'yields the Code::InlineComment' do
19
19
  seen = []
20
20
  call("# c1\n"\
21
21
  "123 # c2 # x\n"\
@@ -23,16 +23,16 @@ describe SeeingIsBelieving::Binary::RewriteComments do
23
23
  " \t # c3\n"\
24
24
  "%Q{\n"\
25
25
  " 1}#c4\n"\
26
- "# c5") do |*args|
27
- seen << args
28
- args[-2..-1]
26
+ "# c5") do |comment|
27
+ seen << comment
28
+ ['', '']
29
29
  end
30
- expect(seen).to eq [
31
- [1, "", "", "# c1"],
32
- [2, "123", " ", "# c2 # x"],
33
- [4, "", " \t ", "# c3"],
34
- [6, " 1}", "", "#c4"],
35
- [7, "", "", "# c5"],
30
+ expect(seen.map(&:text)).to eq [
31
+ "# c1",
32
+ "# c2 # x",
33
+ "# c3",
34
+ "#c4",
35
+ "# c5",
36
36
  ]
37
37
  end
38
38
 
@@ -42,8 +42,8 @@ describe SeeingIsBelieving::Binary::RewriteComments do
42
42
  "n456\n"\
43
43
  " \t # c3\n"\
44
44
  "%Q{\n"\
45
- " 1}#c4") do |line_number, *|
46
- ["NEW_WHITESPACE#{line_number}", "--COMMENT-#{line_number}--"]
45
+ " 1}#c4") do |c|
46
+ ["NEW_WHITESPACE#{c.line_number}", "--COMMENT-#{c.line_number}--"]
47
47
  end
48
48
  expect(rewritten).to eq "NEW_WHITESPACE1--COMMENT-1--\n"\
49
49
  "123NEW_WHITESPACE2--COMMENT-2--\n"\
data/spec/code_spec.rb ADDED
@@ -0,0 +1,49 @@
1
+ # require 'seeing_is_believing/code'
2
+
3
+ # RSpec.describe 'SeeingIsBelieving::Code' do
4
+ # describe '#range' do
5
+ # it 'returns a range object with the specified start and ends'
6
+ # end
7
+
8
+ # describe '#inline_comments' do
9
+ # it 'finds their line_number, column_number, preceding_whitespace, text, preceding_whitespace_range, and comment_range' do
10
+ # code = code_for <<-CODE.gsub(/^\s*/, '')
11
+ # # c1
12
+ # not c
13
+ # # c2
14
+
15
+ # not c
16
+ # =begin
17
+ # mul
18
+ # ti
19
+ # line
20
+ # =end
21
+ # preceding code \t # c3
22
+ # CODE
23
+ # cs = code.inline_comments
24
+ # expect(cs.map &:line_number ).to eq [1, 3, 4]
25
+ # expect(cs.map &:column_number ).to eq [1, 1, 18]
26
+ # expect(cs.map &:preceding_whitespace).to eq ["", "", " \t "]
27
+ # expect(cs.map &:text ).to eq ['# c1', '# c2', '# c3']
28
+
29
+ # preceding_whitespace_range
30
+ # comment_range
31
+
32
+ # expect(code.inline_comments.map &:ra)
33
+ # multilines = code.multiline_comments
34
+ # end
35
+
36
+ # it 'finds multiline comments'
37
+
38
+ # it 'finds comments in syntactically invalid files'
39
+ # end
40
+
41
+ # it 'knows whether a string is a heredoc'
42
+ # it 'knows if the file is syntactically valid'
43
+ # it 'knows whether there is a data segment'
44
+ # it 'knows what line the data segment starts on'
45
+ # it 'knows how many lines there are'
46
+ # it 'provides the ast'
47
+ # it 'provides the ability to rewrite code'
48
+ # it 'provides access to the source with #[]'
49
+ # end
@@ -2,7 +2,7 @@ require 'spec_helper'
2
2
  require 'seeing_is_believing/debugger'
3
3
  require 'stringio'
4
4
 
5
- describe SeeingIsBelieving::Debugger do
5
+ RSpec.describe SeeingIsBelieving::Debugger do
6
6
  let(:stream) { StringIO.new }
7
7
 
8
8
  specify 'is enabled when given a stream' do
@@ -4,12 +4,16 @@ require 'spec_helper'
4
4
  require 'seeing_is_believing/evaluate_by_moving_files'
5
5
  require 'fileutils'
6
6
 
7
- describe SeeingIsBelieving::EvaluateByMovingFiles do
7
+ RSpec.describe SeeingIsBelieving::EvaluateByMovingFiles do
8
8
  let(:filedir) { File.expand_path '../../proving_grounds', __FILE__ }
9
9
  let(:filename) { File.join filedir, 'some_filename' }
10
10
 
11
11
  before { FileUtils.mkdir_p filedir }
12
12
 
13
+ def matrix_file
14
+ 'seeing_is_believing/the_matrix'
15
+ end
16
+
13
17
  def invoke(program, options={})
14
18
  evaluator = described_class.new(program, filename, options)
15
19
  FileUtils.rm_f evaluator.temp_filename
@@ -87,13 +91,13 @@ describe SeeingIsBelieving::EvaluateByMovingFiles do
87
91
  other_filename2 = File.join filedir, 'other2.rb'
88
92
  File.open(other_filename1, 'w') { |f| f.puts "puts 123" }
89
93
  File.open(other_filename2, 'w') { |f| f.puts "puts 456" }
90
- result = invoke '', require: [other_filename1, other_filename2]
94
+ result = invoke '', require: [matrix_file, other_filename1, other_filename2]
91
95
  expect(result.stdout).to eq "123\n456\n"
92
96
  end
93
97
 
94
98
  it 'can set the load path' do
95
99
  File.open(File.join(filedir, 'other1.rb'), 'w') { |f| f.puts "puts 123" }
96
- result = invoke '', require: ['other1'], load_path: [filedir]
100
+ result = invoke '', require: [matrix_file, 'other1'], load_path: [filedir]
97
101
  expect(result.stdout).to eq "123\n"
98
102
  end
99
103
 
@@ -0,0 +1,390 @@
1
+ # encoding: utf-8
2
+
3
+ require 'seeing_is_believing/event_stream/producer'
4
+ require 'seeing_is_believing/event_stream/consumer'
5
+
6
+ module SeeingIsBelieving::EventStream
7
+ RSpec.describe SeeingIsBelieving::EventStream do
8
+ attr_accessor :producer, :consumer, :readstream, :writestream
9
+
10
+ before do
11
+ self.readstream, self.writestream = IO.pipe
12
+ self.producer = SeeingIsBelieving::EventStream::Producer.new(writestream)
13
+ self.consumer = SeeingIsBelieving::EventStream::Consumer.new(readstream)
14
+ end
15
+
16
+ after {
17
+ producer.finish!
18
+ readstream.close unless readstream.closed?
19
+ writestream.close unless writestream.closed?
20
+ }
21
+
22
+ describe 'emitting an event' do
23
+ # TODO: could not fucking figure out how to ask the goddam thing if it has data
24
+ # read docs for over an hour -.0
25
+ it 'writes a line to stdout'
26
+
27
+ # This test is irrelevant on MRI b/c of the GIL,
28
+ # but I ran it on Rbx to make sure it works
29
+ it 'is wrapped in a mutex to prevent multiple values from writing at the same time' do
30
+ num_threads = 10
31
+ num_results = 600
32
+ line_nums_and_inspections = num_threads.times.flat_map { |line_num|
33
+ num_results.times.map { |value| "#{line_num}|#{value.inspect}" }
34
+ }
35
+
36
+ producer_threads = num_threads.times.map { |line_num|
37
+ Thread.new {
38
+ num_results.times { |value| producer.record_result :type, line_num, value }
39
+ }
40
+ }
41
+
42
+ (num_threads * num_results).times do |n|
43
+ result = consumer.call
44
+ ary_val = "#{result.line_number}|#{result.inspected}"
45
+ index = line_nums_and_inspections.index(ary_val)
46
+ raise "#{ary_val.inspect} is already consumed!" unless index
47
+ line_nums_and_inspections.delete_at index
48
+ end
49
+
50
+ expect(line_nums_and_inspections).to eq []
51
+ expect(producer_threads).to be_none(&:alive?)
52
+ end
53
+
54
+ it 'raises NoMoreInput and marks itself finished if input is closed before it finishes reading the number of requested inputs' do
55
+ producer.finish!
56
+ expect { consumer.call 10 }.to raise_error SeeingIsBelieving::EventStream::Consumer::NoMoreInput
57
+ end
58
+
59
+ it 'raises NoMoreInput and marks itself finished once it receives the finish event' do
60
+ producer.finish!
61
+ consumer.call 5
62
+ expect { consumer.call }.to raise_error SeeingIsBelieving::EventStream::Consumer::NoMoreInput
63
+ expect(consumer).to be_finished
64
+ end
65
+
66
+ it 'raises NoMoreInput and marks itself finished once the other end of the stream is closed' do
67
+ writestream.close
68
+ expect { consumer.call }.to raise_error SeeingIsBelieving::EventStream::Consumer::NoMoreInput
69
+ expect(consumer).to be_finished
70
+ end
71
+
72
+ it 'raises WtfWhoClosedMyShit and marks itself finished if its end of the stream is closed' do
73
+ readstream.close
74
+ expect { consumer.call }.to raise_error SeeingIsBelieving::EventStream::Consumer::WtfWhoClosedMyShit
75
+ expect(consumer).to be_finished
76
+ end
77
+ end
78
+
79
+ describe 'each' do
80
+ it 'loops through and yields all events except the finish event' do
81
+ producer.record_result :inspect, 100, 2
82
+ producer.finish!
83
+
84
+ events = []
85
+ consumer.each { |e| events << e }
86
+ finish_event = events.find { |e| e.kind_of? Events::Finish }
87
+ line_result = events.find { |e| e.kind_of? Events::LineResult }
88
+ exitstatus = events.find { |e| e.kind_of? Events::Exitstatus }
89
+ expect(finish_event).to be_nil
90
+ expect(line_result.line_number).to eq 100
91
+ expect(exitstatus.value).to eq 0
92
+ end
93
+
94
+ it 'stops looping if there is no more input' do
95
+ writestream.close
96
+ expect(consumer.each.map { |e| e }).to eq []
97
+ end
98
+
99
+ it 'returns nil' do
100
+ producer.finish!
101
+ expect(consumer.each { 1 }).to eq nil
102
+ end
103
+
104
+ it 'returns an enumerator if not given a block' do
105
+ producer.finish!
106
+ expect(consumer.each.map &:class).to include Events::Exitstatus
107
+ end
108
+ end
109
+
110
+
111
+ describe 'record_results' do
112
+ it 'emits a type, line_number, and escaped string' do
113
+ producer.record_result :type1, 123, [*'a'..'z', *'A'..'Z', *'0'..'9'].join("")
114
+ producer.record_result :type1, 123, '"'
115
+ producer.record_result :type1, 123, '""'
116
+ producer.record_result :type1, 123, "\n"
117
+ producer.record_result :type1, 123, "\r"
118
+ producer.record_result :type1, 123, "\n\r\n"
119
+ producer.record_result :type1, 123, "\#{}"
120
+ producer.record_result :type1, 123, [*0..127].map(&:chr).join("")
121
+ producer.record_result :type1, 123, "Ω≈ç√∫˜µ≤≥"
122
+
123
+ expect(consumer.call 9).to eq [
124
+ Events::LineResult.new(:type1, 123, [*'a'..'z', *'A'..'Z', *'0'..'9'].join("").inspect),
125
+ Events::LineResult.new(:type1, 123, '"'.inspect),
126
+ Events::LineResult.new(:type1, 123, '""'.inspect),
127
+ Events::LineResult.new(:type1, 123, "\n".inspect),
128
+ Events::LineResult.new(:type1, 123, "\r".inspect),
129
+ Events::LineResult.new(:type1, 123, "\n\r\n".inspect),
130
+ Events::LineResult.new(:type1, 123, "\#{}".inspect),
131
+ Events::LineResult.new(:type1, 123, [*0..127].map(&:chr).join("").inspect),
132
+ Events::LineResult.new(:type1, 123, "Ω≈ç√∫˜µ≤≥".inspect),
133
+ ]
134
+ end
135
+
136
+ it 'indicates that there are more results once it hits the max, but does not continue reporting them' do
137
+ producer.max_line_captures = 2
138
+
139
+ producer.record_result :type1, 123, 1
140
+ expect(consumer.call 1).to eq Events::LineResult.new(:type1, 123, '1')
141
+
142
+ producer.record_result :type1, 123, 2
143
+ expect(consumer.call 1).to eq Events::LineResult.new(:type1, 123, '2')
144
+
145
+ producer.record_result :type1, 123, 3
146
+ producer.record_result :type1, 123, 4
147
+ producer.record_result :type2, 123, 1
148
+ expect(consumer.call 2).to eq [Events::UnrecordedResult.new(:type1, 123),
149
+ Events::LineResult.new(:type2, 123, '1')]
150
+ end
151
+
152
+ it 'scopes the max to a given type/line' do
153
+ producer.max_line_captures = 1
154
+
155
+ producer.record_result :type1, 1, 1
156
+ producer.record_result :type1, 1, 2
157
+ producer.record_result :type1, 2, 3
158
+ producer.record_result :type1, 2, 4
159
+ producer.record_result :type2, 1, 5
160
+ producer.record_result :type2, 1, 6
161
+ expect(consumer.call 6).to eq [
162
+ Events::LineResult.new(:type1, 1, '1'),
163
+ Events::UnrecordedResult.new(:type1, 1),
164
+ Events::LineResult.new(:type1, 2, '3'),
165
+ Events::UnrecordedResult.new(:type1, 2),
166
+ Events::LineResult.new(:type2, 1, '5'),
167
+ Events::UnrecordedResult.new(:type2, 1),
168
+ ]
169
+ end
170
+
171
+ it 'returns the value' do
172
+ o = Object.new
173
+ expect(producer.record_result :type, 123, o).to equal o
174
+ end
175
+
176
+ # Some examples, mostly for the purpose of running individually if things get confusing
177
+ example 'Example: Simple' do
178
+ producer.record_result :type, 1, "a"
179
+ expect(consumer.call).to eq Events::LineResult.new(:type, 1, '"a"')
180
+
181
+ producer.record_result :type, 1, 1
182
+ expect(consumer.call).to eq Events::LineResult.new(:type, 1, '1')
183
+ end
184
+
185
+ example 'Example: Complex' do
186
+ str1 = (0...128).map(&:chr).join('') << "Ω≈ç√∫˜µ≤≥åß∂ƒ©˙∆˚¬…æœ∑´®†¥¨ˆøπ“‘¡™£¢ªº’”"
187
+ str2 = str1.dup
188
+ producer.record_result :type, 1, str2
189
+ expect(str2).to eq str1 # just making sure it doesn't mutate since this one is so complex
190
+ expect(consumer.call).to eq Events::LineResult.new(:type, 1, str1.inspect)
191
+ end
192
+
193
+ context 'calls #inspect when no block is given' do
194
+ it "doesn't blow up when there is no #inspect available e.g. BasicObject" do
195
+ obj = BasicObject.new
196
+ producer.record_result :type, 1, obj
197
+ expect(consumer.call).to eq Events::LineResult.new(:type, 1, "#<no inspect available>")
198
+ end
199
+
200
+
201
+ it "doesn't blow up when #inspect returns a not-String (e.g. pathalogical libraries like FactoryGirl)" do
202
+ obj = BasicObject.new
203
+ def obj.inspect
204
+ nil
205
+ end
206
+ producer.record_result :type, 1, obj
207
+ expect(consumer.call).to eq Events::LineResult.new(:type, 1, "#<no inspect available>")
208
+ end
209
+
210
+ it 'only calls inspect once' do
211
+ count, obj = 0, Object.new
212
+ obj.define_singleton_method :inspect do
213
+ count += 1
214
+ 'a'
215
+ end
216
+ producer.record_result :type, 1, obj
217
+ expect(count).to eq 1
218
+ end
219
+ end
220
+
221
+ context 'inspect performed by the block' do
222
+ it 'yields the object to the block and uses the block\'s result as the inspect value instead of calling inspect' do
223
+ o = Object.new
224
+ def o.inspect() 'real-inspect' end
225
+ def o.other_inspect() 'other-inspect' end
226
+ producer.record_result(:type, 1, o) { |x| x.other_inspect }
227
+ expect(consumer.call).to eq Events::LineResult.new(:type, 1, 'other-inspect')
228
+ end
229
+
230
+ it 'doesn\'t blow up if the block raises' do
231
+ o = Object.new
232
+ producer.record_result(:type, 1, o) { raise Exception, "zomg" }
233
+ expect(consumer.call).to eq Events::LineResult.new(:type, 1, '#<no inspect available>')
234
+ end
235
+
236
+ it 'doesn\'t blow up if the block returns a non-string' do
237
+ o = Object.new
238
+ producer.record_result(:type, 1, o) { nil }
239
+ expect(consumer.call).to eq Events::LineResult.new(:type, 1, '#<no inspect available>')
240
+
241
+ stringish = Object.new
242
+ def stringish.to_str() 'actual string' end
243
+ producer.record_result(:type, 1, o) { stringish }
244
+ expect(consumer.call).to eq Events::LineResult.new(:type, 1, 'actual string')
245
+ end
246
+
247
+ it 'invokes the block only once' do
248
+ o = Object.new
249
+ count = 0
250
+
251
+ producer.record_result(:type, 1, o) { count += 1 }
252
+ expect(count).to eq 1
253
+
254
+ producer.record_result(:type, 1, o) { count += 1; 'inspected-value' }
255
+ expect(count).to eq 2
256
+ end
257
+ end
258
+ end
259
+
260
+ describe 'exceptions' do
261
+ def assert_exception(recorded_exception, recorded_line_no, class_name, message_matcher, backtrace_index, backtrace_line)
262
+ expect(recorded_exception).to be_a_kind_of Events::Exception
263
+ expect(recorded_exception.line_number).to eq recorded_line_no
264
+ expect(recorded_exception.class_name).to eq class_name
265
+ expect(recorded_exception.message).to match message_matcher
266
+
267
+ backtrace = recorded_exception.backtrace
268
+ expect(backtrace).to be_a_kind_of Array
269
+ expect(backtrace).to be_all { |frame| String === frame }
270
+ frame = backtrace[backtrace_index]
271
+ expect(frame).to match __FILE__
272
+ expect(frame).to match /\b#{backtrace_line}\b/
273
+ end
274
+
275
+ it 'emits the line_number, an escaped class_name, an escaped message, and escaped backtrace' do
276
+ begin
277
+ raise ZeroDivisionError, 'omg'
278
+ rescue
279
+ producer.record_exception 12, $!
280
+ end
281
+ assert_exception consumer.call, 12, 'ZeroDivisionError', /\Aomg\Z/, 0, __LINE__-4
282
+ end
283
+
284
+ example 'Example: Common edge case: name error' do
285
+ begin
286
+ not_a_local_or_meth
287
+ rescue
288
+ producer.record_exception 99, $!
289
+ end
290
+ backtrace_frame = 1 # b/c this one will get caught by method missing
291
+ assert_exception consumer.call, 99, 'NameError', /\bnot_a_local_or_meth\b/, 1, __LINE__-5
292
+ end
293
+ end
294
+
295
+ describe 'stdout' do
296
+ it 'is an escaped string' do
297
+ producer.record_stdout("this is the stdout¡")
298
+ expect(consumer.call).to eq Events::Stdout.new("this is the stdout¡")
299
+ end
300
+ end
301
+
302
+ describe 'stderr' do
303
+ it 'is an escaped string' do
304
+ producer.record_stderr("this is the stderr¡")
305
+ expect(consumer.call).to eq Events::Stderr.new("this is the stderr¡")
306
+ end
307
+ end
308
+
309
+ describe 'finish!' do
310
+ def final_event(producer, consumer, event_class)
311
+ producer.finish!
312
+ consumer.call(5).find { |e| e.class == event_class }
313
+ end
314
+
315
+ describe 'bug_in_sib' do
316
+ it 'truthy values are transated to true' do
317
+ producer.bug_in_sib = 'a value'
318
+ expect(final_event(producer, consumer, Events::BugInSiB).value).to equal true
319
+ end
320
+
321
+ it 'falsy values are translated to false' do
322
+ producer.bug_in_sib = nil
323
+ expect(final_event(producer, consumer, Events::BugInSiB).value).to equal false
324
+ end
325
+
326
+ it 'is false by default, and is always emitted' do
327
+ expect(final_event(producer, consumer, Events::BugInSiB).value).to equal false
328
+ end
329
+ end
330
+
331
+ describe 'max_line_captures' do
332
+ it 'interprets numbers' do
333
+ producer.max_line_captures = 12
334
+ expect(final_event(producer, consumer, Events::MaxLineCaptures).value).to eq 12
335
+ end
336
+
337
+ it 'interprets infinity' do
338
+ producer.max_line_captures = Float::INFINITY
339
+ expect(final_event(producer, consumer, Events::MaxLineCaptures).value).to eq Float::INFINITY
340
+ end
341
+
342
+ it 'is infinity by default' do
343
+ expect(final_event(producer, consumer, Events::MaxLineCaptures).value).to eq Float::INFINITY
344
+ end
345
+ end
346
+
347
+ describe 'num_lines' do
348
+ it 'interprets numbers' do
349
+ producer.num_lines = 21
350
+ expect(final_event(producer, consumer, Events::NumLines).value).to eq 21
351
+ end
352
+
353
+ it 'is 0 by default' do
354
+ expect(final_event(producer, consumer, Events::NumLines).value).to eq 0
355
+ end
356
+
357
+ it 'updates its value if it sees a result from a line larger than its value' do
358
+ producer.num_lines = 2
359
+ producer.record_result :sometype, 5, :someval
360
+ expect(final_event(producer, consumer, Events::NumLines).value).to eq 5
361
+ end
362
+
363
+ it 'updates its value if it sees an exception from a line larger than its value' do
364
+ producer.num_lines = 2
365
+ begin; raise; rescue; e = $!; end
366
+ producer.record_exception 5, e
367
+ expect(final_event(producer, consumer, Events::NumLines).value).to eq 5
368
+ end
369
+ end
370
+
371
+ describe 'exitstatus' do
372
+ it 'is 0 by default' do
373
+ expect(final_event(producer, consumer, Events::Exitstatus).value).to eq 0
374
+ end
375
+
376
+ it 'can be overridden' do
377
+ producer.exitstatus = 74
378
+ expect(final_event(producer, consumer, Events::Exitstatus).value).to eq 74
379
+ end
380
+ end
381
+
382
+ describe 'finish' do
383
+ it 'is the last thing that will be read' do
384
+ expect(final_event(producer, consumer, Events::Finish)).to be_a_kind_of Events::Finish
385
+ expect { p consumer.call }.to raise_error SeeingIsBelieving::EventStream::Consumer::NoMoreInput
386
+ end
387
+ end
388
+ end
389
+ end
390
+ end