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.
Files changed (68) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +8 -0
  3. data/Gemfile +1 -0
  4. data/README.md +23 -0
  5. data/Rakefile +15 -4
  6. data/bin/bash_stub.sh +92 -0
  7. data/bin/bash_wrapper.sh.erb +12 -0
  8. data/bin/ruby_stub.rb +33 -0
  9. data/lib/rspec/bash.rb +5 -4
  10. data/lib/rspec/bash/command.rb +5 -0
  11. data/lib/rspec/bash/command/call_configuration.rb +76 -0
  12. data/lib/rspec/bash/command/call_configuration_manager.rb +24 -0
  13. data/lib/rspec/bash/command/call_log.rb +48 -0
  14. data/lib/rspec/bash/command/call_log_manager.rb +38 -0
  15. data/lib/rspec/bash/command/stubbed_command.rb +64 -0
  16. data/lib/rspec/bash/server.rb +3 -0
  17. data/lib/rspec/bash/server/bash_stub_marshaller.rb +19 -0
  18. data/lib/rspec/bash/server/ruby_stub_marshaller.rb +13 -0
  19. data/lib/rspec/bash/server/stub_server.rb +47 -0
  20. data/lib/rspec/bash/stubbed_env.rb +75 -54
  21. data/lib/rspec/bash/util/call_conf_argument_list_matcher.rb +5 -5
  22. data/lib/rspec/bash/util/call_log_argument_list_matcher.rb +1 -1
  23. data/lib/rspec/bash/wrapper.rb +4 -0
  24. data/lib/rspec/bash/wrapper/bash_stub_script.rb +15 -0
  25. data/lib/rspec/bash/wrapper/bash_wrapper.rb +54 -0
  26. data/lib/rspec/bash/wrapper/ruby_stub_script.rb +15 -0
  27. data/lib/rspec/bash/wrapper/stub_function.rb +36 -0
  28. data/rspec-bash.gemspec +2 -1
  29. data/spec/classes/command/call_configuration_manager_spec.rb +68 -0
  30. data/spec/classes/{call_configuration_spec.rb → command/call_configuration_spec.rb} +51 -114
  31. data/spec/classes/command/call_log_manager_spec.rb +83 -0
  32. data/spec/classes/{call_log_spec.rb → command/call_log_spec.rb} +23 -82
  33. data/spec/classes/command/stubbed_command_spec.rb +118 -0
  34. data/spec/classes/server/bash_stub_marshaller_spec.rb +38 -0
  35. data/spec/classes/server/ruby_stub_marshaller_spec.rb +31 -0
  36. data/spec/classes/server/stub_server_spec.rb +121 -0
  37. data/spec/classes/stubbed_env_spec.rb +141 -280
  38. data/spec/classes/util/call_conf_argument_list_matcher_spec.rb +17 -17
  39. data/spec/classes/util/call_log_argument_list_matcher_spec.rb +24 -18
  40. data/spec/classes/wrapper/bash_wrapper_spec.rb +37 -0
  41. data/spec/classes/wrapper/ruby_stub_script_spec.rb +204 -0
  42. data/spec/helper/string_file_io.rb +1 -1
  43. data/spec/integration/call_log/called_with_args_spec.rb +8 -4
  44. data/spec/integration/call_log/called_with_no_args_spec.rb +1 -1
  45. data/spec/integration/call_log/stdin_spec.rb +10 -4
  46. data/spec/integration/edge_cases_spec.rb +34 -0
  47. data/spec/integration/matchers/be_called_with_arguments_spec.rb +12 -13
  48. data/spec/integration/matchers/be_called_with_no_arguments_spec.rb +6 -7
  49. data/spec/integration/stubbed_command/outputs_spec.rb +111 -91
  50. data/spec/integration/stubbed_command/returns_exitstatus_spec.rb +46 -37
  51. data/spec/integration/stubbed_env/execute_with_env_vars_spec.rb +3 -4
  52. data/spec/integration/stubbed_env/execute_with_path_spec.rb +6 -7
  53. data/spec/integration/stubbed_env/execute_with_stub_wrapper_spec.rb +4 -12
  54. data/spec/integration/stubbed_env/override_spec.rb +354 -0
  55. data/spec/integration/wrapper/bash_stub_script_spec.rb +383 -0
  56. data/spec/integration/wrapper/bash_wrapper_spec.rb +48 -0
  57. data/spec/scripts/function_library.sh +9 -1
  58. data/spec/spec_helper.rb +2 -0
  59. metadata +65 -21
  60. data/bin/function_override.sh.erb +0 -7
  61. data/bin/function_override_wrapper.sh.erb +0 -19
  62. data/bin/stub.rb.erb +0 -56
  63. data/lib/rspec/bash/call_configuration.rb +0 -62
  64. data/lib/rspec/bash/call_log.rb +0 -71
  65. data/lib/rspec/bash/stubbed_command.rb +0 -88
  66. data/spec/classes/stub_spec.rb +0 -510
  67. data/spec/classes/stubbed_command_spec.rb +0 -134
  68. 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,3 @@
