rspec-bash-x 1.0.1 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: '0943308df600188a73abc10314d5ecebb942670b'
4
- data.tar.gz: c86f4a7ff3988a3014fc1a31b3c9053fd1c224da
3
+ metadata.gz: 74cf305ed10730b811f278b17e84579525ca2f75
4
+ data.tar.gz: 69fafdc217a018885b31b568ac0a7f95c0752bdb
5
5
  SHA512:
6
- metadata.gz: c7efe7d2457e5e2d166037b01deaf8f66da11e590c48538aa8b56df5963fe26ab83978c0cf9270f1c0a26c9e6da824434d2c4c4bc5d9f456b2b2d148f09ee203
7
- data.tar.gz: 4ff1bc4e194464cff9b2318343048355f7de467d678ab88b8403b854e83244c298a855947868200ca356bec3994b405f1e769e4a73bdb2ce361ba6f075b20bfe
6
+ metadata.gz: 335ba43a3659066d595d1dd6e085c6ba6f1503e5d24730adf808764c1a0a0394ccebf4d31d07185517b15cf05b577e265019f3b968f1b04252a08ed8143c87b5
7
+ data.tar.gz: 39decb1ca2a1193e0c7eb4046d572e8454dd9e8b289f1bff4db8efbf53bf9bfca8c2492b8b5d2bd8acf0eeaf346aad8d3580e09a39ce9c539e5d70ac620762bf
data/lib/rspec/bash/fd.rb CHANGED
@@ -5,10 +5,10 @@ module RSpec
5
5
  begin
6
6
  !fd.closed? && !fd.eof?
7
7
  rescue IOError => e
8
- if e.to_s == "stream closed"
8
+ if noise? e
9
9
  return false
10
10
  else
11
- throw
11
+ throw e
12
12
  end
13
13
  end
14
14
  end
@@ -22,7 +22,7 @@ module RSpec
22
22
  IO.select([ fd ])
23
23
  retry
24
24
  rescue IOError => e
25
- if e.to_s == "stream closed" || e.to_s == "closed stream"
25
+ if noise? e
26
26
  break
27
27
  else
28
28
  throw e
@@ -32,6 +32,10 @@ module RSpec
32
32
  end
33
33
  end
34
34
  end
35
+
36
+ def self.noise?(error)
37
+ error.to_s == "stream closed" || error.to_s == "closed stream"
38
+ end
35
39
  end
36
40
  end
37
41
  end
@@ -0,0 +1,70 @@
1
+ module RSpec
2
+ module Bash
3
+ class MessageDecoder
4
+ READING_FRAME_COUNT = 1
5
+ READING_FRAME_SZ = 2
6
+ READING_FRAME = 3
7
+ RE_NUMBER = /\d/
8
+
9
+ def self.decode(buffer)
10
+ state = READING_FRAME_COUNT
11
+ buffersz = buffer.length
12
+ frames = []
13
+ frame_buf = ''
14
+ frame_count_buf = ''
15
+ frame_count = Float::INFINITY
16
+ framesz_buf = ''
17
+ framesz = Float::INFINITY
18
+ cursor = 0
19
+
20
+ while cursor < buffersz && frames.count < frame_count
21
+ char = buffer[cursor]
22
+ cursor += 1
23
+
24
+ case state
25
+ when READING_FRAME_COUNT
26
+ case char
27
+ when ';'
28
+ state = READING_FRAME_SZ
29
+ frame_count = frame_count_buf.to_i
30
+ frame_count_buf = ''
31
+ framesz_buf = ''
32
+ when /\d/
33
+ frame_count_buf += char
34
+ else
35
+ return nil, "invalid payload: illegal character in header '#{char}' (#{cursor}/#{buffersz})"
36
+ end
37
+ when READING_FRAME_SZ
38
+ case char
39
+ when ';'
40
+ state = READING_FRAME
41
+ frame_buf = ''
42
+ framesz = framesz_buf.to_i
43
+ framesz_buf = ''
44
+ when /\d/
45
+ framesz_buf += char
46
+ else
47
+ return nil, "invalid payload: illegal character in frame header '#{char}' (#{cursor}/#{buffersz})"
48
+ end
49
+ when READING_FRAME
50
+ frame_buf += char
51
+
52
+ if frame_buf.length == framesz
53
+ state = READING_FRAME_SZ
54
+ frames.push(frame_buf)
55
+ framesz = Float::INFINITY
56
+ end
57
+ end
58
+ end
59
+
60
+ if frames.count != frame_count
61
+ return nil, "invalid payload: expected #{frame_count} frames but got #{frames.count}"
62
+ end
63
+
64
+ [ frames.map do |frame|
65
+ frame_class, frame_content = frame[0..1].to_i, frame.slice(2..-1)
66
+ end ]
67
+ end
68
+ end
69
+ end
70
+ end
@@ -3,15 +3,17 @@ module RSpec
3
3
  module Mocks
