expect-behaviors 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,97 @@
1
+ require 'helper'
2
+ require 'sshd'
3
+
4
+ require 'expect/ssh'
5
+
6
+ class TestExpectSSH < Test::Unit::TestCase
7
+
8
+ def self.startup
9
+ @@hostname = '127.0.0.1'
10
+ @@username = ENV['USER']
11
+ @@sshd = SSHD.new
12
+ @@sshd.start
13
+ @@port = @@sshd.port
14
+ end
15
+
16
+
17
+ def self.shutdown
18
+ @@sshd.teardown
19
+ end
20
+
21
+ context :init_and_start do
22
+
23
+ setup do
24
+ @ssh = Expect::SSH.new(@@hostname, @@username, port: @@port, key_file: @@sshd.client_key_path, ignore_known_hosts: true)
25
+ end
26
+
27
+ should "return an options hash when calling #options" do
28
+ expected = {
29
+ :auth_methods => ["none", "publickey", "password"],
30
+ :keys => [@@sshd.client_key_path],
31
+ :logger => nil,
32
+ :port => @@port,
33
+ :user_known_hosts_file => "/dev/null"
34
+ }
35
+ result = @ssh.send(:options)
36
+ result[:logger] = nil
37
+ assert_equal(expected, result)
38
+ end
39
+ end
40
+
41
+ context :startup do
42
+
43
+ setup do
44
+ @ssh = Expect::SSH.new(@@hostname, @@username, port: @@port, key_file: @@sshd.client_key_path, ignore_known_hosts: true)
45
+ end
46
+
47
+ should "be able to authenticate" do
48
+ @ssh.start
49
+ @ssh.stop
50
+ end
51
+
52
+ end
53
+
54
+ context :expect do
55
+
56
+ setup do
57
+ @ssh = Expect::SSH.new(@@hostname, @@username, port: @@port, key_file: @@sshd.client_key_path, ignore_known_hosts: true)
58
+ @ssh.start
59
+ end
60
+
61
+ teardown do
62
+ @ssh.stop
63
+ end
64
+
65
+ should "be able to send a few commands" do
66
+ result = @ssh.expect do
67
+ when_matching(/.*\$.*/) { @exp_match }
68
+ end
69
+ assert_match(/Last login:/, result.to_s)
70
+ @ssh.send_data("cat /etc/resolv.conf")
71
+ result = @ssh.expect do
72
+ when_matching(/.*nameserver.*/) { @exp_match }
73
+ end
74
+ assert_match(/nameserver [\d\.]+.*/, result.exact_match_string)
75
+ @ssh.send_data("date")
76
+ result = @ssh.expect do
77
+ when_matching(/.*20.*/) { @exp_match }
78
+ end
79
+ assert_match(/20/, result.exact_match_string)
80
+ end
81
+
82
+ should "timeout as expected" do
83
+ result = @ssh.expect do
84
+ when_matching(/.*\$.*/) { @exp_match }
85
+ end
86
+ assert_match(/Last login:/, result.to_s)
87
+ @ssh.send_data("sleep 2")
88
+ result = @ssh.expect do
89
+ when_matching(/.*\$.*/) { @exp_match }
90
+ when_timeout(1) { "timeout" }
91
+ end
92
+ assert_match(/timeout/, result)
93
+ end
94
+
95
+ end
96
+
97
+ end
@@ -0,0 +1,33 @@
1
+ require 'expect/behavior'
2
+
3
+ class ClassIncludingExpectBehavior
4
+ include Expect::Behavior
5
+ # :Required methods to be created by the class mixing Expect::Behaviors :
6
+ # #exp_process - should do one iteration of handle input and append buffer
7
+ # #exp_buffer - provide the current buffer contents and empty it
8
+
9
+ attr_accessor :exp_buffer_values, :wait_sec
10
+
11
+ def initialize(wait: nil, values: [])
12
+ @exp_buffer = ''
13
+ @exp_buffer_values = values
14
+ @wait_sec = wait
15
+ end
16
+
17
+ ##
18
+ # exp_buffer - provide the current buffer contents and empty it
19
+ def exp_buffer
20
+ result = @exp_buffer
21
+ @exp_buffer = ''
22
+ result
23
+ end
24
+
25
+ ##
26
+ # exp_process - should do one iteration of handle input and append buffer
27
+ def exp_process
28
+ sleep(@wait_sec.to_f)
29
+ # handle input
30
+ @exp_buffer << @exp_buffer_values.shift.to_s
31
+ end
32
+
33
+ end
@@ -0,0 +1,22 @@
1
+ require "codeclimate-test-reporter"
2
+ CodeClimate::TestReporter.start
3
+
4
+ require 'rubygems'
5
+ require 'bundler'
6
+ gem 'mocha'
7
+ require 'mocha/test_unit'
8
+ begin
9
+ Bundler.setup(:default, :development)
10
+ rescue Bundler::BundlerError => e
11
+ $stderr.puts e.message
12
+ $stderr.puts "Run `bundle install` to install missing gems"
13
+ exit e.status_code
14
+ end
15
+ require 'test/unit'
16
+ require 'shoulda'
17
+
18
+ # add lib folder from parent
19
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
20
+ # ...and the test folder
21
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
22
+ require 'expect/behavior'
@@ -0,0 +1,139 @@
1
+ require 'erb'
2
+ require 'fileutils'
3
+ require 'socket'
4
+
5
+
6
+ ##
7
+ # A Helper to start a second instance of SSHD on an unprivilege port which allows for a custom client key
8
+ # All keys for this server are created just in time.
9
+ class SSHD
10
+
11
+ TEST_ROOT = File.dirname(__FILE__)
12
+ SSHD_CFG_ROOT = File.join(TEST_ROOT, 'acceptance', 'sshd')
13
+ SSHD_TMP_ROOT = File.join(TEST_ROOT, 'tmp', 'sshd')
14
+ PIDFILE_PATH = File.join(SSHD_TMP_ROOT, 'sshd.pid')
15
+ ERB_ROOT = File.join(SSHD_CFG_ROOT, 'erb')
16
+ SSHD_CONFIG_ERB_PATH = File.join(ERB_ROOT, 'sshd_config.erb')
17
+ SSH_CONFIG_ERB_PATH = File.join(ERB_ROOT, 'ssh_config.erb')
18
+ SSHD_CONFIG_PATH = File.join(SSHD_TMP_ROOT, 'sshd_config')
19
+ SSH_CONFIG_PATH = File.join(SSHD_TMP_ROOT, 'ssh_config')
20
+ SSHD_LOG_PATH = File.join(SSHD_TMP_ROOT, 'sshd.log')
21
+
22
+ KEY_ROOT = SSHD_TMP_ROOT
23
+ SSHD_CLIENT_KEY_PATH = File.join(KEY_ROOT, 'id_rsa')
24
+ SSHD_CLIENT_PUBKEY_PATH = File.join(KEY_ROOT, 'id_rsa.pub')
25
+ SSHD_RSA_HOST_KEY_PATH = File.join(KEY_ROOT, 'ssh_host_key_rsa')
26
+ SSHD_DSA_HOST_KEY_PATH = File.join(KEY_ROOT, 'ssh_host_key_dsa')
27
+ SSHD_AUTHORIZED_KEYS_PATH = SSHD_CLIENT_PUBKEY_PATH
28
+
29
+ attr_reader :port
30
+
31
+ def initialize(address = '127.0.0.1')
32
+ @tcpserver = nil
33
+ @address = address
34
+ @port = reserve_unprivileged_tcp_port
35
+ @sshd_filepath = %x(which sshd).chomp
36
+ @ssh_keygen_filepath = %x(which ssh-keygen).chomp
37
+ teardown
38
+ end
39
+
40
+ def client_key_path
41
+ SSHD_CLIENT_KEY_PATH
42
+ end
43
+
44
+ def start
45
+ unless openssh_files_found?
46
+ raise(RuntimeError, "[SSHD] Error: Unable to locate sshd or ssh-keygen.")
47
+ end
48
+ create_sshd_config
49
+ create_ssh_config
50
+ generate_keys
51
+ release_port
52
+ start_ssh_server
53
+ end
54
+
55
+ def stop
56
+ this_pid = pid
57
+ unless this_pid.nil?
58
+ $stdout.puts("[SSHD] [#{__method__}]: Killing SSHD, [pid=#{this_pid}]")
59
+ begin
60
+ Process.kill(0, this_pid)
61
+ rescue Errno::ESRCH
62
+ $stderr.puts("[SSHD] [#{__method__}]: No Action: Process not found, [pid=#{this_pid}]")
63
+ ensure
64
+ File.delete(PIDFILE_PATH) if File.exists?(PIDFILE_PATH)
65
+ end
66
+ else
67
+ $stderr.puts("[SSHD] [#{__method__}]: No Action: PIDFILE Doesnt exist: #{PIDFILE_PATH}")
68
+ end
69
+ end
70
+
71
+ def teardown
72
+ stop
73
+ clean_tmp_root
74
+ end
75
+
76
+
77
+ #####################
78
+ private
79
+ #####################
80
+
81
+ def clean_tmp_root
82
+ FileUtils.rmtree(SSHD_TMP_ROOT)
83
+ FileUtils.mkpath(SSHD_TMP_ROOT)
84
+ nil
85
+ end
86
+
87
+ def create_ssh_config
88
+ erb = ERB.new(IO.read(SSH_CONFIG_ERB_PATH))
89
+ result = erb.result(binding)
90
+ File.open(SSH_CONFIG_PATH, 'w') {|f| f.write(result)}
91
+ result
92
+ end
93
+
94
+ def create_sshd_config
95
+ erb = ERB.new(IO.read(SSHD_CONFIG_ERB_PATH))
96
+ result = erb.result(binding)
97
+ File.open(SSHD_CONFIG_PATH, 'w') {|f| f.write(result)}
98
+ result
99
+ end
100
+
101
+ def generate_keys
102
+ [SSHD_CLIENT_KEY_PATH, SSHD_RSA_HOST_KEY_PATH, SSHD_DSA_HOST_KEY_PATH].each do |key_path|
103
+ %x(#{@ssh_keygen_filepath} -t rsa -b 4096 -C user@localhost -f #{key_path} -N '')
104
+ end
105
+ end
106
+
107
+ def openssh_files_found?
108
+ not @ssh_keygen_filepath.empty? and not @sshd_filepath.empty?
109
+ end
110
+
111
+ def pid
112
+ pid = nil
113
+ if File.exists?(PIDFILE_PATH)
114
+ pid = IO.read(PIDFILE_PATH).chomp.to_i
115
+ end
116
+ pid
117
+ end
118
+
119
+ def reserve_unprivileged_tcp_port
120
+ @tcpserver ||= TCPServer.new(@host, 0)
121
+ @port = @tcpserver.addr[1]
122
+ end
123
+
124
+ def release_port
125
+ @tcpserver.close
126
+ @tcpserver = nil
127
+ @port
128
+ end
129
+
130
+ def start_ssh_server
131
+ %x(#{@sshd_filepath} -4 -f #{SSHD_CONFIG_PATH})
132
+ # -E #{SSHD_LOG_PATH}
133
+ $stdout.puts("[SSHD] [#{__method__}]: Starting on [port=#{@port}]")
134
+ sleep(0.5)
135
+ $stdout.puts("[SSHD] [#{__method__}]: Started on [port=#{@port}] [pid=#{pid}]")
136
+ pid
137
+ end
138
+ end
139
+
@@ -0,0 +1,178 @@
1
+ require 'helper'
2
+ require 'expect/behavior'
3
+ require 'class_including_expect_behavior'
4
+ gem 'mocha'
5
+ require 'mocha/test_unit'
6
+
7
+ class TestExpectBehaviors < Test::Unit::TestCase
8
+
9
+ ####################################
10
+ context "expect" do
11
+ setup do
12
+ @values = []
13
+ @values << "the sun is a mass"
14
+ @values << "\nof incandescent gas"
15
+ @values << "\nswitch-prompt#"
16
+ @values << "\nblip blip"
17
+ @values << "\nblah blah"
18
+ @values << "\nswitch-prompt2#"
19
+ @includer = ClassIncludingExpectBehavior.new(values: @values)
20
+ @wait_sec = 0.1
21
+ end
22
+
23
+ should "match up to first switch-prompt" do
24
+ result = @includer.expect do
25
+ when_matching(/switch-prompt#/) do
26
+ @exp_match
27
+ end
28
+ end
29
+ expected = "the sun is a mass\nof incandescent gas\nswitch-prompt#"
30
+ assert_equal(expected, result.to_s)
31
+ end
32
+
33
+ should "timeout for switch-prompt2#" do
34
+ @includer.wait_sec = @wait_sec
35
+ @includer.exp_timeout_sec = @wait_sec * 4
36
+ result = @includer.expect do
37
+ when_matching(/switch-prompt2#/) do
38
+ @exp_match
39
+ end
40
+ when_timeout(@wait_sec * 3) do
41
+ "timed out"
42
+ end
43
+ end
44
+ assert_equal('timed out', result.to_s)
45
+ end
46
+
47
+ should "not timeout for switch-prompt2# if expect_continue for blip" do
48
+ omit("See acceptance version of this test... doesn't work for such a short interval: (#{@wait_sec}")
49
+ end
50
+
51
+ should "timeout for switch-prompt2# if expect_continue for incandescent" do
52
+ omit("See acceptance version of this test... doesn't work for such a short interval: (#{@wait_sec}")
53
+ end
54
+
55
+
56
+ should "handle subsequent expect statements" do
57
+ @includer.wait_sec = @wait_sec
58
+ @includer.expect do
59
+ when_matching(/incandescent/) do
60
+ @exp_match
61
+ end
62
+ when_timeout(@wait_sec * 3) do
63
+ "timed out"
64
+ end
65
+ end
66
+ result = @includer.expect do
67
+ when_matching(/switch-prompt#/) do
68
+ @exp_match
69
+ end
70
+ when_timeout(@wait_sec * 3) do
71
+ "timed out"
72
+ end
73
+ end
74
+ expected = "\nswitch-prompt#"
75
+ assert_equal(expected, result.to_s)
76
+ end
77
+
78
+ end
79
+
80
+
81
+ ####################################
82
+ context "initialize_expect" do
83
+ setup do
84
+ @includer = ClassIncludingExpectBehavior.new(values: ["bobert"])
85
+ end
86
+
87
+ should "be run when calling expect" do
88
+ @includer.expects(:initialize_expect)
89
+ @includer.expects(:execute_expect_loop)
90
+ @includer.expect do
91
+ when_matching(/bob/) do
92
+ return_value
93
+ end
94
+ end
95
+ end
96
+
97
+ should "key instance variables undefined before first expect" do
98
+ # undefined/nil before first expect
99
+ assert_equal(nil, @includer.instance_variable_get(:@exp_match_registry))
100
+ assert_equal(nil, @includer.instance_variable_get(:@exp_timeout_sec))
101
+ assert_equal(nil, @includer.instance_variable_get(:@exp_match))
102
+ assert_equal(nil, @includer.instance_variable_get(:@exp_timeout_block))
103
+ assert_equal(nil, @includer.instance_variable_get(:@__exp_buffer))
104
+ end
105
+
106
+ should "setup instance variables prior to expect" do
107
+ @includer.expects(:execute_expect_loop) #stub out expect loop
108
+ @includer.expect do
109
+ "bob"
110
+ end
111
+ assert_equal({}, @includer.instance_variable_get(:@exp_match_registry))
112
+ assert_equal(10, @includer.instance_variable_get(:@exp_timeout_sec))
113
+ assert_equal(nil, @includer.instance_variable_get(:@exp_match))
114
+ assert_equal(nil, @includer.instance_variable_get(:@exp_timeout_block))
115
+ assert_equal('', @includer.instance_variable_get(:@__exp_buffer))
116
+ end
117
+ end
118
+
119
+
120
+ ####################################
121
+ context "match registry" do
122
+ setup do
123
+ @includer = ClassIncludingExpectBehavior.new
124
+ end
125
+
126
+ should "be populated by when_matching statements" do
127
+ @includer.stubs(:execute_expect_loop)
128
+ return_value = "Matched BOB"
129
+ @includer.expect do
130
+ when_matching(/bob/) do
131
+ return_value
132
+ end
133
+ when_matching(/joe/) do
134
+ "Matched JOE"
135
+ end
136
+ when_timeout do
137
+ "TIMEOUT"
138
+ end
139
+ end
140
+ assert_equal(/bob/, @includer.instance_variable_get(:@exp_match_registry).keys.first)
141
+ assert_equal(2, @includer.instance_variable_get(:@exp_match_registry).length)
142
+ assert_equal(return_value, @includer.instance_variable_get(:@exp_match_registry).values.first.call)
143
+ assert_equal("TIMEOUT", @includer.instance_variable_get(:@exp_timeout_block).call)
144
+ end
145
+ end
146
+
147
+
148
+ ####################################
149
+ context "timeout" do
150
+ setup do
151
+ @includer = ClassIncludingExpectBehavior.new(wait: 0.2)
152
+ end
153
+
154
+ should "raise TimeoutError when timeout is reached before match is found" do
155
+ @includer.exp_timeout_sec = 0.1
156
+ assert_raises(Expect::TimeoutError) do
157
+ @includer.expect do
158
+ when_matching(/bob/) do
159
+ return_value
160
+ end
161
+ end
162
+ end
163
+ end
164
+
165
+ should "execute arbitrary block on timeout with override" do
166
+ result = @includer.expect do
167
+ when_matching(/bob/) do
168
+ return_value
169
+ end
170
+ when_timeout(0.1) do
171
+ "timeout"
172
+ end
173
+ end
174
+ assert_equal('timeout', result)
175
+ end
176
+ end
177
+
178
+ end