seeing_is_believing 3.0.0.beta.4 → 3.0.0.beta.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (72) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +0 -8
  3. data/Rakefile +1 -1
  4. data/Readme.md +65 -25
  5. data/bin/seeing_is_believing +1 -0
  6. data/docs/sib-streaming.gif +0 -0
  7. data/features/deprecated-flags.feature +62 -2
  8. data/features/errors.feature +12 -7
  9. data/features/examples.feature +143 -4
  10. data/features/flags.feature +89 -29
  11. data/features/regression.feature +58 -14
  12. data/features/support/env.rb +4 -0
  13. data/features/xmpfilter-style.feature +181 -36
  14. data/lib/seeing_is_believing.rb +44 -33
  15. data/lib/seeing_is_believing/binary.rb +31 -88
  16. data/lib/seeing_is_believing/binary/align_chunk.rb +30 -11
  17. data/lib/seeing_is_believing/binary/annotate_end_of_file.rb +10 -16
  18. data/lib/seeing_is_believing/binary/annotate_every_line.rb +5 -25
  19. data/lib/seeing_is_believing/binary/annotate_marked_lines.rb +136 -0
  20. data/lib/seeing_is_believing/binary/comment_lines.rb +8 -10
  21. data/lib/seeing_is_believing/binary/commentable_lines.rb +20 -26
  22. data/lib/seeing_is_believing/binary/config.rb +392 -0
  23. data/lib/seeing_is_believing/binary/data_structures.rb +57 -0
  24. data/lib/seeing_is_believing/binary/engine.rb +104 -0
  25. data/lib/seeing_is_believing/binary/{comment_formatter.rb → format_comment.rb} +6 -6
  26. data/lib/seeing_is_believing/binary/remove_annotations.rb +29 -28
  27. data/lib/seeing_is_believing/binary/rewrite_comments.rb +42 -43
  28. data/lib/seeing_is_believing/code.rb +105 -49
  29. data/lib/seeing_is_believing/debugger.rb +6 -5
  30. data/lib/seeing_is_believing/error.rb +6 -17
  31. data/lib/seeing_is_believing/evaluate_by_moving_files.rb +78 -129
  32. data/lib/seeing_is_believing/event_stream/consumer.rb +114 -64
  33. data/lib/seeing_is_believing/event_stream/events.rb +169 -11
  34. data/lib/seeing_is_believing/event_stream/handlers/debug.rb +57 -0
  35. data/lib/seeing_is_believing/event_stream/handlers/record_exitstatus.rb +18 -0
  36. data/lib/seeing_is_believing/event_stream/handlers/stream_json_events.rb +45 -0
  37. data/lib/seeing_is_believing/event_stream/handlers/update_result.rb +39 -0
  38. data/lib/seeing_is_believing/event_stream/producer.rb +25 -24
  39. data/lib/seeing_is_believing/hash_struct.rb +206 -0
  40. data/lib/seeing_is_believing/result.rb +20 -3
  41. data/lib/seeing_is_believing/the_matrix.rb +20 -12
  42. data/lib/seeing_is_believing/version.rb +1 -1
  43. data/lib/seeing_is_believing/wrap_expressions.rb +55 -115
  44. data/lib/seeing_is_believing/wrap_expressions_with_inspect.rb +14 -0
  45. data/seeing_is_believing.gemspec +1 -1
  46. data/spec/binary/alignment_specs.rb +27 -0
  47. data/spec/binary/comment_lines_spec.rb +3 -2
  48. data/spec/binary/config_spec.rb +657 -0
  49. data/spec/binary/engine_spec.rb +97 -0
  50. data/spec/binary/{comment_formatter_spec.rb → format_comment_spec.rb} +2 -2
  51. data/spec/binary/marker_spec.rb +71 -0
  52. data/spec/binary/options_spec.rb +0 -0
  53. data/spec/binary/remove_annotations_spec.rb +31 -18
  54. data/spec/binary/rewrite_comments_spec.rb +26 -11
  55. data/spec/code_spec.rb +190 -6
  56. data/spec/debugger_spec.rb +4 -0
  57. data/spec/evaluate_by_moving_files_spec.rb +38 -20
  58. data/spec/event_stream_spec.rb +265 -116
  59. data/spec/hash_struct_spec.rb +514 -0
  60. data/spec/seeing_is_believing_spec.rb +108 -46
  61. data/spec/spec_helper.rb +9 -0
  62. data/spec/wrap_expressions_spec.rb +207 -172
  63. metadata +30 -18
  64. data/docs/for-presentations +0 -33
  65. data/lib/seeing_is_believing/binary/annotate_xmpfilter_style.rb +0 -128
  66. data/lib/seeing_is_believing/binary/interpret_flags.rb +0 -156
  67. data/lib/seeing_is_believing/binary/parse_args.rb +0 -263
  68. data/lib/seeing_is_believing/event_stream/update_result.rb +0 -24
  69. data/lib/seeing_is_believing/inspect_expressions.rb +0 -21
  70. data/lib/seeing_is_believing/parser_helpers.rb +0 -82
  71. data/spec/binary/interpret_flags_spec.rb +0 -332
  72. data/spec/binary/parse_args_spec.rb +0 -415
@@ -38,4 +38,8 @@ RSpec.describe SeeingIsBelieving::Debugger do
38
38
  described_class.new.context('C') { fail }
39
39
  end
40
40
  end
41
+
42
+ specify '::Null is a disabled debugger' do
43
+ expect(described_class::Null).to_not be_enabled
44
+ end
41
45
  end