4
4
  module Doubles
5
5
  class AbstractDouble
6
- attr_accessor :body,
6
+ attr_accessor(
7
+ :behaviors,
7
8
  :call_original,
8
9
  :calls,
9
10
  :expected_call_count,
10
11
  :expected_calls,
11
12
  :subshell
13
+ )
12
14
 
13
15
  def initialize(*)
14
- @body = nil
16
+ @behaviors = []
15
17
  @call_original = false
16
18
  @calls = []
17
19
  @expected_call_count = [:at_least, 1]
@@ -3,6 +3,8 @@ module RSpec
3
3
  module Mocks
4
4
  module Doubles
5
5
  class ConditionalDouble < AbstractDouble
6
+ attr_accessor :expr
7
+
6
8
  def initialize(expr)
7
9
  super()
8
10
 
@@ -10,7 +12,9 @@ module RSpec
10
12
  end
11
13
 
12
14
  def apply(script)
13
- script.stub_conditional(@expr, &body)
15
+ script.stub_conditional(@expr,
16
+ behaviors: behaviors
17
+ )
14
18
  end
15
19
 
16
20
  def call_count(script)
@@ -18,7 +22,7 @@ module RSpec
18
22
  end
19
23
 
20
24
  def call_args(script)
21
- script.conditional_calls_for(@expr).map { |x| x[:args] }
25
+ script.conditional_calls_for(@expr)
22
26
  end
23
27
 
24
28
  def to_s
@@ -0,0 +1,35 @@
1
+ module RSpec
2
+ module Bash
3
+ module Mocks
4
+ module Doubles
5
+ class ExactConditionalDouble < AbstractDouble
6
+ attr_accessor :fullexpr
7
+
8
+ def initialize(fullexpr)
9
+ super()
10
+
11
+ @fullexpr = fullexpr
12
+ end
13
+
14
+ def apply(script)
15
+ script.stub_conditional(@fullexpr,
16
+ behaviors: behaviors
17
+ )
18
+ end
19
+
20
+ def call_count(script)
21
+ script.exact_conditional_calls_for(@fullexpr).count
22
+ end
23
+
24
+ def call_args(script)
25
+ script.exact_conditional_calls_for(@fullexpr)
26
+ end
27
+
28
+ def to_s
29
+ @fullexpr.to_s
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -10,7 +10,11 @@ module RSpec
10
10
  end
11
11
 
12
12
  def apply(script)
13
- script.stub(@routine, call_original: call_original, subshell: subshell, &body)
13
+ script.stub(@routine,
14
+ call_original: call_original,
15
+ behaviors: behaviors,
16
+ subshell: subshell,
17
+ )
14
18
  end
15
19
 
16
20
  def call_count(script)
@@ -1,5 +1,6 @@
1
1
  require_relative './doubles/abstract_double'
2
2
  require_relative './doubles/conditional_double'
3
+ require_relative './doubles/exact_conditional_double'
3
4
  require_relative './doubles/function_double'
4
5
 
5
6
  module RSpec
@@ -20,22 +20,47 @@ module RSpec
20
20
  end
21
21
 
22
22
  def with_args(args)
23
- tap { @double.expected_calls << args }
24
- end
25
-
26
- def and_return(code)
27
- tap { @double.body = lambda { |*| "return #{code}" } }
23
+ tap {
24
+ @double.expected_calls << args
25
+ @double.behaviors << create_behavior({ args: args })
26
+ }
28
27
  end
29
28
 
30
- def and_yield(subshell: true, &block)
29
+ def and_yield(subshell: true, times: 1, &body)
31
30
  tap {
32
- @double.body = block
33
31
  @double.subshell = subshell
32
+
33
+ behavior = find_last_blank_or_create_behavior
34
+ behavior[:body] = body
35
+ behavior[:charges] = behavior[:charges] == 0 ? times : behavior[:charges]
36
+ behavior[:subshell] = subshell
34
37
  }
35
38
  end
36
39
 
40
+ def and_return(code, times: 1)
41
+ and_yield(subshell: false, times: times) { |*| "return #{code}" }
42
+ end
43
+
44
+ def and_always_return(code)
45
+ and_return(code, times: Float::INFINITY)
46
+ end
47
+
48
+ def and_always_yield(subshell: true, &body)
49
+ and_yield(subshell: subshell, times: Float::INFINITY, &body)
50
+ end
51
+
37
52
  def exactly(n)
