expect-behaviors 0.1.1

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,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