@@ -2,6 +2,7 @@
2
2
 
3
3
  require 'spec_helper'
4
4
  require 'seeing_is_believing/evaluate_by_moving_files'
5
+ require 'seeing_is_believing/event_stream/handlers/update_result'
5
6
  require 'fileutils'
6
7
 
7
8
  RSpec.describe SeeingIsBelieving::EvaluateByMovingFiles do
@@ -14,10 +15,17 @@ RSpec.describe SeeingIsBelieving::EvaluateByMovingFiles do
14
15
  'seeing_is_believing/the_matrix'
15
16
  end
16
17
 
18
+ def null_options(overrides={})
19
+ { event_handler: lambda { |*| } }
20
+ end
21
+
17
22
  def invoke(program, options={})
23
+ result = SeeingIsBelieving::Result.new
24
+ options[:event_handler] ||= SeeingIsBelieving::EventStream::Handlers::UpdateResult.new(result)
18
25
  evaluator = described_class.new(program, filename, options)
19
- FileUtils.rm_f evaluator.temp_filename
26
+ FileUtils.rm_f evaluator.backup_filename
20
27
  evaluator.call
28
+ result
21
29
  end
22
30
 
23
31
  it 'evaluates the code when the file DNE' do
@@ -31,8 +39,8 @@ RSpec.describe SeeingIsBelieving::EvaluateByMovingFiles do
31
39
  end
32
40
 
33
41
  it 'raises an error when the temp file already exists' do
34
- evaluator = described_class.new('', filename)
35
- FileUtils.touch evaluator.temp_filename
42
+ evaluator = described_class.new('', filename, null_options)
43
+ FileUtils.touch evaluator.backup_filename
36
44
  expect { evaluator.call }.to raise_error SeeingIsBelieving::TempFileAlreadyExists
37
45
  end
38
46
 
@@ -47,29 +55,29 @@ RSpec.describe SeeingIsBelieving::EvaluateByMovingFiles do
47
55
  end
48
56
 
49
57
  it 'uses HardCoreEnsure to move the file back' do
50
- evaluator = described_class.new 'PROGRAM', filename
58
+ evaluator = described_class.new 'PROGRAM', filename, null_options
51
59
  File.open(filename, 'w') { |f| f.write 'ORIGINAL' }
52
- FileUtils.rm_rf evaluator.temp_filename
60
+ FileUtils.rm_rf evaluator.backup_filename
53
61
  expect(SeeingIsBelieving::HardCoreEnsure).to receive(:call) do |options|
54
62
  # initial state
55
- expect(File.exist? evaluator.temp_filename).to eq false
63
+ expect(File.exist? evaluator.backup_filename).to eq false
56
64
  expect(File.read filename).to eq 'ORIGINAL'
57
65
 
58
66
  # after code
59
67
  options[:code].call rescue nil
60
- expect(File.read evaluator.temp_filename).to eq 'ORIGINAL'
68
+ expect(File.read evaluator.backup_filename).to eq 'ORIGINAL'
61
69
  expect(File.read filename).to eq 'PROGRAM'
62
70
 
63
71
  # after ensure
64
72
  options[:ensure].call
65
73
  expect(File.read filename).to eq 'ORIGINAL'
66
- expect(File.exist? evaluator.temp_filename).to eq false
74
+ expect(File.exist? evaluator.backup_filename).to eq false
67
75
  end
68
76
  evaluator.call
69
77
  end
70
78
 
71
79
  it 'uses HardCoreEnsure to delete the file if it wrote it where one did not previously exist' do
72
- evaluator = described_class.new 'PROGRAM', filename
80
+ evaluator = described_class.new 'PROGRAM', filename, null_options
73
81
  FileUtils.rm_rf filename
74
82
  expect(SeeingIsBelieving::HardCoreEnsure).to receive(:call) do |options|
75
83
  # initial state
@@ -91,13 +99,13 @@ RSpec.describe SeeingIsBelieving::EvaluateByMovingFiles do
91
99
  other_filename2 = File.join filedir, 'other2.rb'
92
100
  File.open(other_filename1, 'w') { |f| f.puts "puts 123" }
93
101
  File.open(other_filename2, 'w') { |f| f.puts "puts 456" }
94
- result = invoke '', require: [matrix_file, other_filename1, other_filename2]
102
+ result = invoke '', require_files: [matrix_file, other_filename1, other_filename2]
95
103
  expect(result.stdout).to eq "123\n456\n"
96
104
  end
97
105
 
98
106
  it 'can set the load path' do
99
107
  File.open(File.join(filedir, 'other1.rb'), 'w') { |f| f.puts "puts 123" }
100
- result = invoke '', require: [matrix_file, 'other1'], load_path: [filedir]
108
+ result = invoke '', require_files: [matrix_file, 'other1'], load_path_dirs: [filedir]
101
109
  expect(result.stdout).to eq "123\n"
102
110
  end
103
111
 
@@ -111,16 +119,26 @@ RSpec.describe SeeingIsBelieving::EvaluateByMovingFiles do
111
119
  end
112
120
  end
113
121
 
114
- it 'if it fails, it tells the debugger some information and raises an error' do
115
- error_stream = StringIO.new
116
- evaluator = described_class.new 'raise "omg"', filename, debugger: SeeingIsBelieving::Debugger.new(stream: error_stream)
117
- expect(evaluator).to receive(:evaluate_file).and_raise("whatevz")
118
- FileUtils.rm_f evaluator.temp_filename
119
- expect { evaluator.call }.to raise_error SeeingIsBelieving::BugInSib
120
- expect(error_stream.string).to include "Program could not be evaluated"
121
- end
122
-
123
122
  it 'does not blow up on exceptions raised in at_exit blocks' do