38
- tap { @double.expected_call_count = [:exactly, n] }
53
+ tap do
54
+ if @double.behaviors.last
55
+ @double.behaviors.last[:charges] = n
56
+
57
+ (n-1).times do
58
+ @double.expected_calls << @double.expected_calls.last
59
+ end
60
+ else
61
+ @double.expected_call_count = [:exactly, n]
62
+ end
63
+ end
39
64
  end
40
65
 
41
66
  def at_least(n)
@@ -89,11 +114,25 @@ module RSpec
89
114
  )
90
115
  end
91
116
 
92
- private
117
+ protected
93
118
 
94
119
  def proxy_for(subject)
95
120
  ::RSpec::Mocks.space.proxy_for(subject)
96
121
  end
122
+
123
+ def find_last_blank_or_create_behavior
124
+ @double.behaviors.detect { |x| x[:body].nil? } || begin
125
+ create_behavior.tap { |x| @double.behaviors << x }
126
+ end
127
+ end
128
+
129
+ def create_behavior(args: nil, body: nil, charges: 0)
130
+ {
131
+ args: args,
132
+ body: body,
133
+ charges: charges
134
+ }
135
+ end
97
136
  end
98
137
  end
99
138
  end
@@ -0,0 +1,29 @@
1
+ require_relative '../doubles'
2
+ require_relative './base_matcher'
3
+
4
+ module RSpec
5
+ module Bash
6
+ module Mocks
7
+ module Matchers
8
+ # @private
9
+ class TestBy < BaseMatcher
10
+ def initialize(expr)
11
+ @double = Doubles::ConditionalDouble.new(expr)
12
+ @display_name = "test_by"
13
+
14
+ super()
15
+ end
16
+
17
+ def with_args(args)
18
+ tap {
19
+ fullexpr = "#{@double.expr} #{args}"
20
+
21
+ @double.expected_calls << fullexpr
22
+ @double.behaviors << create_behavior(args: fullexpr)
23
+ }
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,30 @@
1
+ require_relative '../doubles'
2
+ require_relative './base_matcher'
3
+
4
+ module RSpec
5
+ module Bash
6
+ module Mocks
7
+ module Matchers
8
+ # @private
9
+ class TestFor < BaseMatcher
10
+ def initialize(fullexpr)
11
+ @double = Doubles::ExactConditionalDouble.new(fullexpr)
12
+ @display_name = "test_for"
13
+
14
+ super()
15
+ end
16
+
17
+ def with_args(args)
18
+ fail "#{to_s}: cannot be used with '.with_args', use 'test_by' instead"
19
+ end
20
+
21
+ protected
22
+
23
+ def create_behavior(**rest)
24
+ super(**rest, args: @double.fullexpr)
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -1,5 +1,6 @@
1
1
  require_relative './matchers/receive'
2
- require_relative './matchers/test'
2
+ require_relative './matchers/test_for'
3
+ require_relative './matchers/test_by'
3
4
 
4
5
  module RSpec
5
6
  module Bash
@@ -9,8 +10,16 @@ module RSpec
9
10
  Receive.new(*args)
10
11
  end
11
12
 
12
- def test(*args)
13
- Test.new(*args)
13
+ def receive_function(method_name, &block)
14
+ ::RSpec::Mocks::Matchers::Receive.new(method_name, block)
15
+ end
16
+
17
+ def test_for(*args)
18
+ TestFor.new(*args)
19
+ end
20
+
21
+ def test_by(*args)
22
+ TestBy.new(*args)
14
23
  end
15
24
  end
16
25
  end
@@ -28,12 +28,12 @@ module RSpec
28
28
  def expect_message(double:, display_name:)
29
29
  allow_message(double: double)
30
30
 
31
- @expectations << ScriptMessageExpectation.new(
31
+ ScriptMessageExpectation.new(
32
32
  double: double,
33
33
  display_name: display_name,
34
34
  error_generator: @error_generator,
35
35
  backtrace_line: ::RSpec::CallerFilter.first_non_rspec_line
36
- )
36
+ ).tap { |x| @expectations << x }
37
37
  end
38
38
 
39
39
  def allow_message(double:)
@@ -1,14 +1,18 @@
1
+ require_relative './stub_behavior'
2
+ require_relative './script_generator'
3
+
1
4
  module RSpec
2
5
  module Bash
3
6
  class Script
4
7
  MAIN_SCRIPT_FILE = File.expand_path('../controller.sh', __FILE__)
5
8
  NOOP = lambda { |*| '' }
9
+ NOOP_BEHAVIOR = StubBehavior.new(body: NOOP, charges: Float::INFINITY)
6
10
 
