seeing_is_believing 2.2.0 → 3.0.0.beta.1
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 +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
|