seeing_is_believing 2.2.0 → 3.0.0.beta.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 (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