7
11
  def self.load(path)
8
12
  new(File.read(path))
9
13
  end
10
14
 
11
- attr_reader :source, :source_file, :stdout, :stderr, :exit_code
15
+ attr_reader :exit_code, :source, :source_file, :stdout, :stderr, :stubs
12
16
 
13
17
  def initialize(source, path = 'Anonymous')
14
18
  @conditional_stubs = []
@@ -23,41 +27,46 @@ module RSpec
23
27
  end
24
28
 
25
29
  def to_s
26
- to_bash_script
30
+ ScriptGenerator.generate(self)
27
31
  end
28
32
 
29
33
  def inspect
30
34
  "Script(\"#{File.basename(@source_file)}\")"
31
35
  end
32
36
 
33
- def stub(fn, call_original: false, subshell: true, &body)
37
+ def stub(fn, behaviors:, call_original: false, subshell: true)
34
38
  @stubs[fn.to_sym] = {
35
- body: (call_original || !body) ? NOOP : body,
39
+ behaviors: behaviors.map { |x| StubBehavior.new(x) },
36
40
  subshell: subshell,
37
41
  call_original: call_original
38
42
  }
39
43
  end
40
44
 
41
- def stub_conditional(expr, &body)
42
- @conditional_stubs << { expr: expr, body: body || NOOP }
45
+ def stub_conditional(expr, behaviors:)
46
+ @conditional_stubs << {
47
+ behaviors: behaviors.map { |x| StubBehavior.new(x) },
48
+ expr: expr,
49
+ }
43
50
  end
44
51
 
45
52
  def stubbed(name, args)
46
- fail "#{name} is not stubbed" unless @stubs.key?(name.to_sym)
47
-
48
- @stubs[name.to_sym][:body].call(args)
53
+ apply_matching_behavior @stubs[name.to_sym], args
49
54
  end
50
55
 
51
- def stubbed_conditional(expr, args)
52
- conditional_stub = @conditional_stubs.detect { |x| x[:expr] == expr }
56
+ def stubbed_conditional(fullexpr)
57
+ conditional_stub = @conditional_stubs.detect { |x| fullexpr.index(x[:expr]) == 0 }
53
58
 
54
59
  if conditional_stub
55
- conditional_stub[:body].call(args)
60
+ apply_matching_behavior conditional_stub, fullexpr
56
61
  else
57
62
  ""
58
63
  end
59
64
  end
60
65
 
66
+ def has_stub?(name)
67
+ @stubs.key?(name.to_sym)
68
+ end
69
+
61
70
  def has_conditional_stubs?
62
71
  @conditional_stubs.any?
63
72
  end
@@ -67,7 +76,11 @@ module RSpec
67
76
  end
68
77
 
69
78
  def conditional_calls_for(expr)
70
- @conditional_stub_calls.select { |x| x[:expr] == expr }
79
+ @conditional_stub_calls.select { |x| x.index(expr) == 0 }
80
+ end
81
+
82
+ def exact_conditional_calls_for(fullexpr)
83
+ @conditional_stub_calls.select { |x| x == fullexpr }
71
84
  end
72
85
 
73
86
  def track_call(name, args)
@@ -76,8 +89,8 @@ module RSpec
76
89
  @stub_calls[name.to_sym].push({ args: args })
77
90
  end
78
91
 
79
- def track_conditional_call(expr, args)
80
- @conditional_stub_calls.push({ expr: expr, args: args })
92
+ def track_conditional_call(fullexpr)
93
+ @conditional_stub_calls.push(fullexpr)
81
94
  end
82
95
 
83
96
  def track_exit_code(code)
@@ -86,36 +99,11 @@ module RSpec
86
99
 
87
100
  private
88
101
 
89
- def to_bash_script
90
- buffer = ""
91
- buffer << "builtin source '#{Script::MAIN_SCRIPT_FILE}'"
92
- buffer << "\n"
93
-
94
- @stubs.keys.each do |name|
95
- stub_def = @stubs[name]
96
-
97
- if stub_def[:call_original] then
98
- buffer << <<-EOF
99
- #{name}() {
100
- __rspec_bash_run_stub '#{name}' $@
101
-
102
- builtin #{name} $@
103
- }
104
- EOF
105
- elsif stub_def[:subshell] == false then
106
- buffer << <<-EOF
107
- #{name}() {
108
- __rspec_bash_run_stub '#{name}' $@
109
- }
110
- EOF
111
- else
112
- buffer << "#{name}()(__rspec_bash_run_stub '#{name}' $@)\n"
113
- end
114
- end
115
-
116
- buffer << "\n"
117
- buffer << @source
118
- buffer
102
+ def apply_matching_behavior(stub, args)
103
+ behavior = stub[:behaviors].detect { |x| x.usable? && x.applicable?(args) }
104
+ behavior ||= stub[:behaviors].detect { |x| x.usable? && x.context_free? }
105
+ behavior ||= NOOP_BEHAVIOR
106
+ behavior.apply!(args)
119
107
  end
