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.
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- seeing_is_believing (0.0.16)
4
+ seeing_is_believing (0.0.17)
5
5
 
6
6
  GEM
7
7
  remote: http://rubygems.org/
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
  =======
@@ -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
+
@@ -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| @last_executed.send(stream_name).should == '' }
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 }
@@ -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 lik to refactor this, but I was unsatisfied with the three different things I tried.
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' then options[:help] = self.class.help_screen
20
- when '-v', '--version' then options[:version] = true
21
- when '-c', '--clean' then options[:clean] = true
22
- when '-l', '--start-line' then extract_positive_int_for :start_line, arg
23
- when '-L', '--end-line' then extract_positive_int_for :end_line, arg
24
- when '-d', '--line-length' then extract_positive_int_for :line_length, arg
25
- when '-D', '--result-length' then extract_positive_int_for :result_length, arg
26
- when '-r', '--require' then next_arg("#{arg} expected a filename as the following argument but did not see one") { |filename| options[:require] << filename }
27
- 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 }
28
- when '-e', '--program' then next_arg("#{arg} expected a program as the following argument but did not see one") { |program| options[:program] = program }
29
- when '-a', '--as' then next_arg("#{arg} expected a filename as the following argument but did not see one") { |filename| options[:as] = filename }
30
- when /\A-K(.+)/ then options[:encoding] = $1
31
- when '-K', '--encoding' then next_arg("#{arg} expects an encoding, see `man ruby` for possibile values") { |encoding| options[:encoding] = encoding }
32
- when /^-/ then options[:errors] << "Unknown option: #{arg.inspect}" # unknown flags
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: false,
62
- clean: false,
63
- program: nil,
64
- filename: nil,
65
- start_line: 1,
66
- line_length: Float::INFINITY,
67
- end_line: Float::INFINITY,
68
- result_length: Float::INFINITY,
69
- errors: [],
70
- require: [],
71
- load_path: [],
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 # line number to begin showing results on
100
- -L, --end-line n # line number to stop showing results on
101
- -d, --line-length n # max length of the entire line (only truncates results, not source lines)
102
- -D, --result-length n # max length of the portion after the "# => "
103
- -I, --load-path dir # a dir that should be added to the $LOAD_PATH
104
- -r, --require file # additional files to be required before running the program
105
- -e, --program program # Pass the program to execute as an argument
106
- -K, --encoding encoding # sets file encoding, equivalent to Ruby's -Kx (see `man ruby` for valid values)
107
- -a, --as filename # run the program as if it was the specified filename
108
- -c, --clean # remove annotations from previous runs of seeing_is_believing
109
- -v, --version # print the version (#{VERSION})
110
- -h, --help # this help screen
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 = body
35
- self.options = options
36
- self.file_result = 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: max_source_line_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
- record_error
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
- self.stdout = out_reader.value
99
- self.stderr = err_reader.value
100
- self.exitstatus = thread.value
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
- Marshal.load stdout
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 record_error
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 Marshal.dump $seeing_is_believing_current_result
18
+ real_stdout.write YAML.dump($seeing_is_believing_current_result).force_encoding("utf-8")
18
19
  end
@@ -1,3 +1,3 @@
1
1
  class SeeingIsBelieving
2
- VERSION = '0.0.16'
2
+ VERSION = '0.0.17'
3
3
  end
@@ -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
 
@@ -279,4 +279,9 @@ describe SeeingIsBelieving do
279
279
  [], [],
280
280
  ['6']]
281
281
  end
282
+
283
+ it 'times out if the timeout limit is exceeded' do
284
+ expect { invoke "sleep 0.2", timeout: 0.1 }.to raise_error Timeout::Error
285
+ end
286
+
282
287
  end
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.16
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-19 00:00:00.000000000 Z
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