124
123
  expect { invoke 'at_exit { raise "zomg" }' }.to_not raise_error
125
124
  end
125
+
126
+ it 'can provide stdin as a string or stream' do
127
+ expect(invoke('p gets', provided_input: 'a').stdout).to eq %("a"\n)
128
+ require 'stringio'
129
+ result = invoke 'p gets', provided_input: StringIO.new('b')
130
+ expect(result.stdout).to eq %("b"\n)
131
+ end
132
+
133
+ it 'can set a timeout' do
134
+ expect(Timeout).to receive(:timeout).with(123).and_raise(Timeout::Error)
135
+ expect(Process).to receive(:kill)
136
+ expect { expect(invoke('p gets', timeout_seconds: 123).stdout).to eq %("a"\n) }
137
+ .to raise_error Timeout::Error
138
+ end
139
+
140
+ it 'raises an ArgumentError if given arguments it doesn\'t know' do
141
+ expect { invoke '1', watisthis: :idontknow }
142
+ .to raise_error ArgumentError, /watisthis/
143
+ end
126
144
  end
@@ -2,6 +2,8 @@
2
2
 
3
3
  require 'seeing_is_believing/event_stream/producer'
4
4
  require 'seeing_is_believing/event_stream/consumer'
5
+ require 'seeing_is_believing/event_stream/handlers/debug'
6
+ require 'seeing_is_believing/debugger'
5
7
 
6
8
  module SeeingIsBelieving::EventStream
7
9
  RSpec.describe SeeingIsBelieving::EventStream do
@@ -16,6 +18,7 @@ module SeeingIsBelieving::EventStream
16
18
 
17
19
  def finish!
18
20
  producer.finish!
21
+ consumer.process_exitstatus(0)
19
22
  close_streams eventstream_producer, stdout_producer, stderr_producer
20
23
  end
21
24
 
@@ -77,49 +80,88 @@ module SeeingIsBelieving::EventStream
77
80
  expect(producer_threads).to be_none(&:alive?)
78
81
  end
79
82
 
80
- it 'raises NoMoreInput if input is closed before it finishes reading the number of requested inputs' do
83
+ it 'transcodes any received messages to UTF8' do
84
+ utf8 = "こんにちは" # from https://github.com/svenfuchs/i18n/blob/ee7fef8e9b9ee2f7d16e6c36d669ee7fb24ec613/lib/i18n/tests/interpolation.rb#L72
85
+ eucjp = utf8.encode(Encoding::EUCJP)
86
+ producer.record_sib_version(eucjp)
87
+ version = consumer.call.value
88
+ expect(version).to eq utf8
89
+ expect(version).to_not eq eucjp # general sanity checks to make
90
+ expect(utf8.bytes).to_not eq eucjp.bytes # sure I don't accidentally pass
91
+ end
92
+
93
+ def ascii8bit(str)
94
+ str.force_encoding Encoding::ASCII_8BIT
95
+ end
96
+
97
+ it 'force encodes the message to UTF8 when it can\'t validly transcode' do
98
+ producer.record_sib_version(ascii8bit("åß∂ƒ"))
99
+ version = consumer.call.value
100
+ expect(version).to eq "åß∂ƒ"
101
+ expect(version).to_not eq ascii8bit("åß∂ƒ")
102
+ end
103
+
104
+ it 'scrubs any invalid bytes to "�" when the force encoding isn\'t valid' do
105
+ producer.record_sib_version(ascii8bit "a\xFF å") # unicode bytes can't begin with
106
+ expect(consumer.call.value).to eq "a� å" # space just so its easier to see
107
+ end
108
+
109
+ it 'raises NoMoreEvents if input is closed before it finishes reading the number of requested inputs' do
81
110
  finish!
82
- expect { consumer.call 10 }.to raise_error SeeingIsBelieving::EventStream::Consumer::NoMoreInput
111
+ expect { consumer.call 10 }.to raise_error SeeingIsBelieving::NoMoreEvents
83
112
  end
84
113
 
85
- it 'raises NoMoreInput once it its input streams are all closed' do
86
- producer.finish!
114
+ it 'raises NoMoreEvents once it its input streams are all closed and its seen the exit status' do
87
115
  close_streams eventstream_producer, stdout_producer, stderr_producer
88
- consumer.call 2
89
- expect { consumer.call }.to raise_error SeeingIsBelieving::EventStream::Consumer::NoMoreInput
116
+ producer.finish!
117
+ consumer.process_exitstatus 0
118
+ consumer.each { }
119
+ expect { consumer.call }.to raise_error SeeingIsBelieving::NoMoreEvents
90
120
  end
91
121
 
92
- it 'raises NoMoreInput if its end of the stream is closed and there is no more stdout/stderr' do
93
- close_streams eventstream_consumer, stdout_producer, stderr_producer
94
- expect { consumer.call }.to raise_error SeeingIsBelieving::EventStream::Consumer::WtfWhoClosedMyShit
95
- expect { consumer.call }.to raise_error SeeingIsBelieving::EventStream::Consumer::NoMoreInput
122
+ it 'gracefully handles its side of the streams getting closed' do
123
+ close_streams eventstream_consumer, stdout_consumer, stderr_consumer
124
+ consumer.process_exitstatus 0
125
+ consumer.each { }
126
+ expect { consumer.call }.to raise_error SeeingIsBelieving::NoMoreEvents
96
127
  end
