seeing_is_believing 3.0.0.beta.4 → 3.0.0.beta.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (72) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +0 -8
  3. data/Rakefile +1 -1
  4. data/Readme.md +65 -25
  5. data/bin/seeing_is_believing +1 -0
  6. data/docs/sib-streaming.gif +0 -0
  7. data/features/deprecated-flags.feature +62 -2
  8. data/features/errors.feature +12 -7
  9. data/features/examples.feature +143 -4
  10. data/features/flags.feature +89 -29
  11. data/features/regression.feature +58 -14
  12. data/features/support/env.rb +4 -0
  13. data/features/xmpfilter-style.feature +181 -36
  14. data/lib/seeing_is_believing.rb +44 -33
  15. data/lib/seeing_is_believing/binary.rb +31 -88
  16. data/lib/seeing_is_believing/binary/align_chunk.rb +30 -11
  17. data/lib/seeing_is_believing/binary/annotate_end_of_file.rb +10 -16
  18. data/lib/seeing_is_believing/binary/annotate_every_line.rb +5 -25
  19. data/lib/seeing_is_believing/binary/annotate_marked_lines.rb +136 -0
  20. data/lib/seeing_is_believing/binary/comment_lines.rb +8 -10
  21. data/lib/seeing_is_believing/binary/commentable_lines.rb +20 -26
  22. data/lib/seeing_is_believing/binary/config.rb +392 -0
  23. data/lib/seeing_is_believing/binary/data_structures.rb +57 -0
  24. data/lib/seeing_is_believing/binary/engine.rb +104 -0
  25. data/lib/seeing_is_believing/binary/{comment_formatter.rb → format_comment.rb} +6 -6
  26. data/lib/seeing_is_believing/binary/remove_annotations.rb +29 -28
  27. data/lib/seeing_is_believing/binary/rewrite_comments.rb +42 -43
  28. data/lib/seeing_is_believing/code.rb +105 -49
  29. data/lib/seeing_is_believing/debugger.rb +6 -5
  30. data/lib/seeing_is_believing/error.rb +6 -17
  31. data/lib/seeing_is_believing/evaluate_by_moving_files.rb +78 -129
  32. data/lib/seeing_is_believing/event_stream/consumer.rb +114 -64
  33. data/lib/seeing_is_believing/event_stream/events.rb +169 -11
  34. data/lib/seeing_is_believing/event_stream/handlers/debug.rb +57 -0
  35. data/lib/seeing_is_believing/event_stream/handlers/record_exitstatus.rb +18 -0
  36. data/lib/seeing_is_believing/event_stream/handlers/stream_json_events.rb +45 -0
  37. data/lib/seeing_is_believing/event_stream/handlers/update_result.rb +39 -0
  38. data/lib/seeing_is_believing/event_stream/producer.rb +25 -24
  39. data/lib/seeing_is_believing/hash_struct.rb +206 -0
  40. data/lib/seeing_is_believing/result.rb +20 -3
  41. data/lib/seeing_is_believing/the_matrix.rb +20 -12
  42. data/lib/seeing_is_believing/version.rb +1 -1
  43. data/lib/seeing_is_believing/wrap_expressions.rb +55 -115
  44. data/lib/seeing_is_believing/wrap_expressions_with_inspect.rb +14 -0
  45. data/seeing_is_believing.gemspec +1 -1
  46. data/spec/binary/alignment_specs.rb +27 -0
  47. data/spec/binary/comment_lines_spec.rb +3 -2
  48. data/spec/binary/config_spec.rb +657 -0
  49. data/spec/binary/engine_spec.rb +97 -0
  50. data/spec/binary/{comment_formatter_spec.rb → format_comment_spec.rb} +2 -2
  51. data/spec/binary/marker_spec.rb +71 -0
  52. data/spec/binary/options_spec.rb +0 -0
  53. data/spec/binary/remove_annotations_spec.rb +31 -18
  54. data/spec/binary/rewrite_comments_spec.rb +26 -11
  55. data/spec/code_spec.rb +190 -6
  56. data/spec/debugger_spec.rb +4 -0
  57. data/spec/evaluate_by_moving_files_spec.rb +38 -20
  58. data/spec/event_stream_spec.rb +265 -116
  59. data/spec/hash_struct_spec.rb +514 -0
  60. data/spec/seeing_is_believing_spec.rb +108 -46
  61. data/spec/spec_helper.rb +9 -0
  62. data/spec/wrap_expressions_spec.rb +207 -172
  63. metadata +30 -18
  64. data/docs/for-presentations +0 -33
  65. data/lib/seeing_is_believing/binary/annotate_xmpfilter_style.rb +0 -128
  66. data/lib/seeing_is_believing/binary/interpret_flags.rb +0 -156
  67. data/lib/seeing_is_believing/binary/parse_args.rb +0 -263
  68. data/lib/seeing_is_believing/event_stream/update_result.rb +0 -24
  69. data/lib/seeing_is_believing/inspect_expressions.rb +0 -21
  70. data/lib/seeing_is_believing/parser_helpers.rb +0 -82
  71. data/spec/binary/interpret_flags_spec.rb +0 -332
  72. data/spec/binary/parse_args_spec.rb +0 -415
@@ -3,7 +3,7 @@ class SeeingIsBelieving
3
3
  include Enumerable
4
4
  RecordedException = Struct.new :line_number, :class_name, :message, :backtrace
5
5
 
6
- attr_accessor :stdout, :stderr, :exitstatus, :number_of_captures, :exception, :num_lines, :sib_version, :ruby_version, :filename
6
+ attr_accessor :stdout, :stderr, :exitstatus, :max_line_captures, :exception, :num_lines, :sib_version, :ruby_version, :filename
7
7
 
8
8
  def initialize
9
9
  self.stdout = ''
@@ -52,8 +52,25 @@ class SeeingIsBelieving
52
52
  "#<SIB::Result #{variables.join "\n "}>"
53
53
  end
54
54
 
55
- def number_of_captures
56
- @number_of_captures || Float::INFINITY
55
+ def max_line_captures
56
+ @max_line_captures || Float::INFINITY
57
+ end
58
+
59
+ def as_json
60
+ ex = has_exception? && {
61
+ line_number_in_this_file: exception.line_number,
62
+ class_name: exception.class_name,
63
+ message: exception.message,
64
+ backtrace: exception.backtrace,
65
+ }
66
+
67
+ { stdout: stdout,
68
+ stderr: stderr,
69
+ exitstatus: exitstatus,
70
+ exception: ex,
71
+ lines: each.with_object(Hash.new)
72
+ .with_index(1) { |(result, hash), line_number| hash[line_number] = result },
73
+ }
57
74
  end
58
75
 
59
76
  private
@@ -1,16 +1,16 @@
1
- # WARNING: DO NOT REQUIRE THIS FILE, IT WILL FUCK YOU UP!!!!!!
2
-
3
- # READ THIS IF YOU WANT TO USE YOUR OWN MATRIX FILE:
4
- # https://github.com/JoshCheek/seeing_is_believing/issues/24
5
- #
6
- # (or if you want to understand why we do the pipe dance)
7
-
8
1
  require_relative 'version'
