test-kitchen 1.3.1 → 1.4.0.beta.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (101) hide show
  1. checksums.yaml +4 -4
  2. data/.cane +2 -0
  3. data/.gitignore +4 -0
  4. data/CHANGELOG.md +45 -0
  5. data/Rakefile +15 -0
  6. data/features/kitchen_action_commands.feature +12 -9
  7. data/features/kitchen_defaults.feature +38 -0
  8. data/features/kitchen_init_command.feature +0 -1
  9. data/features/kitchen_list_command.feature +2 -2
  10. data/features/kitchen_login_command.feature +7 -1
  11. data/features/kitchen_test_command.feature +4 -4
  12. data/lib/kitchen.rb +40 -11
  13. data/lib/kitchen/cli.rb +38 -22
  14. data/lib/kitchen/command/list.rb +5 -2
  15. data/lib/kitchen/config.rb +45 -18
  16. data/lib/kitchen/configurable.rb +137 -1
  17. data/lib/kitchen/data_munger.rb +248 -17
  18. data/lib/kitchen/driver.rb +1 -1
  19. data/lib/kitchen/driver/base.rb +1 -83
  20. data/lib/kitchen/driver/dummy.rb +0 -5
  21. data/lib/kitchen/driver/ssh_base.rb +177 -22
  22. data/lib/kitchen/instance.rb +140 -20
  23. data/lib/kitchen/logger.rb +43 -8
  24. data/lib/kitchen/login_command.rb +14 -5
  25. data/lib/kitchen/platform.rb +19 -0
  26. data/lib/kitchen/provisioner.rb +5 -3
  27. data/lib/kitchen/provisioner/base.rb +46 -48
  28. data/lib/kitchen/provisioner/chef/common_sandbox.rb +322 -0
  29. data/lib/kitchen/provisioner/chef_base.rb +179 -286
  30. data/lib/kitchen/provisioner/chef_solo.rb +11 -5
  31. data/lib/kitchen/provisioner/chef_zero.rb +108 -94
  32. data/lib/kitchen/provisioner/dummy.rb +47 -0
  33. data/lib/kitchen/provisioner/shell.rb +45 -12
  34. data/lib/kitchen/rake_tasks.rb +1 -1
  35. data/lib/kitchen/ssh.rb +1 -1
  36. data/lib/kitchen/thor_tasks.rb +1 -1
  37. data/lib/kitchen/transport.rb +54 -0
  38. data/lib/kitchen/transport/base.rb +146 -0
  39. data/lib/kitchen/transport/dummy.rb +75 -0
  40. data/lib/kitchen/transport/ssh.rb +325 -0
  41. data/lib/kitchen/transport/winrm.rb +508 -0
  42. data/lib/kitchen/transport/winrm/command_executor.rb +188 -0
  43. data/lib/kitchen/transport/winrm/file_transporter.rb +454 -0
  44. data/lib/kitchen/transport/winrm/logging.rb +50 -0
  45. data/lib/kitchen/transport/winrm/template.rb +74 -0
  46. data/lib/kitchen/transport/winrm/tmp_zip.rb +187 -0
  47. data/lib/kitchen/verifier.rb +55 -0
  48. data/lib/kitchen/verifier/base.rb +191 -0
  49. data/lib/kitchen/verifier/busser.rb +266 -0
  50. data/lib/kitchen/verifier/dummy.rb +75 -0
  51. data/lib/kitchen/version.rb +1 -1
  52. data/spec/kitchen/cli_spec.rb +56 -0
  53. data/spec/kitchen/config_spec.rb +61 -20
  54. data/spec/kitchen/configurable_spec.rb +327 -1
  55. data/spec/kitchen/data_munger_spec.rb +777 -14
  56. data/spec/kitchen/driver/base_spec.rb +7 -38
  57. data/spec/kitchen/driver/dummy_spec.rb +0 -29
  58. data/spec/kitchen/driver/ssh_base_spec.rb +580 -236
  59. data/spec/kitchen/driver_spec.rb +1 -0
  60. data/spec/kitchen/instance_spec.rb +383 -83
  61. data/spec/kitchen/login_command_spec.rb +29 -10
  62. data/spec/kitchen/platform_spec.rb +58 -2
  63. data/spec/kitchen/provisioner/base_spec.rb +170 -18
  64. data/spec/kitchen/provisioner/chef_base_spec.rb +454 -104
  65. data/spec/kitchen/provisioner/chef_solo_spec.rb +307 -104
  66. data/spec/kitchen/provisioner/chef_zero_spec.rb +561 -230
  67. data/spec/kitchen/provisioner/dummy_spec.rb +91 -0
  68. data/spec/kitchen/provisioner/shell_spec.rb +158 -56
  69. data/spec/kitchen/provisioner_spec.rb +37 -0
  70. data/spec/kitchen/ssh_spec.rb +19 -19
  71. data/spec/kitchen/transport/base_spec.rb +89 -0
  72. data/spec/kitchen/transport/ssh_spec.rb +1147 -0
  73. data/spec/kitchen/transport/winrm/command_executor_spec.rb +400 -0
  74. data/spec/kitchen/transport/winrm/file_transporter_spec.rb +876 -0
  75. data/spec/kitchen/transport/winrm/logging_spec.rb +92 -0
  76. data/spec/kitchen/transport/winrm/template_spec.rb +51 -0
  77. data/spec/kitchen/transport/winrm/tmp_zip_spec.rb +132 -0
  78. data/spec/kitchen/transport/winrm_spec.rb +1069 -0
  79. data/spec/kitchen/transport_spec.rb +112 -0
  80. data/spec/kitchen/verifier/base_spec.rb +310 -0
  81. data/spec/kitchen/verifier/busser_spec.rb +540 -0
  82. data/spec/kitchen/verifier/dummy_spec.rb +91 -0
  83. data/spec/kitchen/verifier_spec.rb +120 -0
  84. data/spec/kitchen_spec.rb +7 -0
  85. data/spec/spec_helper.rb +8 -0
  86. data/spec/support/powershell_max_size_spec.rb +40 -0
  87. data/support/busser_install_command.ps1 +14 -0
  88. data/support/busser_install_command.sh +15 -0
  89. data/support/check_files.ps1.erb +48 -0
  90. data/support/chef_base_init_command.ps1 +18 -0
  91. data/support/chef_base_init_command.sh +2 -0
  92. data/support/chef_base_install_command.ps1 +76 -0
  93. data/support/chef_base_install_command.sh +137 -0
  94. data/support/chef_zero_prepare_command_legacy.ps1 +9 -0
  95. data/support/chef_zero_prepare_command_legacy.sh +10 -0
  96. data/support/decode_files.ps1.erb +61 -0
  97. data/test-kitchen.gemspec +2 -0
  98. metadata +97 -8
  99. data/lib/kitchen/busser.rb +0 -316
  100. data/spec/kitchen/busser_spec.rb +0 -490
  101. data/support/chef_helpers.sh +0 -16
