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

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