9
2
  require_relative 'event_stream/producer'
10
3
 
11
- event_stream = IO.open(ARGV.shift.to_i, "w")
4
+ sib_vars = Marshal.load ENV["SIB_VARIABLES.MARSHAL.B64"].unpack('m0').first
5
+ event_stream = IO.open sib_vars.fetch(:event_stream_fd), "w"
12
6
  $SiB = SeeingIsBelieving::EventStream::Producer.new(event_stream)
7
+ $SiB.record_ruby_version RUBY_VERSION
8
+ $SiB.record_sib_version SeeingIsBelieving::VERSION
9
+ $SiB.record_filename sib_vars.fetch(:filename)
10
+ $SiB.record_num_lines sib_vars.fetch(:num_lines)
11
+ $SiB.record_max_line_captures sib_vars.fetch(:max_line_captures)
13
12
 
13
+ STDOUT.sync = true
14
14
  stdout, stderr = STDOUT, STDERR
15
15
  finish = lambda do
16
16
  $SiB.finish!
@@ -19,17 +19,25 @@ finish = lambda do
19
19
  stderr.flush
20
20
  end
21
21
 
22
- real_exec = method :exec
22
+ real_exec = method :exec
23
+ real_exit_bang = method :exit!
23
24
  Kernel.module_eval do
24
25
  private
25
- define_method :exec do |*args, &block| # TODO: Add an event for exec?
26
+
27
+ define_method :exec do |*args, &block|
28
+ $SiB.record_exec(args)
26
29
  finish.call
27
30
  real_exec.call(*args, &block)
28
31
  end
32
+
33
+ define_method :exit! do |status=false|
34
+ finish.call
35
+ real_exit_bang.call(status)
36
+ end
29
37
  end
30
38
 
31
39
  at_exit do
32
- $SiB.record_exception nil, $! if $!
40
+ exitstatus = ($! ? $SiB.record_exception(nil, $!) : 0)
33
41
  finish.call
34
- Kernel.exit! 0 # clear the exception so it doesn't print to stderr and change the processes actual exit status (we recorded what it should be)
42
+ real_exit_bang.call(exitstatus) # clears exceptions so they don't print to stderr and change the processes actual exit status (we recorded what it should be)
35
43
  end
@@ -1,3 +1,3 @@
1
1
  class SeeingIsBelieving
2
- VERSION = '3.0.0.beta.4'
2
+ VERSION = '3.0.0.beta.5'
3
3
  end
@@ -1,44 +1,39 @@
1
- require 'seeing_is_believing/parser_helpers'
1
+ require 'seeing_is_believing/code'
2
2
 
3
3
  # comprehensive list of syntaxes that can come up
4
4
  # https://github.com/whitequark/parser/blob/master/doc/AST_FORMAT.md
5
5
  class SeeingIsBelieving
6
6
  class WrapExpressions
7
7
 
8
- include ParserHelpers
9
-
10
8
  def self.call(program, wrappings)
11
9
  new(program, wrappings).call
12
10
  end
13
11
 
14
12
  def initialize(program, wrappings)
15
- self.program = program
16
- self.before_all = wrappings.fetch :before_all, -> { ''.freeze }
17
- self.after_all = wrappings.fetch :after_all, -> { ''.freeze }
13
+ self.before_all = wrappings.fetch :before_all, -> { '' }
14
+ self.after_all = wrappings.fetch :after_all, -> { '' }
18
15
  self.before_each = wrappings.fetch :before_each, -> * { '' }
19
16
  self.after_each = wrappings.fetch :after_each, -> * { '' }
20
- self.buffer, parser, self.rewriter = initialize_parser(program, 'program-without-annotations')
21
- self.root = parser.parse buffer
22
17
  self.wrappings = {}
23
- rescue Parser::SyntaxError => e
24
- raise ::SyntaxError, e.message
18
+ self.code = Code.new(program, 'program-without-annotations')
19
+ code.syntax.valid? || raise(::SyntaxError, code.syntax.error_message)
25
20
  end
26
21
 
27
22
  def call
28
23
  @called ||= begin
29
- wrap_recursive
24
+ wrap_recursive code.root
30
25
 
31
26
  rewriter.insert_before root_range, before_all.call
32
27
 
33
- if root # file may be empty
34
- wrappings.each do |line_num, (range, last_col, meta)|
35
- rewriter.insert_before range, before_each.call(line_num)
36
- if meta == :total_fucking_failure
37
- rewriter.replace range, '.....TOTAL FUCKING FAILURE!.....'
38
- end
39
- rewriter.insert_after range, after_each.call(line_num)
28
+ wrappings.each do |line_num, (range, last_col, meta)|
29
+ rewriter.insert_before range, before_each.call(line_num)
30
+ case meta
31
+ when :total_fucking_failure
32
+ rewriter.replace range, '.....TOTAL FUCKING FAILURE!.....'
33
+ when :match_current_line
34
+ rewriter.insert_before range, '~' # Regexp#~
40
35
  end
41
- range = root.location.expression
36
+ rewriter.insert_after range, after_each.call(line_num)
42
37
  end
43
38
 
44
39
  rewriter.insert_after root_range, after_all_text
@@ -48,14 +43,14 @@ class SeeingIsBelieving
48
43
 
49
44
  private
50
45
 
51
- attr_accessor :program, :before_all, :after_all, :before_each, :after_each, :buffer, :root, :rewriter, :wrappings
46
+ attr_accessor :before_all, :after_all, :before_each, :after_each
47
+ attr_accessor :code, :wrappings
48
+
49
+ def buffer() code.buffer end
50
+ def rewriter() code.rewriter end
52
51
 
53
52
  def root_range
54
- if root
55
- root.location.expression
56
- else
57
- Parser::Source::Range.new buffer, 0, 0
58
- end
53
+ code.root.location.expression
59
54
  end
60
55
 
61
56
  def after_all_text
@@ -72,7 +67,15 @@ class SeeingIsBelieving
72
67
 
73
68
  def add_to_wrappings(range_or_ast, meta=nil)
74
69
  range = range_or_ast
75
- range = range_or_ast.location.expression if range.kind_of? ::AST::Node
70
+ if range.kind_of? ::AST::Node
71
+ location = range_or_ast.location
72
+ # __ENCODING__ becomes: (const (const nil :Encoding) :UTF_8)
73
+ # Where the inner const doesn't have a location because it doesn't correspond to a real token.
74
+ # There is not currently a way to turn this off, but it would be nice to have one like __LINE__ does
75
+ # https://github.com/whitequark/parser/blob/e2249d7051b1adb6979139928e14a81bc62f566e/lib/parser/builders/default.rb#L333-343
76
+ return unless location.respond_to? :expression
77
+ range = location.expression
78
+ end
76
79
  line, col = buffer.decompose_position range.end_pos
77
80
  _, prev_col, _ = wrappings[line]
