seeing_is_believing 2.2.0 → 3.0.0.beta.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/Changelog.md +33 -0
- data/bin/seeing_is_believing +1 -1
- data/features/errors.feature +3 -3
- data/features/examples.feature +6 -6
- data/features/flags.feature +51 -196
- data/features/regression.feature +12 -3
- data/features/safe.feature +33 -0
- data/features/support/env.rb +20 -0
- data/features/xmpfilter-style.feature +156 -0
- data/lib/seeing_is_believing.rb +17 -35
- data/lib/seeing_is_believing/binary.rb +81 -176
- data/lib/seeing_is_believing/binary/align_chunk.rb +5 -7
- data/lib/seeing_is_believing/binary/align_file.rb +4 -5
- data/lib/seeing_is_believing/binary/align_line.rb +4 -4
- data/lib/seeing_is_believing/binary/annotate_end_of_file.rb +60 -0
- data/lib/seeing_is_believing/binary/annotate_every_line.rb +64 -0
- data/lib/seeing_is_believing/binary/annotate_xmpfilter_style.rb +133 -0
- data/lib/seeing_is_believing/binary/comment_formatter.rb +19 -5
- data/lib/seeing_is_believing/binary/comment_lines.rb +1 -1
- data/lib/seeing_is_believing/binary/commentable_lines.rb +1 -1
- data/lib/seeing_is_believing/binary/interpret_flags.rb +149 -0
- data/lib/seeing_is_believing/binary/parse_args.rb +96 -104
- data/lib/seeing_is_believing/binary/remove_annotations.rb +95 -0
- data/lib/seeing_is_believing/binary/rewrite_comments.rb +8 -30
- data/lib/seeing_is_believing/code.rb +99 -0
- data/lib/seeing_is_believing/evaluate_by_moving_files.rb +27 -19
- data/lib/seeing_is_believing/evaluate_with_eval_in.rb +27 -0
- data/lib/seeing_is_believing/event_stream/consumer.rb +111 -0
- data/lib/seeing_is_believing/event_stream/events.rb +16 -0
- data/lib/seeing_is_believing/event_stream/producer.rb +106 -0
- data/lib/seeing_is_believing/event_stream/update_result.rb +21 -0
- data/lib/seeing_is_believing/inspect_expressions.rb +24 -0
- data/lib/seeing_is_believing/parser_helpers.rb +1 -11
- data/lib/seeing_is_believing/result.rb +14 -56
- data/lib/seeing_is_believing/the_matrix.rb +14 -14
- data/lib/seeing_is_believing/version.rb +1 -1
- data/lib/seeing_is_believing/wrap_expressions.rb +32 -9
- data/seeing_is_believing.gemspec +7 -7
- data/spec/binary/comment_formatter_spec.rb +169 -18
- data/spec/binary/comment_lines_spec.rb +1 -1
- data/spec/binary/interpret_flags_spec.rb +307 -0
- data/spec/binary/parse_args_spec.rb +93 -91
- data/spec/binary/{clean_body_spec.rb → remove_annotations_spec.rb} +29 -22
- data/spec/binary/rewrite_comments_spec.rb +13 -13
- data/spec/code_spec.rb +49 -0
- data/spec/debugger_spec.rb +1 -1
- data/spec/evaluate_by_moving_files_spec.rb +7 -3
- data/spec/event_stream_spec.rb +390 -0
- data/spec/hard_core_ensure_spec.rb +1 -1
- data/spec/seeing_is_believing_spec.rb +137 -40
- data/spec/spec_helper.rb +3 -3
- data/spec/wrap_expressions_spec.rb +48 -35
- metadata +58 -35
- data/lib/seeing_is_believing/binary/add_annotations.rb +0 -144
- data/lib/seeing_is_believing/binary/clean_body.rb +0 -95
- data/lib/seeing_is_believing/has_exception.rb +0 -27
- data/lib/seeing_is_believing/line.rb +0 -90
- data/spec/line_spec.rb +0 -86
@@ -0,0 +1,99 @@
|
|
1
|
+
require 'parser/current'
|
2
|
+
class SeeingIsBelieving
|
3
|
+
class Code
|
4
|
+
InlineComment = Struct.new :line_number,
|
5
|
+
:whitespace_col,
|
6
|
+
:whitespace,
|
7
|
+
:text_col,
|
8
|
+
:text,
|
9
|
+
:full_range,
|
10
|
+
:whitespace_range,
|
11
|
+
:comment_range
|
12
|
+
|
13
|
+
# At prsent, it is expected that the syntax is validated before code arrives here
|
14
|
+
# or that its validity doesn't matter (e.g. extracting comments)
|
15
|
+
def initialize(raw_ruby_code, name="SeeingIsBelieving")
|
16
|
+
self.code = raw_ruby_code
|
17
|
+
self.name = name
|
18
|
+
end
|
19
|
+
|
20
|
+
def buffer() @buffer ||= (parse && @buffer ) end
|
21
|
+
def parser() @parser ||= (parse && @parser ) end
|
22
|
+
def rewriter() @rewriter ||= (parse && @rewriter ) end
|
23
|
+
def inline_comments() @comments ||= (parse && @inline_comments) end
|
24
|
+
def root() @root ||= (parse && @root ) end
|
25
|
+
|
26
|
+
def range_for(start_index, end_index)
|
27
|
+
Parser::Source::Range.new buffer, start_index, end_index
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
attr_accessor :code, :name
|
33
|
+
|
34
|
+
def parse
|
35
|
+
@buffer = Parser::Source::Buffer.new(name)
|
36
|
+
@buffer.source = code
|
37
|
+
builder = Parser::Builders::Default.new
|
38
|
+
builder.emit_file_line_as_literals = false # should be injectible?
|
39
|
+
@parser = Parser::CurrentRuby.new builder
|
40
|
+
@rewriter = Parser::Source::Rewriter.new @buffer
|
41
|
+
|
42
|
+
can_parse_invalid_code(@parser)
|
43
|
+
|
44
|
+
@root, all_comments, tokens = parser.tokenize(@buffer)
|
45
|
+
|
46
|
+
@inline_comments = all_comments.select(&:inline?).map { |c| wrap_comment c }
|
47
|
+
end
|
48
|
+
|
49
|
+
def can_parse_invalid_code(parser)
|
50
|
+
# THIS IS SO WE CAN EXTRACT COMMENTS FROM INVALID FILES.
|
51
|
+
|
52
|
+
# We do it by telling Parser's diagnostic to not blow up.
|
53
|
+
# https://github.com/whitequark/parser/blob/2d69a1b5f34ef15b3a8330beb036ac4bf4775e29/lib/parser/diagnostic/engine.rb
|
54
|
+
|
55
|
+
# However, this probably implies SiB won't work on Rbx/JRuby
|
56
|
+
# https://github.com/whitequark/parser/blob/2d69a1b5f34ef15b3a8330beb036ac4bf4775e29/lib/parser/base.rb#L129-134
|
57
|
+
|
58
|
+
# Ideally we could just do this
|
59
|
+
# parser.diagnostics.all_errors_are_fatal = false
|
60
|
+
# parser.diagnostics.ignore_warnings = false
|
61
|
+
|
62
|
+
# But, the parser will still blow up on "fatal" errors (e.g. unterminated string) So we need to actually change it.
|
63
|
+
# https://github.com/whitequark/parser/blob/2d69a1b5f34ef15b3a8330beb036ac4bf4775e29/lib/parser/diagnostic/engine.rb#L99
|
64
|
+
|
65
|
+
# We could make a NullDiagnostics like this:
|
66
|
+
# class NullDiagnostics < Parser::Diagnostic::Engine
|
67
|
+
# def process(*)
|
68
|
+
# # no op
|
69
|
+
# end
|
70
|
+
# end
|
71
|
+
|
72
|
+
# But we don't control initialization of the variable, and the value gets passed around, at least into the lexer.
|
73
|
+
# https://github.com/whitequark/parser/blob/2d69a1b5f34ef15b3a8330beb036ac4bf4775e29/lib/parser/base.rb#L139
|
74
|
+
# and since it's all private, it could change at any time (Parser is very state based),
|
75
|
+
# so I think it's just generally safer to mutate that one object, as we do now.
|
76
|
+
diagnostics = parser.diagnostics
|
77
|
+
def diagnostics.process(*)
|
78
|
+
self
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def wrap_comment(comment)
|
83
|
+
last_char = comment.location.expression.begin_pos
|
84
|
+
first_char = last_char
|
85
|
+
first_char -= 1 while first_char > 0 && code[first_char-1] =~ /[ \t]/
|
86
|
+
preceding_whitespace = buffer.source[first_char...last_char]
|
87
|
+
preceding_whitespace_range = range_for first_char, last_char
|
88
|
+
|
89
|
+
InlineComment.new comment.location.line,
|
90
|
+
preceding_whitespace_range.column,
|
91
|
+
preceding_whitespace,
|
92
|
+
comment.location.column,
|
93
|
+
comment.text,
|
94
|
+
range_for(first_char, comment.location.expression.end_pos),
|
95
|
+
preceding_whitespace_range,
|
96
|
+
comment.location.expression
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
@@ -11,15 +11,16 @@
|
|
11
11
|
# read the wrong file... of course, since we rewrite the file,
|
12
12
|
# its body will be incorrect, anyway.
|
13
13
|
|
14
|
-
require 'json'
|
15
14
|
require 'open3'
|
16
15
|
require 'timeout'
|
17
16
|
require 'stringio'
|
18
|
-
require 'fileutils'
|
17
|
+
require 'fileutils' # DELETE?
|
19
18
|
require 'seeing_is_believing/error'
|
20
19
|
require 'seeing_is_believing/result'
|
21
20
|
require 'seeing_is_believing/debugger'
|
22
21
|
require 'seeing_is_believing/hard_core_ensure'
|
22
|
+
require 'seeing_is_believing/event_stream/consumer'
|
23
|
+
require 'seeing_is_believing/event_stream/update_result'
|
23
24
|
|
24
25
|
class SeeingIsBelieving
|
25
26
|
class EvaluateByMovingFiles
|
@@ -28,14 +29,13 @@ class SeeingIsBelieving
|
|
28
29
|
new(*args).call
|
29
30
|
end
|
30
31
|
|
31
|
-
attr_accessor :program, :filename, :input_stream, :
|
32
|
+
attr_accessor :program, :filename, :input_stream, :require_flags, :load_path_flags, :encoding, :timeout, :ruby_executable, :debugger, :result
|
32
33
|
|
33
34
|
def initialize(program, filename, options={})
|
34
35
|
self.program = program
|
35
36
|
self.filename = filename
|
36
37
|
self.input_stream = options.fetch :input_stream, StringIO.new('')
|
37
|
-
self.
|
38
|
-
self.require_flags = options.fetch(:require, []).map { |filename| ['-r', filename] }.flatten
|
38
|
+
self.require_flags = options.fetch(:require, ['seeing_is_believing/the_matrix']).map { |filename| ['-r', filename] }.flatten
|
39
39
|
self.load_path_flags = options.fetch(:load_path, []).map { |dir| ['-I', dir] }.flatten
|
40
40
|
self.encoding = options.fetch :encoding, nil
|
41
41
|
self.timeout = options[:timeout]
|
@@ -51,7 +51,8 @@ class SeeingIsBelieving
|
|
51
51
|
write_program_to_file
|
52
52
|
begin
|
53
53
|
evaluate_file
|
54
|
-
|
54
|
+
fail if result.bug_in_sib?
|
55
|
+
result
|
55
56
|
rescue Exception => error
|
56
57
|
error = wrap_error error if error_implies_bug_in_sib? error
|
57
58
|
raise error
|
@@ -102,17 +103,27 @@ class SeeingIsBelieving
|
|
102
103
|
|
103
104
|
def evaluate_file
|
104
105
|
Open3.popen3 ENV, *popen_args do |process_stdin, process_stdout, process_stderr, thread|
|
105
|
-
|
106
|
-
|
107
|
-
Thread.new do
|
106
|
+
# send stdin
|
107
|
+
Thread.new {
|
108
108
|
input_stream.each_char { |char| process_stdin.write char }
|
109
109
|
process_stdin.close
|
110
|
+
}
|
111
|
+
|
112
|
+
# consume events
|
113
|
+
self.result = Result.new
|
114
|
+
event_consumer = Thread.new do
|
115
|
+
EventStream::Consumer.new(process_stdout)
|
116
|
+
.each { |event| EventStream::UpdateResult.call result, event }
|
110
117
|
end
|
118
|
+
|
119
|
+
# process stderr
|
120
|
+
err_reader = Thread.new { process_stderr.read }
|
121
|
+
|
111
122
|
begin
|
112
123
|
Timeout::timeout timeout do
|
113
|
-
self.stdout = out_reader.value
|
114
124
|
self.stderr = err_reader.value
|
115
125
|
self.exitstatus = thread.value
|
126
|
+
event_consumer.join
|
116
127
|
end
|
117
128
|
rescue Timeout::Error
|
118
129
|
Process.kill "TERM", thread.pid
|
@@ -126,7 +137,6 @@ class SeeingIsBelieving
|
|
126
137
|
'-W0', # no warnings (b/c I hijack STDOUT/STDERR)
|
127
138
|
*(encoding ? ["-K#{encoding}"] : []), # allow the encoding to be set
|
128
139
|
'-I', File.expand_path('../..', __FILE__), # add lib to the load path
|
129
|
-
'-r', matrix_filename, # hijack the environment so it can be recorded
|
130
140
|
*load_path_flags, # users can inject dirs to be added to the load path
|
131
141
|
*require_flags, # users can inject files to be required
|
132
142
|
filename]
|
@@ -136,16 +146,14 @@ class SeeingIsBelieving
|
|
136
146
|
raise "Exitstatus: #{exitstatus.inspect},\nError: #{stderr.inspect}"
|
137
147
|
end
|
138
148
|
|
139
|
-
def deserialize_result
|
140
|
-
Result.from_primitive JSON.load stdout
|
141
|
-
end
|
142
|
-
|
143
149
|
def wrap_error(error)
|
144
150
|
debugger.context "Program could not be evaluated" do
|
145
|
-
"Program:
|
146
|
-
"
|
147
|
-
"
|
148
|
-
"
|
151
|
+
"Program: #{program.inspect.chomp}\n\n"\
|
152
|
+
"Stderr: #{stderr.inspect.chomp}\n\n"\
|
153
|
+
"Status: #{exitstatus.inspect.chomp}\n\n"\
|
154
|
+
"Result: #{result.inspect.chomp}\n\n"\
|
155
|
+
"Actual Error: #{error.inspect.chomp}\n"+
|
156
|
+
error.backtrace.map { |sf| " #{sf}\n" }.join("")
|
149
157
|
end
|
150
158
|
BugInSib.new error
|
151
159
|
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'eval_in'
|
2
|
+
require 'stringio'
|
3
|
+
|
4
|
+
class SeeingIsBelieving
|
5
|
+
class EvaluateWithEvalIn
|
6
|
+
def self.call(*args)
|
7
|
+
new(*args).call
|
8
|
+
end
|
9
|
+
|
10
|
+
attr_accessor :program, :filename, :input_stream, :matrix_filename, :require_flags, :load_path_flags, :encoding, :timeout, :ruby_executable, :debugger
|
11
|
+
|
12
|
+
def initialize(program, filename, options={})
|
13
|
+
self.program = program
|
14
|
+
# self.input_stream = options.fetch :input_stream, StringIO.new('')
|
15
|
+
# self.timeout = options[:timeout]
|
16
|
+
# self.debugger = options.fetch :debugger, Debugger.new(stream: nil)
|
17
|
+
end
|
18
|
+
|
19
|
+
def call
|
20
|
+
eval_in_result = ::EvalIn.call(program, language: 'ruby/mri-2.1')
|
21
|
+
result = Result.new
|
22
|
+
EventStream::Consumer.new(StringIO.new eval_in_result.output)
|
23
|
+
.each { |event| EventStream::UpdateResult.call result, event }
|
24
|
+
result
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,111 @@
|
|
1
|
+
require 'seeing_is_believing/event_stream/events'
|
2
|
+
require 'seeing_is_believing/error'
|
3
|
+
class SeeingIsBelieving
|
4
|
+
module EventStream
|
5
|
+
class Consumer
|
6
|
+
NoMoreInput = Class.new SeeingIsBelievingError
|
7
|
+
WtfWhoClosedMyShit = Class.new SeeingIsBelievingError
|
8
|
+
|
9
|
+
def initialize(readstream)
|
10
|
+
@readstream = readstream
|
11
|
+
end
|
12
|
+
|
13
|
+
def call(n=1)
|
14
|
+
events = n.times.map do
|
15
|
+
raise NoMoreInput if finished?
|
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
|
29
|
+
end
|
30
|
+
|
31
|
+
def each
|
32
|
+
return to_enum :each unless block_given?
|
33
|
+
loop do
|
34
|
+
event = call
|
35
|
+
yield event unless Events::Finish === event
|
36
|
+
end
|
37
|
+
rescue NoMoreInput
|
38
|
+
return nil
|
39
|
+
end
|
40
|
+
|
41
|
+
def finished?
|
42
|
+
@finished
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
def extract_token(line)
|
48
|
+
event_name = line[/[^ ]+/]
|
49
|
+
line.sub! /^\s*[^ ]+\s*/, ''
|
50
|
+
event_name
|
51
|
+
end
|
52
|
+
|
53
|
+
# for a consideration of many different ways of doing this, see 5633064
|
54
|
+
def extract_string(line)
|
55
|
+
Marshal.load extract_token(line).unpack('m0').first
|
56
|
+
end
|
57
|
+
|
58
|
+
def tokenize(line)
|
59
|
+
line.split(' ')
|
60
|
+
end
|
61
|
+
|
62
|
+
def event_for(line)
|
63
|
+
line.chomp!
|
64
|
+
event_name = extract_token(line).intern
|
65
|
+
case event_name
|
66
|
+
when :result
|
67
|
+
line_number = extract_token(line).to_i
|
68
|
+
type = extract_token(line).intern
|
69
|
+
inspected = extract_string(line)
|
70
|
+
Events::LineResult.new(type, line_number, inspected)
|
71
|
+
when :maxed_result
|
72
|
+
line_number = extract_token(line).to_i
|
73
|
+
type = extract_token(line).intern
|
74
|
+
Events::UnrecordedResult.new(type, line_number)
|
75
|
+
when :exception
|
76
|
+
Events::Exception.new(-1, '', '', []).tap do |exception|
|
77
|
+
loop do
|
78
|
+
line = @readstream.gets.chomp
|
79
|
+
case extract_token(line).intern
|
80
|
+
when :line_number then exception.line_number = extract_token(line).to_i
|
81
|
+
when :class_name then exception.class_name = extract_string(line)
|
82
|
+
when :message then exception.message = extract_string(line)
|
83
|
+
when :backtrace then exception.backtrace << extract_string(line)
|
84
|
+
when :end then break
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
when :stdout
|
89
|
+
Events::Stdout.new(extract_string line)
|
90
|
+
when :stderr
|
91
|
+
Events::Stderr.new(extract_string line)
|
92
|
+
when :bug_in_sib
|
93
|
+
Events::BugInSiB.new(extract_token(line) == 'true')
|
94
|
+
when :max_line_captures
|
95
|
+
token = extract_token(line)
|
96
|
+
value = token =~ /infinity/i ? Float::INFINITY : token.to_i
|
97
|
+
Events::MaxLineCaptures.new(value)
|
98
|
+
when :exitstatus
|
99
|
+
# TODO: Will this fuck it up if you run `exit true`?
|
100
|
+
Events::Exitstatus.new(extract_token(line).to_i)
|
101
|
+
when :finish
|
102
|
+
Events::Finish.new
|
103
|
+
when :num_lines
|
104
|
+
Events::NumLines.new(extract_token(line).to_i)
|
105
|
+
else
|
106
|
+
raise "IDK what #{event_name.inspect} is!"
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
class SeeingIsBelieving
|
2
|
+
module EventStream
|
3
|
+
module Events
|
4
|
+
LineResult = Struct.new(:type, :line_number, :inspected)
|
5
|
+
UnrecordedResult = Struct.new(:type, :line_number)
|
6
|
+
Stdout = Struct.new(:value)
|
7
|
+
Stderr = Struct.new(:value)
|
8
|
+
BugInSiB = Struct.new(:value)
|
9
|
+
MaxLineCaptures = Struct.new(:value)
|
10
|
+
NumLines = Struct.new(:value)
|
11
|
+
Exitstatus = Struct.new(:value)
|
12
|
+
Exception = Struct.new(:line_number, :class_name, :message, :backtrace)
|
13
|
+
Finish = Class.new
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
require 'seeing_is_believing/event_stream/events'
|
2
|
+
class SeeingIsBelieving
|
3
|
+
module EventStream
|
4
|
+
require 'thread'
|
5
|
+
class Producer
|
6
|
+
attr_accessor :exitstatus, :bug_in_sib, :max_line_captures, :num_lines
|
7
|
+
|
8
|
+
def initialize(resultstream)
|
9
|
+
self.exitstatus = 0
|
10
|
+
self.bug_in_sib = false
|
11
|
+
self.max_line_captures = Float::INFINITY
|
12
|
+
self.num_lines = 0
|
13
|
+
self.recorded_results = []
|
14
|
+
self.queue = Thread::Queue.new
|
15
|
+
self.producer_thread = Thread.new do
|
16
|
+
finish = "finish"
|
17
|
+
begin
|
18
|
+
loop do
|
19
|
+
to_publish = queue.shift
|
20
|
+
if to_publish == finish
|
21
|
+
resultstream << "finish\n"
|
22
|
+
break
|
23
|
+
else
|
24
|
+
resultstream << (to_publish << "\n")
|
25
|
+
end
|
26
|
+
end
|
27
|
+
rescue IOError, Errno::EPIPE
|
28
|
+
loop { break if queue.shift == finish }
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def bug_in_sib=(bool)
|
34
|
+
@bug_in_sib = (bool ? true : false)
|
35
|
+
end
|
36
|
+
|
37
|
+
# for a consideration of many different ways of doing this, see 5633064
|
38
|
+
def to_string_token(string)
|
39
|
+
[Marshal.dump(string.to_s)].pack('m0')
|
40
|
+
end
|
41
|
+
|
42
|
+
StackErrors = [SystemStackError]
|
43
|
+
StackErrors << Java::JavaLang::StackOverflowError if defined?(RUBY_PLATFORM) && RUBY_PLATFORM == 'java'
|
44
|
+
def record_result(type, line_number, value)
|
45
|
+
self.num_lines = line_number if num_lines < line_number
|
46
|
+
counts = recorded_results[line_number] ||= Hash.new(0)
|
47
|
+
count = counts[type]
|
48
|
+
recorded_results[line_number][type] = count.next
|
49
|
+
if count < max_line_captures
|
50
|
+
begin
|
51
|
+
if block_given?
|
52
|
+
inspected = yield(value).to_str
|
53
|
+
else
|
54
|
+
inspected = value.inspect.to_str
|
55
|
+
end
|
56
|
+
rescue *StackErrors
|
57
|
+
# this is necessary because SystemStackError won't show the backtrace of the method we tried to call
|
58
|
+
# which means there won't be anything showing the user where this came from
|
59
|
+
# so we need to re-raise the error to get a backtrace that shows where we came from
|
60
|
+
# otherwise it looks like the bug is in SiB and not the user's program, see https://github.com/JoshCheek/seeing_is_believing/issues/37
|
61
|
+
raise SystemStackError, "Calling inspect blew the stack (is it recursive w/o a base case?)"
|
62
|
+
rescue Exception
|
63
|
+
inspected = "#<no inspect available>"
|
64
|
+
end
|
65
|
+
queue << "result #{line_number} #{type} #{to_string_token inspected}"
|
66
|
+
elsif count == max_line_captures
|
67
|
+
queue << "maxed_result #{line_number} #{type}"
|
68
|
+
end
|
69
|
+
value
|
70
|
+
end
|
71
|
+
|
72
|
+
def record_exception(line_number, exception)
|
73
|
+
self.num_lines = line_number if num_lines < line_number
|
74
|
+
queue << "exception"
|
75
|
+
queue << " line_number #{line_number}"
|
76
|
+
queue << " class_name #{to_string_token exception.class.name}"
|
77
|
+
queue << " message #{to_string_token exception.message}"
|
78
|
+
exception.backtrace.each { |line|
|
79
|
+
queue << " backtrace #{to_string_token line}"
|
80
|
+
}
|
81
|
+
queue << "end"
|
82
|
+
end
|
83
|
+
|
84
|
+
def record_stdout(stdout)
|
85
|
+
queue << "stdout #{to_string_token stdout}"
|
86
|
+
end
|
87
|
+
|
88
|
+
def record_stderr(stderr)
|
89
|
+
queue << "stderr #{to_string_token stderr}"
|
90
|
+
end
|
91
|
+
|
92
|
+
def finish!
|
93
|
+
queue << "bug_in_sib #{bug_in_sib}"
|
94
|
+
queue << "max_line_captures #{max_line_captures}"
|
95
|
+
queue << "num_lines #{num_lines}"
|
96
|
+
queue << "exitstatus #{exitstatus}"
|
97
|
+
queue << "finish".freeze
|
98
|
+
producer_thread.join
|
99
|
+
end
|
100
|
+
|
101
|
+
private
|
102
|
+
|
103
|
+
attr_accessor :resultstream, :queue, :producer_thread, :recorded_results
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|