97
128
 
98
- it 'raises WtfWhoClosedMyShit if its end of the stream is closed' do
99
- close_streams eventstream_consumer, stdout_producer, stderr_producer
100
- expect { consumer.call }.to raise_error SeeingIsBelieving::EventStream::Consumer::WtfWhoClosedMyShit
129
+ specify 'if an incomprehensible event is received, it raises an UnknownEvent' do
130
+ eventstream_producer.puts "this is nonsense!"
131
+ expect{ consumer.call }.to raise_error SeeingIsBelieving::UnknownEvent, /nonsense/
101
132
  end
102
133
  end
103
134
 
104
135
  describe 'each' do
105
136
  it 'loops through and yields all events' do
137
+ # declare 2 events
106
138
  producer.record_result :inspect, 100, 2
139
+ producer.record_sib_version('some ver')
140
+
141
+ # close streams so that it won't block waiting for more events
107
142
  finish!
108
143
 
144
+ # record events
109
145
  events = []
110
146
  consumer.each { |e| events << e }
111
- line_result = events.find { |e| e.kind_of? Events::LineResult }
112
- exitstatus = events.find { |e| e.kind_of? Events::Exitstatus }
147
+
148
+ # it yielded the line result
149
+ line_result = events.find { |e| e.kind_of? Events::LineResult }
113
150
  expect(line_result.line_number).to eq 100
114
- expect(exitstatus.value).to eq 0
151
+
152
+ # it yielded the version
153
+ version = events.find { |e| e.kind_of? Events::SiBVersion }
154
+ expect(version.value).to eq 'some ver'
115
155
  end
116
156
 
117
157
  it 'stops looping if there is no more input' do
158
+ producer.record_result :inspect, 100, 2
159
+ producer.record_sib_version('some ver')
118
160
  finish!
119
- expect(consumer.each.map { |e| e }).to eq [
120
- Events::NumLines.new(0),
121
- Events::Exitstatus.new(0),
122
- ]
161
+ expect(consumer.each.map { |e| e.class }.sort_by(&:to_s))
162
+ .to eq [ Events::EventStreamClosed, Events::Exitstatus, Events::Finished,
163
+ Events::LineResult, Events::SiBVersion, Events::StderrClosed, Events::StdoutClosed,
164
+ ]
123
165
  end
124
166
 
125
167
  it 'returns nil' do
@@ -128,8 +170,10 @@ module SeeingIsBelieving::EventStream
128
170
  end
129
171
 
130
172
  it 'returns an enumerator if not given a block' do
173
+ producer.record_sib_version('some ver')
131
174
  finish!
132
- expect(consumer.each.map &:class).to include Events::Exitstatus
175
+ classes = consumer.each.map &:class
176
+ expect(classes).to include Events::SiBVersion
133
177
  end
134
178
  end
135
179
 
@@ -147,15 +191,15 @@ module SeeingIsBelieving::EventStream
147
191
  producer.record_result :type1, 123, "Ω≈ç√∫˜µ≤≥"
148
192
 
149
193
  expect(consumer.call 9).to eq [
150
- Events::LineResult.new(:type1, 123, [*'a'..'z', *'A'..'Z', *'0'..'9'].join("").inspect),
151
- Events::LineResult.new(:type1, 123, '"'.inspect),
152
- Events::LineResult.new(:type1, 123, '""'.inspect),
153
- Events::LineResult.new(:type1, 123, "\n".inspect),
154
- Events::LineResult.new(:type1, 123, "\r".inspect),
155
- Events::LineResult.new(:type1, 123, "\n\r\n".inspect),
156
- Events::LineResult.new(:type1, 123, "\#{}".inspect),
157
- Events::LineResult.new(:type1, 123, [*0..127].map(&:chr).join("").inspect),
158
- Events::LineResult.new(:type1, 123, "Ω≈ç√∫˜µ≤≥".inspect),
194
+ Events::LineResult.new(type: :type1, line_number: 123, inspected: [*'a'..'z', *'A'..'Z', *'0'..'9'].join("").inspect),
195
+ Events::LineResult.new(type: :type1, line_number: 123, inspected: '"'.inspect),
196
+ Events::LineResult.new(type: :type1, line_number: 123, inspected: '""'.inspect),
197
+ Events::LineResult.new(type: :type1, line_number: 123, inspected: "\n".inspect),
198
+ Events::LineResult.new(type: :type1, line_number: 123, inspected: "\r".inspect),
199
+ Events::LineResult.new(type: :type1, line_number: 123, inspected: "\n\r\n".inspect),
200
+ Events::LineResult.new(type: :type1, line_number: 123, inspected: "\#{}".inspect),
201
+ Events::LineResult.new(type: :type1, line_number: 123, inspected: [*0..127].map(&:chr).join("").inspect),
202
+ Events::LineResult.new(type: :type1, line_number: 123, inspected: "Ω≈ç√∫˜µ≤≥".inspect),
159
203
  ]
160
204
  end
161
205
 
@@ -163,16 +207,16 @@ module SeeingIsBelieving::EventStream
163
207
  producer.max_line_captures = 2
164
208
 
165
209
  producer.record_result :type1, 123, 1
166
- expect(consumer.call 1).to eq Events::LineResult.new(:type1, 123, '1')
210
+ expect(consumer.call 1).to eq Events::LineResult.new(type: :type1, line_number: 123, inspected: '1')
167
211
 