120
108
  end
121
109
  end
@@ -4,12 +4,18 @@ require 'tempfile'
4
4
  require_relative './fd'
5
5
  require_relative './open3'
6
6
  require_relative './noisy_thread'
7
+ require_relative './message_decoder'
7
8
 
8
9
  module RSpec
9
10
  module Bash
10
11
  class ScriptEvaluator
12
+ BLOCK_SIZE = 1024
11
13
  CONDITIONAL_EXPR_STUB = 'conditional_expr'.freeze
12
- BLOCK_SIZE = 4096
14
+ FRAME_NAME = 1
15
+ FRAME_ARG = 2
16
+ FRAME_TRACE = 3
17
+ MESSAGE_REQ = '<rspec-bash::req>'.freeze
18
+ MESSAGE_ACK = '<rspec-bash::ack>'.freeze
13
19
 
14
20
  # (String, Object?): Boolean
15
21
  #
@@ -63,8 +69,8 @@ module RSpec
63
69
 
64
70
  # accept & respond to prompts
65
71
  workers << NoisyThread.new do
66
- FD.poll(b2r, throttle: Bash.configuration.throttle) do
67
- respond_to_prompts(r2b, b2r, script, bus_file)
72
+ FD.poll(b2r, throttle: 0) do
73
+ respond_to_prompts(r2b, b2r, bus_file, script)
68
74
  end
69
75
  end
70
76
 
@@ -87,92 +93,96 @@ module RSpec
87
93
 
88
94
  private
89
95
 
90
- def respond_to_prompts(fd_in, fd_out, script, bus_file)
91
- fd_out.expect("</rspec_bash::stub>", 1) do |result|
96
+ def respond_to_prompts(fd_in, fd_out, bus_file, script)
97
+ fd_out.expect(MESSAGE_REQ, 1) do |result|
92
98
  break if result.nil?
93
99
 
94
- prompts = result[0].split("\n").reject(&:empty?).reduce([]) do |acc, line|
95
- if line == "</rspec_bash::stub>"
96
- if acc[-1]
97
- acc[-1].merge!(classify_stub(acc[-1][:buffer]))
98
- else
99
- puts "[WARN] cannot match stub entry: #{line} => #{acc}"
100
- end
101
- else
102
- acc.push({ type: :unknown, buffer: line })
103
- end
100
+ lines = result[0].split("\n").reject(&:empty?)
104
101
 
105
- acc
106
- end
107
-
108
- prompts.each do |type:, buffer:, **stub|
109
- case type
110
- when :conditional
111
- if !script.has_conditional_stubs? && !Bash.configuration.allow_unstubbed_conditionals
112
- fail "conditional expressions are not stubbed!"
113
- end
102
+ stubs = lines.each_with_index.reduce([]) do |acc, (line, index)|
103
+ next acc unless line == MESSAGE_REQ
114
104
 
115
- File.write(bus_file, script.stubbed_conditional(stub[:expr], stub[:args]))
105
+ message = lines[index-1]
106
+ frames, err = MessageDecoder.decode(message)
116
107
 
117
- fd_in.puts bus_file.path
118
- fd_in.flush
108
+ if err
109
+ STDERR.puts <<-EOF
110
+ bash-rspec: communication between Ruby and Bash failed, this is
111
+ most likely an internal error.
119
112
 
120
- fd_out.expect('</rspec_bash::stub-body>', 1)
113
+ #{err}
114
+ EOF
121
115
 
122
- script.track_conditional_call(stub[:expr], stub[:args])
123
- when :function
124
- routine = stub[:name]
125
- args = stub[:args]
126
- body = script.stubbed(routine, args)
116
+ next acc
117
+ end
127
118
 
128
- File.write(bus_file, body)
119
+ stub = frames.reduce({ expr: nil, type: nil, args: [], stacktrace: [] }) do |x, (type, content)|
120
+ case type
121
+ when FRAME_NAME
122
+ x[:expr] = content
123
+ x[:type] = content == CONDITIONAL_EXPR_STUB ? :conditional : :function
124
+ when FRAME_ARG
125
+ x[:args] << content
126
+ when FRAME_TRACE
127
+ x[:stacktrace] << content unless content.empty?
128
+ else
129
+ STDERR.puts "rspec-bash: unrecognized frame '#{type}' => '#{content}'"
130
+ STDERR.puts "rspec-bash: source:\n#{message}"
131
+ end
129
132
 
