seeing_is_believing 3.0.0.beta.3 → 3.0.0.beta.4
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.
- 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
|