1
+ require 'rspec/bash/server/bash_stub_marshaller'
2
+ require 'rspec/bash/server/ruby_stub_marshaller'
3
+ require 'rspec/bash/server/stub_server'
@@ -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,13 @@
1
+ module Rspec
2
+ module Bash
3
+ class RubyStubMarshaller
4
+ def unmarshal(message_to_unmarshal)
5
+ Marshal.load(message_to_unmarshal)
6
+ end
7
+
8
+ def marshal(object_to_marshal)
9
+ Marshal.dump(object_to_marshal)
10
+ end
11
+ end
12
+ end
13
+ 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.new
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
- attr_reader :dir
15
+ RUBY_STUB = :ruby_stub
16
+ BASH_STUB = :bash_stub
16
17
 
17
- def initialize
18
- @dir = Dir.mktmpdir
19
- at_exit { cleanup }
18
+ def initialize(stub_type)
19
+ start_stub_server(stub_type)
20
20
  end
21
21
 
22
- def cleanup
23
- FileUtils.remove_entry_secure @dir if Pathname.new(@dir).exist?
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
- write_function_override_file_for_command command
28
- StubbedCommand.new command, @dir
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
- full_command = get_wrapped_execution_with_function_overrides(
33
- <<-multiline_script
34
- source #{command}
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
- full_command = get_wrapped_execution_with_function_overrides(
43
- <<-multiline_script
44
- source #{script}
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
- temp_command_path = Dir::Tmpname.make_tmpname("#{@dir}/inline-", nil)
54
- File.write(temp_command_path, command_string)
55
- execute(temp_command_path, env_vars)
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
- def write_function_override_file_for_command(command)
61
- function_command_binding_for_template = command
62
- function_command_path_binding_for_template = File.join(@dir, command)
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
- function_override_file_path = File.join(@dir, "#{command}_overrides.sh")
65
- function_override_file_template = ERB.new(
66
- File.new(function_override_template_path).read, nil, '%'
67
- )
68
- function_override_file_content = function_override_file_template.result(binding)
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
- File.write(function_override_file_path, function_override_file_content)
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 get_wrapped_execution_with_function_overrides(execution_snippet)
74
- execution_binding_for_template = execution_snippet
75
- function_override_path_binding_for_template = "#{@dir}/*_overrides.sh"
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
- function_override_wrapper_template = ERB.new(
79
- File.new(function_override_wrapper_template_path).read, nil, '%'
80
- )
95
+ def wrap_script(script)
96
+ @stub_wrapper.wrap_script(script)
97
+ end
81
98
 
82
- function_override_wrapper_template.result(binding)
99
+ def delete_file(file_path)
100
+ File.delete(file_path)
83
101
  end
84
102
 
85
- def function_override_template_path
86
- project_root.join('bin', 'function_override.sh.erb')
103
+ def write_file(file_path, contents)
104
+ File.write(file_path, contents)
87
105
  end
88
106
 
89
- def function_override_wrapper_template_path
90
- project_root.join('bin', 'function_override_wrapper.sh.erb')
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 project_root
94
- Pathname.new(File.dirname(File.expand_path(__FILE__))).join('..', '..', '..')
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?(*call_arguments)
17
- !get_call_conf_matches(*call_arguments).empty?
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(*call_arguments)
21
- get_call_conf_matches(*call_arguments).sort_by do |call_conf|
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(*call_arguments)
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(*expected_args)
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,4 @@
1
+ require 'rspec/bash/wrapper/bash_stub_script'
2
+ require 'rspec/bash/wrapper/ruby_stub_script'
3
+ require 'rspec/bash/wrapper/bash_wrapper'
4
+ require 'rspec/bash/wrapper/stub_function'
@@ -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