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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: b8768c8e7a3e2d1a30a82c8a7a4608786d38a619
4
- data.tar.gz: aa8e860680f978205a989b10f7f141d89f4c9bb3
3
+ metadata.gz: 840b1879e77a1e03bd1d2b0ee87e1ccc47126294
4
+ data.tar.gz: 73cc3d08c7cb46f4f85198f477aeb2417d7774ed
5
5
  SHA512:
6
- metadata.gz: 2ad5a2600f1aa8e2e30ff92834aedcbd070d01067f2f9e51683de3624c957ee6bbbd86214dd30bcd4d6816c86714156b2abafeee83791de33757acdaaccf2e4e
7
- data.tar.gz: 4bd0fa4d49b57f6618c5ba970359d3f6a2472dc21b5d6d0c6c1f30d709f7f0e9e2eff1f50785cb10cfccece5458428c99d4da59340103377d57c9959e5d8adc8
6
+ metadata.gz: 853e2d1bcf90edcc2ce938269fd9360f225fc46f1a386f929955ad8544ea5de3b7e5bbd0da22b58a4bd0c402f8243901d49fad68e3d560569fd25c7051e00025
7
+ data.tar.gz: b1a371ab0fa15617d931ad5579d108189d7d0efe20087902dff1beb0ff3bdac136b21b43acba14dae73c7483e13088fbbb09d4926870e38546bb8b2b0633131a
data/.rubocop.yml CHANGED
@@ -7,13 +7,21 @@ Style/NumericLiteralPrefix:
7
7
  Style/Documentation:
8
8
  Enabled: false
9
9
 
10
+ Security/MarshalLoad:
11
+ Enabled: false
12
+
10
13
  Lint/HandleExceptions:
11
14
  Exclude:
12
15
  - 'spec/classes/stub_spec.rb'
16
+ - 'spec/classes/wrapper/ruby_stub_script_spec.rb'
13
17
 
14
18
  Metrics/BlockLength:
15
19
  Max: 428
16
20
 
21
+ Metrics/MethodLength:
22
+ Exclude:
23
+ - 'lib/rspec/bash/stubbed_command.rb'
24
+
17
25
  Security/YAMLLoad:
18
26
  Exclude:
19
27
  - 'bin/stub.rb.erb'
data/Gemfile CHANGED
@@ -5,3 +5,4 @@ gem 'rake'
5
5
  gem 'rspec', '3.5.0'
6
6
  gem 'rubocop', '0.47.1'
7
7
  gem 'simplecov', require: false, group: :test
8
+ gem 'sparsify', '~> 1.1'
data/README.md CHANGED
@@ -68,15 +68,37 @@ see specs in *spec/integration* folder:
68
68
  end
69
69
  ```
70
70
 
71
+ ### Choosing a stub type
72
+ The stubbed functions/commands for the test runner were traditionally built with ruby. A faster alternative stub, written in bash, has recently been introduced, and is the current default. This can be configured, however, as shown.
73
+ For ruby:
74
+ ```ruby
75
+ let(:stubbed_env) { create_stubbed_env(StubbedEnv::RUBY_STUB) }
76
+ ```
77
+ For bash (default, if not provided):
78
+ ```ruby
79
+ let(:stubbed_env) { create_stubbed_env(StubbedEnv::BASH_STUB) }
80
+ ```
81
+
82
+ Via environment variable:
83
+ ```bash
84
+ export RSPEC_BASH_STUB_TYPE=ruby_stub
85
+ # <run your tests here>
86
+ ...
87
+ ```
88
+
71
89
  ### Stubbing commands:
72
90
 
73
91
  ```ruby
74
92
  let(:stubbed_env) { create_stubbed_env }
75
93
  let!(:bundle) { stubbed_env.stub_command('bundle') }
94
+ let!(:absolute_command) { stubbed_env.stub_command('/path/to/bundle') }
95
+ let!(:relative_command) { stubbed_env.stub_command('./path/to/bundle') }
76
96
 
77
97
  it 'is stubbed' do
78
98
  stubbed_env.execute 'my-script.sh'
79
99
  expect(bundle).to be_called_with_arguments('install')
100
+ expect(absolute_command).to be_called_with_arguments('hello')
101
+ expect(relative_command).to be_called_with_arguments('world')
80
102
  end