78
81
  wrappings[line] = (!wrappings[line] || prev_col < col ? [range, col, meta] : wrappings[line] )
@@ -83,33 +86,31 @@ class SeeingIsBelieving
83
86
  .each { |child| wrap_recursive child }
84
87
  end
85
88
 
86
- # todo: is this actually add_wrappings
87
- # and add_wrappings is actually add_wrapping?
88
- def wrap_recursive(ast=root)
89
+ def wrap_recursive(ast)
89
90
  return wrappings unless ast.kind_of? ::AST::Node
90
91
  case ast.type
91
- when :args, :redo, :retry, :alias, :undef, :splat, :match_current_line
92
+ when :args, :redo, :retry, :alias, :undef, :null_node
92
93
  # no op
93
- when :defs
94
+ when :defs, :class, :module
94
95
  add_to_wrappings ast
95
96
  add_children ast, true
96
- when :rescue, :ensure, :return, :break, :next
97
+ when :rescue, :ensure, :return, :break, :next, :splat, :kwsplat
97
98
  add_children ast
98
99
  when :if
99
100
  if ast.location.kind_of? Parser::Source::Map::Ternary
100
- add_to_wrappings ast unless ast.children.any? { |child| void_value? child }
101
+ add_to_wrappings ast unless ast.children.any? { |child| code.void_value? child }
101
102
  add_children ast
102
103
  else
103
- keyword = ast.location.keyword.source
104
- if (keyword == 'if' || keyword == 'unless') && ast.children.none? { |child| void_value? child }
104
+ keyword = ast.location.keyword.source # if, elsif, unless, else, ....
105
+ if (keyword == 'if' || keyword == 'unless') && ast.children.none? { |child| code.void_value? child }
105
106
  add_to_wrappings ast
106
107
  end
107
108
  add_children ast
108
109
  end
109
- when :when, :pair, :class, :module, :sclass
110
+ when :when, :pair # pair is 1=>2
110
111
  wrap_recursive ast.children.last
111
112
  when :resbody
112
- exception_type, variable_name, body = ast.children
113
+ _exception_type, _variable_name, body = ast.children
113
114
  wrap_recursive body
114
115
  when :array
115
116
  add_to_wrappings ast
@@ -154,8 +155,8 @@ class SeeingIsBelieving
154
155
  add_children ast, true
155
156
  else
156
157
  begin_pos = ast.location.expression.begin_pos
157
- end_pos = heredoc_hack(array.children.last).location.expression.end_pos
158
- range = Parser::Source::Range.new buffer, begin_pos, end_pos
158
+ end_pos = array.children.last.location.expression.end_pos
159
+ range = code.range_for(begin_pos, end_pos)
159
160
  add_to_wrappings range
160
161
  add_children ast.children.last
161
162
  end
@@ -168,95 +169,28 @@ class SeeingIsBelieving
168
169
  :and_asgn, # a &&= b
169
170
  :op_asgn # a += b, a -= b, a *= b, etc
170
171
 
171
- # because the RHS can be a heredoc, and parser currently handles heredocs locations incorrectly
172
- # we must hack around this
172
+ # a=b gets wrapped <a=b>
173
+ # but we don't wrap the lvar in `for a in range`
173
174
  if ast.children.last.kind_of? ::AST::Node
174
175
  begin_pos = ast.location.expression.begin_pos
175
- end_pos = heredoc_hack(ast.children.last).location.expression.end_pos
176
- range = Parser::Source::Range.new buffer, begin_pos, end_pos
176
+ end_pos = ast.children.last.location.expression.end_pos
177
+ range = code.range_for(begin_pos, end_pos)
177
178
  add_to_wrappings range
178
179
  add_children ast, true
179
180
  end
180
181
  when :send
181
- # because the target and the last child can be heredocs
182
- # and the method may or may not have parens,
183
- # it can inadvertently inherit the incorrect location of the heredocs
184
- # so we check for this case, that way we can construct the correct range instead
185
- range = ast.location.expression
186
-
187
- # first two children: target, message, so we want the last child only if it is an argument
188
- children = ast.children
189
- target = children[0]
190
- message = children[1]
191
- last_arg = children.size > 2 ? children[-1] : nil
192
-
193
-
194
- # last arg is a heredoc, use the closing paren, or the end of the first line of the heredoc
195
- if heredoc? last_arg
196
- end_pos = heredoc_hack(last_arg).location.expression.end_pos
197
- if buffer.source[ast.location.selector.end_pos] == '('
198
- end_pos += 1 until buffer.source[end_pos] == ')'
199
- end_pos += 1
200
- end
201
-
202
- # target is a heredoc, so we can't trust the expression
203
- # but method has parens, so we can't trust the last arg
204
- elsif heredoc?(target) && last_arg && buffer.source[ast.location.selector.end_pos] == '('
205
- end_pos = last_arg.location.expression.end_pos
206
- end_pos += 1 until buffer.source[end_pos] == ')'
207
- end_pos += 1
208
-
209
- elsif heredoc?(target) && last_arg
210
- end_pos = last_arg.location.expression.end_pos
211
-
212
- # neither the target, nor the last arg are heredocs, the range of the expression can be trusted
213
- elsif last_arg
214
- end_pos = ast.location.expression.end_pos
215
-
216
- # in lambda{}.() the send has no selector, so use the expression
217
- # I'm going to ignore the fact that you could define call on a heredoc and do <<HERE.(),
218
- elsif !ast.location.selector
219
- end_pos = ast.location.expression.end_pos
220
-
221
- # there is no last arg, but there are parens, find the closing paren
222
- # we can't trust the expression range because the *target* could be a heredoc
223
- elsif buffer.source[ast.location.selector.end_pos] == '('
224
- closing_paren_index = ast.location.selector.end_pos + 1
225
- closing_paren_index += 1 until buffer.source[closing_paren_index] == ')'
226
- end_pos = closing_paren_index + 1
227
-
228
- # use the selector because we can't trust expression since target can be a heredoc
229
- elsif heredoc? target
230
- end_pos = ast.location.selector.end_pos
231
-
232
- # use the expression because it could be something like !1, in which case the selector would return the rhs of the !
233
- else
234
- end_pos = ast.location.expression.end_pos
235
- end
236
-
237
- begin_pos = ast.location.expression.begin_pos
238
- range = Parser::Source::Range.new(buffer, begin_pos, end_pos)
239
-
240
- meta = nil
241
- meta = :total_fucking_failure if message == :__TOTAL_FUCKING_FAILURE__
242
- add_to_wrappings range, meta
182
+ _target, message, * = ast.children
183
+ meta = (:total_fucking_failure if message == :__TOTAL_FUCKING_FAILURE__)
184
+ add_to_wrappings ast, meta
243
185
  add_children ast
244
186
  when :begin
245
187
  if ast.location.expression.source.start_with?("(") && # e.g. `(1)` we want `<(1)>`
246
- !void_value?(ast) # e.g. `(return 1)` we want `(return <1>)`
188
+ !code.void_value?(ast) # e.g. `(return 1)` we want `(return <1>)`
247
189
  add_to_wrappings ast