130
- fd_in.puts bus_file.path
131
- fd_in.flush
133
+ x
134
+ end.tap do |stub|
135
+ stub[:args] = stub[:args].join(' ')
136
+ end
132
137
 
133
- fd_out.expect('</rspec_bash::stub-body>', 1)
138
+ acc.push(stub)
139
+ end
134
140
 
135
- script.track_call(routine, args)
141
+ stubs.each do |stub|
142
+ case stub[:type]
143
+ when :conditional
144
+ relay_conditional_stub(fd_in, fd_out, bus_file, script: script, stub: stub)
145
+ when :function
146
+ relay_command_stub(fd_in, fd_out, bus_file, script: script, stub: stub)
136
147
  when :unknown
137
- STDERR.write "[err] unexpected message from bash: #{buffer}"
148
+ STDERR.puts "[err] unexpected message from bash: #{stub.inspect}"
138
149
  end
139
150
  end
140
151
  end
141
152
  end
142
153
 
143
- def classify_stub(command)
144
- identifier, args = split_by_first_space(command)
145
-
146
- case identifier
147
- when CONDITIONAL_EXPR_STUB
148
- expr, expr_args = split_by_first_space(args)
149
-
150
- {
151
- type: :conditional,
152
- expr: expr,
153
- args: expr_args,
154
- }
155
- else
156
- {
157
- type: :function,
158
- name: identifier,
159
- args: args,
160
- }
154
+ def relay_command_stub(fd_in, fd_out, bus_file, script:, stub:)
155
+ if !script.has_stub?(stub[:expr])
156
+ fail(
157
+ "#{stub[:expr]} is not stubbed!\n\n" +
158
+ "Call stack:\n" +
159
+ stub[:stacktrace].map { |x| "- #{x}" }.join("\n")
160
+ )
161
161
  end
162
- end
163
162
 
164
- def split_by_first_space(string)
165
- delim = string.index(' ')
163
+ File.write(bus_file, script.stubbed(stub[:expr], stub[:args]))
164
+
165
+ fd_in.puts bus_file.path
166
+ fd_in.flush
167
+
168
+ fd_out.expect(MESSAGE_ACK, 1)
166
169
 
167
- # single-argument expressions, this usually happens in unary tests
168
- # where the argument evaluates to an empty string, a la:
169
- #
170
- # test -z "${string}" => "-z"
171
- if delim.nil?
172
- return [ string, '' ]
170
+ script.track_call(stub[:expr], stub[:args])
171
+ end
172
+
173
+ def relay_conditional_stub(fd_in, fd_out, bus_file, script:, stub:)
174
+ if !script.has_conditional_stubs? && !Bash.configuration.allow_unstubbed_conditionals
175
+ fail "conditional expressions are not stubbed!\n#{stub[:stacktrace]}"
173
176
  end
174
177
 
175
- [ string[0..delim - 1], string[delim + 1..-1] ]
178
+ File.write(bus_file, script.stubbed_conditional(stub[:args]))
179
+
180
+ fd_in.puts bus_file.path
181
+ fd_in.flush
182
+
183
+ fd_out.expect(MESSAGE_ACK, 1)
184
+
185
+ script.track_conditional_call(stub[:args])
176
186
  end
177
187
 
178
188
  def try_hard(what)
