rspec-bash-x 1.0.1 → 1.1.0
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.
- checksums.yaml +4 -4
- data/lib/rspec/bash/fd.rb +7 -3
- data/lib/rspec/bash/message_decoder.rb +70 -0
- data/lib/rspec/bash/mocks/doubles/abstract_double.rb +4 -2
- data/lib/rspec/bash/mocks/doubles/conditional_double.rb +6 -2
- data/lib/rspec/bash/mocks/doubles/exact_conditional_double.rb +35 -0
- data/lib/rspec/bash/mocks/doubles/function_double.rb +5 -1
- data/lib/rspec/bash/mocks/doubles.rb +1 -0
- data/lib/rspec/bash/mocks/matchers/base_matcher.rb +48 -9
- data/lib/rspec/bash/mocks/matchers/test_by.rb +29 -0
- data/lib/rspec/bash/mocks/matchers/test_for.rb +30 -0
- data/lib/rspec/bash/mocks/matchers.rb +12 -3
- data/lib/rspec/bash/mocks/script_proxy.rb +2 -2
- data/lib/rspec/bash/script.rb +33 -45
- data/lib/rspec/bash/script_evaluator.rb +77 -67
- data/lib/rspec/bash/script_generator/conditional.sh +17 -0
- data/lib/rspec/bash/script_generator/controller.sh +53 -0
- data/lib/rspec/bash/script_generator.rb +58 -0
- data/lib/rspec/bash/stub_behavior.rb +35 -0
- data/lib/rspec/bash/support.rb +4 -0
- data/lib/rspec/bash/version.rb +1 -1
- data/lib/rspec/bash.rb +1 -0
- metadata +9 -3
- data/lib/rspec/bash/controller.sh +0 -49
- data/lib/rspec/bash/mocks/matchers/test.rb +0 -20
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 74cf305ed10730b811f278b17e84579525ca2f75
|
4
|
+
data.tar.gz: 69fafdc217a018885b31b568ac0a7f95c0752bdb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
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
|
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
|
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
|
-
@
|
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,
|
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)
|
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,
|
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)
|
@@ -20,22 +20,47 @@ module RSpec
|
|
20
20
|
end
|
21
21
|
|
22
22
|
def with_args(args)
|
23
|
-
tap {
|
24
|
-
|
25
|
-
|
26
|
-
|
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, &
|
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
|
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
|
-
|
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/
|
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
|
13
|
-
|
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
|
-
|
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:)
|
data/lib/rspec/bash/script.rb
CHANGED
@@ -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, :
|
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
|
-
|
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
|
37
|
+
def stub(fn, behaviors:, call_original: false, subshell: true)
|
34
38
|
@stubs[fn.to_sym] = {
|
35
|
-
|
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,
|
42
|
-
@conditional_stubs << {
|
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
|
-
|
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(
|
52
|
-
conditional_stub = @conditional_stubs.detect { |x| x[: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
|
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
|
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(
|
80
|
-
@conditional_stub_calls.push(
|
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
|
90
|
-
|
91
|
-
|
92
|
-
|
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
|
-
|
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:
|
67
|
-
respond_to_prompts(r2b, b2r,
|
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,
|
91
|
-
fd_out.expect(
|
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
|
-
|
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
|
-
|
106
|
-
|
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
|
-
|
105
|
+
message = lines[index-1]
|
106
|
+
frames, err = MessageDecoder.decode(message)
|
116
107
|
|
117
|
-
|
118
|
-
|
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
|
-
|
113
|
+
#{err}
|
114
|
+
EOF
|
121
115
|
|
122
|
-
|
123
|
-
|
124
|
-
routine = stub[:name]
|
125
|
-
args = stub[:args]
|
126
|
-
body = script.stubbed(routine, args)
|
116
|
+
next acc
|
117
|
+
end
|
127
118
|
|
128
|
-
|
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
|
-
|
131
|
-
|
133
|
+
x
|
134
|
+
end.tap do |stub|
|
135
|
+
stub[:args] = stub[:args].join(' ')
|
136
|
+
end
|
132
137
|
|
133
|
-
|
138
|
+
acc.push(stub)
|
139
|
+
end
|
134
140
|
|
135
|
-
|
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.
|
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
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
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
|
-
|
165
|
-
|
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
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
if
|
172
|
-
|
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
|
-
|
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
|
data/lib/rspec/bash/support.rb
CHANGED
data/lib/rspec/bash/version.rb
CHANGED
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
|
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/
|
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
|