seeing_is_believing 0.0.16 → 0.0.17
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile.lock +1 -1
- data/Readme.md +2 -1
- data/features/errors.feature +14 -0
- data/features/flags.feature +111 -0
- data/features/step_definitions/steps.rb +6 -1
- data/lib/seeing_is_believing.rb +5 -2
- data/lib/seeing_is_believing/binary.rb +16 -2
- data/lib/seeing_is_believing/binary/align_all.rb +28 -0
- data/lib/seeing_is_believing/binary/align_chunk.rb +37 -0
- data/lib/seeing_is_believing/binary/align_line.rb +29 -0
- data/lib/seeing_is_believing/binary/arg_parser.rb +76 -37
- data/lib/seeing_is_believing/binary/print_results_next_to_lines.rb +9 -21
- data/lib/seeing_is_believing/evaluate_by_moving_files.rb +22 -7
- data/lib/seeing_is_believing/has_exception.rb +22 -0
- data/lib/seeing_is_believing/the_matrix.rb +2 -1
- data/lib/seeing_is_believing/version.rb +1 -1
- data/spec/arg_parser_spec.rb +52 -0
- data/spec/seeing_is_believing_spec.rb +5 -0
- metadata +5 -2
data/Gemfile.lock
CHANGED
data/Readme.md
CHANGED
@@ -130,9 +130,10 @@ Known Issues
|
|
130
130
|
|
131
131
|
* `BEGIN/END` breaks things and I probably won't take the time to fix it, becuase it's nontrivial and its really meant for command-line scripts, but there is currently a cuke for it
|
132
132
|
* Heredocs aren't recorded. It might actually be possible if the ExpressionList were to get smarter
|
133
|
-
* Return statements are dealt with poorly, causing some situations where you could capture and display a value to not capture
|
133
|
+
* Return statements and other control-flow changing keywords (next/break/redo/retry) are dealt with poorly, causing some situations where you could capture and display a value to not capture
|
134
134
|
* errors come out really shitty if you're calling them from another program like TextMate, would be better to put a line in that shows where the error is.
|
135
135
|
* Add a time limit to auto-kill it if it gets stuck or something (e.g. stack overflow is painful to wait for)
|
136
|
+
* Doesn't handle # ~> -:5: stack level too deep (SystemStackError) gracefully
|
136
137
|
|
137
138
|
License
|
138
139
|
=======
|
data/features/errors.feature
CHANGED
@@ -39,3 +39,17 @@ Feature: Running the binary unsuccessfully
|
|
39
39
|
Then stderr is "this_file_does_not_exist.rb does not exist!"
|
40
40
|
And the exit status is 1
|
41
41
|
And stdout is empty
|
42
|
+
|
43
|
+
Scenario: Passing unknown options
|
44
|
+
Given the file "some_file" "1"
|
45
|
+
When I run "seeing_is_believing --unknown-option"
|
46
|
+
Then stderr is 'Unknown option: "--unknown-option"'
|
47
|
+
And the exit status is 1
|
48
|
+
And stdout is empty
|
49
|
+
|
50
|
+
Scenario: Passing an unknown option with a value but forgetting the filename
|
51
|
+
When I run "seeing_is_believing --unknown-option some-value"
|
52
|
+
Then stderr is 'Unknown option: "--unknown-option"'
|
53
|
+
And the exit status is 1
|
54
|
+
And stdout is empty
|
55
|
+
|
data/features/flags.feature
CHANGED
@@ -242,3 +242,114 @@ Feature: Using flags
|
|
242
242
|
Then stderr is empty
|
243
243
|
And the exit status is 0
|
244
244
|
And stdout includes "Usage"
|
245
|
+
|
246
|
+
Scenario: --timeout
|
247
|
+
Given the file "example.rb" "sleep 1"
|
248
|
+
When I run "seeing_is_believing --timeout 0.1 example.rb"
|
249
|
+
Then stdout is empty
|
250
|
+
And the exit status is 1
|
251
|
+
And stderr is "Timeout Error after 0.1 seconds!"
|
252
|
+
|
253
|
+
Scenario: --timeout
|
254
|
+
Given the file "example.rb" "1 + 1"
|
255
|
+
When I run "seeing_is_believing --timeout 1.0 example.rb"
|
256
|
+
Then stderr is empty
|
257
|
+
And the exit status is 0
|
258
|
+
And stdout is "1 + 1 # => 2"
|
259
|
+
|
260
|
+
Scenario: --alignment-strategy file
|
261
|
+
Given the file "file_alignments.rb":
|
262
|
+
"""
|
263
|
+
# comment
|
264
|
+
1
|
265
|
+
|
266
|
+
=begin
|
267
|
+
multiline comment
|
268
|
+
=end
|
269
|
+
1 + 1
|
270
|
+
1 + 1 + 1
|
271
|
+
"""
|
272
|
+
When I run "seeing_is_believing --alignment-strategy file file_alignments.rb"
|
273
|
+
Then stderr is empty
|
274
|
+
And the exit status is 0
|
275
|
+
And stdout is:
|
276
|
+
"""
|
277
|
+
# comment
|
278
|
+
1 # => 1
|
279
|
+
|
280
|
+
=begin
|
281
|
+
multiline comment
|
282
|
+
=end
|
283
|
+
1 + 1 # => 2
|
284
|
+
1 + 1 + 1 # => 3
|
285
|
+
"""
|
286
|
+
|
287
|
+
Scenario: --alignment-strategy chunk
|
288
|
+
Given the file "chunk_alignments.rb":
|
289
|
+
"""
|
290
|
+
# comment
|
291
|
+
1
|
292
|
+
|
293
|
+
=begin
|
294
|
+
multiline comment
|
295
|
+
=end
|
296
|
+
1 + 1
|
297
|
+
1 + 1 + 1
|
298
|
+
|
299
|
+
1+1+1
|
300
|
+
1+1
|
301
|
+
|
302
|
+
1 + 1
|
303
|
+
# comment in the middle!
|
304
|
+
1 + 1 + 1 + 1
|
305
|
+
1 + 1
|
306
|
+
"""
|
307
|
+
When I run "seeing_is_believing --alignment-strategy chunk chunk_alignments.rb"
|
308
|
+
Then stderr is empty
|
309
|
+
And the exit status is 0
|
310
|
+
And stdout is:
|
311
|
+
"""
|
312
|
+
# comment
|
313
|
+
1 # => 1
|
314
|
+
|
315
|
+
=begin
|
316
|
+
multiline comment
|
317
|
+
=end
|
318
|
+
1 + 1 # => 2
|
319
|
+
1 + 1 + 1 # => 3
|
320
|
+
|
321
|
+
1+1+1 # => 3
|
322
|
+
1+1 # => 2
|
323
|
+
|
324
|
+
1 + 1 # => 2
|
325
|
+
# comment in the middle!
|
326
|
+
1 + 1 + 1 + 1 # => 4
|
327
|
+
1 + 1 # => 2
|
328
|
+
"""
|
329
|
+
|
330
|
+
Scenario: --alignment-strategy line
|
331
|
+
Given the file "line_alignments.rb":
|
332
|
+
"""
|
333
|
+
# comment
|
334
|
+
1
|
335
|
+
|
336
|
+
=begin
|
337
|
+
multiline comment
|
338
|
+
=end
|
339
|
+
1 + 1
|
340
|
+
1 + 1 + 1
|
341
|
+
"""
|
342
|
+
When I run "seeing_is_believing --alignment-strategy line line_alignments.rb"
|
343
|
+
Then stderr is empty
|
344
|
+
And the exit status is 0
|
345
|
+
And stdout is:
|
346
|
+
"""
|
347
|
+
# comment
|
348
|
+
1 # => 1
|
349
|
+
|
350
|
+
=begin
|
351
|
+
multiline comment
|
352
|
+
=end
|
353
|
+
1 + 1 # => 2
|
354
|
+
1 + 1 + 1 # => 3
|
355
|
+
"""
|
@@ -6,6 +6,11 @@ When('I run "$command"') { |command| @last_exec
|
|
6
6
|
When("I run '$command'") { |command| @last_executed = CommandLineHelpers.execute command, @stdin_data }
|
7
7
|
Then(/^(stderr|stdout) is:$/) { |stream_name, output| @last_executed.send(stream_name).chomp.should == eval_curlies(output) }
|
8
8
|
Then(/^(stderr|stdout) is ["'](.*?)["']$/) { |stream_name, output| @last_executed.send(stream_name).chomp.should == eval_curlies(output) }
|
9
|
-
Then(/^(stderr|stdout) is empty$/) { |stream_name|
|
9
|
+
Then(/^(stderr|stdout) is empty$/) { |stream_name|
|
10
|
+
stream = @last_executed.send(stream_name)
|
11
|
+
if stream != ''
|
12
|
+
raise "EXPECTED NOTHING, GOT #{stream}"
|
13
|
+
end
|
14
|
+
}
|
10
15
|
Then(/^(stderr|stdout) includes "([^"]*)"$/) { |stream_name, content| @last_executed.send(stream_name).should include eval_curlies(content) }
|
11
16
|
Then('the exit status is $status') { |status| @last_executed.exitstatus.to_s.should == status }
|
data/lib/seeing_is_believing.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
require 'stringio'
|
2
2
|
require 'tmpdir'
|
3
|
+
require 'timeout'
|
3
4
|
|
4
5
|
require 'seeing_is_believing/queue'
|
5
6
|
require 'seeing_is_believing/result'
|
@@ -26,9 +27,10 @@ class SeeingIsBelieving
|
|
26
27
|
@load_path = options.fetch :load_path, []
|
27
28
|
@encoding = options.fetch :encoding, nil
|
28
29
|
@line_number = 1
|
30
|
+
@timeout = options[:timeout]
|
29
31
|
end
|
30
32
|
|
31
|
-
# I'd
|
33
|
+
# I'd like to refactor this, but I was unsatisfied with the three different things I tried.
|
32
34
|
# In the end, I prefer keeping all manipulation of the line number here in the main function
|
33
35
|
# And I like that the higher-level construct of how the program gets built can be found here.
|
34
36
|
def call
|
@@ -118,7 +120,8 @@ class SeeingIsBelieving
|
|
118
120
|
matrix_filename: matrix_filename,
|
119
121
|
require: @require,
|
120
122
|
load_path: @load_path,
|
121
|
-
encoding: @encoding
|
123
|
+
encoding: @encoding,
|
124
|
+
timeout: @timeout)
|
122
125
|
.call
|
123
126
|
.track_line_number(max_line_number)
|
124
127
|
end
|
@@ -1,11 +1,12 @@
|
|
1
1
|
require 'seeing_is_believing'
|
2
2
|
require 'seeing_is_believing/binary/arg_parser'
|
3
3
|
require 'seeing_is_believing/binary/print_results_next_to_lines'
|
4
|
+
require 'timeout'
|
4
5
|
|
5
6
|
|
6
7
|
class SeeingIsBelieving
|
7
8
|
class Binary
|
8
|
-
attr_accessor :argv, :stdin, :stdout, :stderr
|
9
|
+
attr_accessor :argv, :stdin, :stdout, :stderr, :timeout_error
|
9
10
|
|
10
11
|
def initialize(argv, stdin, stdout, stderr)
|
11
12
|
self.argv = argv
|
@@ -21,6 +22,7 @@ class SeeingIsBelieving
|
|
21
22
|
elsif has_filename? && file_dne? then print_file_dne ; 1
|
22
23
|
elsif should_clean? then print_cleaned_program ; 0
|
23
24
|
elsif invalid_syntax? then print_syntax_error ; 1
|
25
|
+
elsif program_timedout? then print_timeout_error ; 1
|
24
26
|
else print_program ; (results.has_exception? ? 1 : 0)
|
25
27
|
end
|
26
28
|
end
|
@@ -33,6 +35,15 @@ class SeeingIsBelieving
|
|
33
35
|
flags[:filename]
|
34
36
|
end
|
35
37
|
|
38
|
+
def program_timedout?
|
39
|
+
results
|
40
|
+
timeout_error
|
41
|
+
end
|
42
|
+
|
43
|
+
def print_timeout_error
|
44
|
+
stderr.puts "Timeout Error after #{@flags[:timeout]} seconds!"
|
45
|
+
end
|
46
|
+
|
36
47
|
def cleaned_body
|
37
48
|
@body ||= PrintResultsNextToLines.remove_previous_output_from \
|
38
49
|
flags[:program] || (file_is_on_stdin? && stdin.read) || File.read(flags[:filename])
|
@@ -44,7 +55,10 @@ class SeeingIsBelieving
|
|
44
55
|
require: flags[:require],
|
45
56
|
load_path: flags[:load_path],
|
46
57
|
encoding: flags[:encoding],
|
47
|
-
stdin: (file_is_on_stdin? ? '' : stdin)
|
58
|
+
stdin: (file_is_on_stdin? ? '' : stdin),
|
59
|
+
timeout: flags[:timeout]
|
60
|
+
rescue Timeout::Error
|
61
|
+
self.timeout_error = true
|
48
62
|
end
|
49
63
|
|
50
64
|
def printer
|
@@ -0,0 +1,28 @@
|
|
1
|
+
class SeeingIsBelieving
|
2
|
+
class Binary
|
3
|
+
class AlignAll
|
4
|
+
attr_accessor :body, :start_line, :end_line
|
5
|
+
|
6
|
+
def initialize(body, start_line, end_line)
|
7
|
+
self.body, self.start_line, self.end_line = body, start_line, end_line
|
8
|
+
end
|
9
|
+
|
10
|
+
# max line length of the lines to output (exempting comments) + 2 spaces for padding
|
11
|
+
def line_length_for(line_number)
|
12
|
+
@max_source_line_length ||= 2 + body.each_line
|
13
|
+
.map(&:chomp)
|
14
|
+
.select.with_index(1) { |line, index| start_line <= index && index <= end_line }
|
15
|
+
.take_while { |line| not start_of_data_segment? line }
|
16
|
+
.select { |line| not SyntaxAnalyzer.begins_multiline_comment?(line) .. SyntaxAnalyzer.ends_multiline_comment?(line ) }
|
17
|
+
.reject { |line| SyntaxAnalyzer.ends_in_comment? line }
|
18
|
+
.map(&:length)
|
19
|
+
.concat([0])
|
20
|
+
.max
|
21
|
+
end
|
22
|
+
|
23
|
+
def start_of_data_segment?(line)
|
24
|
+
SyntaxAnalyzer.begins_data_segment?(line.chomp)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
class SeeingIsBelieving
|
2
|
+
class Binary
|
3
|
+
class AlignChunk
|
4
|
+
attr_accessor :body, :start_line, :end_line
|
5
|
+
|
6
|
+
def initialize(body, start_line, end_line)
|
7
|
+
self.body, self.start_line, self.end_line = body, start_line, end_line
|
8
|
+
end
|
9
|
+
|
10
|
+
# max line length of the the chunk (newline separated sections of code exempting comments) + 2 spaces for padding
|
11
|
+
def line_length_for(line_number)
|
12
|
+
line_lengths[line_number]
|
13
|
+
end
|
14
|
+
|
15
|
+
def line_lengths
|
16
|
+
@line_lengths ||= Hash[
|
17
|
+
body.each_line
|
18
|
+
.map(&:chomp)
|
19
|
+
.map.with_index(1) { |line, index| [line, index] }
|
20
|
+
.take_while { |line, index| not start_of_data_segment? line }
|
21
|
+
.select { |line, index| not SyntaxAnalyzer.begins_multiline_comment?(line) .. SyntaxAnalyzer.ends_multiline_comment?(line ) }
|
22
|
+
.reject { |line, index| SyntaxAnalyzer.ends_in_comment? line }
|
23
|
+
.slice_before { |line, index| line == '' }
|
24
|
+
.map { |slice|
|
25
|
+
max_chunk_length = 2 + slice.map { |line, index| line.length }.max
|
26
|
+
slice.map { |line, index| [index, max_chunk_length] }
|
27
|
+
}
|
28
|
+
.flatten(1)
|
29
|
+
]
|
30
|
+
end
|
31
|
+
|
32
|
+
def start_of_data_segment?(line)
|
33
|
+
SyntaxAnalyzer.begins_data_segment?(line.chomp)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
class SeeingIsBelieving
|
2
|
+
class Binary
|
3
|
+
class AlignLine
|
4
|
+
attr_accessor :body, :start_line, :end_line
|
5
|
+
|
6
|
+
def initialize(body, start_line, end_line)
|
7
|
+
self.body, self.start_line, self.end_line = body, start_line, end_line
|
8
|
+
end
|
9
|
+
|
10
|
+
# length of the line + 2 spaces for padding
|
11
|
+
def line_length_for(line_number)
|
12
|
+
line_lengths[line_number]
|
13
|
+
end
|
14
|
+
|
15
|
+
def line_lengths
|
16
|
+
@line_lengths ||= Hash[ body.each_line
|
17
|
+
.map(&:chomp)
|
18
|
+
.each
|
19
|
+
.with_index(1)
|
20
|
+
.map { |line, index| [index, line.length+2] }
|
21
|
+
]
|
22
|
+
end
|
23
|
+
|
24
|
+
def start_of_data_segment?(line)
|
25
|
+
SyntaxAnalyzer.begins_data_segment?(line.chomp)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -1,3 +1,8 @@
|
|
1
|
+
require 'seeing_is_believing/version'
|
2
|
+
require 'seeing_is_believing/binary/align_all'
|
3
|
+
require 'seeing_is_believing/binary/align_line'
|
4
|
+
require 'seeing_is_believing/binary/align_chunk'
|
5
|
+
|
1
6
|
class SeeingIsBelieving
|
2
7
|
class Binary
|
3
8
|
class ArgParser
|
@@ -16,20 +21,22 @@ class SeeingIsBelieving
|
|
16
21
|
@result ||= begin
|
17
22
|
until args.empty?
|
18
23
|
case (arg = args.shift)
|
19
|
-
when '-h', '--help'
|
20
|
-
when '-v', '--version'
|
21
|
-
when '-c', '--clean'
|
22
|
-
when '-l', '--start-line'
|
23
|
-
when '-L', '--end-line'
|
24
|
-
when '-d', '--line-length'
|
25
|
-
when '-D', '--result-length'
|
26
|
-
when '-
|
27
|
-
when '-
|
28
|
-
when '-
|
29
|
-
when '-
|
30
|
-
when
|
31
|
-
when '-
|
32
|
-
when
|
24
|
+
when '-h', '--help' then options[:help] = self.class.help_screen
|
25
|
+
when '-v', '--version' then options[:version] = true
|
26
|
+
when '-c', '--clean' then options[:clean] = true
|
27
|
+
when '-l', '--start-line' then extract_positive_int_for :start_line, arg
|
28
|
+
when '-L', '--end-line' then extract_positive_int_for :end_line, arg
|
29
|
+
when '-d', '--line-length' then extract_positive_int_for :line_length, arg
|
30
|
+
when '-D', '--result-length' then extract_positive_int_for :result_length, arg
|
31
|
+
when '-t', '--timeout' then extract_non_negative_float_for :timeout, arg
|
32
|
+
when '-r', '--require' then next_arg("#{arg} expected a filename as the following argument but did not see one") { |filename| options[:require] << filename }
|
33
|
+
when '-I', '--load-path' then next_arg("#{arg} expected a directory as the following argument but did not see one") { |dir| options[:load_path] << dir }
|
34
|
+
when '-e', '--program' then next_arg("#{arg} expected a program as the following argument but did not see one") { |program| options[:program] = program }
|
35
|
+
when '-a', '--as' then next_arg("#{arg} expected a filename as the following argument but did not see one") { |filename| options[:as] = filename }
|
36
|
+
when '-s', '--alignment-strategy' then extract_alignment_strategy
|
37
|
+
when /\A-K(.+)/ then options[:encoding] = $1
|
38
|
+
when '-K', '--encoding' then next_arg("#{arg} expects an encoding, see `man ruby` for possibile values") { |encoding| options[:encoding] = encoding }
|
39
|
+
when /^-/ then options[:errors] << "Unknown option: #{arg.inspect}" # unknown flags
|
33
40
|
else
|
34
41
|
filenames << arg
|
35
42
|
options[:filename] = arg
|
@@ -58,18 +65,36 @@ class SeeingIsBelieving
|
|
58
65
|
|
59
66
|
def options
|
60
67
|
@options ||= {
|
61
|
-
version:
|
62
|
-
clean:
|
63
|
-
program:
|
64
|
-
filename:
|
65
|
-
start_line:
|
66
|
-
line_length:
|
67
|
-
end_line:
|
68
|
-
result_length:
|
69
|
-
|
70
|
-
|
71
|
-
|
68
|
+
version: false,
|
69
|
+
clean: false,
|
70
|
+
program: nil,
|
71
|
+
filename: nil,
|
72
|
+
start_line: 1,
|
73
|
+
line_length: Float::INFINITY,
|
74
|
+
end_line: Float::INFINITY,
|
75
|
+
result_length: Float::INFINITY,
|
76
|
+
timeout: 0, # timeout lib treats this as infinity
|
77
|
+
errors: [],
|
78
|
+
require: [],
|
79
|
+
load_path: [],
|
80
|
+
alignment_strategy: AlignAll,
|
81
|
+
}
|
82
|
+
end
|
83
|
+
|
84
|
+
|
85
|
+
def extract_alignment_strategy
|
86
|
+
strategies = {
|
87
|
+
'file' => AlignAll,
|
88
|
+
'chunk' => AlignChunk,
|
89
|
+
'line' => AlignLine,
|
72
90
|
}
|
91
|
+
next_arg "alignment-strategy expected an alignment strategy as the following argument but did not see one" do |strategy_name|
|
92
|
+
if strategies[strategy_name]
|
93
|
+
options[:alignment_strategy] = strategies[strategy_name]
|
94
|
+
else
|
95
|
+
options[:errors] << "alignment-strategy does not know #{strategy_name}, only knows: #{strategies.keys.join(', ')}"
|
96
|
+
end
|
97
|
+
end
|
73
98
|
end
|
74
99
|
|
75
100
|
def next_arg(error_message, &success_block)
|
@@ -86,6 +111,15 @@ class SeeingIsBelieving
|
|
86
111
|
options[:errors] << "#{flag} expects a positive integer argument"
|
87
112
|
end
|
88
113
|
end
|
114
|
+
|
115
|
+
def extract_non_negative_float_for(key, flag)
|
116
|
+
float = Float args.shift
|
117
|
+
raise if float < 0
|
118
|
+
options[key] = float
|
119
|
+
rescue
|
120
|
+
options[:errors] << "#{flag} expects a positive float or integer argument"
|
121
|
+
end
|
122
|
+
|
89
123
|
end
|
90
124
|
|
91
125
|
def ArgParser.help_screen
|
@@ -96,18 +130,23 @@ Usage: #{$0} [options] [filename]
|
|
96
130
|
|
97
131
|
If no filename is provided, the binary will read the program from standard input.
|
98
132
|
|
99
|
-
-l, --start-line n
|
100
|
-
-L, --end-line n
|
101
|
-
-d, --line-length n
|
102
|
-
-D, --result-length n
|
103
|
-
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
-
|
108
|
-
-
|
109
|
-
-
|
110
|
-
-
|
133
|
+
-l, --start-line n # line number to begin showing results on
|
134
|
+
-L, --end-line n # line number to stop showing results on
|
135
|
+
-d, --line-length n # max length of the entire line (only truncates results, not source lines)
|
136
|
+
-D, --result-length n # max length of the portion after the "# => "
|
137
|
+
-s, --alignment-strategy # select the alignment strategy:
|
138
|
+
file (DEFAULT) => the entire file is at the same alignment
|
139
|
+
chunk => each chunk of code is at the same alignment
|
140
|
+
line => each line is at its own alignment
|
141
|
+
-t, --timeout n # timeout limit in seconds when evaluating source file (ex. -t 0.3 or -t 3)
|
142
|
+
-I, --load-path dir # a dir that should be added to the $LOAD_PATH
|
143
|
+
-r, --require file # additional files to be required before running the program
|
144
|
+
-e, --program program # Pass the program to execute as an argument
|
145
|
+
-K, --encoding encoding # sets file encoding, equivalent to Ruby's -Kx (see `man ruby` for valid values)
|
146
|
+
-a, --as filename # run the program as if it was the specified filename
|
147
|
+
-c, --clean # remove annotations from previous runs of seeing_is_believing
|
148
|
+
-v, --version # print the version (#{VERSION})
|
149
|
+
-h, --help # this help screen
|
111
150
|
HELP_SCREEN
|
112
151
|
end
|
113
152
|
end
|
@@ -2,6 +2,7 @@ require 'seeing_is_believing/queue'
|
|
2
2
|
require 'seeing_is_believing/has_exception'
|
3
3
|
require 'seeing_is_believing/binary/line_formatter'
|
4
4
|
|
5
|
+
|
5
6
|
class SeeingIsBelieving
|
6
7
|
class Binary
|
7
8
|
class PrintResultsNextToLines
|
@@ -29,11 +30,11 @@ class SeeingIsBelieving
|
|
29
30
|
method_from_options :line_length, Float::INFINITY
|
30
31
|
method_from_options :result_length, Float::INFINITY
|
31
32
|
|
32
|
-
|
33
33
|
def initialize(body, file_result, options={})
|
34
|
-
self.body
|
35
|
-
self.options
|
36
|
-
self.file_result
|
34
|
+
self.body = body
|
35
|
+
self.options = options
|
36
|
+
self.file_result = file_result
|
37
|
+
self.alignment_strategy = options[:alignment_strategy].new body, start_line, end_line
|
37
38
|
end
|
38
39
|
|
39
40
|
def new_body
|
@@ -52,7 +53,7 @@ class SeeingIsBelieving
|
|
52
53
|
|
53
54
|
private
|
54
55
|
|
55
|
-
attr_accessor :body, :file_result, :options
|
56
|
+
attr_accessor :body, :file_result, :options, :alignment_strategy
|
56
57
|
|
57
58
|
def add_each_line_until_start_or_data_segment
|
58
59
|
line_queue.until { |line, line_number| line_number == start_line || start_of_data_segment?(line) }
|
@@ -61,7 +62,7 @@ class SeeingIsBelieving
|
|
61
62
|
|
62
63
|
def add_lines_with_results_until_end_or_data_segment
|
63
64
|
line_queue.until { |line, line_number| end_line < line_number || start_of_data_segment?(line) }
|
64
|
-
.each { |line, line_number| new_body << format_line(line.chomp, file_result[line_number]) }
|
65
|
+
.each { |line, line_number| new_body << format_line(line.chomp, line_number, file_result[line_number]) }
|
65
66
|
end
|
66
67
|
|
67
68
|
def add_lines_until_data_segment
|
@@ -81,19 +82,6 @@ class SeeingIsBelieving
|
|
81
82
|
SyntaxAnalyzer.begins_data_segment?(line.chomp)
|
82
83
|
end
|
83
84
|
|
84
|
-
# max line length of the lines to output (exempting comments) + 2 spaces for padding
|
85
|
-
def max_source_line_length
|
86
|
-
@max_source_line_length ||= 2 + body.each_line
|
87
|
-
.map(&:chomp)
|
88
|
-
.select.with_index(1) { |line, index| start_line <= index && index <= end_line }
|
89
|
-
.take_while { |line| not start_of_data_segment? line }
|
90
|
-
.select { |line| not SyntaxAnalyzer.begins_multiline_comment?(line) .. SyntaxAnalyzer.ends_multiline_comment?(line ) }
|
91
|
-
.reject { |line| SyntaxAnalyzer.ends_in_comment? line }
|
92
|
-
.map(&:length)
|
93
|
-
.concat([0])
|
94
|
-
.max
|
95
|
-
end
|
96
|
-
|
97
85
|
def add_stdout
|
98
86
|
return unless file_result.has_stdout?
|
99
87
|
new_body << "\n"
|
@@ -110,8 +98,8 @@ class SeeingIsBelieving
|
|
110
98
|
end
|
111
99
|
end
|
112
100
|
|
113
|
-
def format_line(line, line_results)
|
114
|
-
options = options().merge source_length:
|
101
|
+
def format_line(line, line_number, line_results)
|
102
|
+
options = options().merge source_length: alignment_strategy.line_length_for(line_number)
|
115
103
|
formatted_line = if line_results.has_exception?
|
116
104
|
result = sprintf "%s: %s", line_results.exception.class, line_results.exception.message
|
117
105
|
LineFormatter.new(line, "#{EXCEPTION_PREFIX} ", result, options).call
|
@@ -11,6 +11,7 @@
|
|
11
11
|
# I did look at Ripper, and it will invoke on_kw("__FILE__")
|
12
12
|
# when it sees this.
|
13
13
|
|
14
|
+
require 'yaml'
|
14
15
|
require 'open3'
|
15
16
|
require 'stringio'
|
16
17
|
require 'fileutils'
|
@@ -20,7 +21,7 @@ require 'seeing_is_believing/hard_core_ensure'
|
|
20
21
|
|
21
22
|
class SeeingIsBelieving
|
22
23
|
class EvaluateByMovingFiles
|
23
|
-
attr_accessor :program, :filename, :error_stream, :input_stream, :matrix_filename, :require_flags, :load_path_flags, :encoding
|
24
|
+
attr_accessor :program, :filename, :error_stream, :input_stream, :matrix_filename, :require_flags, :load_path_flags, :encoding, :timeout
|
24
25
|
|
25
26
|
def initialize(program, filename, options={})
|
26
27
|
self.program = program
|
@@ -31,6 +32,7 @@ class SeeingIsBelieving
|
|
31
32
|
self.require_flags = options.fetch(:require, []).map { |filename| ['-r', filename] }.flatten
|
32
33
|
self.load_path_flags = options.fetch(:load_path, []).map { |dir| ['-I', dir] }.flatten
|
33
34
|
self.encoding = options.fetch :encoding, nil
|
35
|
+
self.timeout = options[:timeout]
|
34
36
|
end
|
35
37
|
|
36
38
|
def call
|
@@ -44,7 +46,7 @@ class SeeingIsBelieving
|
|
44
46
|
fail unless exitstatus.success?
|
45
47
|
deserialize_result
|
46
48
|
rescue Exception
|
47
|
-
|
49
|
+
notify_user_of_error if error_implies_bug_in_sib? $!
|
48
50
|
raise $!
|
49
51
|
end
|
50
52
|
},
|
@@ -53,6 +55,10 @@ class SeeingIsBelieving
|
|
53
55
|
}
|
54
56
|
end
|
55
57
|
|
58
|
+
def error_implies_bug_in_sib?(error)
|
59
|
+
not error.kind_of? Timeout::Error
|
60
|
+
end
|
61
|
+
|
56
62
|
def file_directory
|
57
63
|
File.dirname filename
|
58
64
|
end
|
@@ -95,9 +101,16 @@ class SeeingIsBelieving
|
|
95
101
|
input_stream.each_char { |char| process_stdin.write char }
|
96
102
|
process_stdin.close
|
97
103
|
end
|
98
|
-
|
99
|
-
|
100
|
-
|
104
|
+
begin
|
105
|
+
Timeout::timeout timeout do
|
106
|
+
self.stdout = out_reader.value
|
107
|
+
self.stderr = err_reader.value
|
108
|
+
self.exitstatus = thread.value
|
109
|
+
end
|
110
|
+
rescue Timeout::Error
|
111
|
+
Process.kill "TERM", thread.pid
|
112
|
+
raise $!
|
113
|
+
end
|
101
114
|
end
|
102
115
|
end
|
103
116
|
|
@@ -117,10 +130,12 @@ class SeeingIsBelieving
|
|
117
130
|
end
|
118
131
|
|
119
132
|
def deserialize_result
|
120
|
-
|
133
|
+
result = YAML.load stdout.force_encoding("utf-8")
|
134
|
+
result.fix_exception_backtraces_after_yaml_serialization
|
135
|
+
result
|
121
136
|
end
|
122
137
|
|
123
|
-
def
|
138
|
+
def notify_user_of_error
|
124
139
|
error_stream.puts "It blew up. Not too surprising given that seeing_is_believing is pretty rough around the edges, but still this shouldn't happen."
|
125
140
|
error_stream.puts "Please log an issue at: https://github.com/JoshCheek/seeing_is_believing/issues"
|
126
141
|
error_stream.puts
|
@@ -2,5 +2,27 @@ class SeeingIsBelieving
|
|
2
2
|
module HasException
|
3
3
|
attr_accessor :exception
|
4
4
|
alias has_exception? exception
|
5
|
+
|
6
|
+
# NOTE:
|
7
|
+
# zomg, so YAML doesn't serialize an exception's backtrace
|
8
|
+
# and Marshal gets all horked on windows (something like Marshal data too short)
|
9
|
+
# so I'm going back to YAML, but independently storing the backtrace
|
10
|
+
# It will need to get manually set back onto the exception
|
11
|
+
#
|
12
|
+
# However, there is no Exception#backtrace=, so I have to redefine the method
|
13
|
+
# which sucks b/c of cache busting and so forth
|
14
|
+
# but that probably doesn't actually matter for any real-world use case of SeeingIsBelieving
|
15
|
+
|
16
|
+
def exception=(exception)
|
17
|
+
@exception = exception
|
18
|
+
@exception_backtrace = exception.backtrace
|
19
|
+
end
|
20
|
+
|
21
|
+
def fix_exception_backtraces_after_yaml_serialization
|
22
|
+
return unless exception
|
23
|
+
exception_backtrace = @exception_backtrace
|
24
|
+
exception.define_singleton_method(:backtrace) { exception_backtrace }
|
25
|
+
self
|
26
|
+
end
|
5
27
|
end
|
6
28
|
end
|
@@ -1,6 +1,7 @@
|
|
1
1
|
# WARNING: DO NOT REQUIRE THIS FILE, IT WILL FUCK YOU UP!!!!!!
|
2
2
|
|
3
3
|
|
4
|
+
require 'yaml'
|
4
5
|
require 'stringio'
|
5
6
|
real_stdout = STDOUT
|
6
7
|
real_stderr = STDERR
|
@@ -14,5 +15,5 @@ at_exit do
|
|
14
15
|
$seeing_is_believing_current_result.stdout = fake_stdout.string
|
15
16
|
$seeing_is_believing_current_result.stderr = fake_stderr.string
|
16
17
|
|
17
|
-
real_stdout.write
|
18
|
+
real_stdout.write YAML.dump($seeing_is_believing_current_result).force_encoding("utf-8")
|
18
19
|
end
|
data/spec/arg_parser_spec.rb
CHANGED
@@ -40,9 +40,25 @@ describe SeeingIsBelieving::Binary::ArgParser do
|
|
40
40
|
end
|
41
41
|
end
|
42
42
|
|
43
|
+
shared_examples 'it requires a non-negative float or int' do |flags|
|
44
|
+
it 'expects a non-negative float or int argument' do
|
45
|
+
flags.each do |flag|
|
46
|
+
parse([flag, '1']).should_not have_error /#{flag}/
|
47
|
+
parse([flag, '0']).should_not have_error /#{flag}/
|
48
|
+
parse([flag, '-1']).should have_error /#{flag}/
|
49
|
+
parse([flag,'-1.0']).should have_error /#{flag}/
|
50
|
+
parse([flag, '1.0']).should_not have_error /#{flag}/
|
51
|
+
parse([flag, 'a']).should have_error /#{flag}/
|
52
|
+
parse([flag, '' ]).should have_error /#{flag}/
|
53
|
+
parse([flag ]).should have_error /#{flag}/
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
43
58
|
specify 'unknown options set an error' do
|
44
59
|
parse(['--xyz']).should have_error 'Unknown option: "--xyz"'
|
45
60
|
parse(['-x']).should have_error 'Unknown option: "-x"'
|
61
|
+
parse(['-x', 'b']).should have_error 'Unknown option: "-x"'
|
46
62
|
end
|
47
63
|
|
48
64
|
example 'example: multiple args' do
|
@@ -259,5 +275,41 @@ describe SeeingIsBelieving::Binary::ArgParser do
|
|
259
275
|
parse(%w[--version])[:version].should == true
|
260
276
|
end
|
261
277
|
end
|
278
|
+
|
279
|
+
describe ':timeout' do
|
280
|
+
it 'defaults to 0' do
|
281
|
+
parse([])[:timeout].should == 0
|
282
|
+
end
|
283
|
+
|
284
|
+
it_behaves_like 'it requires a non-negative float or int', ['-t', '--timeout']
|
285
|
+
end
|
286
|
+
|
287
|
+
describe ':alignment_strategy' do
|
288
|
+
AlignAll = SeeingIsBelieving::Binary::AlignAll
|
289
|
+
AlignLine = SeeingIsBelieving::Binary::AlignLine
|
290
|
+
AlignChunk = SeeingIsBelieving::Binary::AlignChunk
|
291
|
+
|
292
|
+
# maybe change the default?
|
293
|
+
it 'defaults to AlignAll' do
|
294
|
+
parse([])[:alignment_strategy].should == AlignAll
|
295
|
+
end
|
296
|
+
|
297
|
+
specify '-s and --alignment-strategy sets the alignment strategy' do
|
298
|
+
parse(['-s', 'chunk'])[:alignment_strategy].should == AlignChunk
|
299
|
+
parse(['--alignment-strategy', 'chunk'])[:alignment_strategy].should == AlignChunk
|
300
|
+
end
|
301
|
+
|
302
|
+
it 'accepts values: file, line, chunk' do
|
303
|
+
parse(['-s', 'file'])[:alignment_strategy].should == AlignAll
|
304
|
+
parse(['-s', 'line'])[:alignment_strategy].should == AlignLine
|
305
|
+
parse(['-s', 'chunk'])[:alignment_strategy].should == AlignChunk
|
306
|
+
end
|
307
|
+
|
308
|
+
it 'sets an error if not provided with a strategy, or if provided with an unknown strategy' do
|
309
|
+
parse(['-s', 'file']).should_not have_error /alignment-strategy/
|
310
|
+
parse(['-s', 'abc']).should have_error /alignment-strategy/
|
311
|
+
parse(['-s' ]).should have_error /alignment-strategy/
|
312
|
+
end
|
313
|
+
end
|
262
314
|
end
|
263
315
|
|
metadata
CHANGED
@@ -2,14 +2,14 @@
|
|
2
2
|
name: seeing_is_believing
|
3
3
|
version: !ruby/object:Gem::Version
|
4
4
|
prerelease:
|
5
|
-
version: 0.0.
|
5
|
+
version: 0.0.17
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
8
8
|
- Josh Cheek
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-04-
|
12
|
+
date: 2013-04-23 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
version_requirements: !ruby/object:Gem::Requirement
|
@@ -99,6 +99,9 @@ files:
|
|
99
99
|
- features/support/env.rb
|
100
100
|
- lib/seeing_is_believing.rb
|
101
101
|
- lib/seeing_is_believing/binary.rb
|
102
|
+
- lib/seeing_is_believing/binary/align_all.rb
|
103
|
+
- lib/seeing_is_believing/binary/align_chunk.rb
|
104
|
+
- lib/seeing_is_believing/binary/align_line.rb
|
102
105
|
- lib/seeing_is_believing/binary/arg_parser.rb
|
103
106
|
- lib/seeing_is_believing/binary/line_formatter.rb
|
104
107
|
- lib/seeing_is_believing/binary/print_results_next_to_lines.rb
|