nutshell 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,117 @@
1
+ module MockOpen4
2
+
3
+ CMD_RETURN = {
4
+ Nutshell::RemoteShell::LOGIN_LOOP => [:out, "ready\n"]
5
+ }
6
+
7
+ attr_reader :cmd_log
8
+
9
+ def popen4(*args)
10
+ cmd = args.join(" ")
11
+ @cmd_log ||= []
12
+ @cmd_log << cmd
13
+
14
+
15
+ pid = "test_pid"
16
+ inn_w = StringIO.new
17
+ out_r, out_w = IO.pipe
18
+ err_r, err_w = IO.pipe
19
+
20
+ ios = {:inn => inn_w, :out => out_w, :err => err_w}
21
+ stream, string = output_for cmd
22
+
23
+ ios[stream].write string
24
+ out_w.write nil
25
+ err_w.write nil
26
+
27
+ out_w.close
28
+ err_w.close
29
+
30
+ if block_given?
31
+ yield
32
+ inn_w.close
33
+ out_r.close
34
+ err_r.close
35
+ end
36
+
37
+ return pid, inn_w, out_r, err_r
38
+ end
39
+
40
+ def output_for(cmd)
41
+ @mock_output ||= {}
42
+ if @mock_output
43
+ output = @mock_output[cmd]
44
+ output ||= @mock_output[nil].shift if Array === @mock_output[nil]
45
+ @mock_output.delete(cmd)
46
+ if output
47
+ Process.set_exitcode output.delete(output.last)
48
+ return output
49
+ end
50
+ end
51
+
52
+ CMD_RETURN.each do |cmd_key, return_val|
53
+ return return_val if cmd.include? cmd_key
54
+ end
55
+ return :out, "some_value"
56
+ end
57
+
58
+ def set_mock_response code, stream_vals={}, options={}
59
+ @mock_output ||= {}
60
+ @mock_output[nil] ||= []
61
+ new_stream_vals = {}
62
+
63
+ stream_vals.each do |key, val|
64
+ if Symbol === key
65
+ @mock_output[nil] << [key, val, code]
66
+ next
67
+ end
68
+
69
+ if Nutshell::RemoteShell === self
70
+ key = build_remote_cmd(key, options).join(" ")
71
+ end
72
+
73
+ new_stream_vals[key] = (val.dup << code)
74
+ end
75
+ @mock_output.merge! new_stream_vals
76
+ end
77
+
78
+ end
79
+
80
+
81
+ class StatusStruct < Struct.new("Status", :exitstatus)
82
+ def success?
83
+ self.exitstatus == 0
84
+ end
85
+ end
86
+
87
+
88
+ Process.class_eval do
89
+ class << self
90
+
91
+ def set_exitcode(code)
92
+ @exit_code = code
93
+ end
94
+
95
+ alias old_waitpid2 waitpid2
96
+ undef waitpid2
97
+
98
+ def waitpid2(*args)
99
+ pid = args[0]
100
+ if pid == "test_pid"
101
+ exitcode = @exit_code ||= 0
102
+ @exit_code = 0
103
+ return [StatusStruct.new(exitcode)]
104
+ else
105
+ return old_waitpid2(*args)
106
+ end
107
+ end
108
+
109
+ alias old_kill kill
110
+ undef kill
111
+
112
+ def kill(type, pid)
113
+ return true if type == 0 && pid == "test_pid"
114
+ old_kill(type, pid)
115
+ end
116
+ end
117
+ end
@@ -0,0 +1,108 @@
1
+ require 'nutshell'
2
+ require 'test/unit'
3
+
4
+ require 'test/mocks/mock_object'
5
+ require 'test/mocks/mock_open4'
6
+
7
+
8
+ def mock_remote_shell host=nil
9
+ host ||= "user@some_server.com"
10
+ remote_shell = Nutshell::RemoteShell.new host
11
+
12
+ remote_shell.extend MockOpen4
13
+ remote_shell.extend MockObject
14
+
15
+ use_remote_shell remote_shell
16
+
17
+ remote_shell.connect
18
+ remote_shell
19
+ end
20
+
21
+
22
+ def mock_remote_shell host=nil
23
+ host ||= "user@some_server.com"
24
+ remote_shell = Nutshell::RemoteShell.new host
25
+
26
+ remote_shell.extend MockOpen4
27
+ remote_shell.extend MockObject
28
+
29
+ use_remote_shell remote_shell
30
+
31
+ remote_shell.connect
32
+ remote_shell
33
+ end
34
+
35
+
36
+ def assert_not_called *args
37
+ assert !@remote_shell.method_called?(:call, :args => [*args]),
38
+ "Command called by #{@remote_shell.host} but should't have:\n #{args[0]}"
39
+ end
40
+
41
+
42
+ def assert_server_call *args
43
+ assert @remote_shell.method_called?(:call, :args => [*args]),
44
+ "Command was not called by #{@remote_shell.host}:\n #{args[0]}"
45
+ end
46
+
47
+
48
+ def assert_bash_script name, cmds, check_value
49
+ cmds = cmds.map{|cmd| "(#{cmd})" }
50
+ cmds << "echo true"
51
+
52
+ bash = <<-STR
53
+ #!/bin/bash
54
+ if [ "$1" == "--no-env" ]; then
55
+ #{cmds.flatten.join(" && ")}
56
+ else
57
+ #{@app.root_path}/env #{@app.root_path}/#{name} --no-env
58
+ fi
59
+ STR
60
+
61
+ assert_equal bash, check_value
62
+ end
63
+
64
+
65
+ def assert_ssh_call expected, ds=@remote_shell, options={}
66
+ expected = ds.build_remote_cmd(expected, options).join(" ")
67
+
68
+ error_msg = "No such command in remote_shell log [#{ds.host}]\n#{expected}"
69
+ error_msg << "\n\n#{ds.cmd_log.select{|c| c =~ /^ssh/}.join("\n\n")}"
70
+
71
+ assert ds.cmd_log.include?(expected), error_msg
72
+ end
73
+
74
+
75
+ def assert_rsync from, to, ds=@remote_shell, sudo=false
76
+ received = ds.cmd_log.last
77
+
78
+ rsync_path = if sudo
79
+ path = ds.sudo_cmd('rsync', sudo).join(' ')
80
+ "--rsync-path='#{ path }' "
81
+ end
82
+
83
+ rsync_cmd = "rsync -azP #{rsync_path}-e \"ssh #{ds.ssh_flags.join(' ')}\""
84
+
85
+ error_msg = "No such command in remote_shell log [#{ds.host}]\n#{rsync_cmd}"
86
+ error_msg << "#{from.inspect} #{to.inspect}"
87
+ error_msg << "\n\n#{ds.cmd_log.select{|c| c =~ /^rsync/}.join("\n\n")}"
88
+
89
+ if Regexp === from
90
+ found = ds.cmd_log.select do |cmd|
91
+
92
+ cmd_from = cmd.split(" ")[-2]
93
+ cmd_to = cmd.split(" ").last
94
+
95
+ cmd_from =~ from && cmd_to == to && cmd.index(rsync_cmd) == 0
96
+ end
97
+
98
+ assert !found.empty?, error_msg
99
+ else
100
+ expected = "#{rsync_cmd} #{from} #{to}"
101
+ assert ds.cmd_log.include?(expected), error_msg
102
+ end
103
+ end
104
+
105
+
106
+ def use_remote_shell remote_shell
107
+ @remote_shell = remote_shell
108
+ end
@@ -0,0 +1,102 @@
1
+ require 'test/test_helper'
2
+
3
+ class TestRemoteShell < Test::Unit::TestCase
4
+
5
+ def setup
6
+ Nutshell::RemoteShell.class_eval{ include MockOpen4 }
7
+
8
+ @host = "user@some_server.com"
9
+ @remote_shell = mock_remote_shell @host
10
+ end
11
+
12
+ def teardown
13
+ @remote_shell.disconnect
14
+ end
15
+
16
+ def test_connect
17
+ login_cmd = Nutshell::RemoteShell::LOGIN_LOOP
18
+ login_cmd = @remote_shell.send :quote_cmd, login_cmd
19
+ login_cmd = @remote_shell.send :ssh_cmd, login_cmd, :sudo => false
20
+
21
+ assert @remote_shell.method_called?(:popen4, :args => [login_cmd.join(" ")])
22
+ assert @remote_shell.connected?
23
+ end
24
+
25
+ def test_disconnect
26
+ @remote_shell.disconnect
27
+ assert !@remote_shell.connected?
28
+ end
29
+
30
+ def test_call
31
+ @remote_shell.call "echo 'line1'; echo 'line2'"
32
+ assert_ssh_call "echo 'line1'; echo 'line2'"
33
+
34
+ @remote_shell.sudo = "sudouser"
35
+ @remote_shell.call "sudocall"
36
+ assert_ssh_call "sudocall", @remote_shell, :sudo => "sudouser"
37
+ end
38
+
39
+ def test_call_with_stderr
40
+ @remote_shell.set_mock_response 1, :err => 'this is an error'
41
+ cmd = "echo 'this is an error'"
42
+ @remote_shell.call cmd
43
+ raise "Didn't raise CmdError on stderr"
44
+ rescue Nutshell::CmdError => e
45
+ ssh_cmd = @remote_shell.build_remote_cmd(cmd).join(" ")
46
+ assert_equal "Execution failed with status 1: #{ssh_cmd}", e.message
47
+ end
48
+
49
+ def test_upload
50
+ @remote_shell.upload "test/fixtures/nutshell_test", "nutshell_test"
51
+ assert_rsync "test/fixtures/nutshell_test",
52
+ "#{@remote_shell.host}:nutshell_test"
53
+
54
+ @remote_shell.sudo = "blah"
55
+ @remote_shell.upload "test/fixtures/nutshell_test", "nutshell_test"
56
+ assert_rsync "test/fixtures/nutshell_test",
57
+ "#{@remote_shell.host}:nutshell_test", @remote_shell, "blah"
58
+ end
59
+
60
+ def test_download
61
+ @remote_shell.download "nutshell_test", "."
62
+ assert_rsync "#{@remote_shell.host}:nutshell_test", "."
63
+
64
+ @remote_shell.download "nutshell_test", ".", :sudo => "sudouser"
65
+ assert_rsync "#{@remote_shell.host}:nutshell_test", ".",
66
+ @remote_shell, "sudouser"
67
+ end
68
+
69
+ def test_make_file
70
+ @remote_shell.make_file("some_dir/nutshell_test_file", "test data")
71
+ tmp_file = "#{Nutshell::TMP_DIR}/nutshell_test_file"
72
+ tmp_file = Regexp.escape tmp_file
73
+ assert_rsync(/^#{tmp_file}_[0-9]+/,
74
+ "#{@remote_shell.host}:some_dir/nutshell_test_file")
75
+ end
76
+
77
+ def test_os_name
78
+ @remote_shell.os_name
79
+ assert_ssh_call "uname -s"
80
+ end
81
+
82
+ def test_equality
83
+ ds_equal = Nutshell::RemoteShell.new @host
84
+ ds_diff1 = Nutshell::RemoteShell.new @host, :user => "blarg"
85
+ ds_diff2 = Nutshell::RemoteShell.new "some_other_host"
86
+
87
+ assert_equal ds_equal, @remote_shell
88
+ assert_equal ds_diff1, @remote_shell
89
+ assert ds_diff2 != @remote_shell
90
+ end
91
+
92
+ def test_file?
93
+ @remote_shell.file? "some/file/path"
94
+ assert_ssh_call "test -f some/file/path"
95
+ end
96
+
97
+ def test_symlink
98
+ @remote_shell.symlink "target_file", "sym_name"
99
+ assert_ssh_call "ln -sfT target_file sym_name"
100
+ end
101
+ end
102
+
@@ -0,0 +1,98 @@
1
+ require 'test/test_helper'
2
+
3
+ class TestShell < Test::Unit::TestCase
4
+
5
+ def setup
6
+ @output = StringIO.new
7
+ @output.extend MockObject
8
+
9
+ @shell = Nutshell::Shell.new @output
10
+ @shell.extend MockOpen4
11
+
12
+ @shell.input.extend MockObject
13
+ @shell.input.mock :ask, :return => "someinput"
14
+ end
15
+
16
+
17
+ def test_initialize
18
+ assert_equal @output, @shell.output
19
+ assert_equal HighLine, @shell.input.class
20
+ assert_equal `whoami`.chomp, @shell.user
21
+ assert_equal `hostname`.chomp, @shell.host
22
+ end
23
+
24
+
25
+ def test_ask
26
+ @shell.ask "input something!"
27
+ assert 1, @shell.input.method_call_count(:ask)
28
+ end
29
+
30
+
31
+ def test_close
32
+ @shell.close
33
+ assert 1, @shell.output.method_call_count(:close)
34
+ end
35
+
36
+
37
+ def test_write
38
+ @shell.write "blah"
39
+ assert @output.method_called?(:write, :args => "blah")
40
+ end
41
+
42
+
43
+ def test_prompt_for_password
44
+ @shell.prompt_for_password
45
+
46
+ args = "#{@shell.user}@#{@shell.host} Password:"
47
+ assert @shell.input.method_called?(:ask, :args => args)
48
+ assert_equal "someinput", @shell.password
49
+ end
50
+
51
+
52
+ def test_execute
53
+ @shell.set_mock_response 0, "say hi" => [:out, "hi\n"]
54
+
55
+ response = @shell.execute("say hi") do |stream, data|
56
+ assert_equal :out, stream
57
+ assert_equal "hi\n", data
58
+ end
59
+ assert_equal "hi", response
60
+ end
61
+
62
+
63
+ def test_execute_errorstatus
64
+ @shell.set_mock_response 1, "error me" => [:err, "ERROR'D!"]
65
+
66
+ begin
67
+ @shell.execute("error me") do |stream, data|
68
+ assert_equal :err, stream
69
+ assert_equal "ERROR'D!", data
70
+ end
71
+ raise "Didn't call CmdError when it should have"
72
+ rescue Nutshell::CmdError => e
73
+ msg = "Execution failed with status 1: error me"
74
+ assert_equal msg, e.message
75
+ end
76
+ end
77
+
78
+
79
+ def test_execute_stderronly
80
+ @shell.set_mock_response 0, "stderr" => [:err, "fake error"]
81
+
82
+ response = @shell.execute("stderr") do |stream, data|
83
+ assert_equal :err, stream
84
+ assert_equal "fake error", data
85
+ end
86
+ assert_equal "", response
87
+ end
88
+
89
+
90
+ def test_execute_password_prompt
91
+ @shell.set_mock_response 0, "do that thing" => [:err, "Password:"]
92
+ @shell.input.mock :ask, :return => "new_password"
93
+
94
+ @shell.execute("do that thing")
95
+ assert_equal "new_password", @shell.password
96
+ end
97
+ end
98
+
metadata ADDED
@@ -0,0 +1,120 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: nutshell
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 1
7
+ - 0
8
+ - 0
9
+ version: 1.0.0
10
+ platform: ruby
11
+ authors:
12
+ - Jeremie Castagna
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2010-09-28 00:00:00 -07:00
18
+ default_executable:
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: open4
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - ">="
26
+ - !ruby/object:Gem::Version
27
+ segments:
28
+ - 1
29
+ - 0
30
+ - 1
31
+ version: 1.0.1
32
+ type: :runtime
33
+ version_requirements: *id001
34
+ - !ruby/object:Gem::Dependency
35
+ name: highline
36
+ prerelease: false
37
+ requirement: &id002 !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - ">="
40
+ - !ruby/object:Gem::Version
41
+ segments:
42
+ - 1
43
+ - 5
44
+ - 1
45
+ version: 1.5.1
46
+ type: :runtime
47
+ version_requirements: *id002
48
+ - !ruby/object:Gem::Dependency
49
+ name: hoe
50
+ prerelease: false
51
+ requirement: &id003 !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - ">="
54
+ - !ruby/object:Gem::Version
55
+ segments:
56
+ - 2
57
+ - 3
58
+ - 3
59
+ version: 2.3.3
60
+ type: :development
61
+ version_requirements: *id003
62
+ description: A light weight ssh client that wraps the ssh and rsync commands.
63
+ email:
64
+ - yaksnrainbows@gmail.com
65
+ executables: []
66
+
67
+ extensions: []
68
+
69
+ extra_rdoc_files:
70
+ - History.txt
71
+ - Manifest.txt
72
+ - README.txt
73
+ files:
74
+ - History.txt
75
+ - Manifest.txt
76
+ - README.txt
77
+ - Rakefile
78
+ - lib/nutshell.rb
79
+ - lib/nutshell/remote_shell.rb
80
+ - lib/nutshell/shell.rb
81
+ - test/mocks/mock_object.rb
82
+ - test/mocks/mock_open4.rb
83
+ - test/test_helper.rb
84
+ - test/test_remote_shell.rb
85
+ - test/test_shell.rb
86
+ has_rdoc: true
87
+ homepage: http://github.com/yaksnrainbows/nutshell
88
+ licenses: []
89
+
90
+ post_install_message:
91
+ rdoc_options:
92
+ - --main
93
+ - README.txt
94
+ require_paths:
95
+ - lib
96
+ required_ruby_version: !ruby/object:Gem::Requirement
97
+ requirements:
98
+ - - ">="
99
+ - !ruby/object:Gem::Version
100
+ segments:
101
+ - 0
102
+ version: "0"
103
+ required_rubygems_version: !ruby/object:Gem::Requirement
104
+ requirements:
105
+ - - ">="
106
+ - !ruby/object:Gem::Version
107
+ segments:
108
+ - 0
109
+ version: "0"
110
+ requirements: []
111
+
112
+ rubyforge_project: nutshell
113
+ rubygems_version: 1.3.6
114
+ signing_key:
115
+ specification_version: 3
116
+ summary: A light weight ssh client that wraps the ssh and rsync commands.
117
+ test_files:
118
+ - test/test_helper.rb
119
+ - test/test_remote_shell.rb
120
+ - test/test_shell.rb