test-kitchen 1.4.0.beta.2 → 1.4.0.rc.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (59) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +46 -0
  3. data/features/kitchen_diagnose_command.feature +32 -0
  4. data/lib/kitchen/cli.rb +3 -0
  5. data/lib/kitchen/command/diagnose.rb +6 -1
  6. data/lib/kitchen/configurable.rb +48 -1
  7. data/lib/kitchen/diagnostic.rb +29 -0
  8. data/lib/kitchen/driver/base.rb +25 -0
  9. data/lib/kitchen/driver/dummy.rb +4 -0
  10. data/lib/kitchen/driver/proxy.rb +3 -0
  11. data/lib/kitchen/instance.rb +17 -0
  12. data/lib/kitchen/provisioner/base.rb +30 -1
  13. data/lib/kitchen/provisioner/chef_base.rb +7 -3
  14. data/lib/kitchen/provisioner/chef_solo.rb +4 -0
  15. data/lib/kitchen/provisioner/chef_zero.rb +5 -1
  16. data/lib/kitchen/provisioner/dummy.rb +4 -0
  17. data/lib/kitchen/provisioner/shell.rb +5 -0
  18. data/lib/kitchen/shell_out.rb +6 -2
  19. data/lib/kitchen/transport/base.rb +25 -0
  20. data/lib/kitchen/transport/dummy.rb +4 -0
  21. data/lib/kitchen/transport/ssh.rb +22 -1
  22. data/lib/kitchen/transport/winrm.rb +61 -90
  23. data/lib/kitchen/verifier/base.rb +30 -1
  24. data/lib/kitchen/verifier/busser.rb +4 -0
  25. data/lib/kitchen/verifier/dummy.rb +4 -0
  26. data/lib/kitchen/version.rb +1 -1
  27. data/spec/kitchen/configurable_spec.rb +35 -0
  28. data/spec/kitchen/diagnostic_spec.rb +53 -3
  29. data/spec/kitchen/driver/dummy_spec.rb +8 -0
  30. data/spec/kitchen/driver/proxy_spec.rb +4 -0
  31. data/spec/kitchen/driver/ssh_base_spec.rb +4 -0
  32. data/spec/kitchen/instance_spec.rb +75 -0
  33. data/spec/kitchen/provisioner/base_spec.rb +32 -6
  34. data/spec/kitchen/provisioner/chef_base_spec.rb +3 -2
  35. data/spec/kitchen/provisioner/chef_solo_spec.rb +10 -2
  36. data/spec/kitchen/provisioner/chef_zero_spec.rb +24 -2
  37. data/spec/kitchen/provisioner/dummy_spec.rb +8 -0
  38. data/spec/kitchen/provisioner/shell_spec.rb +10 -0
  39. data/spec/kitchen/shell_out_spec.rb +7 -0
  40. data/spec/kitchen/transport/ssh_spec.rb +90 -1
  41. data/spec/kitchen/transport/winrm_spec.rb +91 -11
  42. data/spec/kitchen/verifier/base_spec.rb +32 -6
  43. data/spec/kitchen/verifier/busser_spec.rb +8 -0
  44. data/spec/kitchen/verifier/dummy_spec.rb +8 -0
  45. data/support/chef_base_install_command.sh +183 -100
  46. data/test-kitchen.gemspec +1 -2
  47. metadata +11 -48
  48. data/lib/kitchen/transport/winrm/command_executor.rb +0 -188
  49. data/lib/kitchen/transport/winrm/file_transporter.rb +0 -454
  50. data/lib/kitchen/transport/winrm/logging.rb +0 -50
  51. data/lib/kitchen/transport/winrm/template.rb +0 -74
  52. data/lib/kitchen/transport/winrm/tmp_zip.rb +0 -187
  53. data/spec/kitchen/transport/winrm/command_executor_spec.rb +0 -400
  54. data/spec/kitchen/transport/winrm/file_transporter_spec.rb +0 -876
  55. data/spec/kitchen/transport/winrm/logging_spec.rb +0 -92
  56. data/spec/kitchen/transport/winrm/template_spec.rb +0 -51
  57. data/spec/kitchen/transport/winrm/tmp_zip_spec.rb +0 -132
  58. data/support/check_files.ps1.erb +0 -48
  59. data/support/decode_files.ps1.erb +0 -62
