seeing_is_believing 3.0.0.beta.3 → 3.0.0.beta.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +1 -0
- data/features/deprecated-flags.feature +12 -0
- data/features/flags.feature +2 -16
- data/features/regression.feature +46 -0
- data/features/xmpfilter-style.feature +55 -0
- data/lib/seeing_is_believing/binary/interpret_flags.rb +1 -5
- data/lib/seeing_is_believing/binary/parse_args.rb +12 -12
- data/lib/seeing_is_believing/binary/remove_annotations.rb +1 -1
- data/lib/seeing_is_believing/binary.rb +3 -3
- data/lib/seeing_is_believing/evaluate_by_moving_files.rb +60 -11
- data/lib/seeing_is_believing/event_stream/consumer.rb +61 -37
- data/lib/seeing_is_believing/event_stream/events.rb +3 -1
- data/lib/seeing_is_believing/event_stream/producer.rb +38 -21
- data/lib/seeing_is_believing/event_stream/update_result.rb +6 -2
- data/lib/seeing_is_believing/inspect_expressions.rb +5 -1
- data/lib/seeing_is_believing/parser_helpers.rb +11 -1
- data/lib/seeing_is_believing/result.rb +6 -1
- data/lib/seeing_is_believing/the_matrix.rb +18 -18
- data/lib/seeing_is_believing/version.rb +1 -1
- data/lib/seeing_is_believing/wrap_expressions.rb +0 -1
- data/lib/seeing_is_believing.rb +0 -2
- data/seeing_is_believing.gemspec +2 -4
- data/spec/binary/interpret_flags_spec.rb +0 -14
- data/spec/binary/parse_args_spec.rb +5 -6
- data/spec/event_stream_spec.rb +161 -70
- data/spec/seeing_is_believing_spec.rb +29 -63
- metadata +8 -37
- data/features/safe.feature +0 -33
- data/lib/seeing_is_believing/evaluate_with_eval_in.rb +0 -27
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 088b9ff70b1691264f78741d8a0ac2ae7b1afdff
|
4
|
+
data.tar.gz: e895553cdb771ebee4558794ae674be0420b3777
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: df2363ea76fdbfbea79b7a224873bd9beb56d2cc97bd81bf650f43adf46c22e7577715c114d2977232b35f8f47aa6d3c528b1e094ac26b2e1933115b2e9aa82a
|
7
|
+
data.tar.gz: d2a0cb8f4959efed7f3bfa5a47c1f7e3ac93c8d2652e4f6911b77d288b0bb02b3b20299ab589de51a4080e317213e70de697b50b04d8c3204095e3b7476902b8
|
data/.travis.yml
CHANGED
@@ -0,0 +1,12 @@
|
|
1
|
+
Feature: Flags that are deprecated
|
2
|
+
|
3
|
+
Features get added, features get removed.
|
4
|
+
Don't want to blow up just b/c of removal of some feature.
|
5
|
+
As such, these flags will continue to not blow up,
|
6
|
+
even though they won't work anymore
|
7
|
+
|
8
|
+
Scenario: --shebang
|
9
|
+
When I run "seeing_is_believing -e 123 --shebang not/a/thing"
|
10
|
+
Then stderr is empty
|
11
|
+
And stdout is "123 # => 123"
|
12
|
+
And the exit status is 0
|
data/features/flags.feature
CHANGED
@@ -196,6 +196,7 @@ Feature: Using flags
|
|
196
196
|
Scenario: --clean
|
197
197
|
Given the file "uncleaned.rb":
|
198
198
|
"""
|
199
|
+
# comment # => still a comment
|
199
200
|
1 + 1 # => not 2
|
200
201
|
2 + 2 # ~> Exception, something
|
201
202
|
|
@@ -211,6 +212,7 @@ Feature: Using flags
|
|
211
212
|
And the exit status is 0
|
212
213
|
And stdout is:
|
213
214
|
"""
|
215
|
+
# comment # => still a comment
|
214
216
|
1 + 1
|
215
217
|
2 + 2
|
216
218
|
|
@@ -412,22 +414,6 @@ Feature: Using flags
|
|
412
414
|
2 # => 2
|
413
415
|
"""
|
414
416
|
|
415
|
-
|
416
|
-
Scenario: --shebang
|
417
|
-
Given the file "fake_ruby":
|
418
|
-
"""
|
419
|
-
#!/usr/bin/env ruby
|
420
|
-
$LOAD_PATH.unshift File.expand_path "{{Haiti.config.proving_grounds_dir}}/../lib", __FILE__
|
421
|
-
require 'seeing_is_believing/event_stream/producer'
|
422
|
-
sib = SeeingIsBelieving::EventStream::Producer.new($stdout)
|
423
|
-
sib.record_result(:inspect, 1, /omg/)
|
424
|
-
sib.finish!
|
425
|
-
"""
|
426
|
-
When I run "chmod +x fake_ruby"
|
427
|
-
When I run "seeing_is_believing -e 123 --shebang ./fake_ruby"
|
428
|
-
Then stdout is "123 # => /omg/"
|
429
|
-
|
430
|
-
|
431
417
|
Scenario: --json
|
432
418
|
Given the file "all_kinds_of_output.rb":
|
433
419
|
"""
|
data/features/regression.feature
CHANGED
@@ -414,3 +414,49 @@ Feature:
|
|
414
414
|
And stdout includes "require_relative 'at_exit_exception_indirect1' # => true"
|
415
415
|
And stdout includes "RuntimeError"
|
416
416
|
And stdout includes "zomg"
|
417
|
+
|
418
|
+
|
419
|
+
Scenario: Comments with makers elsewhere in them
|
420
|
+
Given the file "comments_with_markers_elsewhere.rb":
|
421
|
+
"""
|
422
|
+
# a # => a
|
423
|
+
"""
|
424
|
+
When I run "seeing_is_believing comments_with_markers_elsewhere.rb"
|
425
|
+
Then stdout is:
|
426
|
+
"""
|
427
|
+
# a # => a
|
428
|
+
"""
|
429
|
+
|
430
|
+
|
431
|
+
Scenario: Deadlocked
|
432
|
+
Given the file "deadlocked.rb":
|
433
|
+
"""
|
434
|
+
require 'thread'
|
435
|
+
Thread.new { Queue.new.shift }.join
|
436
|
+
"""
|
437
|
+
When I run "seeing_is_believing deadlocked.rb"
|
438
|
+
Then stdout includes:
|
439
|
+
"""
|
440
|
+
require 'thread' # => false
|
441
|
+
Thread.new { Queue.new.shift }.join # ~> fatal
|
442
|
+
"""
|
443
|
+
|
444
|
+
@wip
|
445
|
+
Scenario: Xmpfilter does not write the error messages inside of strings
|
446
|
+
Given the file "error_within_string.rb":
|
447
|
+
"""
|
448
|
+
raise(ArgumentError, "line1
|
449
|
+
line2")
|
450
|
+
"""
|
451
|
+
When I run "seeing_is_believing --xmpfilter-style error_within_string.rb"
|
452
|
+
Then stdout is:
|
453
|
+
"""
|
454
|
+
raise(ArgumentError, "line1
|
455
|
+
line2") # ~> ArgumentError: line1\n line2
|
456
|
+
|
457
|
+
# ~> ArgumentError
|
458
|
+
# ~> line1
|
459
|
+
# ~> line2
|
460
|
+
# ~>
|
461
|
+
# ~> f4.rb:1:in `<main>'
|
462
|
+
"""
|
@@ -152,6 +152,7 @@ Feature: Xmpfilter style
|
|
152
152
|
Scenario: Cleaning previous output
|
153
153
|
Given the file "xmpfilter_cleaning.rb":
|
154
154
|
"""
|
155
|
+
# commented out # => previous annotation
|
155
156
|
1 # => "1...
|
156
157
|
# => "1111111111...
|
157
158
|
# "1111111111...
|
@@ -161,6 +162,60 @@ Feature: Xmpfilter style
|
|
161
162
|
When I run "seeing_is_believing --xmpfilter-style --clean xmpfilter_cleaning.rb"
|
162
163
|
Then stdout is:
|
163
164
|
"""
|
165
|
+
# commented out # => previous annotation
|
164
166
|
1
|
165
167
|
# normal comment
|
166
168
|
"""
|
169
|
+
|
170
|
+
|
171
|
+
@wip
|
172
|
+
Scenario: Correctly cleans/rewrites when the First line of inspection is indented more than its children
|
173
|
+
Given the file "indented_inspection.rb":
|
174
|
+
"""
|
175
|
+
obj = Object.new
|
176
|
+
def obj.inspect
|
177
|
+
" 1 \n2 2\n"
|
178
|
+
end
|
179
|
+
obj
|
180
|
+
# =>
|
181
|
+
"""
|
182
|
+
When I run "seeing_is_believing -x indented_inspection.rb | seeing_is_believing -x"
|
183
|
+
Then stdout is:
|
184
|
+
"""
|
185
|
+
obj = Object.new
|
186
|
+
def obj.inspect
|
187
|
+
" 1 \n2 2\n"
|
188
|
+
end
|
189
|
+
obj
|
190
|
+
# => 1
|
191
|
+
# 2 2
|
192
|
+
"""
|
193
|
+
|
194
|
+
# this one needs a bit more thought put into it, but I'm kinda fading now
|
195
|
+
@wip
|
196
|
+
Scenario: Error raised on an annotated line does not wipe it out
|
197
|
+
Given the file "error_on_annotated_line.rb":
|
198
|
+
"""
|
199
|
+
a # =>
|
200
|
+
"""
|
201
|
+
When I run "seeing_is_believing --xmpfilter-style error_on_annotated_line.rb"
|
202
|
+
Then I stdout is:
|
203
|
+
"""
|
204
|
+
idk, but we need to be able to fix the thing and run it again
|
205
|
+
without losing the annotation
|
206
|
+
"""
|
207
|
+
|
208
|
+
|
209
|
+
@not-implemented
|
210
|
+
Scenario: It can record values even when method is overridden
|
211
|
+
Given the file "pretty_inspect_with_method_overridden.rb":
|
212
|
+
"""
|
213
|
+
def method()end; self # =>
|
214
|
+
# =>
|
215
|
+
"""
|
216
|
+
When I run "seeing_is_believing --xmpfilter-style pretty_inspect_with_method_overridden.rb"
|
217
|
+
Then stdout is:
|
218
|
+
"""
|
219
|
+
def method()end; self # => main
|
220
|
+
# => main
|
221
|
+
"""
|
@@ -8,7 +8,6 @@ require 'seeing_is_believing/binary/align_chunk'
|
|
8
8
|
|
9
9
|
# Evaluator decision happens here
|
10
10
|
require 'seeing_is_believing/evaluate_by_moving_files'
|
11
|
-
require 'seeing_is_believing/evaluate_with_eval_in'
|
12
11
|
|
13
12
|
# Annotator decision happens here
|
14
13
|
require 'seeing_is_believing/binary/annotate_every_line'
|
@@ -45,7 +44,6 @@ class SeeingIsBelieving
|
|
45
44
|
attr_attribute :markers
|
46
45
|
attr_attribute :marker_regexes
|
47
46
|
attr_attribute :timeout
|
48
|
-
attr_attribute :shebang
|
49
47
|
attr_attribute :filename
|
50
48
|
attr_attribute :body
|
51
49
|
attr_attribute :annotator_options
|
@@ -60,7 +58,6 @@ class SeeingIsBelieving
|
|
60
58
|
attributes[:markers] = flags.fetch(:markers) # TODO: Should probably object-ify these
|
61
59
|
attributes[:marker_regexes] = flags.fetch(:marker_regexes).each_with_object({}) { |(k, v), rs| rs[k] = self.class.to_regex v }
|
62
60
|
attributes[:timeout] = flags.fetch(:timeout) # b/c binary prints this out in the error message TODO: rename seconds_until_timeout
|
63
|
-
attributes[:shebang] = flags.fetch(:shebang) # b/c binary uses this to validate syntax atm
|
64
61
|
attributes[:filename] = flags.fetch(:filename)
|
65
62
|
|
66
63
|
# All predicates
|
@@ -88,9 +85,8 @@ class SeeingIsBelieving
|
|
88
85
|
|
89
86
|
# The lib's options (passed to SeeingIsBelieving.new)
|
90
87
|
attributes[:lib_options] = {
|
91
|
-
evaluate_with:
|
88
|
+
evaluate_with: EvaluateByMovingFiles,
|
92
89
|
filename: (flags.fetch(:as) || filename),
|
93
|
-
ruby_executable: shebang,
|
94
90
|
stdin: (file_is_on_stdin? ? '' : stdin),
|
95
91
|
require: (['seeing_is_believing/the_matrix'] + flags.fetch(:require)), # TODO: rename requires: files_to_require, or :requires or maybe :to_require
|
96
92
|
load_path: ([File.expand_path('../../..', __FILE__)] + flags.fetch(:load_path)),
|
@@ -15,10 +15,10 @@ class SeeingIsBelieving
|
|
15
15
|
|
16
16
|
# TODO: rename to default_marker_regexes ...or turn into fkn objects
|
17
17
|
def self.marker_regexes
|
18
|
-
{ value: '
|
19
|
-
exception: '
|
20
|
-
stdout: '
|
21
|
-
stderr: '
|
18
|
+
{ value: '^#\s*=>\s*',
|
19
|
+
exception: '^#\s*~>\s*',
|
20
|
+
stdout: '^#\s*>>\s*',
|
21
|
+
stderr: '^#\s*!>\s*',
|
22
22
|
}
|
23
23
|
end
|
24
24
|
|
@@ -47,14 +47,14 @@ class SeeingIsBelieving
|
|
47
47
|
when '-D', '--result-length' then extract_positive_int_for :max_result_length, arg
|
48
48
|
when '-n', '--number-of-captures' then extract_positive_int_for :number_of_captures, arg
|
49
49
|
when '-t', '--timeout' then extract_non_negative_float_for :timeout, arg
|
50
|
-
when '-r', '--require' then next_arg("#{arg} expected a filename as the following argument but did not see one")
|
51
|
-
when '-I', '--load-path' then next_arg("#{arg} expected a directory as the following argument but did not see one")
|
52
|
-
when '-e', '--program' then next_arg("#{arg} expected a program as the following argument but did not see one")
|
53
|
-
when '-a', '--as' then next_arg("#{arg} expected a filename as the following argument but did not see one")
|
54
|
-
when '--shebang' then next_arg("#{arg} expects a ruby executable as the following argument but did not see one") { |executable| flags[:shebang] = executable }
|
50
|
+
when '-r', '--require' then next_arg("#{arg} expected a filename as the following argument but did not see one") { |filename| flags[:require] << filename }
|
51
|
+
when '-I', '--load-path' then next_arg("#{arg} expected a directory as the following argument but did not see one") { |dir| flags[:load_path] << dir }
|
52
|
+
when '-e', '--program' then next_arg("#{arg} expected a program as the following argument but did not see one") { |program| flags[:program_from_args] = program }
|
53
|
+
when '-a', '--as' then next_arg("#{arg} expected a filename as the following argument but did not see one") { |filename| flags[:as] = filename }
|
55
54
|
when '-s', '--alignment-strategy' then flags[:alignment_strategy] = args.shift
|
56
55
|
when /\A-K(.+)/ then flags[:encoding] = $1
|
57
56
|
when '-K', '--encoding' then next_arg("#{arg} expects an encoding, see `man ruby` for possibile values") { |encoding| flags[:encoding] = encoding }
|
57
|
+
when '--shebang' then next_arg("#{arg} is deprecated, SiB now uses the Ruby it was invoked with") { |executable| flags[:deprecated_flags] << '--shebang' << executable }
|
58
58
|
when /^(-.|--.*)$/ then flags[:errors] << "Unknown option: #{arg.inspect}" # unknown flags
|
59
59
|
when /^-[^-]/ then args.unshift *normalize_shortflags(arg)
|
60
60
|
else
|
@@ -93,13 +93,13 @@ class SeeingIsBelieving
|
|
93
93
|
require: ['seeing_is_believing/the_matrix'],
|
94
94
|
load_path: [],
|
95
95
|
alignment_strategy: 'chunk',
|
96
|
-
shebang: 'ruby',
|
97
96
|
result_as_json: false,
|
98
97
|
markers: self.class.default_markers,
|
99
98
|
marker_regexes: self.class.marker_regexes,
|
100
99
|
short_help_screen: self.class.help_screen(false),
|
101
100
|
long_help_screen: self.class.help_screen(true),
|
102
101
|
safe: false,
|
102
|
+
deprecated_flags: [],
|
103
103
|
}
|
104
104
|
end
|
105
105
|
|
@@ -166,10 +166,10 @@ Usage: seeing_is_believing [options] [filename]
|
|
166
166
|
-K, --encoding encoding # sets file encoding, equivalent to Ruby's -Kx (see `man ruby` for valid values)
|
167
167
|
-a, --as filename # run the program as if it was the specified filename
|
168
168
|
-c, --clean # remove annotations from previous runs of seeing_is_believing
|
169
|
-
-g, --debug # print debugging information (useful if program is fucking up, or
|
169
|
+
-g, --debug # print debugging information (useful if program is fucking up, or to better understand what SiB does)
|
170
170
|
-x, --xmpfilter-style # annotate marked lines instead of every line
|
171
171
|
-j, --json # print results in json format (i.e. so another program can consume them)
|
172
|
-
-i, --inherit-exit-status # exit with the exit status of the program being
|
172
|
+
-i, --inherit-exit-status # exit with the exit status of the program being evaluated
|
173
173
|
--shebang ruby-executable # if you want SiB to use some ruby other than the one in the path
|
174
174
|
-v, --version # print the version (#{VERSION})
|
175
175
|
-h, --help # help screen without examples
|
@@ -73,7 +73,7 @@ class SeeingIsBelieving
|
|
73
73
|
comment.text[stderr_regex]
|
74
74
|
),
|
75
75
|
comment]}
|
76
|
-
.slice_before { |annotation, comment| annotation } #
|
76
|
+
.slice_before { |annotation, comment| annotation } # annotations begin chunks
|
77
77
|
.select { |(annotation, start), *| annotation } # discard chunks not beginning with an annotation (probably can only happens on first comment)
|
78
78
|
.map { |(annotation, start), *rest| # end the chunk if the comment doesn't meet nextline criteria
|
79
79
|
nextline_comments = []
|
@@ -38,7 +38,7 @@ class SeeingIsBelieving
|
|
38
38
|
return SUCCESS_STATUS
|
39
39
|
end
|
40
40
|
|
41
|
-
syntax_error_notice = syntax_error_notice_for(options.body
|
41
|
+
syntax_error_notice = syntax_error_notice_for(options.body)
|
42
42
|
if syntax_error_notice
|
43
43
|
stderr.puts syntax_error_notice
|
44
44
|
return NONDISPLAYABLE_ERROR_STATUS
|
@@ -88,8 +88,8 @@ class SeeingIsBelieving
|
|
88
88
|
|
89
89
|
private
|
90
90
|
|
91
|
-
def self.syntax_error_notice_for(body
|
92
|
-
out, err, syntax_status = Open3.capture3
|
91
|
+
def self.syntax_error_notice_for(body)
|
92
|
+
out, err, syntax_status = Open3.capture3 RbConfig.ruby, '-c', stdin_data: body
|
93
93
|
return err unless syntax_status.success?
|
94
94
|
|
95
95
|
# The stdin_data may still be getting written when the pipe closes
|
@@ -24,11 +24,51 @@ require 'seeing_is_believing/event_stream/update_result'
|
|
24
24
|
|
25
25
|
class SeeingIsBelieving
|
26
26
|
class EvaluateByMovingFiles
|
27
|
+
|
28
|
+
# *sigh* have to do this b/c can't use Open3, b/c keywords don't work right when the keys are not symbols, and I'm passing a file descriptor
|
29
|
+
# https://github.com/ruby/ruby/pull/808 my pr
|
30
|
+
# https://bugs.ruby-lang.org/issues/10699 they opened an issue
|
31
|
+
# https://bugs.ruby-lang.org/issues/10118 weird feature vs bug conversation
|
32
|
+
module Spawn
|
33
|
+
extend self
|
34
|
+
def popen(*cmd)
|
35
|
+
opts = {}
|
36
|
+
opts = cmd.pop if cmd.last.kind_of? Hash
|
37
|
+
|
38
|
+
in_r, in_w = IO.pipe
|
39
|
+
opts[:in] = in_r
|
40
|
+
in_w.sync = true
|
41
|
+
|
42
|
+
out_r, out_w = IO.pipe
|
43
|
+
opts[:out] = out_w
|
44
|
+
|
45
|
+
err_r, err_w = IO.pipe
|
46
|
+
opts[:err] = err_w
|
47
|
+
|
48
|
+
pid = spawn(*cmd, opts)
|
49
|
+
wait_thr = Process.detach(pid)
|
50
|
+
|
51
|
+
in_r.close
|
52
|
+
out_w.close
|
53
|
+
err_w.close
|
54
|
+
|
55
|
+
begin
|
56
|
+
yield in_w, out_r, err_r, wait_thr
|
57
|
+
in_w.close unless in_w.closed?
|
58
|
+
wait_thr.value
|
59
|
+
ensure
|
60
|
+
[in_w, out_r, err_r].each { |io| io.close unless io.closed? }
|
61
|
+
wait_thr.join
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
|
27
67
|
def self.call(*args)
|
28
68
|
new(*args).call
|
29
69
|
end
|
30
70
|
|
31
|
-
attr_accessor :program, :filename, :input_stream, :require_flags, :load_path_flags, :encoding, :timeout, :
|
71
|
+
attr_accessor :program, :filename, :input_stream, :require_flags, :load_path_flags, :encoding, :timeout, :debugger, :result
|
32
72
|
|
33
73
|
def initialize(program, filename, options={})
|
34
74
|
self.program = program
|
@@ -38,7 +78,6 @@ class SeeingIsBelieving
|
|
38
78
|
self.load_path_flags = options.fetch(:load_path, []).map { |dir| ['-I', dir] }.flatten
|
39
79
|
self.encoding = options.fetch :encoding, nil
|
40
80
|
self.timeout = options[:timeout]
|
41
|
-
self.ruby_executable = options.fetch :ruby_executable, 'ruby'
|
42
81
|
self.debugger = options.fetch :debugger, Debugger.new(stream: nil)
|
43
82
|
end
|
44
83
|
|
@@ -96,7 +135,15 @@ class SeeingIsBelieving
|
|
96
135
|
end
|
97
136
|
|
98
137
|
def evaluate_file
|
99
|
-
|
138
|
+
# the event stream
|
139
|
+
es_read, es_write = IO.pipe
|
140
|
+
es_fd = es_write.to_i.to_s
|
141
|
+
|
142
|
+
# invoke the process
|
143
|
+
Spawn.popen ENV, *popen_args, es_fd, es_write => es_write do |process_stdin, process_stdout, process_stderr, thread|
|
144
|
+
# child writes here, we close b/c won't get EOF until all fds are closed
|
145
|
+
es_write.close
|
146
|
+
|
100
147
|
# send stdin
|
101
148
|
Thread.new {
|
102
149
|
input_stream.each_char { |char| process_stdin.write char }
|
@@ -106,28 +153,30 @@ class SeeingIsBelieving
|
|
106
153
|
# consume events
|
107
154
|
self.result = Result.new # set on self b/c if an error is raised, we still want to keep what we recorded
|
108
155
|
event_consumer = Thread.new do
|
109
|
-
EventStream::Consumer
|
110
|
-
|
156
|
+
EventStream::Consumer
|
157
|
+
.new(events: es_read, stdout: process_stdout, stderr: process_stderr)
|
158
|
+
.each { |event| EventStream::UpdateResult.call result, event }
|
111
159
|
end
|
112
160
|
|
113
|
-
# process stderr
|
114
|
-
err_reader = Thread.new { process_stderr.read }
|
115
|
-
|
116
161
|
begin
|
117
162
|
Timeout::timeout timeout do
|
118
|
-
self.stderr = err_reader.value
|
119
|
-
self.exitstatus = thread.value
|
120
163
|
event_consumer.join
|
164
|
+
# TODO: seems like these belong entirely on result, not as ivars of this class
|
165
|
+
self.exitstatus = thread.value
|
166
|
+
self.stderr = result.stderr
|
121
167
|
end
|
122
168
|
rescue Timeout::Error
|
123
169
|
Process.kill "TERM", thread.pid
|
124
170
|
raise $!
|
125
171
|
end
|
126
172
|
end
|
173
|
+
ensure
|
174
|
+
es_read.close unless es_read.closed?
|
175
|
+
es_write.close unless es_write.closed?
|
127
176
|
end
|
128
177
|
|
129
178
|
def popen_args
|
130
|
-
[
|
179
|
+
[RbConfig.ruby,
|
131
180
|
'-W0', # no warnings (b/c I hijack STDOUT/STDERR)
|
132
181
|
*(encoding ? ["-K#{encoding}"] : []), # allow the encoding to be set
|
133
182
|
'-I', File.expand_path('../..', __FILE__), # add lib to the load path
|
@@ -1,49 +1,74 @@
|
|
1
1
|
require 'seeing_is_believing/event_stream/events'
|
2
2
|
require 'seeing_is_believing/error'
|
3
|
+
require 'thread'
|
4
|
+
|
3
5
|
class SeeingIsBelieving
|
4
6
|
module EventStream
|
5
7
|
class Consumer
|
6
8
|
NoMoreInput = Class.new SeeingIsBelievingError
|
7
9
|
WtfWhoClosedMyShit = Class.new SeeingIsBelievingError
|
10
|
+
UnknownEvent = Class.new SeeingIsBelievingError
|
11
|
+
|
12
|
+
def initialize(streams)
|
13
|
+
self.finished_threads = []
|
14
|
+
self.queue = Queue.new
|
15
|
+
self.event_stream = streams.fetch :events
|
16
|
+
stdout_stream = streams.fetch :stdout
|
17
|
+
stderr_stream = streams.fetch :stderr
|
18
|
+
|
19
|
+
self.stdout_thread = Thread.new do
|
20
|
+
stdout_stream.each_line { |line| queue << Events::Stdout.new(line) }
|
21
|
+
queue << :stdout_thread_finished
|
22
|
+
end
|
23
|
+
|
24
|
+
self.stderr_thread = Thread.new do
|
25
|
+
stderr_stream.each_line { |line| queue << Events::Stderr.new(line) }
|
26
|
+
queue << :stderr_thread_finished
|
27
|
+
end
|
8
28
|
|
9
|
-
|
10
|
-
|
29
|
+
self.event_thread = Thread.new do
|
30
|
+
begin loop do
|
31
|
+
break unless line = event_stream.gets
|
32
|
+
event = event_for line
|
33
|
+
queue << event
|
34
|
+
end
|
35
|
+
rescue IOError; queue << WtfWhoClosedMyShit.new("Our end of the pipe was closed!")
|
36
|
+
rescue SeeingIsBelievingError; queue << $!
|
37
|
+
ensure queue << :event_thread_finished
|
38
|
+
end
|
39
|
+
end
|
11
40
|
end
|
12
41
|
|
13
42
|
def call(n=1)
|
14
|
-
|
15
|
-
|
16
|
-
line = @readstream.gets
|
17
|
-
raise NoMoreInput if line.nil?
|
18
|
-
event = event_for line
|
19
|
-
@finished = true if Events::Finish === event
|
20
|
-
event
|
21
|
-
end
|
22
|
-
n == 1 ? events.first : events
|
23
|
-
rescue IOError
|
24
|
-
@finished = true
|
25
|
-
raise WtfWhoClosedMyShit, "Our end of the pipe was closed!"
|
26
|
-
rescue NoMoreInput
|
27
|
-
@finished = true
|
28
|
-
raise
|
43
|
+
return next_event if n == 1
|
44
|
+
Array.new(n) { next_event }
|
29
45
|
end
|
30
46
|
|
31
47
|
def each
|
32
48
|
return to_enum :each unless block_given?
|
33
|
-
loop
|
34
|
-
event = call
|
35
|
-
yield event unless Events::Finish === event
|
36
|
-
end
|
49
|
+
loop { yield call(1) }
|
37
50
|
rescue NoMoreInput
|
38
|
-
return nil
|
39
|
-
end
|
40
|
-
|
41
|
-
def finished?
|
42
|
-
@finished
|
43
51
|
end
|
44
52
|
|
45
53
|
private
|
46
54
|
|
55
|
+
attr_accessor :queue, :event_stream, :finished_threads
|
56
|
+
attr_accessor :event_thread, :stdout_thread, :stderr_thread
|
57
|
+
|
58
|
+
def next_event
|
59
|
+
raise NoMoreInput if @no_more_input
|
60
|
+
|
61
|
+
case event = queue.shift
|
62
|
+
when Symbol
|
63
|
+
@no_more_input = true if finished_threads.push(event).size == 3
|
64
|
+
next_event
|
65
|
+
when SeeingIsBelievingError
|
66
|
+
raise event
|
67
|
+
else
|
68
|
+
event
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
47
72
|
def extract_token(line)
|
48
73
|
event_name = line[/[^ ]+/]
|
49
74
|
line.sub! /^\s*[^ ]+\s*/, ''
|
@@ -59,8 +84,8 @@ class SeeingIsBelieving
|
|
59
84
|
line.split(' ')
|
60
85
|
end
|
61
86
|
|
62
|
-
def event_for(
|
63
|
-
line.chomp
|
87
|
+
def event_for(original_line)
|
88
|
+
line = original_line.chomp
|
64
89
|
event_name = extract_token(line).intern
|
65
90
|
case event_name
|
66
91
|
when :result
|
@@ -75,7 +100,7 @@ class SeeingIsBelieving
|
|
75
100
|
when :exception
|
76
101
|
Events::Exception.new(-1, '', '', []).tap do |exception|
|
77
102
|
loop do
|
78
|
-
line =
|
103
|
+
line = event_stream.gets.chomp
|
79
104
|
case extract_token(line).intern
|
80
105
|
when :line_number then exception.line_number = extract_token(line).to_i
|
81
106
|
when :class_name then exception.class_name = extract_string(line)
|
@@ -85,23 +110,22 @@ class SeeingIsBelieving
|
|
85
110
|
end
|
86
111
|
end
|
87
112
|
end
|
88
|
-
when :stdout
|
89
|
-
Events::Stdout.new(extract_string line)
|
90
|
-
when :stderr
|
91
|
-
Events::Stderr.new(extract_string line)
|
92
113
|
when :max_line_captures
|
93
114
|
token = extract_token(line)
|
94
115
|
value = token =~ /infinity/i ? Float::INFINITY : token.to_i
|
95
116
|
Events::MaxLineCaptures.new(value)
|
96
117
|
when :exitstatus
|
97
|
-
# TODO: Will this fuck it up if you run `exit true`?
|
98
118
|
Events::Exitstatus.new(extract_token(line).to_i)
|
99
|
-
when :finish
|
100
|
-
Events::Finish.new
|
101
119
|
when :num_lines
|
102
120
|
Events::NumLines.new(extract_token(line).to_i)
|
121
|
+
when :sib_version
|
122
|
+
Events::SiBVersion.new(extract_string line)
|
123
|
+
when :ruby_version
|
124
|
+
Events::RubyVersion.new(extract_string line)
|
125
|
+
when :filename
|
126
|
+
Events::Filename.new(extract_string line)
|
103
127
|
else
|
104
|
-
raise
|
128
|
+
raise UnknownEvent, original_line.inspect
|
105
129
|
end
|
106
130
|
end
|
107
131
|
end
|