168
212
  producer.record_result :type1, 123, 2
169
- expect(consumer.call 1).to eq Events::LineResult.new(:type1, 123, '2')
213
+ expect(consumer.call 1).to eq Events::LineResult.new(type: :type1, line_number: 123, inspected: '2')
170
214
 
171
215
  producer.record_result :type1, 123, 3
172
216
  producer.record_result :type1, 123, 4
173
217
  producer.record_result :type2, 123, 1
174
- expect(consumer.call 2).to eq [Events::UnrecordedResult.new(:type1, 123),
175
- Events::LineResult.new(:type2, 123, '1')]
218
+ expect(consumer.call 2).to eq [Events::ResultsTruncated.new(type: :type1, line_number: 123),
219
+ Events::LineResult.new(type: :type2, line_number: 123, inspected: '1')]
176
220
  end
177
221
 
178
222
  it 'scopes the max to a given type/line' do
@@ -185,12 +229,12 @@ module SeeingIsBelieving::EventStream
185
229
  producer.record_result :type2, 1, 5
186
230
  producer.record_result :type2, 1, 6
187
231
  expect(consumer.call 6).to eq [
188
- Events::LineResult.new(:type1, 1, '1'),
189
- Events::UnrecordedResult.new(:type1, 1),
190
- Events::LineResult.new(:type1, 2, '3'),
191
- Events::UnrecordedResult.new(:type1, 2),
192
- Events::LineResult.new(:type2, 1, '5'),
193
- Events::UnrecordedResult.new(:type2, 1),
232
+ Events::LineResult.new( type: :type1, line_number: 1, inspected: '1'),
233
+ Events::ResultsTruncated.new(type: :type1, line_number: 1),
234
+ Events::LineResult.new( type: :type1, line_number: 2, inspected: '3'),
235
+ Events::ResultsTruncated.new(type: :type1, line_number: 2),
236
+ Events::LineResult.new( type: :type2, line_number: 1, inspected: '5'),
237
+ Events::ResultsTruncated.new(type: :type2, line_number: 1),
194
238
  ]
195
239
  end
196
240
 
@@ -202,10 +246,10 @@ module SeeingIsBelieving::EventStream
202
246
  # Some examples, mostly for the purpose of running individually if things get confusing
203
247
  example 'Example: Simple' do
204
248
  producer.record_result :type, 1, "a"
205
- expect(consumer.call).to eq Events::LineResult.new(:type, 1, '"a"')
249
+ expect(consumer.call).to eq Events::LineResult.new(type: :type, line_number: 1, inspected: '"a"')
206
250
 
207
251
  producer.record_result :type, 1, 1
208
- expect(consumer.call).to eq Events::LineResult.new(:type, 1, '1')
252
+ expect(consumer.call).to eq Events::LineResult.new(type: :type, line_number: 1, inspected: '1')
209
253
  end
210
254
 
211
255
  example 'Example: Complex' do
@@ -213,14 +257,14 @@ module SeeingIsBelieving::EventStream
213
257
  str2 = str1.dup
214
258
  producer.record_result :type, 1, str2
215
259
  expect(str2).to eq str1 # just making sure it doesn't mutate since this one is so complex
216
- expect(consumer.call).to eq Events::LineResult.new(:type, 1, str1.inspect)
260
+ expect(consumer.call).to eq Events::LineResult.new(type: :type, line_number: 1, inspected: str1.inspect)
217
261
  end
218
262
 
219
263
  context 'calls #inspect when no block is given' do
220
264
  it "doesn't blow up when there is no #inspect available e.g. BasicObject" do
221
265
  obj = BasicObject.new
222
266
  producer.record_result :type, 1, obj
223
- expect(consumer.call).to eq Events::LineResult.new(:type, 1, "#<no inspect available>")
267
+ expect(consumer.call).to eq Events::LineResult.new(type: :type, line_number: 1, inspected: "#<no inspect available>")
224
268
  end
225
269
 
226
270
 
@@ -230,7 +274,7 @@ module SeeingIsBelieving::EventStream
230
274
  nil
231
275
  end
232
276
  producer.record_result :type, 1, obj
233
- expect(consumer.call).to eq Events::LineResult.new(:type, 1, "#<no inspect available>")
277
+ expect(consumer.call).to eq Events::LineResult.new(type: :type, line_number: 1, inspected: "#<no inspect available>")
234
278
  end
235
279
 
236
280
  it 'only calls inspect once' do
@@ -250,24 +294,24 @@ module SeeingIsBelieving::EventStream
250
294
  def o.inspect() 'real-inspect' end
251
295
  def o.other_inspect() 'other-inspect' end
252
296
  producer.record_result(:type, 1, o) { |x| x.other_inspect }
253
- expect(consumer.call).to eq Events::LineResult.new(:type, 1, 'other-inspect')
297
+ expect(consumer.call).to eq Events::LineResult.new(type: :type, line_number: 1, inspected: 'other-inspect')
254
298
  end
255
299
 
256
300
  it 'doesn\'t blow up if the block raises' do
257
301
  o = Object.new
258
302
  producer.record_result(:type, 1, o) { raise Exception, "zomg" }
259
- expect(consumer.call).to eq Events::LineResult.new(:type, 1, '#<no inspect available>')
303
+ expect(consumer.call).to eq Events::LineResult.new(type: :type, line_number: 1, inspected: '#<no inspect available>')
260
304
  end
261
305
 
262
306
  it 'doesn\'t blow up if the block returns a non-string' do
