rspec-bash 0.1.1 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +8 -0
- data/Gemfile +1 -0
- data/README.md +23 -0
- data/Rakefile +15 -4
- data/bin/bash_stub.sh +92 -0
- data/bin/bash_wrapper.sh.erb +12 -0
- data/bin/ruby_stub.rb +33 -0
- data/lib/rspec/bash.rb +5 -4
- data/lib/rspec/bash/command.rb +5 -0
- data/lib/rspec/bash/command/call_configuration.rb +76 -0
- data/lib/rspec/bash/command/call_configuration_manager.rb +24 -0
- data/lib/rspec/bash/command/call_log.rb +48 -0
- data/lib/rspec/bash/command/call_log_manager.rb +38 -0
- data/lib/rspec/bash/command/stubbed_command.rb +64 -0
- data/lib/rspec/bash/server.rb +3 -0
- data/lib/rspec/bash/server/bash_stub_marshaller.rb +19 -0
- data/lib/rspec/bash/server/ruby_stub_marshaller.rb +13 -0
- data/lib/rspec/bash/server/stub_server.rb +47 -0
- data/lib/rspec/bash/stubbed_env.rb +75 -54
- data/lib/rspec/bash/util/call_conf_argument_list_matcher.rb +5 -5
- data/lib/rspec/bash/util/call_log_argument_list_matcher.rb +1 -1
- data/lib/rspec/bash/wrapper.rb +4 -0
- data/lib/rspec/bash/wrapper/bash_stub_script.rb +15 -0
- data/lib/rspec/bash/wrapper/bash_wrapper.rb +54 -0
- data/lib/rspec/bash/wrapper/ruby_stub_script.rb +15 -0
- data/lib/rspec/bash/wrapper/stub_function.rb +36 -0
- data/rspec-bash.gemspec +2 -1
- data/spec/classes/command/call_configuration_manager_spec.rb +68 -0
- data/spec/classes/{call_configuration_spec.rb → command/call_configuration_spec.rb} +51 -114
- data/spec/classes/command/call_log_manager_spec.rb +83 -0
- data/spec/classes/{call_log_spec.rb → command/call_log_spec.rb} +23 -82
- data/spec/classes/command/stubbed_command_spec.rb +118 -0
- data/spec/classes/server/bash_stub_marshaller_spec.rb +38 -0
- data/spec/classes/server/ruby_stub_marshaller_spec.rb +31 -0
- data/spec/classes/server/stub_server_spec.rb +121 -0
- data/spec/classes/stubbed_env_spec.rb +141 -280
- data/spec/classes/util/call_conf_argument_list_matcher_spec.rb +17 -17
- data/spec/classes/util/call_log_argument_list_matcher_spec.rb +24 -18
- data/spec/classes/wrapper/bash_wrapper_spec.rb +37 -0
- data/spec/classes/wrapper/ruby_stub_script_spec.rb +204 -0
- data/spec/helper/string_file_io.rb +1 -1
- data/spec/integration/call_log/called_with_args_spec.rb +8 -4
- data/spec/integration/call_log/called_with_no_args_spec.rb +1 -1
- data/spec/integration/call_log/stdin_spec.rb +10 -4
- data/spec/integration/edge_cases_spec.rb +34 -0
- data/spec/integration/matchers/be_called_with_arguments_spec.rb +12 -13
- data/spec/integration/matchers/be_called_with_no_arguments_spec.rb +6 -7
- data/spec/integration/stubbed_command/outputs_spec.rb +111 -91
- data/spec/integration/stubbed_command/returns_exitstatus_spec.rb +46 -37
- data/spec/integration/stubbed_env/execute_with_env_vars_spec.rb +3 -4
- data/spec/integration/stubbed_env/execute_with_path_spec.rb +6 -7
- data/spec/integration/stubbed_env/execute_with_stub_wrapper_spec.rb +4 -12
- data/spec/integration/stubbed_env/override_spec.rb +354 -0
- data/spec/integration/wrapper/bash_stub_script_spec.rb +383 -0
- data/spec/integration/wrapper/bash_wrapper_spec.rb +48 -0
- data/spec/scripts/function_library.sh +9 -1
- data/spec/spec_helper.rb +2 -0
- metadata +65 -21
- data/bin/function_override.sh.erb +0 -7
- data/bin/function_override_wrapper.sh.erb +0 -19
- data/bin/stub.rb.erb +0 -56
- data/lib/rspec/bash/call_configuration.rb +0 -62
- data/lib/rspec/bash/call_log.rb +0 -71
- data/lib/rspec/bash/stubbed_command.rb +0 -88
- data/spec/classes/stub_spec.rb +0 -510
- data/spec/classes/stubbed_command_spec.rb +0 -134
- data/spec/integration/assert_called_spec.rb +0 -0
@@ -0,0 +1,64 @@
|
|
1
|
+
require 'digest'
|
2
|
+
|
3
|
+
module Rspec
|
4
|
+
module Bash
|
5
|
+
class StubbedCommand
|
6
|
+
attr_reader :command, :arguments
|
7
|
+
|
8
|
+
def initialize(command, call_log_manager, call_conf_manager)
|
9
|
+
@command = command
|
10
|
+
@arguments = []
|
11
|
+
@call_log_manager = call_log_manager
|
12
|
+
@call_conf_manager = call_conf_manager
|
13
|
+
end
|
14
|
+
|
15
|
+
def with_args(*args)
|
16
|
+
@arguments = args
|
17
|
+
self
|
18
|
+
end
|
19
|
+
|
20
|
+
def called?
|
21
|
+
@call_log_manager.called_with_args?(@command, @arguments)
|
22
|
+
end
|
23
|
+
|
24
|
+
def called_with_no_args?
|
25
|
+
@call_log_manager.called_with_no_args?(@command)
|
26
|
+
end
|
27
|
+
|
28
|
+
def called_with_args?(*args)
|
29
|
+
@call_log_manager.called_with_args?(@command, args)
|
30
|
+
end
|
31
|
+
|
32
|
+
def call_count(*args)
|
33
|
+
@call_log_manager.call_count(@command, args)
|
34
|
+
end
|
35
|
+
|
36
|
+
def stdin
|
37
|
+
@call_log_manager.stdin_for_args(@command, @arguments)
|
38
|
+
end
|
39
|
+
|
40
|
+
def returns_exitstatus(exitcode)
|
41
|
+
@call_conf_manager.set_exitcode(@command, exitcode, @arguments)
|
42
|
+
self
|
43
|
+
end
|
44
|
+
|
45
|
+
def outputs(contents, to: :stdout)
|
46
|
+
@call_conf_manager.add_output(@command, contents, to, @arguments)
|
47
|
+
self
|
48
|
+
end
|
49
|
+
|
50
|
+
def call_log
|
51
|
+
@call_log_manager.call_log(@command)
|
52
|
+
end
|
53
|
+
|
54
|
+
def inspect
|
55
|
+
if @arguments.any?
|
56
|
+
"<Stubbed #{@call_configuration.command.inspect} " \
|
57
|
+
"args: #{@arguments.join(' ').inspect}>"
|
58
|
+
else
|
59
|
+
"<Stubbed #{@call_configuration.command.inspect}>"
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'sparsify'
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
module Rspec
|
5
|
+
module Bash
|
6
|
+
class BashStubMarshaller
|
7
|
+
def unmarshal(message_to_unmarshal)
|
8
|
+
object_to_unflatten = JSON.parse(message_to_unmarshal)
|
9
|
+
unflattened_object = Sparsify.unsparse(object_to_unflatten)
|
10
|
+
JSON.parse(JSON.dump(unflattened_object), symbolize_names: true)
|
11
|
+
end
|
12
|
+
|
13
|
+
def marshal(object_to_marshal)
|
14
|
+
object_to_dump = Sparsify.sparse(object_to_marshal, sparse_array: true)
|
15
|
+
JSON.pretty_generate(object_to_dump, indent: '', space: '')
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'socket'
|
2
|
+
|
3
|
+
module Rspec
|
4
|
+
module Bash
|
5
|
+
class StubServer
|
6
|
+
def initialize(call_log_manager, call_conf_manager, stub_marshaller)
|
7
|
+
@call_log_manager = call_log_manager
|
8
|
+
@call_conf_manager = call_conf_manager
|
9
|
+
@stub_marshaller = stub_marshaller
|
10
|
+
end
|
11
|
+
|
12
|
+
def start(tcp_server)
|
13
|
+
Thread.new do
|
14
|
+
accept_loop(tcp_server)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def accept_loop(tcp_server, loop_forever = true)
|
19
|
+
loop do
|
20
|
+
tcp_socket = tcp_server.accept
|
21
|
+
message = process(tcp_socket.read)
|
22
|
+
tcp_socket.write(message)
|
23
|
+
tcp_socket.close
|
24
|
+
break unless loop_forever
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def process(client_message)
|
29
|
+
client_call = @stub_marshaller.unmarshal(client_message)
|
30
|
+
server_conf = process_stub_call(client_call)
|
31
|
+
@stub_marshaller.marshal(server_conf)
|
32
|
+
end
|
33
|
+
|
34
|
+
def process_stub_call(stub_call)
|
35
|
+
@call_log_manager.add_log(
|
36
|
+
stub_call[:command],
|
37
|
+
stub_call[:stdin],
|
38
|
+
stub_call[:args]
|
39
|
+
)
|
40
|
+
@call_conf_manager.get_best_call_conf(
|
41
|
+
stub_call[:command],
|
42
|
+
stub_call[:args]
|
43
|
+
)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -1,97 +1,118 @@
|
|
1
1
|
require 'tmpdir'
|
2
2
|
require 'English'
|
3
3
|
require 'open3'
|
4
|
+
require 'tempfile'
|
4
5
|
|
5
6
|
module Rspec
|
6
|
-
# Define stubbed environment to set and assert expectations
|
7
7
|
module Bash
|
8
|
-
def create_stubbed_env
|
9
|
-
StubbedEnv.
|
8
|
+
def create_stubbed_env(stub_type =
|
9
|
+
ENV.fetch('RSPEC_BASH_STUB_TYPE', StubbedEnv::BASH_STUB).to_sym)
|
10
|
+
|
11
|
+
StubbedEnv.new(stub_type)
|
10
12
|
end
|
11
13
|
|
12
|
-
# A shell environment that can manipulate behaviour
|
13
|
-
# of executables
|
14
14
|
class StubbedEnv
|
15
|
-
|
15
|
+
RUBY_STUB = :ruby_stub
|
16
|
+
BASH_STUB = :bash_stub
|
16
17
|
|
17
|
-
def initialize
|
18
|
-
|
19
|
-
at_exit { cleanup }
|
18
|
+
def initialize(stub_type)
|
19
|
+
start_stub_server(stub_type)
|
20
20
|
end
|
21
21
|
|
22
|
-
def
|
23
|
-
|
22
|
+
def start_stub_server(stub_type)
|
23
|
+
tcp_server = create_tcp_server
|
24
|
+
stub_server = create_stub_server(stub_type)
|
25
|
+
stub_server.start(tcp_server)
|
24
26
|
end
|
25
27
|
|
26
28
|
def stub_command(command)
|
27
|
-
|
28
|
-
|
29
|
+
check_if_command_is_allowed(command)
|
30
|
+
add_override_for_command(command)
|
31
|
+
create_stubbed_command(command)
|
29
32
|
end
|
30
33
|
|
31
34
|
def execute(command, env_vars = {})
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
multiline_script
|
36
|
-
)
|
37
|
-
|
38
|
-
Open3.capture3(env_vars, full_command)
|
35
|
+
script_runner = "source #{command}"
|
36
|
+
script_wrapper = wrap_script(script_runner)
|
37
|
+
execute_script(env_vars, script_wrapper)
|
39
38
|
end
|
40
39
|
|
41
40
|
def execute_function(script, command, env_vars = {})
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
#{command}
|
46
|
-
multiline_script
|
47
|
-
)
|
48
|
-
|
49
|
-
Open3.capture3(env_vars, full_command)
|
41
|
+
script_runner = "source #{script}\n#{command}"
|
42
|
+
script_wrapper = wrap_script(script_runner)
|
43
|
+
execute_script(env_vars, script_wrapper)
|
50
44
|
end
|
51
45
|
|
52
46
|
def execute_inline(command_string, env_vars = {})
|
53
|
-
|
54
|
-
|
55
|
-
|
47
|
+
temp_command_file = Tempfile.new('inline-')
|
48
|
+
temp_command_path = temp_command_file.path
|
49
|
+
write_file(temp_command_path, command_string)
|
50
|
+
stdout, stderr, status = execute(temp_command_path, env_vars)
|
51
|
+
delete_file(temp_command_path)
|
52
|
+
[stdout, stderr, status]
|
56
53
|
end
|
57
54
|
|
58
55
|
private
|
59
56
|
|
60
|
-
|
61
|
-
|
62
|
-
|
57
|
+
STUB_MARSHALLER_MAPPINGS = {
|
58
|
+
RUBY_STUB => RubyStubMarshaller,
|
59
|
+
BASH_STUB => BashStubMarshaller
|
60
|
+
}.freeze
|
61
|
+
STUB_SCRIPT_MAPPINGS = {
|
62
|
+
RUBY_STUB => RubyStubScript,
|
63
|
+
BASH_STUB => BashStubScript
|
64
|
+
}.freeze
|
65
|
+
DISALLOWED_COMMANDS = %w(/usr/bin/env bash readonly function).freeze
|
66
|
+
|
67
|
+
def create_tcp_server
|
68
|
+
tcp_server = TCPServer.new('localhost', 0)
|
69
|
+
@stub_server_port = tcp_server.addr[1]
|
70
|
+
tcp_server
|
71
|
+
end
|
63
72
|
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
)
|
68
|
-
|
73
|
+
def create_stub_server(stub_type)
|
74
|
+
stub_marshaller = STUB_MARSHALLER_MAPPINGS[stub_type].new
|
75
|
+
stub_script_class = STUB_SCRIPT_MAPPINGS[stub_type]
|
76
|
+
@stub_wrapper = BashWrapper.new(@stub_server_port)
|
77
|
+
@stub_function = StubFunction.new(@stub_server_port, stub_script_class)
|
78
|
+
@call_log_manager = CallLogManager.new
|
79
|
+
@call_conf_manager = CallConfigurationManager.new
|
80
|
+
StubServer.new(@call_log_manager, @call_conf_manager, stub_marshaller)
|
81
|
+
end
|
69
82
|
|
70
|
-
|
83
|
+
def create_stubbed_command(command)
|
84
|
+
StubbedCommand.new(
|
85
|
+
command,
|
86
|
+
@call_log_manager,
|
87
|
+
@call_conf_manager
|
88
|
+
)
|
71
89
|
end
|
72
90
|
|
73
|
-
def
|
74
|
-
|
75
|
-
|
76
|
-
wrapped_error_path_binding_for_template = "#{@dir}/errors"
|
91
|
+
def execute_script(env_vars, script)
|
92
|
+
Open3.capture3(env_vars, script)
|
93
|
+
end
|
77
94
|
|
78
|
-
|
79
|
-
|
80
|
-
|
95
|
+
def wrap_script(script)
|
96
|
+
@stub_wrapper.wrap_script(script)
|
97
|
+
end
|
81
98
|
|
82
|
-
|
99
|
+
def delete_file(file_path)
|
100
|
+
File.delete(file_path)
|
83
101
|
end
|
84
102
|
|
85
|
-
def
|
86
|
-
|
103
|
+
def write_file(file_path, contents)
|
104
|
+
File.write(file_path, contents)
|
87
105
|
end
|
88
106
|
|
89
|
-
def
|
90
|
-
|
107
|
+
def check_if_command_is_allowed(command)
|
108
|
+
error_message = "Not able to stub command #{command}. Reserved for use by test wrapper."
|
109
|
+
|
110
|
+
raise(error_message) if DISALLOWED_COMMANDS.include? command
|
91
111
|
end
|
92
112
|
|
93
|
-
def
|
94
|
-
|
113
|
+
def add_override_for_command(command)
|
114
|
+
function_override = @stub_function.script(command).chomp
|
115
|
+
@stub_wrapper.add_override(function_override)
|
95
116
|
end
|
96
117
|
end
|
97
118
|
end
|
@@ -13,19 +13,19 @@ module Rspec
|
|
13
13
|
@expected_call_conf_list = call_conf_list
|
14
14
|
end
|
15
15
|
|
16
|
-
def args_match?(
|
17
|
-
!get_call_conf_matches(
|
16
|
+
def args_match?(call_arguments)
|
17
|
+
!get_call_conf_matches(call_arguments).empty?
|
18
18
|
end
|
19
19
|
|
20
|
-
def get_best_call_conf(
|
21
|
-
get_call_conf_matches(
|
20
|
+
def get_best_call_conf(call_arguments)
|
21
|
+
get_call_conf_matches(call_arguments).sort_by do |call_conf|
|
22
22
|
[
|
23
23
|
call_conf[:args].length
|
24
24
|
]
|
25
25
|
end.last || {}
|
26
26
|
end
|
27
27
|
|
28
|
-
def get_call_conf_matches(
|
28
|
+
def get_call_conf_matches(call_arguments)
|
29
29
|
@expected_call_conf_list.select do |expected_call_conf|
|
30
30
|
@expected_args = remap_argument_matchers(expected_call_conf[:args])
|
31
31
|
parent_args_match?(*call_arguments)
|
@@ -9,7 +9,7 @@ module Rspec
|
|
9
9
|
alias parent_args_match? args_match?
|
10
10
|
alias parent_initialize initialize
|
11
11
|
|
12
|
-
def initialize(
|
12
|
+
def initialize(expected_args = [any_args])
|
13
13
|
expected_args = expected_args.empty? ? [any_args] : expected_args
|
14
14
|
parent_initialize(*expected_args)
|
15
15
|
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Rspec
|
2
|
+
module Bash
|
3
|
+
class BashStubScript
|
4
|
+
def self.path
|
5
|
+
File.join(project_root, 'bin', 'bash_stub.sh')
|
6
|
+
end
|
7
|
+
|
8
|
+
def self.project_root
|
9
|
+
File.expand_path(
|
10
|
+
File.join(File.dirname(File.expand_path(__FILE__)), '..', '..', '..', '..')
|
11
|
+
)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module Rspec
|
2
|
+
module Bash
|
3
|
+
class BashWrapper
|
4
|
+
def initialize(port)
|
5
|
+
@port = port
|
6
|
+
@override_list = []
|
7
|
+
at_exit { cleanup }
|
8
|
+
end
|
9
|
+
|
10
|
+
def wrap_script(script)
|
11
|
+
wrapper_template = ERB.new(File.new(wrapper_input_path).read, nil, '%')
|
12
|
+
File.open(wrapper_output_path, 'w') do |file|
|
13
|
+
file.write(wrapper_template.result(binding))
|
14
|
+
end
|
15
|
+
File.chmod(0755, wrapper_output_path)
|
16
|
+
|
17
|
+
wrapper_output_path
|
18
|
+
end
|
19
|
+
|
20
|
+
def wrapper_input_path
|
21
|
+
File.join(project_root, 'bin', 'bash_wrapper.sh.erb')
|
22
|
+
end
|
23
|
+
|
24
|
+
def wrapper_output_path
|
25
|
+
File.join(Dir.tmpdir, "wrapper-#{@port}.sh")
|
26
|
+
end
|
27
|
+
|
28
|
+
def stderr_output_path
|
29
|
+
File.join(Dir.tmpdir, "stderr-#{@port}.tmp")
|
30
|
+
end
|
31
|
+
|
32
|
+
def cleanup
|
33
|
+
remove_file_system_path wrapper_output_path
|
34
|
+
remove_file_system_path stderr_output_path
|
35
|
+
end
|
36
|
+
|
37
|
+
def remove_file_system_path(path)
|
38
|
+
FileUtils.remove_entry_secure(path) if Pathname.new(path).exist?
|
39
|
+
end
|
40
|
+
|
41
|
+
def add_override(override)
|
42
|
+
@override_list << override
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
def project_root
|
48
|
+
File.expand_path(
|
49
|
+
File.join(File.dirname(File.expand_path(__FILE__)), '..', '..', '..', '..')
|
50
|
+
)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Rspec
|
2
|
+
module Bash
|
3
|
+
class RubyStubScript
|
4
|
+
def self.path
|
5
|
+
File.join(project_root, 'bin', 'ruby_stub.rb')
|
6
|
+
end
|
7
|
+
|
8
|
+
def self.project_root
|
9
|
+
File.expand_path(
|
10
|
+
File.join(File.dirname(File.expand_path(__FILE__)), '..', '..', '..', '..')
|
11
|
+
)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|