248
- else # e.g. `A\nB` we want `<A>\n<B>`
249
- last_child = ast.children.last
250
- if heredoc? last_child
251
- range = Parser::Source::Range.new buffer,
252
- ast.location.expression.begin_pos,
253
- heredoc_hack(last_child).location.expression.end_pos
254
- add_to_wrappings range unless void_value? ast.children.last
255
- end
256
190
  end
257
191
  add_children ast
258
192
  when :str, :dstr, :xstr, :regexp
259
- add_to_wrappings heredoc_hack ast
193
+ add_to_wrappings ast
260
194
 
261
195
  when :hash
262
196
  # method arguments might not have braces around them
@@ -264,6 +198,12 @@ class SeeingIsBelieving
264
198
  add_to_wrappings ast, meta if ast.location.begin
265
199
  add_children ast
266
200
 
201
+ when :block_pass, :preexe, :postexe
202
+ add_children ast # strange, I'm not too sure about this :/
203
+
204
+ when :match_current_line # ie `if /abc/; ...; end`
205
+ add_to_wrappings ast, :match_current_line
206
+
267
207
  else
268
208
  add_to_wrappings ast
269
209
  add_children ast
@@ -0,0 +1,14 @@
1
+ require 'seeing_is_believing/wrap_expressions'
2
+ class SeeingIsBelieving
3
+ module WrapExpressionsWithInspect
4
+ def self.call(program)
5
+ WrapExpressions.call program,
6
+ before_each: -> line_number {
7
+ "("
8
+ },
9
+ after_each: -> line_number {
10
+ ").tap { |v| $SiB.record_result :inspect, #{line_number}, v }"
11
+ }
12
+ end
13
+ end
14
+ end
@@ -19,7 +19,7 @@ Gem::Specification.new do |s|
19
19
  s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
20
20
  s.require_paths = ["lib"]
21
21
 
22
- s.add_dependency "parser", ">= 2.2", "< 3.0"
22
+ s.add_dependency "parser", ">= 2.2.0.2", "< 3.0"
23
23
 
24
24
  s.add_development_dependency "haiti", ">= 0.1", "< 0.3"
25
25
  s.add_development_dependency "rake", "~> 10.0"
@@ -0,0 +1,27 @@
1
+ # This is mostly tested in the cukes, but some are hard to hit
2
+
3
+ require 'seeing_is_believing/binary/align_chunk'
4
+
5
+ RSpec.describe '1-off alignment specs' do
6
+ def chunk(code)
7
+ code << "\n"
8
+ SeeingIsBelieving::Binary::AlignChunk.new code
9
+ end
10
+
11
+ describe 'AlignChunk' do
12
+ it 'considers entirely whitespace lines to be a chunk separator' do
13
+ empty_line = chunk "aaaaa\n\n1"
14
+ whitespace_line = chunk "aaaaa\n \t \n1"
15
+ expect(whitespace_line.line_length_for 1).to eq empty_line.line_length_for(1)
16
+ expect(whitespace_line.line_length_for 3).to eq empty_line.line_length_for(3)
17
+ end
18
+
19
+ it 'is not fooled by whitespace on the first/last line' do
20
+ expect(chunk(" \na\n ").line_length_for(2)).to eq 3
21
+ end
22
+
23
+ it 'is not fooled by trailing whitespace in general' do
24
+ expect(chunk("a ").line_length_for(1)).to eq 3
25
+ end
26
+ end
27
+ end
@@ -3,11 +3,12 @@ require 'seeing_is_believing/binary/comment_lines'
3
3
 
4
4
  RSpec.describe SeeingIsBelieving::Binary::CommentLines, 'passes in the each commentable line and the line number, and adds the returned text (whitespace+comment) to the end' do
5
5
  def call(code, &block)
6
- described_class.call code, &block
6
+ return described_class.call(code, &block) if code.end_with? "\n"
7
+ code << "\n"
8
+ described_class.call(code, &block).chomp
7
9
  end
8
10
 
9
11
  example 'just checking some edge cases' do
10
- expect(call("") { ';' }).to eq ";"
11
12
  expect(call("__END__\n1") { ';' }).to eq "__END__\n1"
12
13
  expect(call("1\n__END__") { ';' }).to eq "1;\n__END__"
13
14
  end