263
307
  o = Object.new
264
308
  producer.record_result(:type, 1, o) { nil }
265
- expect(consumer.call).to eq Events::LineResult.new(:type, 1, '#<no inspect available>')
309
+ expect(consumer.call).to eq Events::LineResult.new(type: :type, line_number: 1, inspected: '#<no inspect available>')
266
310
 
267
311
  stringish = Object.new
268
312
  def stringish.to_str() 'actual string' end
269
313
  producer.record_result(:type, 1, o) { stringish }
270
- expect(consumer.call).to eq Events::LineResult.new(:type, 1, 'actual string')
314
+ expect(consumer.call).to eq Events::LineResult.new(type: :type, line_number: 1, inspected: 'actual string')
271
315
  end
272
316
 
273
317
  it 'invokes the block only once' do
@@ -291,17 +335,17 @@ module SeeingIsBelieving::EventStream
291
335
  it 'emits the event and sets the max_line_captures' do
292
336
  producer.record_max_line_captures 123
293
337
  expect(producer.max_line_captures).to eq 123
294
- expect(consumer.call).to eq Events::MaxLineCaptures.new(123)
338
+ expect(consumer.call).to eq Events::MaxLineCaptures.new(value: 123)
295
339
  end
296
340
 
297
341
  it 'interprets numbers' do
298
342
  producer.record_max_line_captures 12
299
- expect(consumer.call).to eq Events::MaxLineCaptures.new(12)
343
+ expect(consumer.call).to eq Events::MaxLineCaptures.new(value: 12)
300
344
  end
301
345
 
302
346
  it 'interprets infinity' do
303
347
  producer.record_max_line_captures Float::INFINITY
304
- expect(consumer.call).to eq Events::MaxLineCaptures.new(Float::INFINITY)
348
+ expect(consumer.call).to eq Events::MaxLineCaptures.new(value: Float::INFINITY)
305
349
  end
306
350
  end
307
351
 
@@ -352,23 +396,17 @@ module SeeingIsBelieving::EventStream
352
396
  end
353
397
 
354
398
  context 'when the exception is a SystemExit' do
355
- it 'sets the exit status to the one provided' do
356
- record_exception { exit 22 }
357
- expect(producer.exitstatus).to eq 22
358
- end
359
-
360
- it 'sets the exit status to 0 or 1 if exited with true or false' do
361
- expect(producer.exitstatus).to eq 0
362
- record_exception { exit true }
363
- expect(producer.exitstatus).to eq 0
364
- record_exception { exit false }
365
- expect(producer.exitstatus).to eq 1
366
- end
399
+ it 'returns the status and does not record the exception' do
400
+ exception = nil
401
+ begin exit 22
402
+ rescue SystemExit
403
+ exception = $!
404
+ end
367
405
 
368
- it 'sets the exit status to 1 if the exception is not a SystemExit' do
369
- expect(producer.exitstatus).to eq 0
370
- record_exception { raise }
371
- expect(producer.exitstatus).to eq 1
406
+ exitstatus = producer.record_exception(1, exception)
407
+ expect(exitstatus).to eq 22
408
+ finish!
409
+ expect(consumer.each.find { |e| e.kind_of? Events::Exception }).to eq nil
372
410
  end
373
411
  end
374
412
 
@@ -426,7 +464,7 @@ module SeeingIsBelieving::EventStream
426
464
  describe 'recording the version' do
427
465
  it 'emits the version info' do
428
466
  producer.record_sib_version '1.2.3'
429
- expect(consumer.call).to eq Events::SiBVersion.new("1.2.3")
467
+ expect(consumer.call).to eq Events::SiBVersion.new(value: "1.2.3")
430
468
  end
431
469
  end
432
470
 
@@ -440,7 +478,7 @@ module SeeingIsBelieving::EventStream
440
478
  describe 'record_ruby_version' do
441
479
  it 'emits the ruby version info' do
442
480
  producer.record_ruby_version 'o.m.g.'
443
- expect(consumer.call).to eq Events::RubyVersion.new('o.m.g.')
481
+ expect(consumer.call).to eq Events::RubyVersion.new(value: 'o.m.g.')
444
482
  end
445
483
  end
446
484
 
@@ -451,96 +489,207 @@ module SeeingIsBelieving::EventStream
451
489
  end
452
490
  it 'emits the filename' do
453
491
  producer.record_filename 'this-iz-mah-file.rb'
454
- expect(consumer.call).to eq Events::Filename.new('this-iz-mah-file.rb')
492
+ expect(consumer.call).to eq Events::Filename.new(value: 'this-iz-mah-file.rb')
455
493
  end
456
494
  end
457
495
 
458
496
  describe 'stdout' do
459
497
  it 'is emitted along with the events from the event stream' do
460
498
  stdout_producer.puts "this is the stdout¡"
461
- expect(consumer.call).to eq Events::Stdout.new("this is the stdout¡\n")
499
+ expect(consumer.call).to eq Events::Stdout.new(value: "this is the stdout¡\n")
462
500
  end
463
501
  specify 'each line is emitted as an event' do
464
502
  stdout_producer.puts "first"
465
503
  stdout_producer.puts "second\nthird"
466
- expect(consumer.call).to eq Events::Stdout.new("first\n")
467
- expect(consumer.call).to eq Events::Stdout.new("second\n")
468
- expect(consumer.call).to eq Events::Stdout.new("third\n")
504
+ expect(consumer.call).to eq Events::Stdout.new(value: "first\n")
505
+ expect(consumer.call).to eq Events::Stdout.new(value: "second\n")
506
+ expect(consumer.call).to eq Events::Stdout.new(value: "third\n")
469
507
  end