@@ -0,0 +1,17 @@
1
+ function test() {
2
+ if __rspec_bash_load_stub 'conditional_expr' "${@}"; then
3
+ builtin . "${__rspec_bash_stub_body}" "${@}"
4
+ else
5
+ builtin test "${@}"
6
+ fi
7
+ }
8
+
9
+ function [() {
10
+ local without_bracket="${@:1:$(($#-1))}"
11
+
12
+ if __rspec_bash_load_stub 'conditional_expr' "${without_bracket[@]}"; then
13
+ builtin . "${__rspec_bash_stub_body}" "${without_bracket[@]}"
14
+ else
15
+ builtin [ "${@}"
16
+ fi
17
+ }
@@ -0,0 +1,53 @@
1
+ export __rspec_bash_stub_body=""
2
+
3
+ function __rspec_bash_read() {
4
+ local fd=${BASHIT_R_FD:-4}
5
+
6
+ builtin read -u $fd "${@}"
7
+ }
8
+
9
+ function __rspec_bash_write() {
10
+ local fd=${BASHIT_W_FD:-5}
11
+
12
+ builtin echo 1>&$fd "${@}"
13
+ }
14
+
15
+ function __rspec_bash_load_stub() {
16
+ local name="${1}"
17
+ local arg
18
+ local message
19
+ local fragments=(
20
+ "1 ${name}"
21
+ "3 $(caller 1)"
22
+ "3 $(caller 2)"
23
+ "3 $(caller 3)"
24
+ )
25
+
26
+ builtin shift 1
27
+
28
+ for arg in "${@}"; do
29
+ fragments+=("2 ${arg}")
30
+ done
31
+
32
+ message="${#fragments[@]};"
33
+
34
+ for fragment in "${fragments[@]}"; do
35
+ message="${message}${#fragment};${fragment}"
36
+ done
37
+
38
+ __rspec_bash_write "${message}"
39
+ __rspec_bash_write "<rspec-bash::req>"
40
+
41
+ __rspec_bash_read __rspec_bash_stub_body
42
+ __rspec_bash_write "<rspec-bash::ack>"
43
+
44
+ builtin test -s "${__rspec_bash_stub_body}"
45
+ }
46
+
47
+ function __rspec_bash_call_stubbed() {
48
+ __rspec_bash_load_stub "${@}"
49
+
50
+ builtin shift 1
51
+
52
+ builtin . "${__rspec_bash_stub_body}" "${@}"
53
+ }
@@ -0,0 +1,58 @@
1
+ module RSpec
2
+ module Bash
3
+ class ScriptGenerator
4
+ NOOP = lambda { |*| '' }
5
+ SCRIPTS = {
6
+ conditionals: File.expand_path('../script_generator/conditional.sh', __FILE__),
7
+ controller: File.expand_path('../script_generator/controller.sh', __FILE__)
8
+ }
9
+ SPIES = {
10
+ builtin: lambda { |name|
11
+ <<-EOF
12
+ #{name}() {
13
+ __rspec_bash_call_stubbed '#{name}' "${@}"
14
+
15
+ builtin #{name} $@
16
+ }
17
+ EOF
18
+ },
19
+ }
20
+ STUBS = {
21
+ function: lambda { |name|
22
+ <<-EOF
23
+ #{name}() {
24
+ __rspec_bash_call_stubbed '#{name}' "${@}"
25
+ }
26
+ EOF
27
+ },
28
+
29
+ function_in_subshell: lambda { |name|
30
+ "#{name}()(__rspec_bash_call_stubbed '#{name}' \"${@}\")\n"
31
+ }
32
+ }
33
+
34
+ def self.generate(script)
35
+ buffer = ""
36
+ buffer << "builtin . '#{SCRIPTS[:controller]}'\n"
37
+ buffer << "builtin . '#{SCRIPTS[:conditionals]}'\n" if script.has_conditional_stubs?
38
+ buffer << "\n"
39
+
40
+ script.stubs.keys.each do |name|
41
+ stub_def = script.stubs[name]
42
+
43
+ if stub_def[:call_original] then
44
+ buffer << SPIES[:builtin].call(name)
45
+ elsif stub_def[:subshell] == false then
46
+ buffer << STUBS[:function].call(name)
47
+ else
48
+ buffer << STUBS[:function_in_subshell].call(name)
49
+ end
50
+ end
51
+
52
+ buffer << "\n"
53
+ buffer << script.source
54
+ buffer
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,35 @@
1
+ module RSpec
2
+ module Bash
3
+ class StubBehavior
4
+ attr_accessor :args, :body
5
+
6
+ def initialize(args: nil, body:, charges: 1, subshell: true)
7
+ @args = args
8
+ @body = body
9
+ @subshell = subshell
10
+ @charges = charges
11
+ end
12
+
13
+ def usable?
14
+ @charges > 0
15
+ end
16
+
17
+ def applicable?(args)
18
+ @args == args
19
+ end
20
+
21
+ def context_free?
22
+ @args.nil?
23
+ end
24
+
25
+ def requires_subshell?
26
+ @subshell
27
+ end
28
+
29
+ def apply!(args)
30
+ @charges -= 1
31
+ @body.call(args)
32
+ end
33
+ end
34
+ end
35
+ end
@@ -1,6 +1,10 @@
1
1
  module RSpec
2
2
  module Bash
3
3
  module Support
4
+ def a_script(*args)
5
+ Script.new(*args)
6
+ end
7
+
4
8
  def run_script(script, args = [], **opts)
5
9
  ScriptEvaluator.new.eval(script, args, opts)
6
10
  end
@@ -1,5 +1,5 @@
1
1
  module RSpec
2
2
  module Bash
3
- VERSION = '1.0.1'
3
+ VERSION = '1.1.0'
4
4
  end
5
5
  end
