methadone 0.5.1 → 1.0.0.rc1
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.
- data/.gitignore +1 -0
- data/Gemfile +1 -0
- data/README.rdoc +59 -1
- data/Rakefile +2 -2
- data/bin/methadone +7 -5
- data/features/bootstrap.feature +1 -0
- data/features/step_definitions/bootstrap_steps.rb +9 -0
- data/features/step_definitions/version_steps.rb +1 -1
- data/features/support/env.rb +5 -0
- data/features/version.feature +0 -1
- data/lib/methadone.rb +7 -0
- data/lib/methadone/error.rb +12 -0
- data/lib/methadone/execution_strategy/base.rb +37 -0
- data/lib/methadone/execution_strategy/jvm.rb +30 -0
- data/lib/methadone/execution_strategy/mri.rb +14 -0
- data/lib/methadone/execution_strategy/open_3.rb +11 -0
- data/lib/methadone/execution_strategy/open_4.rb +16 -0
- data/lib/methadone/execution_strategy/rbx_open_4.rb +10 -0
- data/lib/methadone/sh.rb +143 -0
- data/lib/methadone/version.rb +1 -1
- data/methadone.gemspec +1 -0
- data/test/command_for_tests.rb +5 -0
- data/test/execution_strategy/test_base.rb +24 -0
- data/test/execution_strategy/test_jvm.rb +77 -0
- data/test/execution_strategy/test_mri.rb +32 -0
- data/test/execution_strategy/test_open_3.rb +47 -0
- data/test/execution_strategy/test_open_4.rb +57 -0
- data/test/execution_strategy/test_rbx_open_4.rb +25 -0
- data/test/test_sh.rb +269 -0
- metadata +171 -84
data/lib/methadone/version.rb
CHANGED
data/methadone.gemspec
CHANGED
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'base_test'
|
2
|
+
|
3
|
+
module ExecutionStrategy
|
4
|
+
class TestBase < BaseTest
|
5
|
+
include Methadone::ExecutionStrategy
|
6
|
+
|
7
|
+
[
|
8
|
+
[:run_command,["ls"]],
|
9
|
+
[:exception_meaning_command_not_found,[]],
|
10
|
+
].each do |(method,args)|
|
11
|
+
test_that "#{method} isn't implemented" do
|
12
|
+
Given {
|
13
|
+
@strategy = Base.new
|
14
|
+
}
|
15
|
+
When {
|
16
|
+
@code = lambda { @strategy.send(method,*args) }
|
17
|
+
}
|
18
|
+
Then {
|
19
|
+
assert_raises(RuntimeError,&@code)
|
20
|
+
}
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
require 'base_test'
|
2
|
+
require 'mocha'
|
3
|
+
|
4
|
+
# Defined by JRuby, but this test must pass on any Ruby
|
5
|
+
class NativeException
|
6
|
+
end
|
7
|
+
|
8
|
+
module ExecutionStrategy
|
9
|
+
class TestJVM < BaseTest
|
10
|
+
include Methadone::ExecutionStrategy
|
11
|
+
|
12
|
+
test_that "run_command proxies to Open3.capture3" do
|
13
|
+
Given {
|
14
|
+
@process = mock('java.lang.Process')
|
15
|
+
@runtime = mock('java.lang.Runtime')
|
16
|
+
@command = "ls"
|
17
|
+
@stdout = any_string
|
18
|
+
@stderr = any_string
|
19
|
+
@exitstatus = any_int :min => 1, :max => 127
|
20
|
+
}
|
21
|
+
When the_test_runs
|
22
|
+
Then {
|
23
|
+
expects_lang = mock()
|
24
|
+
JVM.any_instance.expects(:java).returns(expects_lang)
|
25
|
+
expects_Runtime = mock()
|
26
|
+
expects_lang.expects(:lang).returns(expects_Runtime)
|
27
|
+
runtime_klass = mock()
|
28
|
+
expects_Runtime.expects(:Runtime).returns(runtime_klass)
|
29
|
+
runtime_klass.expects(:get_runtime).returns(@runtime)
|
30
|
+
@runtime.expects(:exec).with(@command).returns(@process)
|
31
|
+
|
32
|
+
stdin = mock()
|
33
|
+
@process.expects(:get_output_stream).returns(stdin)
|
34
|
+
stdin.expects(:close)
|
35
|
+
|
36
|
+
stdout_input_stream = mock('InputStream')
|
37
|
+
@process.expects(:get_input_stream).returns(stdout_input_stream)
|
38
|
+
stdout_input_stream.expects(:read).times(2).returns(
|
39
|
+
@stdout,
|
40
|
+
-1)
|
41
|
+
|
42
|
+
stderr_input_stream = mock('InputStream')
|
43
|
+
@process.expects(:get_error_stream).returns(stderr_input_stream)
|
44
|
+
stderr_input_stream.expects(:read).times(2).returns(
|
45
|
+
@stderr,
|
46
|
+
-1)
|
47
|
+
|
48
|
+
@process.expects(:wait_for).returns(@exitstatus)
|
49
|
+
}
|
50
|
+
|
51
|
+
Given new_jvm_strategy
|
52
|
+
When {
|
53
|
+
@results = @strategy.run_command(@command)
|
54
|
+
}
|
55
|
+
Then {
|
56
|
+
@results[0].should == @stdout
|
57
|
+
@results[1].should == @stderr
|
58
|
+
@results[2].exitstatus.should == @exitstatus
|
59
|
+
}
|
60
|
+
end
|
61
|
+
|
62
|
+
test_that "exception_meaning_command_not_found returns NativeException" do
|
63
|
+
Given new_jvm_strategy
|
64
|
+
When {
|
65
|
+
@klass = @strategy.exception_meaning_command_not_found
|
66
|
+
}
|
67
|
+
Then {
|
68
|
+
@klass.should == NativeException
|
69
|
+
}
|
70
|
+
end
|
71
|
+
|
72
|
+
private
|
73
|
+
def new_jvm_strategy
|
74
|
+
lambda { @strategy = JVM.new }
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'base_test'
|
2
|
+
|
3
|
+
module ExecutionStrategy
|
4
|
+
class TestMRI < BaseTest
|
5
|
+
include Methadone::ExecutionStrategy
|
6
|
+
|
7
|
+
test_that "run_command isn't implemented" do
|
8
|
+
Given new_mri_strategy
|
9
|
+
When {
|
10
|
+
@code = lambda { @strategy.run_command("ls") }
|
11
|
+
}
|
12
|
+
Then {
|
13
|
+
assert_raises(RuntimeError,&@code)
|
14
|
+
}
|
15
|
+
end
|
16
|
+
|
17
|
+
test_that "exception_meaning_command_not_found returns Errno::ENOENT" do
|
18
|
+
Given new_mri_strategy
|
19
|
+
When {
|
20
|
+
@klass = @strategy.exception_meaning_command_not_found
|
21
|
+
}
|
22
|
+
Then {
|
23
|
+
@klass.should == Errno::ENOENT
|
24
|
+
}
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
def new_mri_strategy
|
29
|
+
lambda { @strategy = MRI.new }
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'base_test'
|
2
|
+
require 'mocha'
|
3
|
+
require 'open3'
|
4
|
+
|
5
|
+
module ExecutionStrategy
|
6
|
+
class TestOpen_3 < BaseTest
|
7
|
+
include Methadone::ExecutionStrategy
|
8
|
+
|
9
|
+
test_that "run_command proxies to Open3.capture3" do
|
10
|
+
Given {
|
11
|
+
@command = any_string
|
12
|
+
@stdout = any_string
|
13
|
+
@stderr = any_string
|
14
|
+
@status = stub('Process::Status')
|
15
|
+
}
|
16
|
+
When the_test_runs
|
17
|
+
Then {
|
18
|
+
Open3.expects(:capture3).with(@command).returns([@stdout,@stderr,@status])
|
19
|
+
}
|
20
|
+
|
21
|
+
Given new_open_3_strategy
|
22
|
+
When {
|
23
|
+
@results = @strategy.run_command(@command)
|
24
|
+
}
|
25
|
+
Then {
|
26
|
+
@results[0].should == @stdout
|
27
|
+
@results[1].should == @stderr
|
28
|
+
@results[2].should be @status
|
29
|
+
}
|
30
|
+
end
|
31
|
+
|
32
|
+
test_that "exception_meaning_command_not_found returns Errno::ENOENT" do
|
33
|
+
Given new_open_3_strategy
|
34
|
+
When {
|
35
|
+
@klass = @strategy.exception_meaning_command_not_found
|
36
|
+
}
|
37
|
+
Then {
|
38
|
+
@klass.should == Errno::ENOENT
|
39
|
+
}
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
def new_open_3_strategy
|
44
|
+
lambda { @strategy = Open_3.new }
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
require 'base_test'
|
2
|
+
require 'mocha'
|
3
|
+
|
4
|
+
# Define this symbol without requiring the library;
|
5
|
+
# all we're goingn to do is mock calls to it
|
6
|
+
module Open4
|
7
|
+
end
|
8
|
+
|
9
|
+
module ExecutionStrategy
|
10
|
+
class TestOpen_4 < BaseTest
|
11
|
+
include Methadone::ExecutionStrategy
|
12
|
+
|
13
|
+
test_that "run_command proxies to Open4.capture4" do
|
14
|
+
Given {
|
15
|
+
@command = any_string
|
16
|
+
@stdin_io = mock("IO")
|
17
|
+
@stdout = any_string
|
18
|
+
@stdout_io = StringIO.new(@stdout)
|
19
|
+
@stderr = any_string
|
20
|
+
@stderr_io = StringIO.new(@stderr)
|
21
|
+
@pid = any_int :min => 2, :max => 65536
|
22
|
+
@status = stub('Process::Status')
|
23
|
+
}
|
24
|
+
When the_test_runs
|
25
|
+
Then {
|
26
|
+
Open4.expects(:popen4).with(@command).returns([@pid,@stdin_io,@stdout_io,@stderr_io])
|
27
|
+
@stdin_io.expects(:close)
|
28
|
+
Process.expects(:waitpid2).with(@pid).returns([any_string,@status])
|
29
|
+
}
|
30
|
+
|
31
|
+
Given new_open_4_strategy
|
32
|
+
When {
|
33
|
+
@results = @strategy.run_command(@command)
|
34
|
+
}
|
35
|
+
Then {
|
36
|
+
@results[0].should == @stdout
|
37
|
+
@results[1].should == @stderr
|
38
|
+
@results[2].should be @status
|
39
|
+
}
|
40
|
+
end
|
41
|
+
|
42
|
+
test_that "exception_meaning_command_not_found returns Errno::ENOENT" do
|
43
|
+
Given new_open_4_strategy
|
44
|
+
When {
|
45
|
+
@klass = @strategy.exception_meaning_command_not_found
|
46
|
+
}
|
47
|
+
Then {
|
48
|
+
@klass.should == Errno::ENOENT
|
49
|
+
}
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
def new_open_4_strategy
|
54
|
+
lambda { @strategy = Open_4.new }
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'base_test'
|
2
|
+
require 'mocha'
|
3
|
+
|
4
|
+
# Define this symbol without requiring the library;
|
5
|
+
# all we're goingn to do is mock calls to it
|
6
|
+
module Open4
|
7
|
+
end
|
8
|
+
|
9
|
+
module ExecutionStrategy
|
10
|
+
class TestRBXOpen_4 < BaseTest
|
11
|
+
include Methadone::ExecutionStrategy
|
12
|
+
|
13
|
+
test_that "exception_meaning_command_not_found returns Errno::EINVAL" do
|
14
|
+
Given {
|
15
|
+
@strategy = RBXOpen_4.new
|
16
|
+
}
|
17
|
+
When {
|
18
|
+
@klass = @strategy.exception_meaning_command_not_found
|
19
|
+
}
|
20
|
+
Then {
|
21
|
+
@klass.should == Errno::EINVAL
|
22
|
+
}
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
data/test/test_sh.rb
ADDED
@@ -0,0 +1,269 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
require 'clean_test/test_case'
|
3
|
+
|
4
|
+
class TestSH < Clean::Test::TestCase
|
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); @errors << msg; end
|
23
|
+
def fatal(msg); @fatals << msg; end
|
24
|
+
|
25
|
+
end
|
26
|
+
|
27
|
+
[:sh,:sh!].each do |method|
|
28
|
+
test_that "#{method} runs a successful command and logs about it" do
|
29
|
+
Given {
|
30
|
+
use_capturing_logger
|
31
|
+
@command = test_command
|
32
|
+
}
|
33
|
+
When {
|
34
|
+
@exit_code = self.send(method,@command)
|
35
|
+
}
|
36
|
+
Then {
|
37
|
+
assert_successful_command_execution(@exit_code,@logger,@command,test_command_stdout)
|
38
|
+
}
|
39
|
+
end
|
40
|
+
|
41
|
+
test_that "#{method}, when the command succeeds and given a block of one argument, gives that block the stdout" do
|
42
|
+
Given {
|
43
|
+
use_capturing_logger
|
44
|
+
@command = test_command
|
45
|
+
@stdout_received = nil
|
46
|
+
}
|
47
|
+
When {
|
48
|
+
@exit_code = self.send(method,@command) do |stdout|
|
49
|
+
@stdout_received = stdout
|
50
|
+
end
|
51
|
+
}
|
52
|
+
Then {
|
53
|
+
@stdout_received.should == test_command_stdout
|
54
|
+
assert_successful_command_execution(@exit_code,@logger,@command,test_command_stdout)
|
55
|
+
}
|
56
|
+
end
|
57
|
+
|
58
|
+
test_that "#{method}, when the command succeeds and given a block of zero arguments, calls the block" do
|
59
|
+
Given {
|
60
|
+
use_capturing_logger
|
61
|
+
@command = test_command
|
62
|
+
@block_called = false
|
63
|
+
}
|
64
|
+
When {
|
65
|
+
@exit_code = self.send(method,@command) do
|
66
|
+
@block_called = true
|
67
|
+
end
|
68
|
+
}
|
69
|
+
Then {
|
70
|
+
@block_called.should == true
|
71
|
+
assert_successful_command_execution(@exit_code,@logger,@command,test_command_stdout)
|
72
|
+
}
|
73
|
+
end
|
74
|
+
|
75
|
+
test_that "#{method}, when the command succeeds and given a lambda of zero arguments, calls the lambda" do
|
76
|
+
Given {
|
77
|
+
use_capturing_logger
|
78
|
+
@command = test_command
|
79
|
+
@block_called = false
|
80
|
+
@lambda = lambda { @block_called = true }
|
81
|
+
}
|
82
|
+
When {
|
83
|
+
@exit_code = self.send(method,@command,&@lambda)
|
84
|
+
}
|
85
|
+
Then {
|
86
|
+
@block_called.should == true
|
87
|
+
assert_successful_command_execution(@exit_code,@logger,@command,test_command_stdout)
|
88
|
+
}
|
89
|
+
end
|
90
|
+
|
91
|
+
test_that "#{method}, when the command succeeds and given a block of two arguments, calls the block with the stdout and stderr" do
|
92
|
+
Given {
|
93
|
+
use_capturing_logger
|
94
|
+
@command = test_command
|
95
|
+
@block_called = false
|
96
|
+
@stdout_received = nil
|
97
|
+
@stderr_received = nil
|
98
|
+
}
|
99
|
+
When {
|
100
|
+
@exit_code = self.send(method,@command) do |stdout,stderr|
|
101
|
+
@stdout_received = stdout
|
102
|
+
@stderr_received = stderr
|
103
|
+
end
|
104
|
+
}
|
105
|
+
Then {
|
106
|
+
@stdout_received.should == test_command_stdout
|
107
|
+
@stderr_received.length.should == 0
|
108
|
+
assert_successful_command_execution(@exit_code,@logger,@command,test_command_stdout)
|
109
|
+
}
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
test_that "sh, when the command fails and given a block, doesn't call the block" do
|
114
|
+
Given {
|
115
|
+
use_capturing_logger
|
116
|
+
@command = test_command("foo")
|
117
|
+
@block_called = false
|
118
|
+
}
|
119
|
+
When {
|
120
|
+
@exit_code = sh @command do
|
121
|
+
@block_called = true
|
122
|
+
end
|
123
|
+
}
|
124
|
+
Then {
|
125
|
+
@exit_code.should == 1
|
126
|
+
assert_logger_output_for_failure(@logger,@command,test_command_stdout,test_command_stderr)
|
127
|
+
}
|
128
|
+
end
|
129
|
+
|
130
|
+
test_that "sh runs a command that will fail and logs about it" do
|
131
|
+
Given {
|
132
|
+
use_capturing_logger
|
133
|
+
@command = test_command("foo")
|
134
|
+
}
|
135
|
+
When {
|
136
|
+
@exit_code = sh @command
|
137
|
+
}
|
138
|
+
Then {
|
139
|
+
@exit_code.should == 1
|
140
|
+
assert_logger_output_for_failure(@logger,@command,test_command_stdout,test_command_stderr)
|
141
|
+
}
|
142
|
+
end
|
143
|
+
|
144
|
+
test_that "sh runs a non-existent command that will fail and logs about it" do
|
145
|
+
Given {
|
146
|
+
use_capturing_logger
|
147
|
+
@command = "asdfasdfasdfas"
|
148
|
+
}
|
149
|
+
When {
|
150
|
+
@exit_code = sh @command
|
151
|
+
}
|
152
|
+
Then {
|
153
|
+
@exit_code.should == 127 # consistent with what bash does
|
154
|
+
@logger.errors[0].should match /^Error running '#{@command}': .+$/
|
155
|
+
}
|
156
|
+
end
|
157
|
+
|
158
|
+
test_that "sh! runs a command that will fail and logs about it, but throws an exception" do
|
159
|
+
Given {
|
160
|
+
use_capturing_logger
|
161
|
+
@command = test_command("foo")
|
162
|
+
}
|
163
|
+
When {
|
164
|
+
@code = lambda { sh! @command }
|
165
|
+
}
|
166
|
+
Then {
|
167
|
+
exception = assert_raises(Methadone::FailedCommandError,&@code)
|
168
|
+
exception.command.should == @command
|
169
|
+
assert_logger_output_for_failure(@logger,@command,test_command_stdout,test_command_stderr)
|
170
|
+
}
|
171
|
+
end
|
172
|
+
|
173
|
+
class MyTestApp
|
174
|
+
include Methadone::SH
|
175
|
+
def initialize(logger)
|
176
|
+
set_sh_logger(logger)
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
test_that "when we don't have CLILogging included, we can still provide our own logger" do
|
181
|
+
Given {
|
182
|
+
@logger = CapturingLogger.new
|
183
|
+
@test_app = MyTestApp.new(@logger)
|
184
|
+
@command = test_command
|
185
|
+
}
|
186
|
+
When {
|
187
|
+
@exit_code = @test_app.sh @command
|
188
|
+
}
|
189
|
+
Then {
|
190
|
+
assert_successful_command_execution(@exit_code,@logger,@command,test_command_stdout)
|
191
|
+
}
|
192
|
+
end
|
193
|
+
|
194
|
+
class MyExecutionStrategy
|
195
|
+
include Clean::Test::Any
|
196
|
+
attr_reader :command
|
197
|
+
|
198
|
+
def initialize(exitcode)
|
199
|
+
@exitcode = exitcode
|
200
|
+
@command = nil
|
201
|
+
end
|
202
|
+
|
203
|
+
def run_command(command)
|
204
|
+
@command = command
|
205
|
+
[any_string,any_string,OpenStruct.new(:exitstatus => @exit_code)]
|
206
|
+
end
|
207
|
+
|
208
|
+
def exception_meaning_command_not_found
|
209
|
+
RuntimeError
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
class MyExecutionStrategyApp
|
214
|
+
include Methadone::CLILogging
|
215
|
+
include Methadone::SH
|
216
|
+
|
217
|
+
attr_reader :strategy
|
218
|
+
|
219
|
+
def initialize(exit_code)
|
220
|
+
@strategy = MyExecutionStrategy.new(exit_code)
|
221
|
+
set_execution_strategy(@strategy)
|
222
|
+
set_sh_logger(CapturingLogger.new)
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
226
|
+
test_that "when I provide a custom execution strategy, it gets used" do
|
227
|
+
Given {
|
228
|
+
@exit_code = any_int :min => 0, :max => 127
|
229
|
+
@app = MyExecutionStrategyApp.new(@exit_code)
|
230
|
+
@command = "ls"
|
231
|
+
}
|
232
|
+
When {
|
233
|
+
@results = @app.sh(@command)
|
234
|
+
}
|
235
|
+
Then {
|
236
|
+
@app.strategy.command.should == @command
|
237
|
+
@results.should == @exitstatus
|
238
|
+
}
|
239
|
+
end
|
240
|
+
|
241
|
+
private
|
242
|
+
|
243
|
+
def assert_successful_command_execution(exit_code,logger,command,stdout)
|
244
|
+
exit_code.should == 0
|
245
|
+
logger.debugs[0].should == "Executing '#{command}'"
|
246
|
+
logger.debugs[1].should == "Output of '#{command}': #{stdout}"
|
247
|
+
logger.warns.length.should == 0
|
248
|
+
end
|
249
|
+
|
250
|
+
def assert_logger_output_for_failure(logger,command,stdout,stderr)
|
251
|
+
logger.debugs[0].should == "Executing '#{command}'"
|
252
|
+
logger.infos[0].should == "Output of '#{command}': #{stdout}"
|
253
|
+
logger.warns[0].should == "Error output of '#{command}': #{stderr}"
|
254
|
+
logger.warns[1].should == "Error running '#{command}'"
|
255
|
+
end
|
256
|
+
|
257
|
+
def use_capturing_logger
|
258
|
+
@logger = CapturingLogger.new
|
259
|
+
change_logger(@logger)
|
260
|
+
end
|
261
|
+
|
262
|
+
def test_command(args='')
|
263
|
+
File.join(File.dirname(__FILE__),'command_for_tests.rb') + ' ' + args
|
264
|
+
end
|
265
|
+
|
266
|
+
def test_command_stdout; "standard output"; end
|
267
|
+
def test_command_stderr; "standard error"; end
|
268
|
+
|
269
|
+
end
|