@@ -0,0 +1,657 @@
1
+ require 'spec_helper'
2
+ require 'seeing_is_believing/binary/config'
3
+
4
+
5
+ RSpec.describe SeeingIsBelieving::Binary::Config do
6
+ RSpec::Matchers.define :have_error do |error_assertion|
7
+ match do |config|
8
+ config.errors.find do |error|
9
+ case error_assertion
10
+ when Regexp
11
+ error_assertion =~ error.explanation
12
+ else
13
+ error.explanation.include? error_assertion
14
+ end
15
+ end
16
+ end
17
+
18
+ failure_message do |options|
19
+ "#{error_assertion.inspect} should have matched one of the errors: #{options[:errors].inspect}"
20
+ end
21
+
22
+ failure_message_when_negated do |options|
23
+ "#{error_assertion.inspect} should NOT have matched any of the errors: #{options[:errors].inspect}"
24
+ end
25
+ end
26
+
27
+ let(:matrix_file) { 'seeing_is_believing/the_matrix' }
28
+ let(:default_markers) { SeeingIsBelieving::Binary::Markers.new }
29
+
30
+ def parse(args)
31
+ described_class.new.parse_args(args)
32
+ end
33
+
34
+ def assert_deprecated(flag, *args)
35
+ deprecated_args = parse([flag, *args]).deprecations
36
+ expect(deprecated_args.size).to eq 1
37
+ deprecated = deprecated_args.first
38
+ expect(deprecated.args).to eq [flag, *args]
39
+ expect(deprecated.explanation).to be_a_kind_of String
40
+ end
41
+
42
+ shared_examples 'it requires a positive int argument' do |flags|
43
+ it 'expects an integer argument' do
44
+ flags.each do |flag|
45
+ expect(parse([flag, '1'])).to_not have_error /#{flag}/
46
+ expect(parse([flag, '0'])).to have_error /#{flag}/
47
+ expect(parse([flag, '-1'])).to have_error /#{flag}/
48
+ expect(parse([flag, '1.0'])).to have_error /#{flag}/
49
+ expect(parse([flag, 'a'])).to have_error /#{flag}/
50
+ expect(parse([flag, '' ])).to have_error /#{flag}/
51
+ expect(parse([flag ])).to have_error /#{flag}/
52
+ end
53
+ end
54
+ end
55
+
56
+ shared_examples 'it requires a non-negative float or int' do |flags|
57
+ it 'expects a non-negative float or int argument' do
58
+ flags.each do |flag|
59
+ expect(parse([flag, '1'])).to_not have_error /#{flag}/
60
+ expect(parse([flag, '0'])).to_not have_error /#{flag}/
61
+ expect(parse([flag, '-1'])).to have_error /#{flag}/
62
+ expect(parse([flag,'-1.0'])).to have_error /#{flag}/
63
+ expect(parse([flag, '1.0'])).to_not have_error /#{flag}/
64
+ expect(parse([flag, 'a'])).to have_error /#{flag}/
65
+ expect(parse([flag, '' ])).to have_error /#{flag}/
66
+ expect(parse([flag ])).to have_error /#{flag}/
67
+ end
68
+ end
69
+ end
70
+
71
+ describe 'parsing from args' do
72
+ it 'does not mutate the input array' do
73
+ ary = ['a']
74
+ parse(ary)
75
+ expect(ary).to eq ['a']
76
+ end
77
+
78
+ it 'correctly parses multiple args' do
79
+ config = parse(%w[filename -h -r torequire])
80
+ expect(config.filename).to eq 'filename'
81
+ expect(config.lib_options.require_files).to include 'torequire'
82
+ expect(config.print_help?).to eq true
83
+ expect(config.errors).to be_empty
84
+ end
85
+
86
+ # This sill dance is b/c equality assertions are annoying and not relevant anywhere else
87
+ # Don't like having to go implement this stuff just for a few high-level tests.
88
+ def flat_options(config)
89
+ flat_keys = config.keys - [:lib_options, :annotator_options]
90
+ flat_keys.each_with_object({}) { |key, hash| hash[key] = config[key] }
91
+ end
92
+ def assert_same_flat_opts(args1, args2)
93
+ flatopts1 = flat_options parse args1
94
+ flatopts2 = flat_options parse args2
95
+ expect(flatopts1).to eq flatopts2
96
+ end
97
+ it 'can interpret conjoined short-flags' do
98
+ assert_same_flat_opts ['-hjg'], ['-h', '-j', '-g'] # help, json, debug
99
+ end
100
+ it 'can interpret conjoined short-flags where one of them is h+' do
101
+ assert_same_flat_opts ['-h+jg'], ['-h+', '-j', '-g']
102
+ assert_same_flat_opts ['-jh+g'], ['-j', '-h+', '-g']
103
+ assert_same_flat_opts ['-jgh+'], ['-j', '-g', '-h+']
104
+ end
105
+
106
+ specify 'unknown options set an error' do
107
+ expect(parse(['--xyz' ])).to have_error '--xyz is not an option'
108
+ expect(parse(['-y' ])).to have_error '-y is not an option'
109
+ expect(parse(['-y', 'b'])).to have_error '-y is not an option'
110
+ expect(parse(['-+h' ])).to have_error '-+ is not an option'
111
+ end
112
+
113
+ describe 'filename and lib_options.filename' do
114
+ specify 'default to nil' do
115
+ expect(parse([]).filename).to be_nil
116
+ expect(parse([]).lib_options.filename).to be_nil
117
+ end
118
+
119
+ specify 'both filename and lib_options.filename are set when a filename is seen' do
120
+ config = parse ['a']
121
+ expect(config.filename).to eq 'a'
122
+ expect(config.lib_options.filename).to eq 'a'
123
+ end
124
+
125
+ specify 'the filename is a nonflag / nonarg' do
126
+ # nonflag / nonarg
127
+ expect(parse(['-x']).filename).to eq nil
128
+ expect(parse(['-n', '3']).filename).to eq nil
129
+
130
+ # find it amidst flags/largs
131
+ expect(parse(['a']).filename).to eq 'a'
132
+ expect(parse(['-x', 'a']).filename).to eq 'a'
133
+ expect(parse(['a', '-x']).filename).to eq 'a'
134
+ expect(parse(['a', '-n', '3']).filename).to eq 'a'
135
+ expect(parse(['-n', '3', 'a', '-r', 'f']).filename).to eq 'a'
136
+ end
137
+
138
+ it 'does not confuse filenames with unknown args' do
139
+ unknown_arg = '-y'
140
+ expect(parse([unknown_arg]).filename).to be_nil
141
+ end
142
+
143
+ it 'sets an error if given multiple filenames' do
144
+ expect(parse([]).errors).to be_empty
145
+ expect(parse(['a']).errors).to be_empty
146
+ expect(parse(['a', 'b'])).to have_error /"a", "b"/
147
+ end
148
+
149
+ specify '-a and --as set lib_options.filename, but not filename' do
150
+ expect(parse(%w[-a abc]).filename).to eq nil
151
+ expect(parse(%w[--as abc]).filename).to eq nil
152
+ expect(parse(%w[-a abc]).lib_options.filename).to eq 'abc'
153
+ expect(parse(%w[--as abc]).lib_options.filename).to eq 'abc'
154
+ end
155
+
156
+ specify '-a and --as always win over a filename' do
157
+ config = parse(['fn', '-a', 'as'])
158
+ expect(config.filename).to eq 'fn'
159
+ expect(config.lib_options.filename).to eq 'as'
160
+
161
+ config = parse(['-a', 'as', 'fn'])
162
+ expect(config.filename).to eq 'fn'
163
+ expect(config.lib_options.filename).to eq 'as'
164
+ end
165
+
166
+ it 'sets an error if -a/--as are given without the filename to execute as' do
167
+ expect(parse(%w[-a f])).to_not have_error /-a/
168
+ expect(parse(%w[-as f])).to_not have_error /--as/
169
+ expect(parse(%w[-a ])).to have_error /-a/
170
+ expect(parse(%w[--as ])).to have_error /--as/
171
+ end
172
+ end
173
+
174
+
175
+ describe 'annotator_options.max_result_length' do
176
+ it 'defaults to infinity' do
177
+ expect(parse([]).annotator_options.max_result_length).to eq Float::INFINITY
178
+ end
179
+
180
+ it 'is set with -D and --result-length' do
181
+ expect(parse(['-D', '10']).annotator_options.max_result_length).to eq 10
182
+ expect(parse(['--result-length', '10']).annotator_options.max_result_length).to eq 10
183
+ end
184
+
185
+ it_behaves_like 'it requires a positive int argument', ['-D', '--result-length']
186
+ end
187
+
188
+ describe 'annotator_options.max_line_length' do
189
+ it 'defaults to infinity' do
190
+ expect(parse([]).annotator_options.max_line_length).to eq Float::INFINITY
191
+ end
192
+
193
+ it 'is set with -d and --line-length' do
194
+ expect(parse(['-d', '10']).annotator_options.max_line_length).to eq 10
195
+ expect(parse(['--line-length', '10']).annotator_options.max_line_length).to eq 10
196
+ end
197
+
198
+ it_behaves_like 'it requires a positive int argument', ['-d', '--line-length']
199
+ end
200
+
201
+ describe 'lib_options.require_files' do
202
+ it 'defaults to the matrix file array' do
203
+ expect(parse([]).lib_options.require_files).to eq [matrix_file]
204
+ end
205
+
206
+ it 'appends pp for xmpfilter style' do
207
+ expect(parse(['-x']).lib_options.require_files).to eq [matrix_file, 'pp']
208
+ end
209
+
210
+ specify '-r and --require set an error if not provided with a filename' do
211
+ expect(parse(['--require', 'f'])).to_not have_error /-r/
212
+ expect(parse(['-r'])).to have_error /-r\b/
213
+ expect(parse(['--require'])).to have_error /--require\b/
214
+ end
215
+
216
+ specify '-r and --require add the filename into the result array' do
217
+ expect(parse(%w[-r f1 --require f2]).lib_options.require_files).to eq [matrix_file, 'f1', 'f2']
218
+ end
219
+ end
220
+
221
+ describe 'print_help? and help_screen' do
222
+ let(:help_screen) { SeeingIsBelieving::Binary.help_screen default_markers }
223
+ let(:help_screen_extended) { SeeingIsBelieving::Binary.help_screen_extended default_markers }
224
+
225
+ specify 'print_help? defaults to false' do
226
+ expect(parse([]).print_help?).to eq false
227
+ end
228
+
229
+ specify 'help_screen defaults to the short help screen' do
230
+ expect(parse([]).help_screen).to eq help_screen
231
+ end
232
+
233
+ it 'print_help? is set to true with -h, --help, -h+, and --help+' do
234
+ expect(parse(['-h']).print_help?).to eq true
235
+ expect(parse(['-h+']).print_help?).to eq true
236
+ expect(parse(['--help']).print_help?).to eq true
237
+ expect(parse(['--help+']).print_help?).to eq true
238
+ end
239
+
240
+ specify '-h and --help set help_screen to the short help screen' do
241
+ expect(parse(['-h']).help_screen).to eq help_screen
242
+ expect(parse(['--help']).help_screen).to eq help_screen
243
+ end
244
+
245
+ specify '-h+ and --help+ set help_screen to the extended help screen' do
246
+ expect(parse(['-h+']).help_screen).to eq help_screen_extended
247
+ expect(parse(['--help+']).help_screen).to eq help_screen_extended
248
+ end
249
+ end
250
+
251
+
252
+ describe 'body' do
253
+ it 'defaults to nil' do
254
+ expect(parse([]).body).to eq nil
255
+ end
256
+
257
+ it 'is set by an arg to -e and --program' do
258
+ expect(parse(['-e', '1']).body).to eq '1'
259
+ expect(parse(['--program', '1']).body).to eq '1'
260
+ end
261
+
262
+ it 'sets an error if -e and --program are not given an arg' do
263
+ expect(parse([])).to_not have_error /-e/
264
+ expect(parse([])).to_not have_error /--program/
265
+ expect(parse(['-e', 'body'])).to_not have_error /-e/
266
+ expect(parse(['-e' ])).to have_error /-e/
267
+ expect(parse(['--program', 'body'])).to_not have_error /--program/
268
+ expect(parse(['--program' ])).to have_error /--program/
269
+ end
270
+ end
271
+
272
+ describe'lib_options.load_path_dirs' do
273
+ let(:lib_path) { File.expand_path '../../../lib', __FILE__ }
274
+
275
+ it 'defaults to sib\'s lib path' do
276
+ expect(parse([]).lib_options.load_path_dirs).to eq [lib_path]
277
+ end
278
+
279
+ specify '-I and --load-path add their arguments to it' do
280
+ expect(parse(%w[-I f1 --load-path f2]).lib_options.load_path_dirs).to eq [lib_path, 'f1', 'f2']
281
+ end
282
+
283
+ it 'sets an error if not provided with a dir' do
284
+ expect(parse(['--load-path', 'f'])).to_not have_error /--load-path/
285
+ expect(parse(['-I'])).to have_error /-I\b/
286
+ expect(parse(['--load-path'])).to have_error /--load-path\b/
287
+ end
288
+ end
289
+
290
+ describe 'lib_options.encoding' do
291
+ it 'defaults to nil' do
292
+ expect(parse([]).lib_options.encoding).to be_nil
293
+ end
294
+
295
+ specify '-K and --encoding sets the encoding to the next argument' do
296
+ expect(parse(%w[-K u]).lib_options.encoding).to eq 'u'
297
+ expect(parse(%w[--encoding u]).lib_options.encoding).to eq 'u'
298
+ end
299
+
300
+ specify 'with -K, the argument can be placed immediately after it (e.g. -Ku) because Ruby allows this' do
301
+ expect(parse(['-Ku']).lib_options.encoding).to eq 'u'
302
+ expect(parse(['-Ku'])).to_not have_error /-K/
303
+ end
304
+
305
+ it 'sets an error if not provided with an encoding' do
306
+ expect(parse(['-Ku'])).to_not have_error /-K/
307
+ expect(parse(['-K u'])).to_not have_error /-K/
308
+ expect(parse(['--encoding', 'u'])).to_not have_error /--encoding/
309
+ expect(parse(['-K'])).to have_error /-K/
310
+ expect(parse(['--encoding'])).to have_error /--encoding/
311
+ end
312
+ end
313
+
314
+ describe '.print_cleaned?' do
315
+ it 'defaults to false' do
316
+ expect(parse([]).print_cleaned?).to eq false
317
+ end
318
+
319
+ it 'can be set with -c and --clean' do
320
+ expect(parse(%w[-c]).print_cleaned?).to eq true
321
+ expect(parse(%w[--clean]).print_cleaned?).to eq true
322
+ end
323
+ end
324
+
325
+ describe 'print_version?' do
326
+ it 'defaults to false' do
327
+ expect(parse([]).print_version?).to eq false
328
+ end
329
+
330
+ it 'can be set with -v and --version' do
331
+ expect(parse(%w[-v]).print_version?).to eq true
332
+ expect(parse(%w[--version]).print_version?).to eq true
333
+ end
334
+ end
335
+
336
+ describe 'timeout and lib_options.timeout_seconds' do
337
+ it 'defaults to 0 (never timeout)' do
338
+ expect(parse([]).timeout_seconds).to eq 0
339
+ expect(parse([]).lib_options.timeout_seconds).to eq 0
340
+ end
341
+
342
+ it 'can be set with -t and --timeout-seconds' do
343
+ expect(parse(['-t', '1.1']).timeout_seconds).to eq 1.1
344
+ expect(parse(['-t', '1.1']).lib_options.timeout_seconds).to eq 1.1
345
+ expect(parse(['--timeout-seconds', '1.2']).lib_options.timeout_seconds).to eq 1.2
346
+ end
347
+
348
+ it 'can be set with the deprecated flag --timeout' do
349
+ expect(parse(['--timeout', '1.3']).lib_options.timeout_seconds).to eq 1.3
350
+ assert_deprecated '--timeout', 1.4
351
+ end
352
+
353
+ it_behaves_like 'it requires a non-negative float or int', ['-t', '--timeout-seconds', '--timeout']
354
+ end
355
+
356
+ describe 'annotator_options.alignment_strategy' do
357
+ let(:align_chunk) { SeeingIsBelieving::Binary::AlignChunk }
358
+ let(:align_file) { SeeingIsBelieving::Binary::AlignFile }
359
+ let(:align_line) { SeeingIsBelieving::Binary::AlignLine }
360
+
361
+ it 'defaults to AlignChunk' do
362
+ expect(parse([]).annotator_options.alignment_strategy)
363
+ .to eq align_chunk
364
+ end
365
+
366
+ specify '-s and --alignment-strategy sets the alignment strategy' do
367
+ expect(parse(['-s', 'file']).annotator_options.alignment_strategy)
368
+ .to eq align_file
369
+
370
+ expect(parse(['--alignment-strategy', 'file']).annotator_options.alignment_strategy)
371
+ .to eq align_file
372
+ end
373
+
374
+ it 'accepts values: file, line, chunk' do
375
+ expect(parse(['-s', 'file']).annotator_options.alignment_strategy).to eq align_file
376
+ expect(parse(['-s', 'line']).annotator_options.alignment_strategy).to eq align_line
377
+ expect(parse(['-s', 'chunk']).annotator_options.alignment_strategy).to eq align_chunk
378
+ end
379
+
380
+ it 'sets an error if not provided with a strategy' do
381
+ expect(parse(['-s'])).to have_error /-s/
382
+ expect(parse(['-s', 'file'])).to_not have_error /-s/
383
+ end
384
+
385
+ it 'sets an error if provided with an unknown alignment strategy' do
386
+ expect(parse(['-s', 'file'])).to_not have_error '-s'
387
+ expect(parse(['-s', 'unknown'])).to have_error '-s', 'expected one of'
388
+ end
389
+ end
390
+
391
+ describe 'inherit_exitstatus?' do
392
+ it 'defaults to false' do
393
+ expect(parse([]).inherit_exitstatus?).to eq false
394
+ end
395
+
396
+ it 'can be set with --inherit-exitstatus, -i' do
397
+ expect(parse(['--inherit-exitstatus']).inherit_exitstatus?).to be true
398
+ expect(parse(['-i']).inherit_exitstatus?).to be true
399
+ end
400
+
401
+ it 'can be set with the deprecated --inherit-exit-status' do
402
+ expect(parse(['--inherit-exit-status']).inherit_exitstatus?).to be true
403
+ assert_deprecated '--inherit-exit-status'
404
+ end
405
+ end
406
+
407
+ describe 'annotator and lib_options.rewrite_code' do
408
+ specify 'annotator defaults to AnnotateEveryLine' do
409
+ expect(parse([]).annotator).to be SeeingIsBelieving::Binary::AnnotateEveryLine
410
+ end
411
+
412
+ specify 'annotator can be set to AnnotateMarkedLines with --xmpfilter-style or -x' do
413
+ expect(parse(['--xmpfilter-style']).annotator).to eq SeeingIsBelieving::Binary::AnnotateMarkedLines
414
+ expect(parse(['-x']).annotator).to eq SeeingIsBelieving::Binary::AnnotateMarkedLines
415
+ end
416
+
417
+ specify 'lib_options.rewrite_code is set to the xmpfilter rewriter on -x and --xmpfilter-style' do
418
+ # not a great test, but the cukes hit its actual behaviour
419
+ expect(parse([]).lib_options.rewrite_code.call("1 # =>\n")).to_not include "pp"
420
+ expect(parse(['-x']).lib_options.rewrite_code.call("1\n# =>\n")).to include "pp"
421
+ end
422
+ end
423
+
424
+ describe 'remove_value_prefixes?', t:true do
425
+ it 'defaults to true' do
426
+ expect(parse([]).remove_value_prefixes?).to eq true
427
+ end
428
+ it 'is false when xmpfilter style is specified' do
429
+ expect(parse(['-x']).remove_value_prefixes?).to eq false
430
+ end
431
+ end
432
+
433
+ describe 'debug?' do
434
+ specify 'debug? defaults to a false' do
435
+ expect(parse([])[:debug]).to eq false
436
+ end
437
+
438
+ specify '-g and --debug set debug? to true' do
439
+ expect(parse(['-g']).debug?).to eq true
440
+ expect(parse(['--debug']).debug?).to eq true
441
+ end
442
+ end
443
+
444
+ describe '--shebang' do
445
+ it 'is added to the list of deprecated flags' do
446
+ assert_deprecated '--shebang', 'not_ruby'
447
+ end
448
+
449
+ it 'sets an error if not given a next arg to execute' do
450
+ expect(parse([])).to_not have_error /--shebang/
451
+ expect(parse(['--shebang', 'arg'])).to_not have_error /--shebang/
452
+ expect(parse(['--shebang'])).to have_error /--shebang/
453
+ end
454
+ end
455
+
456
+ describe 'lib_options.max_line_captures' do
457
+ it 'defaults to infinity' do
458
+ expect(parse([]).lib_options.max_line_captures).to eq Float::INFINITY
459
+ end
460
+
461
+ it 'can be set with --max-line-captures or -n' do
462
+ expect(parse(['-n', '10']).lib_options.max_line_captures).to eq 10
463
+ expect(parse(['--max-line-captures', '10']).lib_options.max_line_captures).to eq 10
464
+ end
465
+
466
+ it 'can be set with the deprecated flag --number-of-captures' do
467
+ expect(parse(['--number-of-captures', '12']).lib_options.max_line_captures).to eq 12
468
+ assert_deprecated '--number-of-captures', '12'
469
+ assert_deprecated '--number-of-captures'
470
+ end
471
+
472
+ it_behaves_like 'it requires a positive int argument', ['-n', '--max-line-captures', '--number-of-captures']
473
+ end
474
+
475
+ describe 'result_as_json?' do
476
+ it 'defaults to false' do
477
+ expect(parse([]).result_as_json?).to eq false
478
+ end
479
+
480
+ it 'can be enabled with --json or -j' do
481
+ expect(parse(['--json']).result_as_json?).to eq true
482
+ expect(parse(['-j']).result_as_json?).to eq true
483
+ end
484
+
485
+ it 'sets an error if specified with xmpfilter' do
486
+ expect(parse(['--json'])).to_not have_error /json/
487
+ expect(parse(['--json', '-x'])).to have_error /json/
488
+ expect(parse(['--json', '--xmpfilter-style'])).to have_error /json/
489
+ expect(parse(['-x', '--json'])).to have_error /json/
490
+ expect(parse(['--xmpfilter-style', '--json'])).to have_error /json/
491
+ expect(parse(['-j', '-x'])).to have_error /json/
492
+ expect(parse(['-j', '-x'])).to have_error /xmpfilter/
493
+ end
494
+ end
495
+
496
+ describe 'markers' do
497
+ it 'defaults to a hash with :value, :exception, :stdout, and :stderr' do
498
+ expect(default_markers.keys).to eq [:value, :exception, :stdout, :stderr]
499
+ end
500
+
501
+ specify 'each default marker regex can re-find the the marker' do
502
+ default_markers.each do |name, marker|
503
+ comment = "#{marker.prefix}abc"
504
+ extracted = comment[marker.regex]
505
+ expect(extracted).to eq(marker.prefix)
506
+ end
507
+ end
508
+
509
+ it('defaults :value to "# => "') { expect(default_markers.value .prefix).to eq "# => " }
510
+ it('defaults :exception to "# ~> "') { expect(default_markers.exception.prefix).to eq "# ~> " }
511
+ it('defaults :stdout to "# >> "') { expect(default_markers.stdout .prefix).to eq "# >> " }
512
+ it('defaults :stderr to "# !> "') { expect(default_markers.stderr .prefix).to eq "# !> " }
513
+ end
514
+ end
515
+
516
+ describe 'print_event_stream?' do
517
+ it 'print_event_stream? is false by default' do
518
+ expect(parse([]).print_event_stream?).to eq false
519
+ end
520
+ it 'print_event_stream? can be turned on with --stream' do
521
+ expect(parse(['--stream']).print_event_stream?).to eq true
522
+ end
523
+ it 'adds an error if --stream is used with --json' do
524
+ expect(parse(['--stream'])).to_not have_error '--stream'
525
+ expect(parse(['--stream', '--json'])).to have_error '--stream'
526
+ expect(parse(['--json', '--stream'])).to have_error '--stream'
527
+ end
528
+ it 'adds an error if --stream is used with -x or --xmpfilter-style' do
529
+ expect(parse(['--stream'])).to_not have_error '--stream'
530
+ expect(parse(['--stream', '-x'])).to have_error '--stream'
531
+ expect(parse(['-x', '--stream'])).to have_error '--stream'
532
+ expect(parse(['--xmpfilter-style', '--stream'])).to have_error '--stream'
533
+ end
534
+ end
535
+
536
+
537
+ describe '.finalize' do
538
+ let(:stdin_data) { 'stdin data' }
539
+ let(:stdin) { object_double $stdin, read: stdin_data }
540
+ let(:stdout) { object_double $stdout }
541
+ let(:stderr) { object_double $stderr }
542
+
543
+ let(:file_class) { class_double File }
544
+ let(:nonexisting_filename) { 'badfilename' }
545
+ let(:existing_filename) { 'goodfilename' }
546
+ let(:file_body) { 'good file body' }
547
+
548
+ before do
549
+ allow(file_class).to receive(:exist?).with(existing_filename).and_return(true)
550
+ allow(file_class).to receive(:exist?).with(nonexisting_filename).and_return(false)
551
+ allow(file_class).to receive(:read).with(existing_filename).and_return(file_body)
552
+ end
553
+
554
+ def call(attrs={})
555
+ described_class.new(attrs).finalize(stdin, stdout, stderr, file_class)
556
+ end
557
+
558
+ describe 'additional errors' do
559
+ it 'sets an error if given a filename and a program body -- cannot have two body sources' do
560
+ allow(file_class).to receive(:exist?).with('f')
561
+ matcher = /program body and a filename/
562
+ expect(call filename: 'f', body: 'b').to have_error matcher
563
+ expect(call filename: 'f', body: 'b').to have_error matcher
564
+ expect(call filename: nil, body: 'b').to_not have_error matcher
565
+ expect(call filename: 'f', body: nil).to_not have_error matcher
566
+ end
567
+
568
+ it 'sets an error if the provided filename DNE' do
569
+ expect(call filename: existing_filename).to_not have_error /filename/
570
+ expect(call filename: nonexisting_filename).to have_error /filename/
571
+ end
572
+ end
573
+
574
+ describe 'setting the body' do
575
+ it 'does not override the if already set e.g. with -e' do
576
+ expect(call(body: 'b', filename: nil).body).to eq 'b'
577
+ end
578
+
579
+ it 'is the file body if the filename is provded and exists' do
580
+ expect(call(body: nil, filename: existing_filename).body)
581
+ .to eq file_body
582
+ end
583
+
584
+ it 'is an empty string if the filename is provided but DNE' do
585
+ expect(call(body: nil, filename: nonexisting_filename).body)
586
+ .to eq nil
587
+ end
588
+
589
+ it 'reads the body from stdin if not given a filename or body' do
590
+ expect(call(body: nil, filename: nil).body).to eq stdin_data
591
+ end
592
+
593
+ it 'is set to an empty string when we aren\'t evaluating (e.g. when printing version info)' do
594
+ expect(call( ).body).to be_a_kind_of String
595
+ expect(call(print_version: true).body).to eq ''
596
+ expect(call(print_help: true).body).to eq ''
597
+ expect(call(errors: ['e']).body).to eq ''
598
+ end
599
+ end
600
+
601
+ describe 'lib_options.stdin' do
602
+ let(:default) { SeeingIsBelieving::Options.new.stdin }
603
+
604
+ it 'is the default when we aren\'t evaluating' do
605
+ [ {errors: ['e']},
606
+ {filename: existing_filename, body: 'b'},
607
+ {filename: nonexisting_filename},
608
+ {print_version: true},
609
+ {print_help: true},
610
+ ].each do |overrides|
611
+ expect(call(overrides).lib_options.stdin).to eq default
612
+ end
613
+ end
614
+
615
+ it 'is the default when the program was taken off stdin' do
616
+ expect(call.lib_options.stdin).to eq default
617
+ expect(call(body: 'b').lib_options.stdin).to_not eq default
618
+ end
619
+
620
+ it 'is the stdin stream when the program body was provided' do
621
+ expect(call(body: 'b').lib_options.stdin).to eq stdin
622
+ end
623
+
624
+ it 'is the stdin stream when the program was pulled from a file' do
625
+ expect(call(filename: existing_filename).lib_options.stdin).to eq stdin
626
+ expect(call(filename: nonexisting_filename).lib_options.stdin).to eq default
627
+ end
628
+ end
629
+
630
+ describe 'lib_options.event_handler' do
631
+ it 'is an UpdateResult handler when print_event_stream? is false' do
632
+ expect(call(print_event_stream: false).lib_options.event_handler)
633
+ .to be_an_instance_of SeeingIsBelieving::EventStream::Handlers::UpdateResult
634
+ end
635
+ it 'is an StreamJsonEvents handler to stdout when print_event_stream? is true' do
636
+ handler = call(print_event_stream: true).lib_options.event_handler
637
+ expect(handler).to be_an_instance_of SeeingIsBelieving::EventStream::Handlers::StreamJsonEvents
638
+ expect(handler.stream).to eq stdout
639
+ end
640
+ end
641
+
642
+ describe 'debugger, lib_options.debugger' do
643
+ specify 'default to a null debugger' do
644
+ handler = call
645
+ expect(handler.debugger).to_not be_enabled
646
+ expect(handler.lib_options.debugger).to_not be_enabled
647
+ end
648
+
649
+ specify 'are set to debug to stderr when debug? is true' do
650
+ handler = call debug: true
651
+ expect(handler.debugger).to be_enabled
652
+ expect(handler.debugger.stream).to eq stderr
653
+ expect(handler.lib_options.debugger).to equal handler.debugger
654
+ end
655
+ end
656
+ end
657
+ end