470
508
  end
471
509
 
472
510
  describe 'stderr' do
473
511
  it 'is emitted along with the events from the event stream' do
474
512
  stderr_producer.puts "this is the stderr¡"
475
- expect(consumer.call).to eq Events::Stderr.new("this is the stderr¡\n")
513
+ expect(consumer.call).to eq Events::Stderr.new(value: "this is the stderr¡\n")
476
514
  end
477
515
  specify 'each line is emitted as an event' do
478
516
  stderr_producer.puts "first"
479
517
  stderr_producer.puts "second\nthird"
480
- expect(consumer.call).to eq Events::Stderr.new("first\n")
481
- expect(consumer.call).to eq Events::Stderr.new("second\n")
482
- expect(consumer.call).to eq Events::Stderr.new("third\n")
518
+ expect(consumer.call).to eq Events::Stderr.new(value: "first\n")
519
+ expect(consumer.call).to eq Events::Stderr.new(value: "second\n")
520
+ expect(consumer.call).to eq Events::Stderr.new(value: "third\n")
483
521
  end
484
522
  end
485
523
 
524
+ describe 'record_exec' do
525
+ it 'records the event and the inspection of the args that were given to exec' do
526
+ producer.record_exec(["ls", "-l"])
527
+ expect(consumer.call).to eq Events::Exec.new(args: '["ls", "-l"]')
528
+ end
529
+ end
486
530
 
487
- describe 'finish!' do
488
- def final_event(producer, consumer, event_class)
489
- finish!
490
- consumer.call(2).find { |e| e.class == event_class }
531
+ describe 'record_num_lines' do
532
+ it 'interprets numbers' do
533
+ producer.record_num_lines 21
534
+ expect(consumer.call).to eq Events::NumLines.new(value: 21)
491
535
  end
536
+ end
492
537
 
538
+ describe 'finish!' do
493
539
  it 'stops the producer from producing' do
494
540
  read, write = IO.pipe
495
541
  producer = SeeingIsBelieving::EventStream::Producer.new write
496
542
  producer.finish!
497
- read.gets
498
- read.gets
499
543
  producer.record_filename("zomg")
500
544
  write.close
501
545
  expect(read.gets).to eq nil
502
546
  end
547
+ end
503
548
 
504
- describe 'num_lines' do
505
- it 'interprets numbers' do
506
- producer.num_lines = 21
507
- expect(final_event(producer, consumer, Events::NumLines).value).to eq 21
508
- end
549
+ describe 'final events' do
550
+ it 'emits a StdoutClosed event when consumer side of stdout closes' do
551
+ stdout_consumer.close
552
+ expect(consumer.call).to eq Events::StdoutClosed.new(side: :consumer)
553
+ end
554
+ it 'emits a StdoutClosed event when producer side of stdout closes' do
555
+ stdout_producer.close
556
+ expect(consumer.call).to eq Events::StdoutClosed.new(side: :producer)
557
+ end
509
558
 
510
- it 'is 0 by default' do
511
- expect(final_event(producer, consumer, Events::NumLines).value).to eq 0
512
- end
559
+ it 'emits a StderrClosed event when consumer side of stderr closes' do
560
+ stderr_consumer.close
561
+ expect(consumer.call).to eq Events::StderrClosed.new(side: :consumer)
562
+ end
563
+ it 'emits a StderrClosed event when producer side of stderr closes' do
564
+ stderr_producer.close
565
+ expect(consumer.call).to eq Events::StderrClosed.new(side: :producer)
566
+ end
513
567
 
514
- it 'updates its value if it sees a result from a line larger than its value' do
515
- producer.num_lines = 2
516
- producer.record_result :sometype, 100, :someval
517
- expect(final_event(producer, consumer, Events::NumLines).value).to eq 100
518
- end
568
+ it 'emits a EventStreamClosed event when consumer side of event_stream closes' do
569
+ eventstream_consumer.close
570
+ expect(consumer.call).to eq Events::EventStreamClosed.new(side: :consumer)
571
+ end
572
+ it 'emits a EventStreamClosed event when producer side of event_stream closes' do
573
+ eventstream_producer.close
574
+ expect(consumer.call).to eq Events::EventStreamClosed.new(side: :producer)
575
+ end
519
576
 
520
- it 'updates its value if it sees an exception from a line larger than its value' do
521
- producer.num_lines = 2
522
- begin; raise; rescue; e = $!; end
523
- producer.record_exception 100, e
524
- expect(final_event(producer, consumer, Events::NumLines).value).to eq 100
525
- end
577
+ it 'emits a Exitstatus event on process_exitstatus' do
578
+ consumer.process_exitstatus 92
579
+ expect(consumer.call).to eq Events::Exitstatus.new(value: 92)
526
580
  end
527
581
 
528
- describe 'exitstatus' do
529
- it 'is 0 by default' do
530
- expect(final_event(producer, consumer, Events::Exitstatus).value).to eq 0
531
- end
582
+ it 'emits a Finished event when all streams are closed and it has the exit status' do
583
+ finish!
584
+ event_classes = consumer.each.map(&:class)
585
+ expect(event_classes).to include Events::Finished
586
+ end
587
+ end
532
588
 