@@ -1,50 +0,0 @@
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
- module Kitchen
20
-
21
- module Transport
22
-
23
- class Winrm < Kitchen::Transport::Base
24
-
25
- # Mixin to use an optionally provided logger for logging.
26
- #
27
- # @author Fletcher Nichol <fnichol@nichol.ca>
28
- module Logging
29
-
30
- # Logs a message on the logger at the debug level, if a logger is
31
- # present.
32
- #
33
- # @param msg [String] a message to log
34
- # @yield evaluates and uses return value as message to log. If msg
35
- # parameter is set, it will take precedence over the block.
36
- def debug(msg = nil, &block)
37
- return if logger.nil? || !logger.debug?
38
- logger.debug("[#{log_subject}] " << (msg || block.call))
39
- end
40
-
41
- # The subject for log messages.
42
- #
43
- # @return [String] log subject
44
- def log_subject
45
- @log_subject ||= self.class.to_s.split("::").last
46
- end
47
- end
48
- end
49
- end
50
- end
@@ -1,74 +0,0 @@
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 "erb"
20
- require "ostruct"
21
-
22
- module Kitchen
23
-
24
- module Transport
25
-
26
- class Winrm < Kitchen::Transport::Base
27
-
28
- # Wraps an ERb template which can be called multiple times with
29
- # different binding contexts.
30
- #
31
- # @author Fletcher Nichol <fnichol@nichol.ca>
32
- # @api private
33
- class Template
34
-
35
- # Initializes an ERb template using a file as the template source.
36
- #
37
- # @param file [String] path to an ERb template file
38
- def initialize(file)
39
- @erb = ERB.new(IO.read(file))
40
- end
41
-
42
- # Renders the template using a hash as context.
43
- #
44
- # @param vars [Hash] a hash used for context
45
- # @return [String] the rendered template
46
- def render(vars)
47
- @erb.result(Context.for(vars))
48
- end
49
- alias_method :%, :render
50
-
51
- # Internal class which wraps a binding context for rendering
52
- # an ERb template.
53
- #
54
- # @author Fletcher Nichol <fnichol@nichol.ca>
55
- # @api private
56
- class Context < OpenStruct
57
-
58
- # Creates a new binding context for a hash of data.
59
- #
60
- # @param vars [Hash] a hash used for context
61
- # @return [Binding] a binding context for the given hash
62
- def self.for(vars)
63
- new(vars).my_binding
64
- end
65
-
66
- # @return [Binding] a binding context
67
- def my_binding
68
- binding
69
- end
70
- end
71
- end
72
- end
73
- end
74
- end
@@ -1,187 +0,0 @@
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 "delegate"
20
- require "pathname"
21
- require "tempfile"
22
- require "zip"
23
-
24
- require "kitchen/transport/winrm/logging"
25
-
26
- module Kitchen
27
-
28
- module Transport
29
-
30
- class Winrm < Kitchen::Transport::Base
31
-
32
- # A temporary Zip file for a given directory.
33
- #
34
- # @author Fletcher Nichol <fnichol@nichol.ca>
35
- class TmpZip
36
-
37
- include Logging
38
-
39
- # Contructs a new Zip file for the given directory.
40
- #
41
- # There are 2 ways to interpret the directory path:
42
- #
43
- # * If the directory has no path separator terminator, then the
44
- # directory basename will be used as the base directory in the
45
- # resulting zip file.
46
- # * If the directory has a path separator terminator (such as `/` or
47
- # `\\`), then the entries under the directory will be added to the
48
- # resulting zip file.
49
- #
50
- # The following emaples assume a directory tree structure of:
51
- #
52
- # src
53
- # |-- alpha.txt
54
- # |-- beta.txt
55
- # \-- sub
56
- # \-- charlie.txt
57
- #
58
- # @example Including the base directory in the zip file
59
- #
60
- # TmpZip.new("/path/to/src")
61
- # # produces a zip file with entries:
62
- # # - src/alpha.txt
63
- # # - src/beta.txt
64
- # # - src/sub/charlie.txt
65
- #
66
- # @example Excluding the base directory in the zip file
67
- #
68
- # TmpZip.new("/path/to/src/")
69
- # # produces a zip file with entries:
70
- # # - alpha.txt
71
- # # - beta.txt
72
- # # - sub/charlie.txt
73
- #
74
- # @param dir [String,Pathname,#to_s] path to the directory
75
- # @param logger [#debug,#debug?] an optional logger/ui object that
76
- # responds to `#debug` and `#debug?` (default `nil`)
77
- def initialize(dir, logger = nil)
78
- @logger = logger
79
- @dir = Pathname.new(dir)
80
- @method = ::Zip::Entry::DEFLATED
81
- @compression = Zlib::BEST_COMPRESSION
82
- @zip_io = Tempfile.open(["tmpzip-", ".zip"], :binmode => true)
83
- write_zip
84
- @zip_io.close
85
- end
86
-
87
- # @return [Pathname] path to zip file
88
- def path
89
- Pathname.new(zip_io.path) if zip_io.path
90
- end
91
-
92
- # Unlinks (deletes) the zip file from the filesystem.
93
- def unlink
94
- zip_io.unlink
95
- end
96
-
97
- private
98
-
99
- # @return [Integer] the compression used for Zip entries. Possible
100
- # values are `Zlib::BEST_COMPRESSION`, `Zlib::DEFAULT_COMPRESSION`,
101
- # and `Zlib::NO_COMPRESSION`.
102
- # @api private
103
- attr_reader :compression
104
-
105
- # @return [Pathname] the directory used to create the Zip file
106
- # @api private
107
- attr_reader :dir
108
-
109
- # @return [#debug] the logger
110
- # @api private
111
- attr_reader :logger
112
-
113
- # @return [Integer] compression method used for Zip entries. Possible
114
- # values are `Zip::Entry::DEFLATED` and `Zip::Entry::STORED`.
115
- # @api private
116
- attr_reader :method
117
-
118
- # @return [IO] the Zip file IO
119
- # @api private
120
- attr_reader :zip_io
121
-
122
- # @return [Pathname] the path segement to be stripped off Zip entries
123
- # @api private
124
- def dir_strip
125
- @dir_strip ||= if dir.to_s.end_with?("/", "\\")
126
- Pathname.new(dir.to_s.chop)
127
- else
128
- dir.dirname
129
- end
130
- end
131
-
132
- # @return [Array<Pathname] all recursive files under the base
133
- # directory, excluding directories
134
- # @api private
135
- def entries
136
- Pathname.glob(dir.join("**/*")).delete_if(&:directory?).sort
137
- end
138
-
139
- # (see Logging.log_subject)
140
- # @api private
141
- def log_subject
142
- @log_subject ||= [self.class.to_s.split("::").last, path].join("::")
143
- end
144
-
145
- # Adds all file entries to the Zip output stream.
146
- #
147
- # @param zos [Zip::OutputStream] zip output stream
148
- # @api private
149
- def produce_zip_entries(zos)
150
- entries.each do |entry|
151
- entry_path = entry.sub("#{dir_strip}/", "")
152
- debug { "+++ Adding #{entry_path}" }
153
- zos.put_next_entry(entry_path, nil, nil, method, compression)
154
- entry.open("rb") { |src| IO.copy_stream(src, zos) }
155
- end
156
- debug { "=== All files added." }
157
- end
158
-
159
- # Writes out a temporary Zip file.
160
- #
161
- # @api private
162
- def write_zip
163
- debug { "Populating files" }
164
- Zip::OutputStream.write_buffer(NoDupIO.new(zip_io)) do |zos|
165
- produce_zip_entries(zos)
166
- end
167
- end
168
-
169
- # Simple delegate wrapper to prevent `#dup` calls being made on IO
170
- # objects. This is used to bypass an issue in the `Zip::Outputstream`
171
- # constructor where an incoming IO is duplicated, leading to races
172
- # on flushing the final stream to disk.
173
- #
174
- # @author Fletcher Nichol <fnichol@nichol.ca>
175
- # @api private
176
- class NoDupIO < SimpleDelegator
177
-
178
- # @return [self] returns self and does *not* return a duplicate
179
- # object
180
- def dup
181
- self
182
- end
183
- end
184
- end
185
- end
186
- end
187
- end
@@ -1,400 +0,0 @@
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/command_executor"
23
-
24
- require "base64"
25
- require "securerandom"
26
- require "winrm"
27
-
28
- describe Kitchen::Transport::Winrm::CommandExecutor do
29
-
30
- let(:logged_output) { StringIO.new }
31
- let(:logger) { Logger.new(logged_output) }
32
- let(:shell_id) { "shell-123" }
33
-
34
- let(:executor) do
35
- Kitchen::Transport::Winrm::CommandExecutor.new(service, logger)
36
- end
37
-
38
- let(:service) do
39
- s = mock("winrm_service")
40
- s.responds_like_instance_of(WinRM::WinRMWebService)
41
- s
42
- end
43
-
44
- let(:version_output) do
45
- o = WinRM::Output.new
46
- o[:exitcode] = 0
47
- o[:data].concat([{ :stdout => "6.3.9600.0\r\n" }])
48
- o
49
- end
50
-
51
- before do
52
- service.stubs(:open_shell).returns(shell_id)
53
-
54
- stub_powershell_script(shell_id,
55
- "[environment]::OSVersion.Version.tostring()", version_output)
56
- end
57
-
58
- describe "#close" do
59
-
60
- it "calls service#close_shell" do
61
- executor.open
62
- service.expects(:close_shell).with(shell_id)
63
-
64
- executor.close
65
- end
66
-
67
- it "only calls service#close_shell once for multiple calls" do
68
- executor.open
69
- service.expects(:close_shell).with(shell_id).once
70
-
71
- executor.close
72
- executor.close
73
- executor.close
74
- end
75
- end
76
-
77
- describe "#open" do
78
-
79
- it "calls service#open_shell" do
80
- service.expects(:open_shell).returns(shell_id)
81
-
82
- executor.open
83
- end
84
-
85
- it "returns a shell id as a string" do
86
- executor.open.must_equal shell_id
87
- end
88
-
89
- describe "for modern windows distributions" do
90
-
91
- let(:version_output) do
92
- o = WinRM::Output.new
93
- o[:exitcode] = 0
94
- o[:data].concat([{ :stdout => "6.3.9600.0\r\n" }])
95
- o
96
- end
97
-
98
- it "sets #max_commands to 1500 - 2" do
99
- executor.max_commands.must_equal nil
100
- executor.open
101
-
102
- executor.max_commands.must_equal(1500 - 2)
103
- end
104
- end
105
-
106
- describe "for older/legacy windows distributions" do
107
-
108
- let(:version_output) do
109
- o = WinRM::Output.new
110
- o[:exitcode] = 0
111
- o[:data].concat([{ :stdout => "6.1.8500.0\r\n" }])
112
- o
113
- end
114
-
115
- it "sets #max_commands to 15 - 2" do
116
- executor.max_commands.must_equal nil
117
- executor.open
118
-
119
- executor.max_commands.must_equal(15 - 2)
120
- end
121
- end
122
- end
123
-
124
- describe "#run_cmd" do
125
-
126
- describe "when #open has not been previously called" do
127
-
128
- it "raises a WinRMError error" do
129
- err = proc { executor.run_cmd("nope") }.must_raise WinRM::WinRMError
130
- err.message.must_equal "#{executor.class}#open must be called " \
131
- "before any run methods are invoked"
132
- end
133
- end
134
-
135
- describe "when #open has been previously called" do
136
-
137
- let(:command_id) { "command-123" }
138
-
139
- let(:echo_output) do
140
- o = WinRM::Output.new
141
- o[:exitcode] = 0
142
- o[:data].concat([
143
- { :stdout => "Hello\r\n" },
144
- { :stderr => "Psst\r\n" }
145
- ])
146
- o
147
- end
148
-
149
- before do
150
- stub_cmd(shell_id, "echo", ["Hello"], echo_output, command_id)
151
-
152
- executor.open
153
- end
154
-
155
- it "calls service#run_command" do
156
- service.expects(:run_command).with(shell_id, "echo", ["Hello"])
157
-
158
- executor.run_cmd("echo", ["Hello"])
159
- end
160
-
161
- it "calls service#get_command_output to get results" do
162
- service.expects(:get_command_output).with(shell_id, command_id)
163
-
164
- executor.run_cmd("echo", ["Hello"])
165
- end
166
-
167
- it "calls service#get_command_output with a block to get results" do
168
- blk = proc { |_, _| "something" }
169
- service.expects(:get_command_output).with(shell_id, command_id, &blk)
170
-
171
- executor.run_cmd("echo", ["Hello"], &blk)
172
- end
173
-
174
- it "returns an Output object hash" do
175
- executor.run_cmd("echo", ["Hello"]).must_equal echo_output
176
- end
177
-
178
- it "runs the block in #get_command_output when given" do
179
- io_out = StringIO.new
180
- io_err = StringIO.new
181
-
182
- output = executor.run_cmd("echo", ["Hello"]) do |stdout, stderr|
183
- io_out << stdout if stdout
184
- io_err << stderr if stderr
185
- end
186
-
187
- io_out.string.must_equal "Hello\r\n"
188
- io_err.string.must_equal "Psst\r\n"
189
- output.must_equal echo_output
190
- end
191
- end
192
-
193
- describe "when called many times over time" do
194
-
195
- # use a "old" version of windows with lower max_commands threshold
196
- # to trigger quicker shell recyles
197
- let(:version_output) do
198
- o = WinRM::Output.new
199
- o[:exitcode] = 0
200
- o[:data].concat([{ :stdout => "6.1.8500.0\r\n" }])
201
- o
202
- end
203
-
204
- let(:echo_output) do
205
- o = WinRM::Output.new
206
- o[:exitcode] = 0
207
- o[:data].concat([{ :stdout => "Hello\r\n" }])
208
- o
209
- end
210
-
211
- before do
212
- service.stubs(:open_shell).returns("s1", "s2")
213
- service.stubs(:close_shell)
214
- service.stubs(:run_command).yields("command-xxx")
215
- service.stubs(:get_command_output).returns(echo_output)
216
- stub_powershell_script("s1",
217
- "[environment]::OSVersion.Version.tostring()", version_output)
218
- end
219
-
220
- it "resets the shell when #max_commands threshold is tripped" do
221
- iterations = 35
222
- reset_times = iterations / (15 - 2)
223
-
224
- service.expects(:close_shell).times(reset_times)
225
- executor.open
226
- iterations.times { executor.run_cmd("echo", ["Hello"]) }
227
-
228
- logged_output.string.lines.select { |l|
229
- l =~ debug_line_with("[CommandExecutor] Resetting WinRM shell")
230
- }.size.must_equal reset_times
231
- end
232
- end
233
- end
234
-
235
- describe "#run_powershell_script" do
236
-
237
- describe "when #open has not been previously called" do
238
-
239
- it "raises a WinRMError error" do
240
- err = proc {
241
- executor.run_powershell_script("nope")
242
- }.must_raise WinRM::WinRMError
243
- err.message.must_equal "#{executor.class}#open must be called " \
244
- "before any run methods are invoked"
245
- end
246
- end
247
-
248
- describe "when #open has been previously called" do
249
-
250
- let(:command_id) { "command-123" }
251
-
252
- let(:echo_output) do
253
- o = WinRM::Output.new
254
- o[:exitcode] = 0
255
- o[:data].concat([
256
- { :stdout => "Hello\r\n" },
257
- { :stderr => "Psst\r\n" }
258
- ])
259
- o
260
- end
261
-
262
- before do
263
- stub_powershell_script(shell_id, "echo Hello", echo_output, command_id)
264
-
265
- executor.open
266
- end
267
-
268
- it "calls service#run_command" do
269
- service.expects(:run_command).with(
270
- shell_id,
271
- "powershell",
272
- ["-encodedCommand", WinRM::PowershellScript.new("echo Hello").encoded]
273
- )
274
-
275
- executor.run_powershell_script("echo Hello")
276
- end
277
-
278
- it "calls service#get_command_output to get results" do
279
- service.expects(:get_command_output).with(shell_id, command_id)
280
-
281
- executor.run_powershell_script("echo Hello")
282
- end
283
-
284
- it "calls service#get_command_output with a block to get results" do
285
- blk = proc { |_, _| "something" }
286
- service.expects(:get_command_output).with(shell_id, command_id, &blk)
287
-
288
- executor.run_powershell_script("echo Hello", &blk)
289
- end
290
-
291
- it "returns an Output object hash" do
292
- executor.run_powershell_script("echo Hello").must_equal echo_output
293
- end
294
-
295
- it "runs the block in #get_command_output when given" do
296
- io_out = StringIO.new
297
- io_err = StringIO.new
298
-
299
- output = executor.run_powershell_script("echo Hello") do |stdout, stderr|
300
- io_out << stdout if stdout
301
- io_err << stderr if stderr
302
- end
303
-
304
- io_out.string.must_equal "Hello\r\n"
305
- io_err.string.must_equal "Psst\r\n"
306
- output.must_equal echo_output
307
- end
308
- end
309
-
310
- describe "when called many times over time" do
311
-
312
- # use a "old" version of windows with lower max_commands threshold
313
- # to trigger quicker shell recyles
314
- let(:version_output) do
315
- o = WinRM::Output.new
316
- o[:exitcode] = 0
317
- o[:data].concat([{ :stdout => "6.1.8500.0\r\n" }])
318
- o
319
- end
320
-
321
- let(:echo_output) do
322
- o = WinRM::Output.new
323
- o[:exitcode] = 0
324
- o[:data].concat([{ :stdout => "Hello\r\n" }])
325
- o
326
- end
327
-
328
- before do
329
- service.stubs(:open_shell).returns("s1", "s2")
330
- service.stubs(:close_shell)
331
- service.stubs(:run_command).yields("command-xxx")
332
- service.stubs(:get_command_output).returns(echo_output)
333
- stub_powershell_script("s1",
334
- "[environment]::OSVersion.Version.tostring()", version_output)
335
- end
336
-
337
- it "resets the shell when #max_commands threshold is tripped" do
338
- iterations = 35
339
- reset_times = iterations / (15 - 2)
340
-
341
- service.expects(:close_shell).times(reset_times)
342
- executor.open
343
- iterations.times { executor.run_powershell_script("echo Hello") }
344
-
345
- logged_output.string.lines.select { |l|
346
- l =~ debug_line_with("[CommandExecutor] Resetting WinRM shell")
347
- }.size.must_equal reset_times
348
- end
349
- end
350
- end
351
-
352
- describe "#shell" do
353
-
354
- it "is initially nil" do
355
- executor.shell.must_equal nil
356
- end
357
-
358
- it "is set after #open is called" do
359
- executor.open
360
-
361
- executor.shell.must_equal shell_id
362
- end
363
- end
364
-
365
- def decode(powershell)
366
- Base64.strict_decode64(powershell).encode("UTF-8", "UTF-16LE")
367
- end
368
-
369
- def debug_line_with(msg)
370
- %r{^D, .* : #{Regexp.escape(msg)}}
371
- end
372
-
373
- def regexify(string)
374
- Regexp.new(Regexp.escape(string))
375
- end
376
-
377
- def regexify_line(string)
378
- Regexp.new("^#{Regexp.escape(string)}$")
379
- end
380
-
381
- # rubocop:disable Metrics/ParameterLists
382
- def stub_cmd(shell_id, cmd, args, output, command_id = nil, &block)
383
- command_id ||= SecureRandom.uuid
384
-
385
- service.stubs(:run_command).with(shell_id, cmd, args).yields(command_id)
386
- service.stubs(:get_command_output).with(shell_id, command_id, &block).
387
- yields(output.stdout, output.stderr).returns(output)
388
- end
389
-
390
- def stub_powershell_script(shell_id, script, output, command_id = nil)
391
- stub_cmd(
392
- shell_id,
393
- "powershell",
394
- ["-encodedCommand", WinRM::PowershellScript.new(script).encoded],
395
- output,
396
- command_id
397
- )
398
- end
399
- # rubocop:enable Metrics/ParameterLists
400
- end