81
103
  ```
82
104
 
@@ -220,6 +242,7 @@ Please see https://www.gnu.org/software/bash/manual/bashref.html#Positional-Para
220
242
 
221
243
  - The `execute_function()` method is recommended to be used only when testing Bash libraries. This is because it needs to source the entire file to run the function under test, so any executable code in the script will be run even if it is outside of the function being tested
222
244
 
245
+ - The current form of stub injection does not allow for stubs to be picked up by other commands. Ex. `xargs stubbed_command` will result in the `stubbed_command` not being found. There is a pending issue for this.
223
246
  ## More examples
224
247
 
225
248
  see the *spec/integration* folder
data/Rakefile CHANGED
@@ -1,13 +1,24 @@
1
1
  require 'bundler/gem_tasks'
2
2
  require 'rspec/core/rake_task'
3
3
 
4
- RSpec::Core::RakeTask.new(:spec) do |t|
5
- t.verbose = false
6
- t.rspec_opts = '--format documentation'
7
- t.rspec_opts << ' --color'
4
+ def spec(task)
5
+ task.verbose = false
6
+ task.rspec_opts = '--format documentation'
7
+ task.rspec_opts << ' --color'
8
+ end
9
+
10
+ RSpec::Core::RakeTask.new(:spec_bash_stub) do |t|
11
+ ENV['RSPEC_BASH_STUB_TYPE'] = :bash_stub.to_s
12
+ spec(t)
13
+ end
14
+
15
+ RSpec::Core::RakeTask.new(:spec_ruby_stub) do |t|
16
+ ENV['RSPEC_BASH_STUB_TYPE'] = :ruby_stub.to_s
17
+ spec(t)
8
18
  end
9
19
 
10
20
  require 'rubocop/rake_task'
11
21
  RuboCop::RakeTask.new
12
22
 
23
+ task spec: [:spec_bash_stub, :spec_ruby_stub]
13
24
  task default: [:rubocop, :spec]
data/bin/bash_stub.sh ADDED
@@ -0,0 +1,92 @@
1
+ #!/usr/bin/env bash
2
+ function json-encode {
3
+ echo -n ".${1}." |
4
+ awk '
5
+ BEGIN {RS=""}
6
+ {
7
+ gsub(/\\/, "\\\\")
8
+ gsub(/\"/, "\\\"")
9
+ gsub(/\t/, "\\t")
10
+ gsub(/\r/, "\\r")
11
+ gsub(/\b/, "\\b")
12
+ gsub(/\n/, "\\n")
13
+ print substr($0, 2, length($0)-2)
14
+ }
15
+ '
16
+ }
17
+
18
+ function json-decode {
19
+ echo -n "${1}" |
20
+ awk '
21
+ {
22
+ gsub(/\\"/, "\"")
23
+ print $0
24
+ }
25
+ '
26
+ }
27
+
28
+ function create-call-log {
29
+ command_name=${1}; shift
30
+ command_port=${1}; shift
31
+ raw_stdin=$([[ -t 0 ]] && echo -n '' || cat -; ret=$?; echo .; exit "$ret")
32
+
33
+ argument_list=( "${@}" )
34
+ command=$(
35
+ echo "\"command\":\"${command_name}\","
36
+ )
37
+ stdin=$(
38
+ echo "\"stdin\":\"$(json-encode "${raw_stdin%.}")\","
39
+ )
40
+ arguments=$(
41
+ for index in "${!argument_list[@]}"; do
42
+ arg="${argument_list[${index}]}"
43
+ echo "\"args.${index}\":\"$(json-encode "${arg}")\","
44
+ done
45
+ )
46
+
47
+ echo "{"
48
+ echo "${command}"
49
+ [[ -n "${arguments}" ]] && echo "${stdin}" || echo "${stdin%,}"
50
+ [[ -n "${arguments}" ]] && echo "${arguments%,}"
51
+ echo "}"
52
+ }
53
+
54
+ function send-to-server {
55
+ echo -n "${2}" | nc localhost ${1}
56
+ }
57
+
58
+ function extract-properties {
59
+ echo -n "${1}" | sed -En "s/^\"${2}\":\"?([^,\"]*)\"?,?$/\1/gp"
60
+ }
61
+
62
+ function print-output {
63
+ case ${1} in
64
+ stdout)
65
+ echo -en "${2}"
66
+ ;;
67
+ stderr)
68
+ echo -en "${2}" >&2
69
+ ;;
70
+ *)
71
+ echo -en "${2}" > ${1}
72
+ ;;
73
+ esac
74
+ }
75
+ function main {
76
+ client_message=$(create-call-log "${@}")
77
+ server_message=$(send-to-server "${2}" "${client_message}")
78
+ IFS=$'\n'
79
+ target_list=( $(extract-properties "${server_message}" "outputs\..*\.target") )
80
+ content_list=( $(extract-properties "${server_message}" "outputs\..*\.content") )
81
+ exit_code=$(extract-properties "${server_message}" "exitcode")
82
+
83
+ for index in "${!target_list[@]}"; do
84
+ target=${target_list[${index}]}
85
+ content=$(json-decode "${content_list[${index}]}")
86
+ print-output "${target}" "${content}"
87
+ done
88
+
89
+ exit ${exit_code}
90
+ }
91
+
92
+ [[ ${0} == ${BASH_SOURCE} ]] && main "${@}"
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env bash
2
+ <%=@override_list.join("\n")%>
3
+ (
4
+ <%=script%>
5
+ ) 2> <%=stderr_output_path%>
6
+ command_exit_code=$?
7
+
8
+ /usr/bin/env bash <<EOF
9
+ grep -v "readonly function" <%=stderr_output_path%> >&2
10
+ EOF
11
+
12
+ exit ${command_exit_code}
data/bin/ruby_stub.rb ADDED
@@ -0,0 +1,33 @@
1
+ #!/usr/bin/env ruby
2
+ require 'socket'
3
+
4
+ name = ARGV.shift
5
+ port = ARGV.shift
6
+
7
+ sock = TCPSocket.new('localhost', port)
8
+ call_from_client = {
9
+ command: name,
10
+ stdin: STDIN.tty? ? '' : $stdin.read,
11
+ args: ARGV
12
+ }
13
+ sock.write(Marshal.dump(call_from_client))
14
+ sock.close_write
15
+ conf_from_server = Marshal.load(sock.read)
16
+ sock.close_read
17
+
18
+ exit 0 if conf_from_server.empty?
19
+
20
+ (conf_from_server[:outputs] || []).each do |data|
21
+ if data[:target] == :stdout
22
+ $stdout.print data[:content]
23
+ next
24
+ end
25
+ if data[:target] == :stderr
26
+ $stderr.print data[:content]
27
+ next
28
+ end
29
+ Pathname.new(data[:target]).open('w') do |f|
30
+ f.print data[:content]
31
+ end
32
+ end
33
+ exit conf_from_server[:exitcode] || 0
data/lib/rspec/bash.rb CHANGED
@@ -1,6 +1,7 @@
1
- require 'rspec/bash/stubbed_command'
1
+ require 'rspec/bash/command'
2
+ require 'rspec/bash/matchers'
3
+ require 'rspec/bash/server'
2
4
  require 'rspec/bash/util'
3
- require 'rspec/bash/call_configuration'
4
- require 'rspec/bash/call_log'
5
+ require 'rspec/bash/wrapper'
6
+
5
7
  require 'rspec/bash/stubbed_env'
6
- require 'rspec/bash/matchers'
@@ -0,0 +1,5 @@
1
+ require 'rspec/bash/command/call_configuration'
2
+ require 'rspec/bash/command/call_configuration_manager'
3
+ require 'rspec/bash/command/call_log'
4
+ require 'rspec/bash/command/call_log_manager'
5
+ require 'rspec/bash/command/stubbed_command'
@@ -0,0 +1,76 @@
1
+ require 'yaml'
2
+
3
+ module Rspec
4
+ module Bash
5
+ class CallConfiguration
6
+ attr_accessor :call_configuration
7
+
8
+ def initialize
9
+ @call_configuration = []
10
+ end
11
+
12
+ def set_exitcode(exitcode, args = [])
13
+ current_conf = create_or_get_conf(args)
14
+ current_conf[:exitcode] = exitcode
15
+ end
16
+
17
+ def add_output(content, target, args = [])
18
+ current_conf = create_or_get_conf(args)
19
+ current_conf[:outputs] << {
20
+ target: target,
21
+ content: content
22
+ }
23
+ end
24
+
25
+ def get_best_call_conf(args = [])
26
+ call_conf_arg_matcher = Util::CallConfArgumentListMatcher.new(@call_configuration)
27
+ best_call_conf = call_conf_arg_matcher.get_best_call_conf(args)
28
+ remove_args_from_conf(
29
+ interpolate_output_targets(
30
+ best_call_conf,
31
+ args
32
+ )
33
+ )
34
+ end
35
+
36
+ private
37
+
38
+ def interpolate_output_targets(conf, args)
39
+ return conf if conf.empty?
40
+ conf[:outputs].each do |output|
41
+ output[:target] = interpolate_target(output[:target], args)
42
+ end
43
+ conf
44
+ end
45
+
46
+ def interpolate_target(target, args)
47
+ return target unless target.is_a? Array
48
+ target.map do |target_element|
49
+ next target_element if target_element.is_a? String
50
+ interpolate_argument(target_element, args)
51
+ end.join
52
+ end
53
+
54
+ def interpolate_argument(name, args)
55
+ matching_arg_index = /^arg(\d+)$/.match(name.to_s)
56
+ return name.to_s unless matching_arg_index
57
+ args[matching_arg_index[1].to_i - 1]
58
+ end
59
+
60
+ def remove_args_from_conf(conf)
61
+ conf.select { |key| ![:args].include?(key) }
62
+ end
63
+
64
+ def create_or_get_conf(args)
65
+ new_conf = {
66
+ args: args,
67
+ exitcode: 0,
68
+ outputs: []
69
+ }
70
+ current_conf = @call_configuration.select { |conf| conf[:args] == args }
71
+ @call_configuration << new_conf if current_conf.empty?
72
+ current_conf.first || new_conf
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,24 @@
1
+ module Rspec
2
+ module Bash
3
+ class CallConfigurationManager
4
+ def initialize
5
+ @call_confs = Hash.new { |hash, key| hash[key] = CallConfiguration.new }
6
+ end
7
+
8
+ def set_exitcode(command, exitcode, args)
9
+ @call_confs[command]
10
+ .set_exitcode(exitcode, args)
11
+ end
12
+
13
+ def add_output(command, output, target, args)
14
+ @call_confs[command]
15
+ .add_output(output, target, args)
16
+ end
17
+
18
+ def get_best_call_conf(command, args)
19
+ @call_confs[command]
20
+ .get_best_call_conf(args)
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,48 @@
1
+ module Rspec
2
+ module Bash
3
+ class CallLog
4
+ attr_accessor :call_log
5
+
6
+ def initialize
7
+ @call_log = []
8
+ end
9
+
10
+ def stdin_for_args(argument_list)
11
+ call_argument_list_matcher = Util::CallLogArgumentListMatcher.new(argument_list)
12
+ matching_call_log_list = call_argument_list_matcher.get_call_log_matches(call_log)
13
+ matching_call_log_list.first[:stdin] unless matching_call_log_list.empty?
14
+ end
15
+
16
+ def call_count(argument_list)
17
+ call_argument_list_matcher = Util::CallLogArgumentListMatcher.new(argument_list)
18
+ call_argument_list_matcher.get_call_count(call_log)
19
+ end
20
+
21
+ def called_with_args?(argument_list)
22
+ call_argument_list_matcher = Util::CallLogArgumentListMatcher.new(argument_list)
23
+ call_argument_list_matcher.args_match?(call_log)
24
+ end
25
+
26
+ def called_with_no_args?
27
+ return false if @call_log.empty?
28
+
29
+ @call_log.all? do |call_log|
30
+ argument_list = call_log[:args] || []
31
+ argument_list.empty?
32
+ end
33
+ end
34
+
35
+ def add_log(stdin, argument_list)
36
+ updated_log = @call_log
37
+ updated_log << {
38
+ args: argument_list,
39
+ stdin: stdin
40
+ }
41
+ end
42
+
43
+ def call_log_arguments
44
+ @call_log.map { |call_log| call_log[:args] || [] }.compact
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,38 @@
1
+ module Rspec
2
+ module Bash
3
+ class CallLogManager
4
+ def initialize
5
+ @call_logs = Hash.new { |hash, key| hash[key] = CallLog.new }
6
+ end
7
+
8
+ def add_log(command, stdin, arguments)
9
+ @call_logs[command]
10
+ .add_log(stdin, arguments)
11
+ end
12
+
13
+ def stdin_for_args(command, arguments)
14
+ @call_logs[command]
15
+ .stdin_for_args(arguments)
16
+ end
17
+
18
+ def call_count(command, arguments)
19
+ @call_logs[command]
20
+ .call_count(arguments)
21
+ end
22
+
23
+ def called_with_args?(command, arguments)
24
+ @call_logs[command]
25
+ .called_with_args?(arguments)
26
+ end
27
+
28
+ def called_with_no_args?(command)
29
+ @call_logs[command]
30
+ .called_with_no_args?
31
+ end
32
+
33
+ def call_log(command)
34
+ @call_logs[command]
35
+ end
36
+ end
37
+ end
38
+ end