methadone-rehab 1.9.2

Sign up to get free protection for your applications and to get access to all the features.
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