@@ -0,0 +1,92 @@
1
+ # -*- encoding: utf-8 -*-
2
+ #
3
+ # Author:: Fletcher (<fnichol@nichol.ca>)
4
+ #
5
+ # Copyright (C) 2015, Fletcher Nichol
6
+ #
7
+ # Licensed under the Apache License, Version 2.0 (the "License");
8
+ # you may not use this file except in compliance with the License.
9
+ # You may obtain a copy of the License at
10
+ #
11
+ # http://www.apache.org/licenses/LICENSE-2.0
12
+ #
13
+ # Unless required by applicable law or agreed to in writing, software
14
+ # distributed under the License is distributed on an "AS IS" BASIS,
15
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ # See the License for the specific language governing permissions and
17
+ # limitations under the License.
18
+
19
+ require_relative "../../../spec_helper"
20
+
21
+ require "kitchen"
22
+ require "kitchen/transport/winrm/logging"
23
+
24
+ require "logger"
25
+
26
+ class ILog
27
+
28
+ include Kitchen::Transport::Winrm::Logging
29
+
30
+ attr_reader :logger
31
+
32
+ def initialize(logger)
33
+ @logger = logger
34
+ end
35
+ end
36
+
37
+ class CustomSubject < ILog
38
+
39
+ def log_subject
40
+ "Wham"
41
+ end
42
+ end
43
+
44
+ describe Kitchen::Transport::Winrm::Logging do
45
+
46
+ let(:logged_output) { StringIO.new }
47
+ let(:logger) { Logger.new(logged_output) }
48
+
49
+ describe "#debug" do
50
+
51
+ it "nothing happens if logger is nil" do
52
+ ILog.new(nil).debug("I'm cool")
53
+
54
+ logged_output.string.must_equal ""
55
+ end
56
+
57
+ it "nothing happens if logger is above debug level" do
58
+ logger.level = Logger::INFO
59
+ ILog.new(logger).debug("I'm skipped")
60
+
61
+ logged_output.string.must_equal ""
62
+ end
63
+
64
+ it "message is logged on debug level" do
65
+ ILog.new(logger).debug("Debugging is fun")
66
+
67
+ logged_output.string.must_match debug_line("[ILog] Debugging is fun")
68
+ end
69
+
70
+ it "block is called and used for debug level" do
71
+ ILog.new(logger).debug { "Debugging is expensive" }
72
+
73
+ logged_output.string.must_match debug_line("[ILog] Debugging is expensive")
74
+ end
75
+
76
+ it "message is used over block" do
77
+ ILog.new(logger).debug("I win") { "I lose" }
78
+
79
+ logged_output.string.must_match debug_line("[ILog] I win")
80
+ end
81
+
82
+ it "custom log subject is used if overridden" do
83
+ CustomSubject.new(logger).debug("Bam")
84
+
85
+ logged_output.string.must_match debug_line("[Wham] Bam")
86
+ end
87
+ end
88
+
89
+ def debug_line(msg)
90
+ %r{^D, .* : #{Regexp.escape(msg)}$}
91
+ end
92
+ end
@@ -0,0 +1,51 @@
1
+ # -*- encoding: utf-8 -*-
2
+ #
3
+ # Author:: Fletcher (<fnichol@nichol.ca>)
4
+ #
5
+ # Copyright (C) 2015, Fletcher Nichol
6
+ #
7
+ # Licensed under the Apache License, Version 2.0 (the "License");
8
+ # you may not use this file except in compliance with the License.
9
+ # You may obtain a copy of the License at
10
+ #
11
+ # http://www.apache.org/licenses/LICENSE-2.0
12
+ #
13
+ # Unless required by applicable law or agreed to in writing, software
14
+ # distributed under the License is distributed on an "AS IS" BASIS,
15
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ # See the License for the specific language governing permissions and
17
+ # limitations under the License.
18
+
19
+ require_relative "../../../spec_helper"
20
+
21
+ require "kitchen"
22
+ require "kitchen/transport/winrm/template"
23
+
24
+ describe Kitchen::Transport::Winrm::Template do
25
+
26
+ let(:path) { "/tmp/tmpl.erb" }
27
+
28
+ let(:template) do
29
+ Kitchen::Transport::Winrm::Template.new(path)
30
+ end
31
+
32
+ it "#render returns the ERb template rendered with the hash context" do
33
+ with_fake_fs do
34
+ create_template(path)
35
+ template.render(:greeting => "Hello", :user => "Fletcher").
36
+ must_equal "Hello, Fletcher!"
37
+ end
38
+ end
39
+
40
+ it "#% returns the ERb template rendered with the hash context" do
41
+ with_fake_fs do
42
+ create_template(path)
43
+ (template % { :greeting => "Hello", :user => "Fletcher" }).
44
+ must_equal "Hello, Fletcher!"
45
+ end
46
+ end
47
+
48
+ def create_template(path)
49
+ File.open(path, "wb") { |f| f.write("<%= greeting %>, <%= user %>!") }
50
+ end
51
+ end
@@ -0,0 +1,132 @@
1
+ # -*- encoding: utf-8 -*-
2
+ #
3
+ # Author:: Fletcher (<fnichol@nichol.ca>)
4
+ #
5
+ # Copyright (C) 2015, Fletcher Nichol
6
+ #
7
+ # Licensed under the Apache License, Version 2.0 (the "License");
8
+ # you may not use this file except in compliance with the License.
9
+ # You may obtain a copy of the License at
10
+ #
11
+ # http://www.apache.org/licenses/LICENSE-2.0
12
+ #
13
+ # Unless required by applicable law or agreed to in writing, software
14
+ # distributed under the License is distributed on an "AS IS" BASIS,
15
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ # See the License for the specific language governing permissions and
17
+ # limitations under the License.
18
+
19
+ require_relative "../../../spec_helper"
20
+
21
+ require "kitchen"
22
+ require "kitchen/transport/winrm/tmp_zip"
23
+
24
+ require "logger"
25
+
26
+ describe Kitchen::Transport::Winrm::TmpZip do
27
+
28
+ let(:logged_output) { StringIO.new }
29
+ let(:logger) { Logger.new(logged_output) }
30
+
31
+ let(:src_dir) do
32
+ tmpdir = Pathname.new(Dir.mktmpdir)
33
+ @tmpdirs << tmpdir
34
+ src_dir = tmpdir.join("src")
35
+ sub_dir = src_dir.join("veggies")
36
+
37
+ src_dir.mkpath
38
+ create_local_file(src_dir.join("apple.txt"), "appleapple")
39
+ create_local_file(src_dir.join("banana.txt"), "bananabanana")
40
+ create_local_file(src_dir.join("cherry.txt"), "cherrycherry")
41
+ sub_dir.mkpath
42
+ create_local_file(sub_dir.join("carrot.txt"), "carrotcarrot")
43
+ src_dir
44
+ end
45
+
46
+ let(:tmp_zip) { Kitchen::Transport::Winrm::TmpZip.new(src_dir, logger) }
47
+
48
+ before { @tmpdirs = [] }
49
+
50
+ after do
51
+ @tmpdirs.each(&:rmtree)
52
+ tmp_zip.unlink if tmp_zip.path
53
+ end
54
+
55
+ it "#path returns path to created zip file" do
56
+ tmp_zip.path.file?.must_equal true
57
+ end
58
+
59
+ it "#unlink removes the file" do
60
+ path = tmp_zip.path
61
+ path.file?.must_equal true
62
+
63
+ tmp_zip.unlink
64
+
65
+ path.file?.must_equal false
66
+ tmp_zip.path.must_equal nil
67
+ end
68
+
69
+ describe "for a zip file containing the base directory" do
70
+
71
+ let(:tmp_zip) { Kitchen::Transport::Winrm::TmpZip.new(src_dir, logger) }
72
+
73
+ it "contains the input entries" do
74
+ zip = Zip::File.new(tmp_zip.path)
75
+
76
+ zip.map(&:name).sort.must_equal(
77
+ %W[src/apple.txt src/banana.txt src/cherry.txt src/veggies/carrot.txt]
78
+ )
79
+ zip.read("src/apple.txt").must_equal "appleapple"
80
+ zip.read("src/banana.txt").must_equal "bananabanana"
81
+ zip.read("src/cherry.txt").must_equal "cherrycherry"
82
+ zip.read("src/veggies/carrot.txt").must_equal "carrotcarrot"
83
+ end
84
+
85
+ it "logs to debug" do
86
+ tmp_zip
87
+ subject = "[TmpZip::#{tmp_zip.path}]"
88
+ logged = logged_output.string
89
+
90
+ logged.must_match debug_line("#{subject} +++ Adding src/apple.txt")
91
+ logged.must_match debug_line("#{subject} +++ Adding src/banana.txt")
92
+ logged.must_match debug_line("#{subject} +++ Adding src/cherry.txt")
93
+ logged.must_match debug_line("#{subject} +++ Adding src/veggies/carrot.txt")
94
+ end
95
+ end
96
+
97
+ describe "for a zip file containing entries under the base directory" do
98
+
99
+ let(:tmp_zip) { Kitchen::Transport::Winrm::TmpZip.new("#{src_dir}/", logger) }
100
+
101
+ it "contains the input entries" do
102
+ zip = Zip::File.new(tmp_zip.path)
103
+
104
+ zip.map(&:name).sort.must_equal(
105
+ %W[apple.txt banana.txt cherry.txt veggies/carrot.txt]
106
+ )
107
+ zip.read("apple.txt").must_equal "appleapple"
108
+ zip.read("banana.txt").must_equal "bananabanana"
109
+ zip.read("cherry.txt").must_equal "cherrycherry"
110
+ zip.read("veggies/carrot.txt").must_equal "carrotcarrot"
111
+ end
112
+
113
+ it "logs to debug" do
114
+ tmp_zip
115
+ subject = "[TmpZip::#{tmp_zip.path}]"
116
+ logged = logged_output.string
117
+
118
+ logged.must_match debug_line("#{subject} +++ Adding apple.txt")
119
+ logged.must_match debug_line("#{subject} +++ Adding banana.txt")
120
+ logged.must_match debug_line("#{subject} +++ Adding cherry.txt")
121
+ logged.must_match debug_line("#{subject} +++ Adding veggies/carrot.txt")
122
+ end
123
+ end
124
+
125
+ def create_local_file(path, content)
126
+ path.open("wb") { |file| file.write(content) }
127
+ end
128
+
129
+ def debug_line(msg)
130
+ %r{^D, .* : #{Regexp.escape(msg)}$}
131
+ end
132
+ end
@@ -0,0 +1,1069 @@
1
+ # -*- encoding: utf-8 -*-
2
+ #
3
+ # Author:: Matt Wrock (<matt@mattwrock.com>)
4
+ #
5
+ # Copyright (C) 2014, Matt Wrock
6
+ #
7
+ # Licensed under the Apache License, Version 2.0 (the "License");
8
+ # you may not use this file except in compliance with the License.
9
+ # You may obtain a copy of the License at
10
+ #
11
+ # http://www.apache.org/licenses/LICENSE-2.0
12
+ #
13
+ # Unless required by applicable law or agreed to in writing, software
14
+ # distributed under the License is distributed on an "AS IS" BASIS,
15
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ # See the License for the specific language governing permissions and
17
+ # limitations under the License.
18
+
19
+ require_relative "../../spec_helper"
20
+
21
+ require "kitchen/transport/winrm"
22
+
23
+ describe Kitchen::Transport::Winrm do
24
+
25
+ let(:logged_output) { StringIO.new }
26
+ let(:logger) { Logger.new(logged_output) }
27
+ let(:config) { Hash.new }
28
+ let(:state) { Hash.new }
29
+
30
+ let(:instance) do
31
+ stub(:name => "coolbeans", :logger => logger, :to_str => "instance")
32
+ end
33
+
34
+ let(:transport) do
35
+ Kitchen::Transport::Winrm.new(config).finalize_config!(instance)
36
+ end
37
+
38
+ describe "default_config" do
39
+
40
+ it "sets :port to 5985 by default" do
41
+ transport[:port].must_equal 5985
42
+ end
43
+
44
+ it "sets :username to .\\administrator by default" do
45
+ transport[:username].must_equal ".\\administrator"
46
+ end
47
+
48
+ it "sets :password to nil by default" do
49
+ transport[:password].must_equal nil
50
+ end
51
+
52
+ it "sets a default :endpoint_template value" do
53
+ transport[:endpoint_template].
54
+ must_equal "http://%{hostname}:%{port}/wsman"
55
+ end
56
+
57
+ it "sets :rdp_port to 3389 by default" do
58
+ transport[:rdp_port].must_equal 3389
59
+ end
60
+
61
+ it "sets :connection_retries to 5 by default" do
62
+ transport[:connection_retries].must_equal 5
63
+ end
64
+
65
+ it "sets :connection_retry_sleep to 1 by default" do
66
+ transport[:connection_retry_sleep].must_equal 1
67
+ end
68
+
69
+ it "sets :max_wait_until_ready to 600 by default" do
70
+ transport[:max_wait_until_ready].must_equal 600
71
+ end
72
+ end
73
+
74
+ describe "#connection" do
75
+
76
+ let(:klass) { Kitchen::Transport::Winrm::Connection }
77
+
78
+ # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
79
+ def self.common_connection_specs
80
+ before do
81
+ config[:hostname] = "here"
82
+ config[:kitchen_root] = "/i/am/root"
83
+ end
84
+
85
+ it "returns a Kitchen::Transport::Winrm::Connection object" do
86
+ transport.connection(state).must_be_kind_of klass
87
+ end
88
+
89
+ it "sets :instance_name to the instance's name" do
90
+ klass.expects(:new).with do |hash|
91
+ hash[:instance_name] == "coolbeans"
92
+ end
93
+
94
+ make_connection
95
+ end
96
+ it "sets :kitchen_root to the transport's kitchen_root" do
97
+ klass.expects(:new).with do |hash|
98
+ hash[:kitchen_root] == "/i/am/root"
99
+ end
100
+
101
+ make_connection
102
+ end
103
+
104
+ it "sets the :logger to the transport's logger" do
105
+ klass.expects(:new).with do |hash|
106
+ hash[:logger] == logger
107
+ end
108
+
109
+ make_connection
110
+ end
111
+
112
+ it "sets the :winrm_transport to :plaintext" do
113
+ klass.expects(:new).with do |hash|
114
+ hash[:winrm_transport] == :plaintext
115
+ end
116
+
117
+ make_connection
118
+ end
119
+
120
+ it "sets the :disable_sspi to true" do
121
+ klass.expects(:new).with do |hash|
122
+ hash[:disable_sspi] == true
123
+ end
124
+
125
+ make_connection
126
+ end
127
+
128
+ it "sets the :basic_auth_only to true" do
129
+ klass.expects(:new).with do |hash|
130
+ hash[:basic_auth_only] == true
131
+ end
132
+
133
+ make_connection
134
+ end
135
+
136
+ it "sets :endpoint from data in config" do
137
+ config[:hostname] = "host_from_config"
138
+ config[:port] = "port_from_config"
139
+
140
+ klass.expects(:new).with do |hash|
141
+ hash[:endpoint] == "http://host_from_config:port_from_config/wsman"
142
+ end
143
+
144
+ make_connection
145
+ end
146
+
147
+ it "sets :endpoint from data in state over config data" do
148
+ state[:hostname] = "host_from_state"
149
+ config[:hostname] = "host_from_config"
150
+ state[:port] = "port_from_state"
151
+ config[:port] = "port_from_config"
152
+
153
+ klass.expects(:new).with do |hash|
154
+ hash[:endpoint] == "http://host_from_state:port_from_state/wsman"
155
+ end
156
+
157
+ make_connection
158
+ end
159
+
160
+ it "sets :user from :username in config" do
161
+ config[:username] = "user_from_config"
162
+
163
+ klass.expects(:new).with do |hash|
164
+ hash[:user] == "user_from_config"
165
+ end
166
+
167
+ make_connection
168
+ end
169
+
170
+ it "sets :user from :username in state over config data" do
171
+ state[:username] = "user_from_state"
172
+ config[:username] = "user_from_config"
173
+
174
+ klass.expects(:new).with do |hash|
175
+ hash[:user] == "user_from_state"
176
+ end
177
+
178
+ make_connection
179
+ end
180
+
181
+ it "sets :pass from :password in config" do
182
+ config[:password] = "pass_from_config"
183
+
184
+ klass.expects(:new).with do |hash|
185
+ hash[:pass] == "pass_from_config"
186
+ end
187
+
188
+ make_connection
189
+ end
190
+
191
+ it "sets :pass from :password in state over config data" do
192
+ state[:password] = "pass_from_state"
193
+ config[:password] = "pass_from_config"
194
+
195
+ klass.expects(:new).with do |hash|
196
+ hash[:pass] == "pass_from_state"
197
+ end
198
+
199
+ make_connection
200
+ end
201
+
202
+ it "sets :rdp_port from config" do
203
+ config[:rdp_port] = "rdp_from_config"
204
+
205
+ klass.expects(:new).with do |hash|
206
+ hash[:rdp_port] == "rdp_from_config"
207
+ end
208
+
209
+ make_connection
210
+ end
211
+
212
+ it "sets :rdp_port from state over config data" do
213
+ state[:rdp_port] = "rdp_from_state"
214
+ config[:rdp_port] = "rdp_from_config"
215
+
216
+ klass.expects(:new).with do |hash|
217
+ hash[:rdp_port] == "rdp_from_state"
218
+ end
219
+
220
+ make_connection
221
+ end
222
+
223
+ it "sets :connection_retries from config" do
224
+ config[:connection_retries] = "retries_from_config"
225
+
226
+ klass.expects(:new).with do |hash|
227
+ hash[:connection_retries] == "retries_from_config"
228
+ end
229
+
230
+ make_connection
231
+ end
232
+
233
+ it "sets :connection_retries from state over config data" do
234
+ state[:connection_retries] = "retries_from_state"
235
+ config[:connection_retries] = "retries_from_config"
236
+
237
+ klass.expects(:new).with do |hash|
238
+ hash[:connection_retries] == "retries_from_state"
239
+ end
240
+
241
+ make_connection
242
+ end
243
+
244
+ it "sets :connection_retry_sleep from config" do
245
+ config[:connection_retry_sleep] = "sleep_from_config"
246
+
247
+ klass.expects(:new).with do |hash|
248
+ hash[:connection_retry_sleep] == "sleep_from_config"
249
+ end
250
+
251
+ make_connection
252
+ end
253
+
254
+ it "sets :connection_retry_sleep from state over config data" do
255
+ state[:connection_retry_sleep] = "sleep_from_state"
256
+ config[:connection_retry_sleep] = "sleep_from_config"
257
+
258
+ klass.expects(:new).with do |hash|
259
+ hash[:connection_retry_sleep] == "sleep_from_state"
260
+ end
261
+
262
+ make_connection
263
+ end
264
+
265
+ it "sets :max_wait_until_ready from config" do
266
+ config[:max_wait_until_ready] = "max_from_config"
267
+
268
+ klass.expects(:new).with do |hash|
269
+ hash[:max_wait_until_ready] == "max_from_config"
270
+ end
271
+
272
+ make_connection
273
+ end
274
+
275
+ it "sets :max_wait_until_ready from state over config data" do
276
+ state[:max_wait_until_ready] = "max_from_state"
277
+ config[:max_wait_until_ready] = "max_from_config"
278
+
279
+ klass.expects(:new).with do |hash|
280
+ hash[:max_wait_until_ready] == "max_from_state"
281
+ end
282
+
283
+ make_connection
284
+ end
285
+
286
+ it "returns the same connection when called again with same state" do
287
+ first_connection = make_connection(state)
288
+ second_connection = make_connection(state)
289
+
290
+ first_connection.object_id.must_equal second_connection.object_id
291
+ end
292
+
293
+ it "logs a debug message when the connection is reused" do
294
+ make_connection(state)
295
+ make_connection(state)
296
+
297
+ logged_output.string.lines.select { |l|
298
+ l =~ debug_line_with("[WinRM] reusing existing connection ")
299
+ }.size.must_equal 1
300
+ end
301
+
302
+ it "returns a new connection when called again if state differs" do
303
+ first_connection = make_connection(state)
304
+ second_connection = make_connection(state.merge(:port => 9000))
305
+
306
+ first_connection.object_id.wont_equal second_connection.object_id
307
+ end
308
+
309
+ it "closes first connection when a second is created" do
310
+ first_connection = make_connection(state)
311
+ first_connection.expects(:close)
312
+
313
+ make_connection(state.merge(:port => 9000))
314
+ end
315
+
316
+ it "logs a debug message a second connection is created" do
317
+ make_connection(state)
318
+ make_connection(state.merge(:port => 9000))
319
+
320
+ logged_output.string.lines.select { |l|
321
+ l =~ debug_line_with("[WinRM] shutting previous connection ")
322
+ }.size.must_equal 1
323
+ end
324
+ end
325
+ # rubocop:enable Metrics/MethodLength, Metrics/AbcSize
326
+
327
+ describe "called without a block" do
328
+
329
+ def make_connection(s = state)
330
+ transport.connection(s)
331
+ end
332
+
333
+ common_connection_specs
334
+ end
335
+
336
+ describe "called with a block" do
337
+
338
+ def make_connection(s = state)
339
+ transport.connection(s) do |conn|
340
+ conn
341
+ end
342
+ end
343
+
344
+ common_connection_specs
345
+ end
346
+ end
347
+
348
+ def debug_line_with(msg)
349
+ %r{^D, .* : #{Regexp.escape(msg)}}
350
+ end
351
+ end
352
+
353
+ describe Kitchen::Transport::Winrm::Connection do
354
+
355
+ let(:logged_output) { StringIO.new }
356
+ let(:logger) { Logger.new(logged_output) }
357
+
358
+ let(:options) do
359
+ { :logger => logger, :user => "me", :pass => "haha",
360
+ :endpoint => "http://foo:5985/wsman", :winrm_transport => :plaintext,
361
+ :kitchen_root => "/i/am/root", :instance_name => "coolbeans",
362
+ :rdp_port => "rdpyeah" }
363
+ end
364
+
365
+ let(:info) do
366
+ copts = { :user => "me", :pass => "haha" }
367
+ "plaintext::http://foo:5985/wsman<#{copts}>"
368
+ end
369
+
370
+ let(:winrm_session) do
371
+ s = mock("winrm_session")
372
+ s.responds_like_instance_of(::WinRM::WinRMWebService)
373
+ s
374
+ end
375
+
376
+ let(:executor) do
377
+ s = mock("command_executor")
378
+ s.responds_like_instance_of(Kitchen::Transport::Winrm::CommandExecutor)
379
+ s
380
+ end
381
+
382
+ let(:connection) do
383
+ Kitchen::Transport::Winrm::Connection.new(options)
384
+ end
385
+
386
+ before do
387
+ logger.level = Logger::DEBUG
388
+ end
389
+
390
+ describe "#close" do
391
+
392
+ let(:response) do
393
+ o = WinRM::Output.new
394
+ o[:exitcode] = 0
395
+ o[:data].concat([{ :stdout => "ok\r\n" }])
396
+ o
397
+ end
398
+
399
+ before do
400
+ Kitchen::Transport::Winrm::CommandExecutor.stubs(:new).returns(executor)
401
+ # disable finalizer as service is a fake anyway
402
+ ObjectSpace.stubs(:define_finalizer).
403
+ with { |obj, _| obj.class == Kitchen::Transport::Winrm::Connection }
404
+ executor.stubs(:open).returns("shell-123")
405
+ executor.stubs(:shell).returns("shell-123")
406
+ executor.stubs(:close)
407
+ executor.stubs(:run_powershell_script).
408
+ with("doit").yields("ok\n", nil).returns(response)
409
+ end
410
+
411
+ it "logger displays closing connection on debug" do
412
+ connection.execute("doit")
413
+ connection.close
414
+
415
+ logged_output.string.must_match debug_line(
416
+ "[WinRM] closing remote shell shell-123 on #{info}"
417
+ )
418
+ logged_output.string.must_match debug_line(
419
+ "[WinRM] remote shell shell-123 closed"
420
+ )
421
+ end
422
+
423
+ it "only closes the shell once for multiple calls" do
424
+ executor.expects(:close).once
425
+
426
+ connection.execute("doit")
427
+ connection.close
428
+ connection.close
429
+ connection.close
430
+ end
431
+ end
432
+
433
+ describe "#execute" do
434
+
435
+ before do
436
+ Kitchen::Transport::Winrm::CommandExecutor.stubs(:new).returns(executor)
437
+ # disable finalizer as service is a fake anyway
438
+ ObjectSpace.stubs(:define_finalizer).
439
+ with { |obj, _| obj.class == Kitchen::Transport::Winrm::Connection }
440
+ end
441
+
442
+ describe "for a successful command" do
443
+
444
+ let(:response) do
445
+ o = WinRM::Output.new
446
+ o[:exitcode] = 0
447
+ o[:data].concat([
448
+ { :stdout => "ok\r\n" },
449
+ { :stderr => "congrats\r\n" }
450
+ ])
451
+ o
452
+ end
453
+
454
+ before do
455
+ executor.expects(:open).returns("shell-123")
456
+ executor.expects(:run_powershell_script).
457
+ with("doit").yields("ok\n", nil).returns(response)
458
+ end
459
+
460
+ it "logger displays command on debug" do
461
+ connection.execute("doit")
462
+
463
+ logged_output.string.must_match debug_line(
464
+ "[WinRM] #{info} (doit)")
465
+ end
466
+
467
+ it "logger displays establishing connection on debug" do
468
+ connection.execute("doit")
469
+
470
+ logged_output.string.must_match debug_line(
471
+ "[WinRM] opening remote shell on #{info}"
472
+ )
473
+ logged_output.string.must_match debug_line(
474
+ "[WinRM] remote shell shell-123 is open on #{info}"
475
+ )
476
+ end
477
+
478
+ it "logger captures stdout" do
479
+ connection.execute("doit")
480
+
481
+ logged_output.string.must_match(/^ok$/)
482
+ end
483
+
484
+ it "logger captures stderr on warn if logger is at debug level" do
485
+ logger.level = Logger::DEBUG
486
+ connection.execute("doit")
487
+
488
+ logged_output.string.must_match warn_line("congrats")
489
+ end
490
+
491
+ it "logger does not log stderr on warn if logger is below debug level" do
492
+ logger.level = Logger::INFO
493
+ connection.execute("doit")
494
+
495
+ logged_output.string.wont_match warn_line("congrats")
496
+ end
497
+ end
498
+
499
+ describe "for a failed command" do
500
+
501
+ let(:response) do
502
+ o = WinRM::Output.new
503
+ o[:exitcode] = 1
504
+ o[:data].concat([
505
+ { :stderr => "#< CLIXML\r\n" },
506
+ { :stderr => "<Objs Version=\"1.1.0.1\" xmlns=\"http://schemas." },
507
+ { :stderr => "microsoft.com/powershell/2004/04\"><S S=\"Error\">" },
508
+ { :stderr => "doit : The term 'doit' is not recognized as the " },
509
+ { :stderr => "name of a cmdlet, function, _x000D__x000A_</S>" },
510
+ { :stderr => "<S S=\"Error\">script file, or operable program. " },
511
+ { :stderr => "Check the spelling of" },
512
+ { :stderr => "the name, or if a path _x000D__x000A_</S><S S=\"E" },
513
+ { :stderr => "rror\">was included, verify that the path is corr" },
514
+ { :stderr => "ect and try again._x000D__x000A_</S><S S=\"Error" },
515
+ { :stderr => "\">At line:1 char:1_x000D__x000A_</S><S S=\"Error" },
516
+ { :stderr => "\">+ doit_x000D__x000A_</S><S S=\"Error\">+ ~~~~_" },
517
+ { :stderr => "x000D__x000A_</S><S S=\"Error\"> + CategoryInf" },
518
+ { :stderr => "o : ObjectNotFound: (doit:String) [], Co" },
519
+ { :stderr => "mmandNotFoun _x000D__x000A_</S><S S=\"Error\"> " },
520
+ { :stderr => "dException_x000D__x000A_</S><S S=\"Error\"> + " },
521
+ { :stderr => "FullyQualifiedErrorId : CommandNotFoundException_" },
522
+ { :stderr => "x000D__x000A_</S><S S=\"Error\"> _x000D__x000A_</" },
523
+ { :stderr => "S></Objs>" }
524
+ ])
525
+ o
526
+ end
527
+
528
+ before do
529
+ executor.expects(:open).returns("shell-123")
530
+ executor.expects(:run_powershell_script).
531
+ with("doit").yields("nope\n", nil).returns(response)
532
+ end
533
+
534
+ # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
535
+ def self.common_failed_command_specs
536
+ it "logger displays command on debug" do
537
+ begin
538
+ connection.execute("doit")
539
+ rescue # rubocop:disable Lint/HandleExceptions
540
+ # the raise is not what is being tested here, rather its side-effect
541
+ end
542
+
543
+ logged_output.string.must_match debug_line(
544
+ "[WinRM] #{info} (doit)"
545
+ )
546
+ end
547
+
548
+ it "logger displays establishing connection on debug" do
549
+ begin
550
+ connection.execute("doit")
551
+ rescue # rubocop:disable Lint/HandleExceptions
552
+ # the raise is not what is being tested here, rather its side-effect
553
+ end
554
+
555
+ logged_output.string.must_match debug_line(
556
+ "[WinRM] opening remote shell on #{info}"
557
+ )
558
+ logged_output.string.must_match debug_line(
559
+ "[WinRM] remote shell shell-123 is open on #{info}"
560
+ )
561
+ end
562
+
563
+ it "logger captures stdout" do
564
+ begin
565
+ connection.execute("doit")
566
+ rescue # rubocop:disable Lint/HandleExceptions
567
+ # the raise is not what is being tested here, rather its side-effect
568
+ end
569
+
570
+ logged_output.string.must_match(/^nope$/)
571
+ end
572
+
573
+ it "stderr is printed on logger warn level" do
574
+ begin
575
+ connection.execute("doit")
576
+ rescue # rubocop:disable Lint/HandleExceptions
577
+ # the raise is not what is being tested here, rather its side-effect
578
+ end
579
+
580
+ message = <<'MSG'.chomp!
581
+ doit : The term 'doit' is not recognized as the name of a cmdlet, function,
582
+ script file, or operable program. Check the spelling ofthe name, or if a path
583
+ was included, verify that the path is correct and try again.
584
+ At line:1 char:1
585
+ + doit
586
+ + ~~~~
587
+ + CategoryInfo : ObjectNotFound: (doit:String) [], CommandNotFoun
588
+ dException
589
+ + FullyQualifiedErrorId : CommandNotFoundException
590
+ MSG
591
+
592
+ message.lines.each do |line|
593
+ logged_output.string.must_match warn_line(line.chomp)
594
+ end
595
+ end
596
+ end
597
+ # rubocop:enable Metrics/MethodLength, Metrics/AbcSize
598
+
599
+ describe "when a non-zero exit code is returned" do
600
+
601
+ common_failed_command_specs
602
+
603
+ it "raises a WinrmFailed exception" do
604
+ err = proc {
605
+ connection.execute("doit")
606
+ }.must_raise Kitchen::Transport::WinrmFailed
607
+ err.message.must_equal "WinRM exited (1) for command: [doit]"
608
+ end
609
+ end
610
+ end
611
+
612
+ describe "for a nil command" do
613
+
614
+ it "does not log on debug" do
615
+ executor.expects(:open).never
616
+ connection.execute(nil)
617
+
618
+ logged_output.string.must_equal ""
619
+ end
620
+ end
621
+
622
+ [
623
+ Errno::EACCES, Errno::EADDRINUSE, Errno::ECONNREFUSED,
624
+ Errno::ECONNRESET, Errno::ENETUNREACH, Errno::EHOSTUNREACH,
625
+ ::WinRM::WinRMHTTPTransportError, ::WinRM::WinRMAuthorizationError,
626
+ HTTPClient::KeepAliveDisconnected
627
+ ].each do |klass|
628
+ describe "raising #{klass}" do
629
+
630
+ before do
631
+ k = if klass == ::WinRM::WinRMHTTPTransportError
632
+ # this exception takes 2 args in its constructor, which is not stock
633
+ klass.new("dang", 200)
634
+ else
635
+ klass
636
+ end
637
+
638
+ options[:connection_retries] = 3
639
+ options[:connection_retry_sleep] = 7
640
+ connection.stubs(:sleep)
641
+ executor.stubs(:open).raises(k)
642
+ end
643
+
644
+ it "reraises the #{klass} exception" do
645
+ proc { connection.execute("nope") }.must_raise klass
646
+ end
647
+
648
+ it "attempts to connect :connection_retries times" do
649
+ begin
650
+ connection.execute("nope")
651
+ rescue # rubocop:disable Lint/HandleExceptions
652
+ # the raise is not what is being tested here, rather its side-effect
653
+ end
654
+
655
+ logged_output.string.lines.select { |l|
656
+ l =~ debug_line("[WinRM] opening remote shell on #{info}")
657
+ }.size.must_equal 3
658
+ logged_output.string.lines.select { |l|
659
+ l =~ debug_line("[WinRM] remote shell shell-123 is open on #{info}")
660
+ }.size.must_equal 0
661
+ end
662
+
663
+ it "sleeps for :connection_retry_sleep seconds between retries" do
664
+ connection.unstub(:sleep)
665
+ connection.expects(:sleep).with(7).twice
666
+
667
+ begin
668
+ connection.execute("nope")
669
+ rescue # rubocop:disable Lint/HandleExceptions
670
+ # the raise is not what is being tested here, rather its side-effect
671
+ end
672
+ end
673
+
674
+ it "logs the first 2 retry failures on info" do
675
+ begin
676
+ connection.execute("nope")
677
+ rescue # rubocop:disable Lint/HandleExceptions
678
+ # the raise is not what is being tested here, rather its side-effect
679
+ end
680
+
681
+ logged_output.string.lines.select { |l|
682
+ l =~ info_line_with(
683
+ "[WinRM] connection failed, retrying in 7 seconds")
684
+ }.size.must_equal 2
685
+ end
686
+
687
+ it "logs the last retry failures on warn" do
688
+ begin
689
+ connection.execute("nope")
690
+ rescue # rubocop:disable Lint/HandleExceptions
691
+ # the raise is not what is being tested here, rather its side-effect
692
+ end
693
+
694
+ logged_output.string.lines.select { |l|
695
+ l =~ warn_line_with("[WinRM] connection failed, terminating ")
696
+ }.size.must_equal 1
697
+ end
698
+ end
699
+ end
700
+ end
701
+
702
+ describe "#login_command" do
703
+
704
+ let(:login_command) { connection.login_command }
705
+ let(:args) { login_command.arguments.join(" ") }
706
+ let(:exec_args) { login_command.exec_args }
707
+
708
+ let(:rdp_doc) do
709
+ File.join(File.join(options[:kitchen_root], ".kitchen", "coolbeans.rdp"))
710
+ end
711
+
712
+ describe "for Mac-based workstations" do
713
+
714
+ before do
715
+ RbConfig::CONFIG.stubs(:[]).with("host_os").returns("darwin14")
716
+ end
717
+
718
+ it "returns a LoginCommand" do
719
+ with_fake_fs do
720
+ FileUtils.mkdir_p(File.dirname(rdp_doc))
721
+ login_command.must_be_instance_of Kitchen::LoginCommand
722
+ end
723
+ end
724
+
725
+ it "creates an rdp document" do
726
+ actual = nil
727
+ with_fake_fs do
728
+ FileUtils.mkdir_p(File.dirname(rdp_doc))
729
+ login_command
730
+ actual = IO.read(rdp_doc)
731
+ end
732
+
733
+ actual.must_equal Kitchen::Util.outdent!(<<-RDP)
734
+ drivestoredirect:s:*
735
+ full address:s:foo:rdpyeah
736
+ prompt for credentials:i:1
737
+ username:s:me
738
+ RDP
739
+ end
740
+
741
+ it "prints the rdp document on debug" do
742
+ with_fake_fs do
743
+ FileUtils.mkdir_p(File.dirname(rdp_doc))
744
+ login_command
745
+ end
746
+
747
+ expected = Kitchen::Util.outdent!(<<-OUTPUT)
748
+ Creating RDP document for coolbeans (/i/am/root/.kitchen/coolbeans.rdp)
749
+ ------------
750
+ drivestoredirect:s:*
751
+ full address:s:foo:rdpyeah
752
+ prompt for credentials:i:1
753
+ username:s:me
754
+ ------------
755
+ OUTPUT
756
+ debug_output(logged_output.string).must_match expected
757
+ end
758
+
759
+ it "returns a LoginCommand which calls open on the rdp document" do
760
+ actual = nil
761
+ with_fake_fs do
762
+ FileUtils.mkdir_p(File.dirname(rdp_doc))
763
+ actual = login_command
764
+ end
765
+
766
+ actual.exec_args.must_equal ["open", rdp_doc, {}]
767
+ end
768
+ end
769
+
770
+ describe "for Windows-based workstations" do
771
+
772
+ before do
773
+ RbConfig::CONFIG.stubs(:[]).with("host_os").returns("mingw32")
774
+ end
775
+
776
+ it "returns a LoginCommand" do
777
+ with_fake_fs do
778
+ FileUtils.mkdir_p(File.dirname(rdp_doc))
779
+ login_command.must_be_instance_of Kitchen::LoginCommand
780
+ end
781
+ end
782
+
783
+ it "creates an rdp document" do
784
+ actual = nil
785
+ with_fake_fs do
786
+ FileUtils.mkdir_p(File.dirname(rdp_doc))
787
+ login_command
788
+ actual = IO.read(rdp_doc)
789
+ end
790
+
791
+ actual.must_equal Kitchen::Util.outdent!(<<-RDP)
792
+ full address:s:foo:rdpyeah
793
+ prompt for credentials:i:1
794
+ username:s:me
795
+ RDP
796
+ end
797
+
798
+ it "prints the rdp document on debug" do
799
+ with_fake_fs do
800
+ FileUtils.mkdir_p(File.dirname(rdp_doc))
801
+ login_command
802
+ end
803
+
804
+ expected = Kitchen::Util.outdent!(<<-OUTPUT)
805
+ Creating RDP document for coolbeans (/i/am/root/.kitchen/coolbeans.rdp)
806
+ ------------
807
+ full address:s:foo:rdpyeah
808
+ prompt for credentials:i:1
809
+ username:s:me
810
+ ------------
811
+ OUTPUT
812
+ debug_output(logged_output.string).must_match expected
813
+ end
814
+
815
+ it "returns a LoginCommand which calls mstsc on the rdp document" do
816
+ actual = nil
817
+ with_fake_fs do
818
+ FileUtils.mkdir_p(File.dirname(rdp_doc))
819
+ actual = login_command
820
+ end
821
+
822
+ actual.exec_args.must_equal ["mstsc", rdp_doc, {}]
823
+ end
824
+ end
825
+
826
+ describe "for Linux-based workstations" do
827
+
828
+ before do
829
+ RbConfig::CONFIG.stubs(:[]).with("host_os").returns("linux-gnu")
830
+ end
831
+
832
+ it "returns a LoginCommand" do
833
+ login_command.must_be_instance_of Kitchen::LoginCommand
834
+ end
835
+
836
+ it "is an rdesktop command" do
837
+ login_command.command.must_equal "rdesktop"
838
+ args.must_match %r{ foo:rdpyeah$}
839
+ end
840
+
841
+ it "sets the user" do
842
+ args.must_match regexify("-u me ")
843
+ end
844
+
845
+ it "sets the pass if given" do
846
+ args.must_match regexify(" -p haha ")
847
+ end
848
+
849
+ it "won't set the pass if not given" do
850
+ options.delete(:pass)
851
+
852
+ args.wont_match regexify(" -p haha ")
853
+ end
854
+ end
855
+
856
+ describe "for unknown workstation platforms" do
857
+
858
+ before do
859
+ RbConfig::CONFIG.stubs(:[]).with("host_os").returns("cray")
860
+ end
861
+
862
+ it "raises an ActionFailed error" do
863
+ err = proc { login_command }.must_raise Kitchen::ActionFailed
864
+ err.message.must_equal "Remote login not supported in " \
865
+ "Kitchen::Transport::Winrm::Connection from host OS 'cray'."
866
+ end
867
+ end
868
+ end
869
+
870
+ describe "#upload" do
871
+
872
+ let(:transporter) do
873
+ t = mock("file_transporter")
874
+ t.responds_like_instance_of(Kitchen::Transport::Winrm::FileTransporter)
875
+ t
876
+ end
877
+
878
+ before do
879
+ # disable finalizer as service is a fake anyway
880
+ ObjectSpace.stubs(:define_finalizer).
881
+ with { |obj, _| obj.class == Kitchen::Transport::Winrm::Connection }
882
+
883
+ Kitchen::Transport::Winrm::CommandExecutor.stubs(:new).returns(executor)
884
+ executor.stubs(:open)
885
+
886
+ Kitchen::Transport::Winrm::FileTransporter.stubs(:new).
887
+ with(executor, logger).returns(transporter)
888
+ transporter.stubs(:upload)
889
+ end
890
+
891
+ # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
892
+ def self.common_specs_for_upload
893
+ it "builds a Winrm::FileTransporter" do
894
+ Kitchen::Transport::Winrm::FileTransporter.unstub(:new)
895
+
896
+ Kitchen::Transport::Winrm::FileTransporter.expects(:new).
897
+ with(executor, logger).returns(transporter)
898
+
899
+ upload
900
+ end
901
+
902
+ it "reuses the Winrm::FileTransporter" do
903
+ Kitchen::Transport::Winrm::FileTransporter.unstub(:new)
904
+
905
+ Kitchen::Transport::Winrm::FileTransporter.expects(:new).
906
+ with(executor, logger).returns(transporter).once
907
+
908
+ upload
909
+ upload
910
+ upload
911
+ end
912
+ end
913
+ # rubocop:enable Metrics/MethodLength, Metrics/AbcSize
914
+
915
+ describe "for a file" do
916
+
917
+ def upload # execute every time, not lazily once
918
+ connection.upload("/tmp/file.txt", "C:\\dest")
919
+ end
920
+
921
+ common_specs_for_upload
922
+ end
923
+
924
+ describe "for a collection of files" do
925
+
926
+ def upload # execute every time, not lazily once
927
+ connection.upload(%W[/tmp/file1.txt /tmp/file2.txt], "C:\\dest")
928
+ end
929
+
930
+ common_specs_for_upload
931
+ end
932
+ end
933
+
934
+ describe "#wait_until_ready" do
935
+
936
+ before do
937
+ Kitchen::Transport::Winrm::CommandExecutor.stubs(:new).returns(executor)
938
+ # disable finalizer as service is a fake anyway
939
+ ObjectSpace.stubs(:define_finalizer).
940
+ with { |obj, _| obj.class == Kitchen::Transport::Winrm::Connection }
941
+ options[:max_wait_until_ready] = 300
942
+ connection.stubs(:sleep)
943
+ end
944
+
945
+ describe "when failing to connect" do
946
+
947
+ before do
948
+ executor.stubs(:open).raises(Errno::ECONNREFUSED)
949
+ end
950
+
951
+ it "attempts to connect :max_wait_until_ready / 3 times if failing" do
952
+ begin
953
+ connection.wait_until_ready
954
+ rescue # rubocop:disable Lint/HandleExceptions
955
+ # the raise is not what is being tested here, rather its side-effect
956
+ end
957
+
958
+ logged_output.string.lines.select { |l|
959
+ l =~ info_line_with(
960
+ "Waiting for WinRM service on http://foo:5985/wsman, retrying in 3 seconds")
961
+ }.size.must_equal((300 / 3) - 1)
962
+ logged_output.string.lines.select { |l|
963
+ l =~ debug_line_with("[WinRM] connection failed ")
964
+ }.size.must_equal((300 / 3) - 1)
965
+ logged_output.string.lines.select { |l|
966
+ l =~ warn_line_with("[WinRM] connection failed, terminating ")
967
+ }.size.must_equal 1
968
+ end
969
+
970
+ it "sleeps for 3 seconds between retries" do
971
+ connection.unstub(:sleep)
972
+ connection.expects(:sleep).with(3).times((300 / 3) - 1)
973
+
974
+ begin
975
+ connection.wait_until_ready
976
+ rescue # rubocop:disable Lint/HandleExceptions
977
+ # the raise is not what is being tested here, rather its side-effect
978
+ end
979
+ end
980
+ end
981
+
982
+ describe "when connection is successful" do
983
+
984
+ let(:response) do
985
+ o = WinRM::Output.new
986
+ o[:exitcode] = 0
987
+ o[:data].concat([{ :stdout => "[WinRM] Established\r\n" }])
988
+ o
989
+ end
990
+
991
+ before do
992
+ executor.stubs(:open).returns("shell-123")
993
+ executor.expects(:run_powershell_script).
994
+ with("Write-Host '[WinRM] Established\n'").returns(response)
995
+ end
996
+
997
+ it "executes an empty command string to ensure working" do
998
+ connection.wait_until_ready
999
+ end
1000
+ end
1001
+
1002
+ describe "when connection suceeds but command fails, sad panda" do
1003
+
1004
+ let(:response) do
1005
+ o = WinRM::Output.new
1006
+ o[:exitcode] = 42
1007
+ o[:data].concat([{ :stderr => "Ah crap.\r\n" }])
1008
+ o
1009
+ end
1010
+
1011
+ before do
1012
+ executor.stubs(:open).returns("shell-123")
1013
+ executor.expects(:run_powershell_script).
1014
+ with("Write-Host '[WinRM] Established\n'").returns(response)
1015
+ end
1016
+
1017
+ it "executes an empty command string to ensure working" do
1018
+ err = proc {
1019
+ connection.wait_until_ready
1020
+ }.must_raise Kitchen::Transport::WinrmFailed
1021
+ err.message.must_equal "WinRM exited (42) for command: " \
1022
+ "[Write-Host '[WinRM] Established\n']"
1023
+ end
1024
+
1025
+ it "stderr is printed on logger warn level" do
1026
+ begin
1027
+ connection.wait_until_ready
1028
+ rescue # rubocop:disable Lint/HandleExceptions
1029
+ # the raise is not what is being tested here, rather its side-effect
1030
+ end
1031
+
1032
+ logged_output.string.must_match warn_line("Ah crap.\n")
1033
+ end
1034
+ end
1035
+ end
1036
+
1037
+ def debug_output(output)
1038
+ regexp = %r{^D, .* DEBUG -- : }
1039
+ output.lines.grep(%r{^D, .* DEBUG -- : }).map { |l| l.sub(regexp, "") }.join
1040
+ end
1041
+
1042
+ def debug_line(msg)
1043
+ %r{^D, .* : #{Regexp.escape(msg)}$}
1044
+ end
1045
+
1046
+ def debug_line_with(msg)
1047
+ %r{^D, .* : #{Regexp.escape(msg)}}
1048
+ end
1049
+
1050
+ def info_line(msg)
1051
+ %r{^I, .* : #{Regexp.escape(msg)}$}
1052
+ end
1053
+
1054
+ def info_line_with(msg)
1055
+ %r{^I, .* : #{Regexp.escape(msg)}}
1056
+ end
1057
+
1058
+ def regexify(string)
1059
+ Regexp.new(Regexp.escape(string))
1060
+ end
1061
+
1062
+ def warn_line(msg)
1063
+ %r{^W, .* : #{Regexp.escape(msg)}$}
1064
+ end
1065
+
1066
+ def warn_line_with(msg)
1067
+ %r{^W, .* : #{Regexp.escape(msg)}}
1068
+ end
1069
+ end