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