nutshell 1.0.0

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.
@@ -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