data/lib/rspec/bash.rb CHANGED
@@ -4,6 +4,7 @@ require_relative './bash/noisy_thread'
4
4
  require_relative './bash/open3'
5
5
  require_relative './bash/script'
6
6
  require_relative './bash/script_evaluator'
7
+ require_relative './bash/script_generator'
7
8
  require_relative './bash/support'
8
9
  require_relative './bash/version'
9
10
  require_relative './bash/mocks/doubles'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rspec-bash-x
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.1
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ahmad Amireh
@@ -50,22 +50,28 @@ files:
50
50
  - ext/rspec-mocks/space.rb
51
51
  - lib/rspec/bash.rb
52
52
  - lib/rspec/bash/configuration.rb
53
- - lib/rspec/bash/controller.sh
54
53
  - lib/rspec/bash/fd.rb
54
+ - lib/rspec/bash/message_decoder.rb
55
55
  - lib/rspec/bash/mocks/doubles.rb
56
56
  - lib/rspec/bash/mocks/doubles/abstract_double.rb
57
57
  - lib/rspec/bash/mocks/doubles/conditional_double.rb
58
+ - lib/rspec/bash/mocks/doubles/exact_conditional_double.rb
58
59
  - lib/rspec/bash/mocks/doubles/function_double.rb
59
60
  - lib/rspec/bash/mocks/matchers.rb
60
61
  - lib/rspec/bash/mocks/matchers/base_matcher.rb
61
62
  - lib/rspec/bash/mocks/matchers/receive.rb
62
- - lib/rspec/bash/mocks/matchers/test.rb
63
+ - lib/rspec/bash/mocks/matchers/test_by.rb
64
+ - lib/rspec/bash/mocks/matchers/test_for.rb
63
65
  - lib/rspec/bash/mocks/script_message_expectation.rb
64
66
  - lib/rspec/bash/mocks/script_proxy.rb
65
67
  - lib/rspec/bash/noisy_thread.rb
66
68
  - lib/rspec/bash/open3.rb
67
69
  - lib/rspec/bash/script.rb
68
70
  - lib/rspec/bash/script_evaluator.rb
71
+ - lib/rspec/bash/script_generator.rb
72
+ - lib/rspec/bash/script_generator/conditional.sh
73
+ - lib/rspec/bash/script_generator/controller.sh
74
+ - lib/rspec/bash/stub_behavior.rb
69
75
  - lib/rspec/bash/support.rb
70
76
  - lib/rspec/bash/version.rb
71
77
  homepage:
@@ -1,49 +0,0 @@
1
- __rspec_bash_stub_body=""
2
-
3
- let r_fd=${BASHIT_R_FD:-4}
4
- let w_fd=${BASHIT_W_FD:-5}
5
-
6
- function __rspec_bash_write() {
7
- builtin echo 1>&$w_fd $@
8
- }
9
-
10
- function __rspec_bash_read() {
11
- builtin read -u $r_fd $@
12
- }
13
-
14
- function __rspec_bash_retrieve_stub() {
15
- local name=$1
16
-
17
- builtin shift 1
18
-
19
- __rspec_bash_write $name $@
20
- __rspec_bash_write "</rspec_bash::stub>"
21
- __rspec_bash_read __rspec_bash_stub_body
22
- __rspec_bash_write "</rspec_bash::stub-body>"
23
-
24
- builtin test -s "${__rspec_bash_stub_body}"
25
- }
26
-
27
- function __rspec_bash_run_stub() {
28
- __rspec_bash_retrieve_stub $@
29
-
30
- builtin . "${__rspec_bash_stub_body}" $@
31
- }
32
-
33
- function test() {
34
- if __rspec_bash_retrieve_stub "conditional_expr" $@; then
35
- builtin . "${__rspec_bash_stub_body}" $@
36
- else
37
- builtin test $@
38
- fi
39
- }
40
-
41
- function [()(
42
- local without_bracket="${@:1:$(($#-1))}"
43
-
44
- if __rspec_bash_retrieve_stub "conditional_expr" "${without_bracket[@]}"; then
45
- builtin . "${__rspec_bash_stub_body}" "${without_bracket[@]}"
46
- else
47
- builtin [ $@
48
- fi
49
- )
@@ -1,20 +0,0 @@
1
- require_relative '../doubles'
2
- require_relative './base_matcher'
3
-
4
- module RSpec
5
- module Bash
6
- module Mocks
7
- module Matchers
8
- # @private
9
- class Test < BaseMatcher
10
- def initialize(expr)
11
- @double = Doubles::ConditionalDouble.new(expr)
12
- @display_name = "test(#{expr})"
13
-
14
- super()
15
- end
16
- end
17
- end
18
- end
19
- end
20
- end