seeing_is_believing 3.0.0.beta.5 → 3.0.0.beta.6
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/.gitignore +15 -0
- data/.rspec +4 -0
- data/.travis.yml +1 -1
- data/Rakefile +28 -18
- data/Readme.md +22 -13
- data/bin/seeing_is_believing +0 -1
- data/features/errors.feature +3 -3
- data/features/examples.feature +65 -0
- data/features/flags.feature +19 -0
- data/lib/seeing_is_believing/binary.rb +1 -1
- data/lib/seeing_is_believing/binary/align_chunk.rb +0 -8
- data/lib/seeing_is_believing/binary/commentable_lines.rb +7 -12
- data/lib/seeing_is_believing/binary/config.rb +18 -6
- data/lib/seeing_is_believing/binary/engine.rb +11 -6
- data/lib/seeing_is_believing/binary/remove_annotations.rb +17 -19
- data/lib/seeing_is_believing/error.rb +2 -2
- data/lib/seeing_is_believing/evaluate_by_moving_files.rb +8 -6
- data/lib/seeing_is_believing/event_stream/consumer.rb +55 -24
- data/lib/seeing_is_believing/event_stream/events.rb +11 -0
- data/lib/seeing_is_believing/event_stream/handlers/record_exit_events.rb +26 -0
- data/lib/seeing_is_believing/event_stream/handlers/stream_json_events.rb +0 -22
- data/lib/seeing_is_believing/event_stream/handlers/update_result.rb +1 -0
- data/lib/seeing_is_believing/result.rb +5 -15
- data/lib/seeing_is_believing/version.rb +1 -1
- data/seeing_is_believing.gemspec +3 -2
- data/spec/binary/config_spec.rb +25 -8
- data/spec/binary/engine_spec.rb +12 -5
- data/spec/evaluate_by_moving_files_spec.rb +17 -4
- data/spec/event_stream_spec.rb +45 -10
- data/spec/seeing_is_believing_spec.rb +29 -4
- data/spec/wrap_expressions_spec.rb +6 -1
- metadata +23 -10
- data/Changelog.md +0 -33
- data/lib/seeing_is_believing/event_stream/handlers/record_exitstatus.rb +0 -18
@@ -68,33 +68,31 @@ class SeeingIsBelieving
|
|
68
68
|
def annotation_chunks_in(code)
|
69
69
|
code
|
70
70
|
.inline_comments
|
71
|
-
.map { |comment|
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
.
|
79
|
-
.
|
71
|
+
.map { |comment| # associate each annotation to its comment
|
72
|
+
annotation = comment.text[value_regex] ||
|
73
|
+
comment.text[exception_regex] ||
|
74
|
+
comment.text[stdout_regex] ||
|
75
|
+
comment.text[stderr_regex]
|
76
|
+
[annotation, comment]
|
77
|
+
}
|
78
|
+
.slice_before { |annotation, comment| annotation } # annotations begin chunks
|
79
|
+
.select { |(annotation, start), *| annotation } # discard chunks not beginning with an annotation (probably can only happens on first comment)
|
80
|
+
.map { |(annotation, start), *rest| # end the chunk if the comment doesn't meet nextline criteria
|
80
81
|
nextline_comments = []
|
81
|
-
prev
|
82
|
+
prev = start
|
82
83
|
rest.each { |_, potential_nextline|
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
84
|
+
sequential = (prev.line_number.next == potential_nextline.line_number)
|
85
|
+
vertically_aligned = start.text_col == potential_nextline.text_col
|
86
|
+
only_preceded_by_whitespace = potential_nextline.whitespace_col.zero?
|
87
|
+
indention_matches_annotation = annotation.length <= potential_nextline.text[/#\s*/].length
|
88
|
+
break unless sequential && vertically_aligned && only_preceded_by_whitespace && indention_matches_annotation
|
88
89
|
nextline_comments << potential_nextline
|
90
|
+
prev = potential_nextline
|
89
91
|
}
|
90
92
|
[start, nextline_comments]
|
91
93
|
}
|
92
94
|
end
|
93
95
|
|
94
|
-
def value_prefix
|
95
|
-
@value_prefix ||= markers.fetch(:value).fetch(:prefix)
|
96
|
-
end
|
97
|
-
|
98
96
|
def value_regex
|
99
97
|
@value_regex ||= markers.fetch(:value).fetch(:regex)
|
100
98
|
end
|
@@ -33,7 +33,7 @@ class SeeingIsBelieving
|
|
33
33
|
self.encoding = options.delete(:encoding)
|
34
34
|
self.timeout_seconds = options.delete(:timeout_seconds) || 0 # 0 is the new infinity
|
35
35
|
self.provided_input = options.delete(:provided_input) || String.new
|
36
|
-
self.event_handler = options.delete(:event_handler) || raise("must provide an
|
36
|
+
self.event_handler = options.delete(:event_handler) || raise(ArgumentError, "must provide an :event_handler")
|
37
37
|
self.load_path_flags = (options.delete(:load_path_dirs) || []).map { |dir| ['-I', dir] }.flatten
|
38
38
|
self.require_flags = (options.delete(:require_files) || ['seeing_is_believing/the_matrix']).map { |filename| ['-r', filename] }.flatten
|
39
39
|
self.max_line_captures = (options.delete(:max_line_captures) || Float::INFINITY) # (optimization: child stops producing results at this number, even though it might make more sense for the consumer to stop emitting them)
|
@@ -110,7 +110,8 @@ class SeeingIsBelieving
|
|
110
110
|
child_eventstream => child_eventstream }
|
111
111
|
child = Process.detach Kernel.spawn(env, *popen_args, opts)
|
112
112
|
|
113
|
-
# close b/c
|
113
|
+
# close child streams b/c they won't emit EOF
|
114
|
+
# until both child and parent references are closed
|
114
115
|
child_eventstream.close
|
115
116
|
child_stdout.close
|
116
117
|
child_stderr.close
|
@@ -123,8 +124,8 @@ class SeeingIsBelieving
|
|
123
124
|
stdin.close
|
124
125
|
}
|
125
126
|
|
126
|
-
#
|
127
|
-
consumer
|
127
|
+
# set up the event consumer
|
128
|
+
consumer = EventStream::Consumer.new(events: eventstream, stdout: stdout, stderr: stderr)
|
128
129
|
consumer_thread = Thread.new { consumer.each { |e| event_handler.call e } }
|
129
130
|
|
130
131
|
# wait for completion
|
@@ -134,8 +135,9 @@ class SeeingIsBelieving
|
|
134
135
|
consumer_thread.join
|
135
136
|
end
|
136
137
|
rescue Timeout::Error
|
137
|
-
Process.kill "
|
138
|
-
|
138
|
+
Process.kill "KILL", child.pid
|
139
|
+
consumer.process_timeout timeout_seconds
|
140
|
+
consumer_thread.join # finish consuming events
|
139
141
|
ensure
|
140
142
|
[stdin, stdout, stderr, eventstream].each { |io| io.close unless io.closed? }
|
141
143
|
end
|
@@ -8,32 +8,56 @@ class SeeingIsBelieving
|
|
8
8
|
module EventStream
|
9
9
|
class Consumer
|
10
10
|
class FinishCriteria
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
].freeze.each do |name|
|
17
|
-
define_method name do
|
18
|
-
@unmet_criteria.delete name
|
19
|
-
@satisfied = @unmet_criteria.empty?
|
20
|
-
end
|
21
|
-
end
|
11
|
+
EventThreadFinished = Module.new
|
12
|
+
StdoutThreadFinished = Module.new
|
13
|
+
StderrThreadFinished = Module.new
|
14
|
+
ProcessExited = Module.new
|
15
|
+
|
22
16
|
def initialize
|
23
|
-
@
|
24
|
-
|
17
|
+
@unmet_criteria = [
|
18
|
+
EventThreadFinished,
|
19
|
+
StdoutThreadFinished,
|
20
|
+
StderrThreadFinished,
|
21
|
+
ProcessExited,
|
22
|
+
]
|
25
23
|
end
|
24
|
+
|
25
|
+
# finish criteria are satisfied,
|
26
|
+
# we can stop processing events
|
26
27
|
def satisfied?
|
27
|
-
@
|
28
|
+
@unmet_criteria.empty?
|
29
|
+
end
|
30
|
+
|
31
|
+
def event_thread_finished!
|
32
|
+
@unmet_criteria.delete EventThreadFinished
|
33
|
+
end
|
34
|
+
|
35
|
+
def stdout_thread_finished!
|
36
|
+
@unmet_criteria.delete StdoutThreadFinished
|
37
|
+
end
|
38
|
+
|
39
|
+
def stderr_thread_finished!
|
40
|
+
@unmet_criteria.delete StderrThreadFinished
|
41
|
+
end
|
42
|
+
|
43
|
+
def received_exitstatus!
|
44
|
+
@unmet_criteria.delete ProcessExited
|
45
|
+
end
|
46
|
+
|
47
|
+
def received_timeout!
|
48
|
+
@unmet_criteria.delete ProcessExited
|
28
49
|
end
|
29
50
|
end
|
30
51
|
|
31
52
|
# https://github.com/JoshCheek/seeing_is_believing/issues/46
|
32
53
|
def self.fix_encoding(str)
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
54
|
+
begin
|
55
|
+
str.encode! Encoding::UTF_8
|
56
|
+
rescue EncodingError
|
57
|
+
str = str.force_encoding(Encoding::UTF_8)
|
58
|
+
end
|
59
|
+
return str.scrub('�') if str.respond_to? :scrub
|
60
|
+
# basically reimplement scrub, b/c it's not implemented on 1.9.3
|
37
61
|
str.each_char.inject("") do |new_str, char|
|
38
62
|
if char.valid_encoding?
|
39
63
|
new_str << char
|
@@ -44,6 +68,7 @@ class SeeingIsBelieving
|
|
44
68
|
end
|
45
69
|
|
46
70
|
def initialize(streams)
|
71
|
+
@finished = false
|
47
72
|
self.finish_criteria = FinishCriteria.new
|
48
73
|
self.queue = Queue.new
|
49
74
|
event_stream = streams.fetch :events
|
@@ -94,16 +119,23 @@ class SeeingIsBelieving
|
|
94
119
|
yield call 1 until @finished
|
95
120
|
end
|
96
121
|
|
97
|
-
# NOTE: Note it's probably a bad plan to call
|
122
|
+
# NOTE: Note it's probably a bad plan to call these methods
|
98
123
|
# from within the same thread as the consumer, because if it
|
99
124
|
# blocks, who will remove items from the queue?
|
100
125
|
def process_exitstatus(status)
|
101
|
-
queue << Events::Exitstatus.new(value: status)
|
102
126
|
queue << lambda {
|
103
|
-
|
127
|
+
queue << Events::Exitstatus.new(value: status)
|
128
|
+
finish_criteria.received_exitstatus!
|
129
|
+
}
|
130
|
+
end
|
131
|
+
def process_timeout(seconds)
|
132
|
+
queue << lambda {
|
133
|
+
queue << Events::Timeout.new(seconds: seconds)
|
134
|
+
finish_criteria.received_timeout!
|
104
135
|
}
|
105
136
|
end
|
106
137
|
|
138
|
+
|
107
139
|
private
|
108
140
|
|
109
141
|
attr_accessor :queue, :finish_criteria
|
@@ -115,9 +147,8 @@ class SeeingIsBelieving
|
|
115
147
|
event_for element
|
116
148
|
when Proc
|
117
149
|
element.call
|
118
|
-
|
150
|
+
finish_criteria.satisfied? &&
|
119
151
|
queue << Events::Finished.new
|
120
|
-
end
|
121
152
|
next_event
|
122
153
|
when Events::Finished
|
123
154
|
@finished = true
|
@@ -125,7 +156,7 @@ class SeeingIsBelieving
|
|
125
156
|
when Event
|
126
157
|
element
|
127
158
|
else
|
128
|
-
|
159
|
+
raise SeeingIsBelieving::UnknownEvent, "WAT IS THIS?: #{element.inspect}"
|
129
160
|
end
|
130
161
|
end
|
131
162
|
|
@@ -92,6 +92,17 @@ class SeeingIsBelieving
|
|
92
92
|
attributes :value
|
93
93
|
end
|
94
94
|
|
95
|
+
# The process timed out
|
96
|
+
# note that you will probably not receive an exitstatus
|
97
|
+
# if this occurs. Though it's hypothetically possible...
|
98
|
+
# this is all asynchronous.
|
99
|
+
class Timeout < Event
|
100
|
+
def self.event_name
|
101
|
+
:timeout
|
102
|
+
end
|
103
|
+
attributes :seconds
|
104
|
+
end
|
105
|
+
|
95
106
|
# Emitted when the process invokes exec.
|
96
107
|
# Note that this could be a child process,
|
97
108
|
# so it does not necessarily mean there won't be any more line results
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'seeing_is_believing/event_stream/events'
|
2
|
+
|
3
|
+
class SeeingIsBelieving
|
4
|
+
module EventStream
|
5
|
+
module Handlers
|
6
|
+
class RecordExitEvents
|
7
|
+
attr_reader :exitstatus
|
8
|
+
attr_reader :timeout_seconds
|
9
|
+
|
10
|
+
def initialize(next_observer)
|
11
|
+
@next_observer = next_observer
|
12
|
+
end
|
13
|
+
|
14
|
+
def call(event)
|
15
|
+
case event
|
16
|
+
when Events::Exitstatus
|
17
|
+
@exitstatus = event.value
|
18
|
+
when Events::Timeout
|
19
|
+
@timeout_seconds = event.seconds
|
20
|
+
end
|
21
|
+
@next_observer.call(event)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -13,32 +13,10 @@ class SeeingIsBelieving
|
|
13
13
|
end
|
14
14
|
|
15
15
|
def call(event)
|
16
|
-
write_event event
|
17
|
-
record_outcome event
|
18
|
-
end
|
19
|
-
|
20
|
-
def has_exception?
|
21
|
-
true
|
22
|
-
end
|
23
|
-
|
24
|
-
def exitstatus
|
25
|
-
@exitstatus
|
26
|
-
end
|
27
|
-
|
28
|
-
private
|
29
|
-
|
30
|
-
def write_event(event)
|
31
16
|
@stream << JSON.dump(event.as_json)
|
32
17
|
@stream << "\n"
|
33
18
|
@stream.flush if @flush
|
34
19
|
end
|
35
|
-
|
36
|
-
def record_outcome(event)
|
37
|
-
case event
|
38
|
-
when Events::Exception then @has_exception = true
|
39
|
-
when Events::Exitstatus then @exitstatus = event.value
|
40
|
-
end
|
41
|
-
end
|
42
20
|
end
|
43
21
|
end
|
44
22
|
end
|
@@ -24,6 +24,7 @@ class SeeingIsBelieving
|
|
24
24
|
when SiBVersion then result.sib_version = event.value
|
25
25
|
when RubyVersion then result.ruby_version = event.value
|
26
26
|
when Filename then result.filename = event.value
|
27
|
+
when Timeout then result.timeout_seconds = event.seconds
|
27
28
|
when Exec,
|
28
29
|
Finished,
|
29
30
|
StdoutClosed,
|
@@ -3,7 +3,7 @@ class SeeingIsBelieving
|
|
3
3
|
include Enumerable
|
4
4
|
RecordedException = Struct.new :line_number, :class_name, :message, :backtrace
|
5
5
|
|
6
|
-
attr_accessor :stdout, :stderr, :exitstatus, :max_line_captures, :exception, :num_lines, :sib_version, :ruby_version, :filename
|
6
|
+
attr_accessor :stdout, :stderr, :exitstatus, :max_line_captures, :exception, :num_lines, :sib_version, :ruby_version, :filename, :timeout_seconds
|
7
7
|
|
8
8
|
def initialize
|
9
9
|
self.stdout = ''
|
@@ -20,6 +20,10 @@ class SeeingIsBelieving
|
|
20
20
|
stderr && !stderr.empty?
|
21
21
|
end
|
22
22
|
|
23
|
+
def timeout?
|
24
|
+
!!timeout_seconds
|
25
|
+
end
|
26
|
+
|
23
27
|
def record_result(type, line_number, value)
|
24
28
|
results_for(line_number, type) << value
|
25
29
|
value
|
@@ -38,20 +42,6 @@ class SeeingIsBelieving
|
|
38
42
|
(1..num_lines).each { |line_number| block.call self[line_number] }
|
39
43
|
end
|
40
44
|
|
41
|
-
def inspect
|
42
|
-
results
|
43
|
-
variables = instance_variables.map do |name|
|
44
|
-
value = instance_variable_get(name)
|
45
|
-
inspected = if name.to_s == '@results'
|
46
|
-
"{#{value.sort_by(&:first).map { |k, v| "#{k.inspect}=>#{v.inspect}"}.join(",\n ")}}"
|
47
|
-
else
|
48
|
-
value.inspect
|
49
|
-
end
|
50
|
-
"#{name}=#{inspected}"
|
51
|
-
end
|
52
|
-
"#<SIB::Result #{variables.join "\n "}>"
|
53
|
-
end
|
54
|
-
|
55
45
|
def max_line_captures
|
56
46
|
@max_line_captures || Float::INFINITY
|
57
47
|
end
|
data/seeing_is_believing.gemspec
CHANGED
@@ -19,11 +19,12 @@ Gem::Specification.new do |s|
|
|
19
19
|
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
20
20
|
s.require_paths = ["lib"]
|
21
21
|
|
22
|
-
s.add_dependency "parser", ">= 2.2.0.
|
22
|
+
s.add_dependency "parser", ">= 2.2.0.3", "< 3.0"
|
23
23
|
|
24
|
+
s.add_development_dependency "what_weve_got_here_is_an_error_to_communicate"
|
24
25
|
s.add_development_dependency "haiti", ">= 0.1", "< 0.3"
|
25
26
|
s.add_development_dependency "rake", "~> 10.0"
|
26
|
-
s.add_development_dependency "rspec", "~> 3.
|
27
|
+
s.add_development_dependency "rspec", "~> 3.2"
|
27
28
|
s.add_development_dependency "cucumber", "~> 1.2"
|
28
29
|
s.add_development_dependency "ichannel", "~> 5.1"
|
29
30
|
|
data/spec/binary/config_spec.rb
CHANGED
@@ -8,9 +8,9 @@ RSpec.describe SeeingIsBelieving::Binary::Config do
|
|
8
8
|
config.errors.find do |error|
|
9
9
|
case error_assertion
|
10
10
|
when Regexp
|
11
|
-
error_assertion =~ error.
|
11
|
+
error_assertion =~ error.to_s
|
12
12
|
else
|
13
|
-
error.
|
13
|
+
error.to_s.include? error_assertion
|
14
14
|
end
|
15
15
|
end
|
16
16
|
end
|
@@ -37,6 +37,7 @@ RSpec.describe SeeingIsBelieving::Binary::Config do
|
|
37
37
|
deprecated = deprecated_args.first
|
38
38
|
expect(deprecated.args).to eq [flag, *args]
|
39
39
|
expect(deprecated.explanation).to be_a_kind_of String
|
40
|
+
expect(deprecated.to_s).to include "Deprecated"
|
40
41
|
end
|
41
42
|
|
42
43
|
shared_examples 'it requires a positive int argument' do |flags|
|
@@ -103,11 +104,27 @@ RSpec.describe SeeingIsBelieving::Binary::Config do
|
|
103
104
|
assert_same_flat_opts ['-jgh+'], ['-j', '-g', '-h+']
|
104
105
|
end
|
105
106
|
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
107
|
+
describe 'ignore_unknown_flags?' do
|
108
|
+
it 'is false by default' do
|
109
|
+
expect(parse([]).ignore_unknown_flags?).to eq false
|
110
|
+
end
|
111
|
+
|
112
|
+
it 'is set to true when it sees the --ignore-unknown-options flag' do
|
113
|
+
expect(parse(['--ignore-unknown-flags']).ignore_unknown_flags?).to eq true
|
114
|
+
end
|
115
|
+
|
116
|
+
specify 'when false, unknown flags set an error' do
|
117
|
+
expect(parse(['--xyz' ])).to have_error '--xyz is not a flag'
|
118
|
+
expect(parse(['-y' ])).to have_error '-y is not a flag'
|
119
|
+
expect(parse(['-y', 'b'])).to have_error '-y is not a flag'
|
120
|
+
expect(parse(['-+h' ])).to have_error '-+ is not a flag'
|
121
|
+
end
|
122
|
+
|
123
|
+
specify 'when true, unknown flags do not set an error' do
|
124
|
+
expect(parse(['--zomg'])).to have_error /zomg/
|
125
|
+
expect(parse(['--ignore-unknown-flags', '--zomg'])).to_not have_error /zomg/
|
126
|
+
expect(parse(['--zomg', '--ignore-unknown-flags'])).to_not have_error /zomg/
|
127
|
+
end
|
111
128
|
end
|
112
129
|
|
113
130
|
describe 'filename and lib_options.filename' do
|
@@ -421,7 +438,7 @@ RSpec.describe SeeingIsBelieving::Binary::Config do
|
|
421
438
|
end
|
422
439
|
end
|
423
440
|
|
424
|
-
describe 'remove_value_prefixes?'
|
441
|
+
describe 'remove_value_prefixes?' do
|
425
442
|
it 'defaults to true' do
|
426
443
|
expect(parse([]).remove_value_prefixes?).to eq true
|
427
444
|
end
|
data/spec/binary/engine_spec.rb
CHANGED
@@ -36,6 +36,7 @@ class SeeingIsBelieving
|
|
36
36
|
allow_any_instance_of(Code::Syntax).to receive(:line_number).and_return 123
|
37
37
|
expect(invalid_engine.syntax_error)
|
38
38
|
.to eq SyntaxErrorMessage.new(line_number: 123, filename: "filename.rb", explanation: "ERR!!")
|
39
|
+
expect(invalid_engine.syntax_error.to_s).to include "ERR!!"
|
39
40
|
end
|
40
41
|
end
|
41
42
|
|
@@ -50,10 +51,11 @@ class SeeingIsBelieving
|
|
50
51
|
end
|
51
52
|
|
52
53
|
context 'before evaluating it raises if asked for' do
|
53
|
-
specify('result')
|
54
|
-
specify('exitstatus')
|
55
|
-
specify('timed_out?')
|
56
|
-
specify('
|
54
|
+
specify('result') { assert_must_evaluate :result }
|
55
|
+
specify('exitstatus') { assert_must_evaluate :exitstatus }
|
56
|
+
specify('timed_out?') { assert_must_evaluate :timed_out? }
|
57
|
+
specify('timeout_seconds') { assert_must_evaluate :timeout_seconds }
|
58
|
+
specify('annotated_body') { assert_must_evaluate :annotated_body }
|
57
59
|
end
|
58
60
|
|
59
61
|
context 'after evaluating' do
|
@@ -68,11 +70,16 @@ class SeeingIsBelieving
|
|
68
70
|
expect(engine.exitstatus).to eq 88
|
69
71
|
end
|
70
72
|
|
71
|
-
specify 'timed_out? is true if
|
73
|
+
specify 'timed_out? is true if a Timeout event was emitted' do
|
72
74
|
expect(call('', timeout: 1).evaluate!.timed_out?).to eq false
|
73
75
|
expect(call('sleep 1', timeout: 0.01).evaluate!.timed_out?).to eq true
|
74
76
|
end
|
75
77
|
|
78
|
+
specify 'timeout_seconds is nil, or the timeout duration' do
|
79
|
+
expect(call('', timeout: 1).evaluate!.timeout_seconds).to eq nil
|
80
|
+
expect(call('sleep 1', timeout: 0.01).evaluate!.timeout_seconds).to eq 0.01
|
81
|
+
end
|
82
|
+
|
76
83
|
context 'annotated_body' do
|
77
84
|
it 'is the body after being run through the annotator' do
|
78
85
|
expect(call("1").evaluate!.annotated_body).to eq "1 # => 1"
|