533
- it 'can be overridden' do
534
- producer.exitstatus = 74
535
- expect(final_event(producer, consumer, Events::Exitstatus).value).to eq 74
536
- end
589
+ describe Events do
590
+ specify 'Event raises an error if .event_name was not overridden' do
591
+ expect { Event.event_name }.to raise_error NotImplementedError
592
+ end
593
+ specify 'all events have a reasonable event name' do
594
+ pairs = [
595
+ [Events::Stdout , :stdout],
596
+ [Events::Stderr , :stderr],
597
+ [Events::MaxLineCaptures , :max_line_captures],
598
+ [Events::Filename , :filename],
599
+ [Events::NumLines , :num_lines],
600
+ [Events::SiBVersion , :sib_version],
601
+ [Events::RubyVersion , :ruby_version],
602
+ [Events::Exitstatus , :exitstatus],
603
+ [Events::Exec , :exec],
604
+ [Events::ResultsTruncated , :results_truncated],
605
+ [Events::LineResult , :line_result],
606
+ [Events::Exception , :exception],
607
+ [Events::StdoutClosed , :stdout_closed],
608
+ [Events::StderrClosed , :stderr_closed],
609
+ [Events::EventStreamClosed, :event_stream_closed],
610
+ [Events::Finished , :finished],
611
+ ]
612
+ pairs.each { |klass, name| expect(klass.event_name).to eq name }
613
+
614
+ events_we_tested = pairs.map(&:first).flatten
615
+ event_classes = Events.constants.map { |name| Events.const_get name }
616
+ expect(event_classes - events_we_tested).to eq []
617
+ end
618
+ specify 'their event_name and attributes are included in their as_json' do
619
+ expect(Events::Stdout.new(value: "abc").as_json).to eq [:stdout, {value: "abc"}]
620
+ end
621
+ specify 'MaxLineCaptures#as_json includes is_infinity, and sets value to -1 in this case' do
622
+ expect(Events::MaxLineCaptures.new(value: Float::INFINITY).as_json).to eq [:max_line_captures, {value: -1, is_infinity: true}]
623
+ expect(Events::MaxLineCaptures.new(value: 123).as_json).to eq [:max_line_captures, {value: 123, is_infinity: false}]
624
+ end
625
+ end
626
+
627
+ require 'seeing_is_believing/event_stream/handlers/stream_json_events'
628
+ describe Handlers::StreamJsonEvents do
629
+ it 'writes each event\'s json representation to the stream' do
630
+ stream = ""
631
+ handler = described_class.new stream
632
+
633
+ handler.call Events::Stdout.new(value: "abc")
634
+ expect(stream).to eq %'["stdout",{"value":"abc"}]\n'
635
+
636
+ handler.call Events::Finished.new
637
+ expect(stream).to eq %'["stdout",{"value":"abc"}]\n'+
638
+ %'["finished",{}]\n'
639
+ end
640
+
641
+ it 'calls flush after each event, when the stream responds to it' do
642
+ stream = object_spy $stdout
643
+ flushcount = 0
644
+ allow(stream).to receive(:flush) { flushcount += 1 }
645
+
646
+ handler = described_class.new stream
647
+ expect(flushcount).to eq 0
648
+
649
+ handler.call Events::Stdout.new(value: "abc")
650
+ expect(flushcount).to eq 1
651
+
652
+ handler.call Events::Finished.new
653
+ expect(flushcount).to eq 2
537
654
  end
538
655
  end
539
656
 
540
- specify 'if an incomprehensible event is received, it raises an UnknownEvent' do
541
- eventstream_producer.puts "this is nonsense!"
542
- eventstream_producer.close
543
- expect{ consumer.call }.to raise_error SeeingIsBelieving::EventStream::Consumer::UnknownEvent, /nonsense/
657
+ describe Handlers::Debug do
658
+ let(:stream) { "" }
659
+ let(:events_seen) { [] }
660
+ let(:debugger) { SeeingIsBelieving::Debugger.new stream: stream }
661
+ let(:parent_observer) { lambda { |event| events_seen << event } }
662
+ let(:debug_handler) { described_class.new(debugger, parent_observer) }
663
+
664
+ it 'passes events through to the parent observer' do
665
+ event = Events::Stdout.new(value: "zomg")
666
+ debug_handler.call(event)
667
+ expect(events_seen).to eq [event]
668
+ end
669
+
670
+ it 'generally prints things, prettily, wide and short' do
671
+ [ Events::Stdout.new(value: "short"),
672
+ Events::Stdout.new(value: "long"*1000),
673
+ Events::Exec.new(args: ["a", "b", "c"]),
674
+ Events::StdoutClosed.new(side: :consumer),
675
+ Events::Exception.new(line_number: 100,
676
+ class_name: "SomethingException",
677
+ message: "The things, they blew up!",
678
+ backtrace: ["a"*10,"b"*2000]),
679
+ Events::Finished.new,
680
+ ].each { |event| debug_handler.call event }
681
+
682
+ expect(stream).to match /^Stdout\b/ # the events al made it
683
+ expect(stream).to match /^Exec\b/
684
+ expect(stream).to match /^StdoutClosed\b/
685
+ expect(stream).to match /^Exception\b/
686
+ expect(stream).to match /^Finished\b/
687
+ expect(stream).to match /^\| - a+/ # a backtrace in there
688
+ expect(stream).to match /\.{3}$/ # truncation indication
689
+ stream.each_line do |line|
690
+ expect(line.length).to be <= 151 # long lines got truncated (151 b/c newline is counted)
691
+ end
692
+ end
544
693
  end
545
694
  end
546
695
  end