methadone-rehab 1.9.2

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.
Files changed (75) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +15 -0
  3. data/.ruby-gemset +1 -0
  4. data/.ruby-version +1 -0
  5. data/.travis.yml +11 -0
  6. data/CHANGES.md +66 -0
  7. data/Gemfile +6 -0
  8. data/LICENSE.txt +201 -0
  9. data/README.rdoc +179 -0
  10. data/Rakefile +98 -0
  11. data/TODO.md +3 -0
  12. data/bin/methadone +157 -0
  13. data/features/bootstrap.feature +169 -0
  14. data/features/license.feature +43 -0
  15. data/features/multilevel_commands.feature +125 -0
  16. data/features/readme.feature +26 -0
  17. data/features/rspec_support.feature +27 -0
  18. data/features/step_definitions/bootstrap_steps.rb +47 -0
  19. data/features/step_definitions/license_steps.rb +30 -0
  20. data/features/step_definitions/readme_steps.rb +26 -0
  21. data/features/step_definitions/version_steps.rb +4 -0
  22. data/features/support/env.rb +26 -0
  23. data/features/version.feature +17 -0
  24. data/lib/methadone.rb +15 -0
  25. data/lib/methadone/argv_parser.rb +50 -0
  26. data/lib/methadone/cli.rb +124 -0
  27. data/lib/methadone/cli_logger.rb +133 -0
  28. data/lib/methadone/cli_logging.rb +138 -0
  29. data/lib/methadone/cucumber.rb +174 -0
  30. data/lib/methadone/error.rb +32 -0
  31. data/lib/methadone/execution_strategy/base.rb +34 -0
  32. data/lib/methadone/execution_strategy/jvm.rb +37 -0
  33. data/lib/methadone/execution_strategy/mri.rb +16 -0
  34. data/lib/methadone/execution_strategy/open_3.rb +16 -0
  35. data/lib/methadone/execution_strategy/open_4.rb +22 -0
  36. data/lib/methadone/execution_strategy/rbx_open_4.rb +12 -0
  37. data/lib/methadone/exit_now.rb +40 -0
  38. data/lib/methadone/main.rb +1039 -0
  39. data/lib/methadone/process_status.rb +45 -0
  40. data/lib/methadone/sh.rb +223 -0
  41. data/lib/methadone/version.rb +3 -0
  42. data/methadone-rehab.gemspec +32 -0
  43. data/templates/full/.gitignore.erb +4 -0
  44. data/templates/full/README.rdoc.erb +25 -0
  45. data/templates/full/Rakefile.erb +74 -0
  46. data/templates/full/_license_head.txt.erb +2 -0
  47. data/templates/full/apache_LICENSE.txt.erb +203 -0
  48. data/templates/full/bin/executable.erb +47 -0
  49. data/templates/full/custom_LICENSE.txt.erb +0 -0
  50. data/templates/full/features/executable.feature.erb +13 -0
  51. data/templates/full/features/step_definitions/executable_steps.rb.erb +1 -0
  52. data/templates/full/features/support/env.rb.erb +16 -0
  53. data/templates/full/gplv2_LICENSE.txt.erb +14 -0
  54. data/templates/full/gplv3_LICENSE.txt.erb +15 -0
  55. data/templates/full/mit_LICENSE.txt.erb +7 -0
  56. data/templates/multicommand/bin/executable.erb +52 -0
  57. data/templates/multicommand/lib/command.rb.erb +40 -0
  58. data/templates/multicommand/lib/commands.rb.erb +7 -0
  59. data/templates/rspec/spec/something_spec.rb.erb +5 -0
  60. data/templates/test_unit/test/tc_something.rb.erb +7 -0
  61. data/test/base_test.rb +20 -0
  62. data/test/command_for_tests.sh +7 -0
  63. data/test/execution_strategy/test_base.rb +24 -0
  64. data/test/execution_strategy/test_jvm.rb +77 -0
  65. data/test/execution_strategy/test_mri.rb +32 -0
  66. data/test/execution_strategy/test_open_3.rb +70 -0
  67. data/test/execution_strategy/test_open_4.rb +86 -0
  68. data/test/execution_strategy/test_rbx_open_4.rb +25 -0
  69. data/test/test_cli_logger.rb +219 -0
  70. data/test/test_cli_logging.rb +243 -0
  71. data/test/test_exit_now.rb +37 -0
  72. data/test/test_main.rb +1213 -0
  73. data/test/test_multi.rb +405 -0
  74. data/test/test_sh.rb +404 -0
  75. metadata +321 -0
