seeing_is_believing 0.0.16 → 0.0.17

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.
@@ -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