@@ -0,0 +1,405 @@
1
+ require 'base_test'
2
+ require 'methadone'
3
+ require 'stringio'
4
+ require 'fileutils'
5
+
6
+ class TestMulti < BaseTest
7
+ include Methadone::Main
8
+ include Methadone::CLILogging
9
+
10
+ module Commands
11
+ class Walk
12
+ include Methadone::Main
13
+ include Methadone::CLILogging
14
+ main do |distance|
15
+ puts "walk called"
16
+ end
17
+ options[:direction] = 0
18
+ description "moves slowly for a given distance"
19
+ on '-s', '--silly-walk'
20
+ on '-d', '--direction DIRECTION', Integer, "Compass cardinal direction"
21
+ arg "distance", "How far to walk"
22
+
23
+ end
24
+ class Run
25
+ include Methadone::Main
26
+ include Methadone::CLILogging
27
+ main do |distance, duty_cycle|
28
+ puts "run called"
29
+ end
30
+ options[:direction] = 0
31
+ description "moves quickly for a given distance"
32
+ on '-s', '--silly-walk'
33
+ on '-d', '--direction DIRECTION', Integer, "Compass cardinal direction"
34
+ arg "distance", "How far to run"
35
+ arg "duty_cycle", "Percent of time spent running (default: 100)", :optional
36
+ end
37
+ class Greet
38
+ include Methadone::Main
39
+
40
+ options[:lang] = 'es'
41
+
42
+ main do
43
+ msg = case options[:lang]
44
+ when 'en'
45
+ 'Hello'
46
+ when 'fr'
47
+ 'Bonjour'
48
+ when 'es'
49
+ 'Hola'
50
+ else
51
+ '????'
52
+ end
53
+
54
+ msg = msg.upcase if options[:yell]
55
+ puts msg
56
+ end
57
+ end
58
+
59
+ class Say
60
+ include Methadone::Main
61
+ on '--yell', "Be loud", :global
62
+ command 'greeting' => ::TestMulti::Commands::Greet
63
+ end
64
+ end
65
+
66
+ def setup
67
+ @original_argv = ARGV.clone
68
+ ARGV.clear
69
+ @old_stdout = $stdout
70
+ $stdout = StringIO.new
71
+ @logged = StringIO.new
72
+ @orig_logger = logger
73
+ @custom_logger = Logger.new(@logged)
74
+ change_logger @custom_logger
75
+
76
+ @original_home = ENV['HOME']
77
+ fake_home = '/tmp/fake-home'
78
+ FileUtils.rm_rf(fake_home)
79
+ FileUtils.mkdir(fake_home)
80
+ ENV['HOME'] = fake_home
81
+ end
82
+
83
+ def teardown
84
+ @commands = nil
85
+ change_logger @orig_logger
86
+ set_argv @original_argv
87
+ ENV.delete('DEBUG')
88
+ ENV.delete('APP_OPTS')
89
+ $stdout = @old_stdout
90
+ ENV['HOME'] = @original_home
91
+ end
92
+
93
+ test_that "commands can be specified" do
94
+ When {
95
+ command "walk" => Commands::Walk
96
+ }
97
+ Then number_of_commands_should_be(1)
98
+ Then commands_should_include("walk")
99
+ Then {
100
+ provider_for_command("walk").should be Commands::Walk
101
+ }
102
+ end
103
+
104
+ test_that "command providers must accept go! message" do
105
+ Given {
106
+ module Commands
107
+ class WontWork
108
+ end
109
+ end
110
+ }
111
+ When {
112
+ @error = nil
113
+ begin
114
+ command "trythis" => Commands::WontWork
115
+ rescue Exception => error
116
+ @error = error
117
+ end
118
+ }
119
+ Then number_of_commands_should_be(0)
120
+ Then {
121
+ @error.should be_a_kind_of(::Methadone::InvalidProvider)
122
+ }
123
+ end
124
+
125
+ test_that "command is detected in the arguments" do
126
+ Given {
127
+ main do
128
+ end
129
+
130
+ command "walk" => Commands::Walk
131
+ set_argv %w(walk 10)
132
+ }
133
+ When run_go_safely
134
+ Then {
135
+ opts.selected_command.should eq('walk')
136
+ }
137
+ end
138
+
139
+ test_that "command in the arguments causes the right command to be called" do
140
+ Given app_has_subcommands('walk','run')
141
+ And {
142
+ version '1.2.3'
143
+ set_argv %w(walk 10)
144
+ }
145
+ When run_go_safely
146
+ Then {
147
+ opts.command_names.should include('walk')
148
+ opts.command_names.should include('run')
149
+ $stdout.string.should match(/walk called/)
150
+ }
151
+ And number_of_commands_should_be(2)
152
+ end
153
+
154
+ test_that "help is displayed if no command on command line" do
155
+ Given app_has_subcommands('walk','run')
156
+ And {
157
+ @main_called = false
158
+ main do
159
+ @main_called = true
160
+ puts 'main called'
161
+ end
162
+ }
163
+ When run_go_safely
164
+ Then main_should_not_be_called
165
+ And help_shown
166
+ And {
167
+ assert_logged_at_error("You must specify a command")
168
+ }
169
+ end
170
+
171
+ test_that "app with subcommands list subcommands in help" do
172
+ Given app_has_subcommands('walk','run')
173
+ When {
174
+ setup_defaults
175
+ opts.post_setup
176
+ }
177
+ Then {
178
+ opts.to_s.should match /(?m)Commands:\n.*walk: moves slowly/
179
+ opts.to_s.should match /(?m)Commands:\n.*run: moves quickly/
180
+ }
181
+ And {
182
+ opts.to_s.should match /Usage:.*command \[command options and args...\]/
183
+ }
184
+ end
185
+
186
+ test_that "app without subcommands do not list command prefix in help" do
187
+ Given {
188
+ main do
189
+ end
190
+ on '--switch'
191
+ on '--flag FOO'
192
+ arg 'must_have'
193
+ arg 'optionals', :any
194
+ }
195
+ When {
196
+ setup_defaults
197
+ opts.post_setup
198
+ }
199
+ Then {
200
+ opts.to_s.should_not match /Commands:/m
201
+ }
202
+ end
203
+
204
+ test_that "subcommand can get its own help" do
205
+ Given app_has_subcommands('walk','run')
206
+ And {
207
+ version '1.2.3'
208
+ set_argv %w(walk -h)
209
+ }
210
+ When run_go_safely
211
+ Then {
212
+ $stdout.string.should match /Usage: #{::File.basename($0)} walk \[options\] distance/
213
+ }
214
+ end
215
+
216
+ someday_test_that "rc_file can specify defaults for each subcommand" do
217
+ end
218
+
219
+ test_that "subcommand help shows global options from parent" do
220
+ Given app_has_subcommands('walk','run')
221
+ And {
222
+ version '1.2.3'
223
+ set_argv %w(walk -h)
224
+ on '-w','--wow', :global, "This is a global option"
225
+ }
226
+ When run_go_safely
227
+ Then {
228
+ $stdout.string.should match /Usage: #{::File.basename($0)} \[global options\] walk \[options\] distance/
229
+ $stdout.string.should match /(?m)Global options:\n.*-w, --wow *This is a global option/
230
+ $stdout.string.should_not match /(?m)Global options:\n.*-v, --version/
231
+ }
232
+ end
233
+
234
+
235
+ test_that "subcommands have access to global options" do
236
+ Given app_has_subcommands('greet')
237
+ And {
238
+ options[:lang] = 'en'
239
+ on '-l', '--lang LANG','Set the language', :global
240
+ set_argv %w(-l fr greet)
241
+ }
242
+ When run_go_safely
243
+ Then {
244
+ $stdout.string.should match /Bonjour/
245
+ $stdout.string.should_not match /Hello/
246
+ $stdout.string.should_not match /Hola/
247
+ $stdout.string.should_not match /\?\?\?\?/
248
+ }
249
+ end
250
+
251
+ test_that "subcommands of subcommands help shows parents global options" do
252
+ Given app_is_three_layers_deep_with_middle_layer_having_global_options
253
+ And {
254
+ set_argv %w(say --yell greeting)
255
+ }
256
+ When run_go_safely
257
+ Then {
258
+ cmd_opts = opts.commands['say'].opts.commands['greeting'].opts
259
+ cmd_opts.to_s.should match /say \[options [f]or say\] greeting/
260
+ cmd_opts.to_s.should match /(?m)Options [f]or say:\n.*--yell *Be loud/
261
+ cmd_opts.to_s.should_not match /\[global options\]/
262
+ cmd_opts.to_s.should_not match /Global options:/
263
+ $stdout.string.should match /HOLA/
264
+ }
265
+ end
266
+
267
+ test_that "subcommands of subcommands help shows parents global options and base global options" do
268
+ Given app_is_three_layers_deep_with_middle_layer_having_global_options
269
+ And {
270
+ on '-l', '--lang LANG','Set the language', :global
271
+ set_argv %w(-l en say --yell greeting)
272
+ }
273
+ When run_go_safely
274
+ Then {
275
+ cmd_opts = opts.commands['say'].opts.commands['greeting'].opts
276
+ cmd_opts.to_s.should match /\[global options\] say \[options [f]or say\] greeting/
277
+ cmd_opts.to_s.should match /(?m)Options [f]or say:\n.*--yell *Be loud/
278
+ cmd_opts.to_s.should match /(?m)Global options:\n.*-l, --lang LANG *Set the language/
279
+ $stdout.string.should match /HELLO/
280
+ }
281
+ end
282
+
283
+ private
284
+
285
+ def commands_should_include(cmd)
286
+ proc { opts.commands.keys.should include(cmd) }
287
+ end
288
+
289
+ def number_of_commands_should_be(num)
290
+ proc { opts.commands.keys.length.should be(num)}
291
+ end
292
+
293
+ def provider_for_command(cmd)
294
+ opts.commands[cmd]
295
+ end
296
+
297
+ def app_has_subcommands(*args)
298
+ proc {
299
+ args.each do |cmd|
300
+ command cmd => get_const("TestMulti::Commands::#{cmd.capitalize}")
301
+ end
302
+ }
303
+ end
304
+
305
+ def app_is_three_layers_deep_with_middle_layer_having_global_options
306
+ proc {
307
+ # Requires special resetting to ensure proper behaviour
308
+ reset!
309
+ command 'say' => get_const("TestMulti::Commands::Say")
310
+ opts.commands['say'].instance_variable_get(:@options).delete_if {|k,v| true}
311
+ opts.commands['say'].opts.commands['greeting'].instance_variable_get(:@options).delete_if {|k,v| true}
312
+ opts.commands['say'].opts.commands['greeting'].instance_variable_get(:@options)[:lang] = 'es'
313
+ }
314
+ end
315
+
316
+
317
+ def help_shown
318
+ proc {assert $stdout.string.include?(opts.to_s),"Expected #{$stdout.string} to contain #{opts.to_s}"}
319
+ end
320
+
321
+ def app_to_use_rc_file
322
+ lambda {
323
+ @switch = nil
324
+ @flag = nil
325
+ @args = nil
326
+ main do |*args|
327
+ @switch = options[:switch]
328
+ @flag = options[:flag]
329
+ @args = args
330
+ end
331
+
332
+ defaults_from_config_file '.my_app.rc'
333
+
334
+ on('--switch','Some Switch')
335
+ on('--flag FOO','Some Flag')
336
+ }
337
+ end
338
+
339
+ def main_that_exits(exit_status)
340
+ proc { main { exit_status } }
341
+ end
342
+
343
+ def app_to_use_environment
344
+ lambda {
345
+ @switch = nil
346
+ @flag = nil
347
+ @args = nil
348
+ main do |*args|
349
+ @switch = options[:switch]
350
+ @flag = options[:flag]
351
+ @args = args
352
+ end
353
+
354
+ defaults_from_env_var 'APP_OPTS'
355
+
356
+ on('--switch','Some Switch')
357
+ on('--flag FOO','Some Flag')
358
+ }
359
+ end
360
+
361
+ def main_should_not_be_called
362
+ Proc.new { assert !@main_called,"Main block was called?!" }
363
+ end
364
+
365
+ def main_shouldve_been_called
366
+ Proc.new { assert @main_called,"Main block wasn't called?!" }
367
+ end
368
+
369
+ def run_go_safely
370
+ Proc.new { safe_go! }
371
+ end
372
+
373
+ # Calls go!, but traps the exit
374
+ def safe_go!
375
+ go!
376
+ rescue SystemExit
377
+ end
378
+
379
+ def run_go!; proc { go! }; end
380
+
381
+ def assert_logged_at_error(expected_message)
382
+ @logged.string.should include expected_message
383
+ end
384
+
385
+ def assert_exits(exit_code,message='',&block)
386
+ block.call
387
+ fail "Expected an exit of #{exit_code}, but we didn't even exit!"
388
+ rescue SystemExit => ex
389
+ assert_equal exit_code,ex.status,@logged.string
390
+ end
391
+
392
+ def set_argv(args)
393
+ ARGV.clear
394
+ args.each { |arg| ARGV << arg }
395
+ end
396
+
397
+ def get_const(class_name)
398
+ unless /\A(?:::)?([A-Z]\w*(?:::[A-Z]\w*)*)\z/ =~ class_name
399
+ raise NameError, "#{class_name.inspect} is not a valid constant name!"
400
+ end
401
+
402
+ Object.module_eval("::#{$1}", __FILE__, __LINE__)
403
+ end
404
+
405
+ end
@@ -0,0 +1,404 @@
1
+ require 'base_test'
2
+ require 'methadone'
3
+
4
+ class TestSH < BaseTest
5
+ include Methadone::SH
6
+ include Methadone::CLILogging
7
+
8
+ class CapturingLogger
9
+ attr_reader :debugs, :infos, :warns, :errors, :fatals
10
+
11
+ def initialize
12
+ @debugs = []
13
+ @infos = []
14
+ @warns = []
15
+ @errors = []
16
+ @fatals = []
17
+ end
18
+
19
+ def debug(msg); @debugs << msg; end
20
+ def info(msg); @infos << msg; end
21
+ def warn(msg); @warns << msg; end
22
+ def error(msg)
23
+ # Try to figure out what's going on on Travis
24
+ STDERR.puts msg if RUBY_PLATFORM == 'java'
25
+ @errors << msg
26
+ end
27
+ def fatal(msg); @fatals << msg; end
28
+
29
+ end
30
+
31
+ [:sh,:sh!].each do |method|
32
+ test_that "#{method} runs a successful command and logs about it" do
33
+ Given {
34
+ use_capturing_logger
35
+ @command = test_command
36
+ }
37
+ When {
38
+ @exit_code = self.send(method,@command)
39
+ }
40
+ Then {
41
+ assert_successful_command_execution(@exit_code,@logger,@command,test_command_stdout)
42
+ }
43
+ end
44
+
45
+ test_that "#{method}, when the command succeeds and given a block of one argument, gives that block the stdout" do
46
+ Given {
47
+ use_capturing_logger
48
+ @command = test_command
49
+ @stdout_received = nil
50
+ }
51
+ When {
52
+ @exit_code = self.send(method,@command) do |stdout|
53
+ @stdout_received = stdout
54
+ end
55
+ }
56
+ Then {
57
+ @stdout_received.should == test_command_stdout
58
+ assert_successful_command_execution(@exit_code,@logger,@command,test_command_stdout)
59
+ }
60
+ end
61
+
62
+ test_that "#{method}, when the command succeeds and given a block of zero arguments, calls the block" do
63
+ Given {
64
+ use_capturing_logger
65
+ @command = test_command
66
+ @block_called = false
67
+ }
68
+ When {
69
+ @exit_code = self.send(method,@command) do
70
+ @block_called = true
71
+ end
72
+ }
73
+ Then {
74
+ @block_called.should == true
75
+ assert_successful_command_execution(@exit_code,@logger,@command,test_command_stdout)
76
+ }
77
+ end
78
+
79
+ test_that "#{method}, when the command succeeds and given a lambda of zero arguments, calls the lambda" do
80
+ Given {
81
+ use_capturing_logger
82
+ @command = test_command
83
+ @block_called = false
84
+ @lambda = lambda { @block_called = true }
85
+ }
86
+ When {
87
+ @exit_code = self.send(method,@command,&@lambda)
88
+ }
89
+ Then {
90
+ @block_called.should == true
91
+ assert_successful_command_execution(@exit_code,@logger,@command,test_command_stdout)
92
+ }
93
+ end
94
+
95
+ test_that "#{method}, when the command succeeds and given a block of two arguments, calls the block with the stdout and stderr" do
96
+ Given {
97
+ use_capturing_logger
98
+ @command = test_command
99
+ @block_called = false
100
+ @stdout_received = nil
101
+ @stderr_received = nil
102
+ }
103
+ When {
104
+ @exit_code = self.send(method,@command) do |stdout,stderr|
105
+ @stdout_received = stdout
106
+ @stderr_received = stderr
107
+ end
108
+ }
109
+ Then {
110
+ @stdout_received.should == test_command_stdout
111
+ @stderr_received.length.should == 0
112
+ assert_successful_command_execution(@exit_code,@logger,@command,test_command_stdout)
113
+ }
114
+ end
115
+
116
+ test_that "#{method}, when the command succeeds and given a block of three arguments, calls the block with the stdout, stderr, and exit code" do
117
+ Given {
118
+ use_capturing_logger
119
+ @command = test_command
120
+ @block_called = false
121
+ @stdout_received = nil
122
+ @stderr_received = nil
123
+ @exitstatus_received = nil
124
+ }
125
+ When {
126
+ @exit_code = self.send(method,@command) do |stdout,stderr,exitstatus|
127
+ @stdout_received = stdout
128
+ @stderr_received = stderr
129
+ @exitstatus_received = exitstatus
130
+ end
131
+ }
132
+ Then {
133
+ @stdout_received.should == test_command_stdout
134
+ @stderr_received.length.should == 0
135
+ @exitstatus_received.should == 0
136
+ assert_successful_command_execution(@exit_code,@logger,@command,test_command_stdout)
137
+ }
138
+ end
139
+ end
140
+
141
+ test_that "sh, when the command fails and given a block, doesn't call the block" do
142
+ Given {
143
+ use_capturing_logger
144
+ @command = test_command("foo")
145
+ @block_called = false
146
+ }
147
+ When {
148
+ @exit_code = sh @command do
149
+ @block_called = true
150
+ end
151
+ }
152
+ Then {
153
+ @exit_code.should == 1
154
+ assert_logger_output_for_failure(@logger,@command,test_command_stdout,test_command_stderr)
155
+ }
156
+ end
157
+
158
+ test_that "sh, when the command fails with an unexpected status, and given a block, doesn't call the block" do
159
+ Given {
160
+ use_capturing_logger
161
+ @command = test_command("foo")
162
+ @block_called = false
163
+ }
164
+ When {
165
+ @exit_code = sh @command, :expected => [2] do
166
+ @block_called = true
167
+ end
168
+ }
169
+ Then {
170
+ @exit_code.should == 1
171
+ assert_logger_output_for_failure(@logger,@command,test_command_stdout,test_command_stderr)
172
+ }
173
+ end
174
+
175
+ [1,[1],[1,2]].each do |expected|
176
+ [:sh,:sh!].each do |method|
177
+ test_that "#{method}, when the command fails with an expected error code (using syntax #{expected}/#{expected.class}), treats it as success" do
178
+ Given {
179
+ use_capturing_logger
180
+ @command = test_command("foo")
181
+ @block_called = false
182
+ @exitstatus_received = nil
183
+ }
184
+ When {
185
+ @exit_code = self.send(method,@command,:expected => expected) do |_,_,exitstatus|
186
+ @block_called = true
187
+ @exitstatus_received = exitstatus
188
+ end
189
+ }
190
+ Then {
191
+ @exit_code.should == 1
192
+ @block_called.should == true
193
+ @exitstatus_received.should == 1
194
+ @logger.debugs[0].should == "Executing '#{test_command}foo'"
195
+ @logger.debugs[1].should == "stdout output of '#{test_command}foo': #{test_command_stdout}"
196
+ @logger.warns[0].should == "stderr output of '#{test_command}foo': #{test_command_stderr}"
197
+ }
198
+ end
199
+ end
200
+ end
201
+
202
+ test_that "sh runs a command that will fail and logs about it" do
203
+ Given {
204
+ use_capturing_logger
205
+ @command = test_command("foo")
206
+ }
207
+ When {
208
+ @exit_code = sh @command
209
+ }
210
+ Then {
211
+ @exit_code.should == 1
212
+ assert_logger_output_for_failure(@logger,@command,test_command_stdout,test_command_stderr)
213
+ }
214
+ end
215
+
216
+ test_that "sh runs a non-existent command that will fail and logs about it" do
217
+ Given {
218
+ use_capturing_logger
219
+ @command = "asdfasdfasdfas"
220
+ }
221
+ When {
222
+ @exit_code = sh @command
223
+ }
224
+ Then {
225
+ @exit_code.should == 127 # consistent with what bash does
226
+ @logger.errors[0].should match /^Error running '#{@command}': .+$/
227
+ }
228
+ end
229
+
230
+ test_that "sh! runs a command that will fail and logs about it, but throws an exception" do
231
+ Given {
232
+ use_capturing_logger
233
+ @command = test_command("foo")
234
+ }
235
+ When {
236
+ @code = lambda { sh! @command }
237
+ }
238
+ Then {
239
+ exception = assert_raises(Methadone::FailedCommandError,&@code)
240
+ exception.command.should == @command
241
+ assert_logger_output_for_failure(@logger,@command,test_command_stdout,test_command_stderr)
242
+ }
243
+ end
244
+
245
+ test_that "sh! runs a command that will fail and includes an error message that appears in the exception" do
246
+ Given {
247
+ use_capturing_logger
248
+ @command = test_command("foo")
249
+ @custom_error_message = any_sentence
250
+ }
251
+ When {
252
+ @code = lambda { sh! @command, :on_fail => @custom_error_message }
253
+ }
254
+ Then {
255
+ exception = assert_raises(Methadone::FailedCommandError,&@code)
256
+ exception.command.should == @command
257
+ exception.message.should == @custom_error_message
258
+ assert_logger_output_for_failure(@logger,@command,test_command_stdout,test_command_stderr)
259
+ }
260
+ end
261
+
262
+ class MyTestApp
263
+ include Methadone::SH
264
+ def initialize(logger=nil)
265
+ set_sh_logger(logger) if logger
266
+ end
267
+ end
268
+
269
+ test_that "when we don't have CLILogging included, we can still provide our own logger" do
270
+ Given {
271
+ @logger = CapturingLogger.new
272
+ @test_app = MyTestApp.new(@logger)
273
+ @command = test_command
274
+ }
275
+ When {
276
+ @exit_code = @test_app.sh @command
277
+ }
278
+ Then {
279
+ assert_successful_command_execution(@exit_code,@logger,@command,test_command_stdout)
280
+ }
281
+ end
282
+
283
+ test_that "when we don't have CLILogging included and fail to provide a logger, an exception is thrown" do
284
+ Given {
285
+ @test_app = MyTestApp.new
286
+ @command = test_command
287
+ }
288
+ When {
289
+ @code = lambda { @test_app.sh @command }
290
+ }
291
+ Then {
292
+ exception = assert_raises(StandardError,&@code)
293
+ }
294
+ end
295
+
296
+ class MyExecutionStrategy
297
+ include Clean::Test::Any
298
+ attr_reader :command
299
+
300
+ def initialize(exitcode)
301
+ @exitcode = exitcode
302
+ @command = nil
303
+ end
304
+
305
+ def run_command(command)
306
+ @command = command
307
+ if @exitcode.kind_of? Fixnum
308
+ [any_string,any_string,OpenStruct.new(:exitstatus => @exitcode)]
309
+ else
310
+ [any_string,any_string,@exitcode]
311
+ end
312
+ end
313
+
314
+ def exception_meaning_command_not_found
315
+ RuntimeError
316
+ end
317
+ end
318
+
319
+ class MyExecutionStrategyApp
320
+ include Methadone::CLILogging
321
+ include Methadone::SH
322
+
323
+ attr_reader :strategy
324
+
325
+ def initialize(exit_code)
326
+ @strategy = MyExecutionStrategy.new(exit_code)
327
+ set_execution_strategy(@strategy)
328
+ set_sh_logger(CapturingLogger.new)
329
+ end
330
+ end
331
+
332
+ test_that "when I provide a custom execution strategy, it gets used" do
333
+ Given {
334
+ @exit_code = any_int :min => 0, :max => 127
335
+ @app = MyExecutionStrategyApp.new(@exit_code)
336
+ @command = "ls"
337
+ }
338
+ When {
339
+ @results = @app.sh(@command)
340
+ }
341
+ Then {
342
+ @app.strategy.command.should == @command
343
+ @results.should == @exit_code
344
+ }
345
+ end
346
+
347
+ test_that "when the execution strategy returns a non-int, but truthy value, it gets coerced into a 0" do
348
+ Given {
349
+ @app = MyExecutionStrategyApp.new(true)
350
+ @command = "ls"
351
+ }
352
+ When {
353
+ @results = @app.sh(@command)
354
+ }
355
+ Then {
356
+ @app.strategy.command.should == @command
357
+ @results.should == 0
358
+ }
359
+ end
360
+
361
+ test_that "when the execution strategy returns a non-int, but falsey value, it gets coerced into a 1" do
362
+ Given {
363
+ @app = MyExecutionStrategyApp.new(false)
364
+ @command = "ls"
365
+ }
366
+ When {
367
+ @results = @app.sh(@command)
368
+ }
369
+ Then {
370
+ @app.strategy.command.should == @command
371
+ @results.should == 1
372
+ }
373
+ end
374
+
375
+ private
376
+
377
+ def assert_successful_command_execution(exit_code,logger,command,stdout)
378
+ exit_code.should == 0
379
+ logger.debugs[0].should == "Executing '#{command}'"
380
+ logger.debugs[1].should == "stdout output of '#{command}': #{stdout}"
381
+ logger.warns.length.should == 0
382
+ end
383
+
384
+ def assert_logger_output_for_failure(logger,command,stdout,stderr)
385
+ logger.debugs[0].should == "Executing '#{command}'"
386
+ logger.infos[0].should == "stdout output of '#{command}': #{stdout}"
387
+ logger.warns[0].should == "stderr output of '#{command}': #{stderr}"
388
+ logger.warns[1].should == "Error running '#{command}'"
389
+ end
390
+
391
+ def use_capturing_logger
392
+ @logger = CapturingLogger.new
393
+ change_logger(@logger)
394
+ end
395
+
396
+ # Runs the test command which exits with the length of ARGV/args
397
+ def test_command(args='')
398
+ File.join(File.expand_path(File.dirname(__FILE__)),'command_for_tests.sh') + ' ' + args
399
+ end
400
+
401
+ def test_command_stdout; "standard output"; end
402
+ def test_command_stderr; "standard error"; end
403
+
404
+ end