test-kitchen 1.0.0.beta.4 → 1.0.0.rc.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.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +50 -1
  3. data/Gemfile +1 -1
  4. data/README.md +18 -7
  5. data/Rakefile +8 -1
  6. data/features/kitchen_init_command.feature +90 -11
  7. data/features/step_definitions/git_steps.rb +3 -0
  8. data/lib/kitchen/busser.rb +79 -45
  9. data/lib/kitchen/cli.rb +14 -13
  10. data/lib/kitchen/config.rb +79 -138
  11. data/lib/kitchen/data_munger.rb +224 -0
  12. data/lib/kitchen/driver/base.rb +4 -31
  13. data/lib/kitchen/driver/ssh_base.rb +6 -16
  14. data/lib/kitchen/driver.rb +4 -0
  15. data/lib/kitchen/generator/init.rb +20 -9
  16. data/lib/kitchen/instance.rb +53 -58
  17. data/lib/kitchen/lazy_hash.rb +50 -0
  18. data/lib/kitchen/platform.rb +2 -31
  19. data/lib/kitchen/provisioner/base.rb +55 -9
  20. data/lib/kitchen/provisioner/chef/berkshelf.rb +76 -0
  21. data/lib/kitchen/provisioner/chef/librarian.rb +72 -0
  22. data/lib/kitchen/provisioner/chef_base.rb +159 -78
  23. data/lib/kitchen/provisioner/chef_solo.rb +6 -36
  24. data/lib/kitchen/provisioner/chef_zero.rb +70 -59
  25. data/lib/kitchen/provisioner/dummy.rb +28 -0
  26. data/lib/kitchen/provisioner.rb +6 -4
  27. data/lib/kitchen/shell_out.rb +2 -5
  28. data/lib/kitchen/ssh.rb +1 -1
  29. data/lib/kitchen/suite.rb +10 -79
  30. data/lib/kitchen/util.rb +2 -2
  31. data/lib/kitchen/version.rb +2 -2
  32. data/lib/kitchen.rb +5 -0
  33. data/spec/kitchen/config_spec.rb +84 -123
  34. data/spec/kitchen/data_munger_spec.rb +1412 -0
  35. data/spec/kitchen/driver/base_spec.rb +30 -0
  36. data/spec/kitchen/instance_spec.rb +868 -86
  37. data/spec/kitchen/lazy_hash_spec.rb +63 -0
  38. data/spec/kitchen/platform_spec.rb +0 -22
  39. data/spec/kitchen/provisioner/base_spec.rb +210 -0
  40. data/spec/kitchen/provisioner_spec.rb +70 -0
  41. data/spec/kitchen/suite_spec.rb +25 -38
  42. data/spec/spec_helper.rb +1 -0
  43. data/support/chef-client-zero.rb +51 -35
  44. data/support/dummy-validation.pem +27 -0
  45. data/templates/init/kitchen.yml.erb +10 -22
  46. data/test-kitchen.gemspec +1 -2
  47. metadata +20 -18
@@ -33,6 +33,11 @@ module Kitchen
33
33
  default_config :edible, true
34
34
  end
35
35
 
36
+ class SubclassDefaults < StaticDefaults
37
+
38
+ default_config :yea, "ya"
39
+ end
40
+
36
41
  class ComputedDefaults < Base
37
42
 
38
43
  default_config :beans, "kidney"
@@ -101,6 +106,31 @@ describe Kitchen::Driver::Base do
101
106
  end
102
107
  end
103
108
 
109
+ describe "inherited static default config" do
110
+
111
+ let(:driver) do
112
+ p = Kitchen::Driver::SubclassDefaults.new(config)
113
+ p.instance = instance
114
+ p
115
+ end
116
+
117
+ it "contains defaults from superclass" do
118
+ driver[:beans].must_equal "kidney"
119
+ driver[:tunables]['flimflam'].must_equal 'positate'
120
+ driver[:edible].must_equal true
121
+ driver[:yea].must_equal "ya"
122
+ end
123
+
124
+ it "uses user config over default config" do
125
+ config[:beans] = "pinto"
126
+ config[:edible] = false
127
+
128
+ driver[:beans].must_equal "pinto"
129
+ driver[:edible].must_equal false
130
+ driver[:yea].must_equal "ya"
131
+ end
132
+ end
133
+
104
134
  describe "computed default config" do
105
135
 
106
136
  let(:driver) do
@@ -17,7 +17,6 @@
17
17
  # limitations under the License.
18
18
 
19
19
  require_relative '../spec_helper'
20
- require 'logger'
21
20
  require 'stringio'
22
21
 
23
22
  require 'kitchen/logging'
@@ -25,141 +24,924 @@ require 'kitchen/instance'
25
24
  require 'kitchen/driver'
26
25
  require 'kitchen/driver/dummy'
27
26
  require 'kitchen/platform'
27
+ require 'kitchen/provisioner'
28
+ require 'kitchen/provisioner/dummy'
28
29
  require 'kitchen/suite'
29
30
 
30
- describe Kitchen::Instance do
31
+ class DummyStateFile
32
+
33
+ def initialize(*args) ; end
34
+
35
+ def read
36
+ @_state = Hash.new unless @_state
37
+ @_state.dup
38
+ end
39
+
40
+ def write(state)
41
+ @_state = state.dup
42
+ end
43
+
44
+ def destroy
45
+ @_state = nil
46
+ end
47
+ end
48
+
49
+ class SerialDummyDriver < Kitchen::Driver::Dummy
31
50
 
32
- let(:suite) do
33
- Kitchen::Suite.new({ :name => 'suite',
34
- :run_list => 'suite_list', :attributes => { :s => 'ss' } })
51
+ no_parallel_for :create, :verify, :destroy
52
+
53
+ attr_reader :action_in_mutex
54
+
55
+ def track_locked(action)
56
+ @action_in_mutex = Hash.new unless @action_in_mutex
57
+ @action_in_mutex[action] = Kitchen::Instance.mutexes[self.class].locked?
58
+ end
59
+
60
+ def create(state)
61
+ track_locked(:create)
62
+ super
63
+ end
64
+
65
+ def converge(state)
66
+ track_locked(:converge)
67
+ super
68
+ end
69
+
70
+ def setup(state)
71
+ track_locked(:setup)
72
+ super
35
73
  end
36
74
 
37
- let(:platform) do
38
- Kitchen::Platform.new({ :name => 'platform',
39
- :run_list => 'platform_list', :attributes => { :p => 'pp' } })
75
+ def verify(state)
76
+ track_locked(:verify)
77
+ super
40
78
  end
41
79
 
42
- let(:driver) { Kitchen::Driver::Dummy.new({}) }
80
+ def destroy(state)
81
+ track_locked(:destroy)
82
+ super
83
+ end
84
+ end
85
+
86
+ describe Kitchen::Instance do
87
+
88
+ let(:driver) { Kitchen::Driver::Dummy.new({}) }
89
+ let(:logger_io) { StringIO.new }
90
+ let(:logger) { Kitchen::Logger.new(:logdev => logger_io) }
91
+ let(:instance) { Kitchen::Instance.new(opts) }
92
+ let(:provisioner) { Kitchen::Provisioner::Dummy.new({}) }
93
+ let(:state_file) { DummyStateFile.new }
94
+ let(:busser) { Kitchen::Busser.new(suite.name, {}) }
43
95
 
44
96
  let(:opts) do
45
- { :suite => suite, :platform => platform, :driver => driver }
97
+ { :suite => suite, :platform => platform, :driver => driver,
98
+ :provisioner => provisioner, :busser => busser,
99
+ :logger => logger, :state_file => state_file }
100
+ end
101
+
102
+ def suite(name = "suite")
103
+ @suite ||= Kitchen::Suite.new({ :name => name })
104
+ end
105
+
106
+ def platform(name = "platform")
107
+ @platform ||= Kitchen::Platform.new({ :name => name })
108
+ end
109
+
110
+ describe ".name_for" do
111
+
112
+ it "combines the suite and platform names with a dash" do
113
+ Kitchen::Instance.name_for(suite("suite"), platform("platform")).
114
+ must_equal "suite-platform"
115
+ end
116
+
117
+ it "squashes periods in suite name" do
118
+ Kitchen::Instance.name_for(suite("suite.ness"), platform("platform")).
119
+ must_equal "suiteness-platform"
120
+ end
121
+
122
+ it "squashes periods in platform name" do
123
+ Kitchen::Instance.name_for(suite("suite"), platform("platform.s")).
124
+ must_equal "suite-platforms"
125
+ end
126
+
127
+ it "squashes periods in suite and platform names" do
128
+ Kitchen::Instance.name_for(suite("s.s"), platform("p.p")).
129
+ must_equal "ss-pp"
130
+ end
131
+
132
+ it "transforms underscores to dashes in suite name" do
133
+ Kitchen::Instance.name_for(suite("suite_ness"), platform("platform")).
134
+ must_equal "suite-ness-platform"
135
+ end
136
+
137
+ it "transforms underscores to dashes in platform name" do
138
+ Kitchen::Instance.name_for(suite("suite"), platform("platform_s")).
139
+ must_equal "suite-platform-s"
140
+ end
141
+
142
+ it "transforms underscores to dashes in suite and platform names" do
143
+ Kitchen::Instance.name_for(suite("_s__s_"), platform("pp_")).
144
+ must_equal "-s--s--pp-"
145
+ end
46
146
  end
47
147
 
48
- let(:instance) { Kitchen::Instance.new(opts) }
148
+ describe "#suite" do
49
149
 
50
- it "raises an ArgumentError if suite is missing" do
51
- opts.delete(:suite)
52
- proc { Kitchen::Instance.new(opts) }.must_raise Kitchen::ClientError
150
+ it "returns its suite" do
151
+ instance.suite.must_equal suite
152
+ end
153
+
154
+ it "raises an ArgumentError if missing" do
155
+ opts.delete(:suite)
156
+ proc { Kitchen::Instance.new(opts) }.must_raise Kitchen::ClientError
157
+ end
53
158
  end
54
159
 
55
- it "raises an ArgumentError if platform is missing" do
56
- opts.delete(:platform)
57
- proc { Kitchen::Instance.new(opts) }.must_raise Kitchen::ClientError
160
+ describe "#platform" do
161
+
162
+ it "returns its platform" do
163
+ instance.platform.must_equal platform
164
+ end
165
+
166
+ it "raises an ArgumentError if missing" do
167
+ opts.delete(:platform)
168
+ proc { Kitchen::Instance.new(opts) }.must_raise Kitchen::ClientError
169
+ end
58
170
  end
59
171
 
60
- it "returns suite" do
61
- instance.suite.must_equal suite
172
+ describe "#driver" do
173
+
174
+ it "returns its driver" do
175
+ instance.driver.must_equal driver
176
+ end
177
+
178
+ it "raises an ArgumentError if missing" do
179
+ opts.delete(:driver)
180
+ proc { Kitchen::Instance.new(opts) }.must_raise Kitchen::ClientError
181
+ end
182
+
183
+ it "sets Driver#instance to itself" do
184
+ # it's mind-bottling
185
+ instance.driver.instance.must_equal instance
186
+ end
62
187
  end
63
188
 
64
- it "returns platform" do
65
- instance.platform.must_equal platform
189
+ describe "#logger" do
190
+
191
+ it "returns its logger" do
192
+ instance.logger.must_equal logger
193
+ end
194
+
195
+ it "uses Kitchen.logger by default" do
196
+ opts.delete(:logger)
197
+ instance.logger.must_equal Kitchen.logger
198
+ end
66
199
  end
67
200
 
68
- describe "#name" do
201
+ describe "#provisioner" do
69
202
 
70
- def combo(suite_name, platform_name)
71
- opts[:suite] = Kitchen::Suite.new(
72
- :name => suite_name, :run_list => []
73
- )
74
- opts[:platform] = Kitchen::Platform.new(
75
- :name => platform_name
76
- )
77
- Kitchen::Instance.new(opts)
203
+ it "returns its provisioner" do
204
+ instance.provisioner.must_equal provisioner
78
205
  end
79
206
 
80
- it "combines the suite and platform names with a dash" do
81
- combo('suite', 'platform').name.must_equal "suite-platform"
207
+ it "raises an ArgumentError if missing" do
208
+ opts.delete(:provisioner)
209
+ proc { Kitchen::Instance.new(opts) }.must_raise Kitchen::ClientError
210
+ end
211
+
212
+ it "sets Provisioner#instance to itself" do
213
+ # it's mind-bottling
214
+ instance.provisioner.instance.must_equal instance
82
215
  end
216
+ end
217
+
218
+ describe "#busser" do
83
219
 
84
- it "squashes periods" do
85
- combo('suite.ness', 'platform').name.must_equal "suiteness-platform"
86
- combo('suite', 'platform.s').name.must_equal "suite-platforms"
87
- combo('s.s.', '.p.p').name.must_equal "ss-pp"
220
+ it "returns its busser" do
221
+ instance.busser.must_equal busser
88
222
  end
89
223
 
90
- it "transforms underscores to dashes" do
91
- combo('suite_ness', 'platform').name.must_equal "suite-ness-platform"
92
- combo('suite', 'platform-s').name.must_equal "suite-platform-s"
93
- combo('_s__s_', 'pp_').name.must_equal "-s--s--pp-"
224
+ it "raises and ArgumentError if missing" do
225
+ opts.delete(:busser)
226
+ proc { Kitchen::Instance.new(opts) }.must_raise Kitchen::ClientError
94
227
  end
95
228
  end
96
229
 
97
- describe 'Cheflike' do
230
+ describe "#state_file" do
231
+
232
+ it "raises an ArgumentError if missing" do
233
+ opts.delete(:state_file)
234
+ proc { Kitchen::Instance.new(opts) }.must_raise Kitchen::ClientError
235
+ end
236
+ end
237
+
238
+ it "#name returns it name" do
239
+ instance.name.must_equal "suite-platform"
240
+ end
241
+
242
+ it "#to_str returns a string representation with its name" do
243
+ instance.to_str.must_equal "<suite-platform>"
244
+ end
245
+
246
+ it "#login executes the driver's login_command" do
247
+ driver.stubs(:login_command).with(Hash.new).
248
+ returns(Kitchen::LoginCommand.new(["echo", "hello"], {:purple => true}))
249
+ Kernel.expects(:exec).with("echo", "hello", {:purple => true})
250
+
251
+ instance.login
252
+ end
253
+
254
+ describe "performing actions" do
255
+
256
+ describe "#create" do
98
257
 
99
- describe "#run_list" do
258
+ describe "with no state" do
259
+
260
+ it "calls Driver#create with empty state hash" do
261
+ driver.expects(:create).with(Hash.new)
262
+
263
+ instance.create
264
+ end
265
+
266
+ it "writes the state file with last_action" do
267
+ instance.create
268
+
269
+ state_file.read[:last_action].must_equal "create"
270
+ end
271
+
272
+ it "logs the action start" do
273
+ instance.create
274
+
275
+ logger_io.string.must_match regex_for("Creating #{instance.to_str}")
276
+ end
277
+
278
+ it "logs the action finish" do
279
+ instance.create
280
+
281
+ logger_io.string.
282
+ must_match regex_for("Finished creating #{instance.to_str}")
283
+ end
100
284
 
101
- def combo(suite_list, platform_list)
102
- opts[:suite] = Kitchen::Suite.new(
103
- :name => 'suite', :run_list => suite_list
104
- ).extend(Kitchen::Suite::Cheflike)
105
- opts[:platform] = Kitchen::Platform.new(
106
- :name => 'platform', :run_list => platform_list
107
- ).extend(Kitchen::Platform::Cheflike)
108
- Kitchen::Instance.new(opts).extend(Kitchen::Instance::Cheflike)
109
285
  end
110
286
 
111
- it "combines the platform then suite run_lists" do
112
- combo(%w{s1 s2}, %w{p1 p2}).run_list.must_equal %w{p1 p2 s1 s2}
287
+ describe "with last_action of create" do
288
+
289
+ before { state_file.write({ :last_action => "create"}) }
290
+
291
+ it "calls Driver#create with state hash" do
292
+ driver.expects(:create).
293
+ with { |state| state[:last_action] == "create" }
294
+
295
+ instance.create
296
+ end
297
+
298
+ it "writes the state file with last_action" do
299
+ instance.create
300
+
301
+ state_file.read[:last_action].must_equal "create"
302
+ end
113
303
  end
304
+ end
305
+
306
+ describe "#converge" do
307
+
308
+ describe "with no state" do
309
+
310
+ it "calls Driver#create and converge with empty state hash" do
311
+ driver.expects(:create).with(Hash.new)
312
+ driver.expects(:converge).
313
+ with { |state| state[:last_action] == "create" }
314
+
315
+ instance.converge
316
+ end
317
+
318
+ it "writes the state file with last_action" do
319
+ instance.converge
320
+
321
+ state_file.read[:last_action].must_equal "converge"
322
+ end
114
323
 
115
- it "uses the suite run_list only when platform run_list is empty" do
116
- combo(%w{sa sb}, nil).run_list.must_equal %w{sa sb}
324
+ it "logs the action start" do
325
+ instance.converge
326
+
327
+ logger_io.string.must_match regex_for("Converging #{instance.to_str}")
328
+ end
329
+
330
+ it "logs the action finish" do
331
+ instance.converge
332
+
333
+ logger_io.string.
334
+ must_match regex_for("Finished converging #{instance.to_str}")
335
+ end
117
336
  end
118
337
 
119
- it "returns an emtpy Array if both run_lists are empty" do
120
- combo([], nil).run_list.must_equal []
338
+ describe "with last action of create" do
339
+
340
+ before { state_file.write({ :last_action => "create"}) }
341
+
342
+ it "calls Driver#converge with state hash" do
343
+ driver.expects(:converge).
344
+ with { |state| state[:last_action] == "create" }
345
+
346
+ instance.converge
347
+ end
348
+
349
+ it "writes the state file with last_action" do
350
+ instance.converge
351
+
352
+ state_file.read[:last_action].must_equal "converge"
353
+ end
354
+ end
355
+
356
+ describe "with last action of converge" do
357
+
358
+ before { state_file.write({ :last_action => "converge"}) }
359
+
360
+ it "calls Driver#converge with state hash" do
361
+ driver.expects(:converge).
362
+ with { |state| state[:last_action] == "converge" }
363
+
364
+ instance.converge
365
+ end
366
+
367
+ it "writes the state file with last_action" do
368
+ instance.converge
369
+
370
+ state_file.read[:last_action].must_equal "converge"
371
+ end
121
372
  end
122
373
  end
123
374
 
124
- describe "#attributes" do
375
+ describe "#setup" do
376
+
377
+ describe "with no state" do
378
+
379
+ it "calls Driver#create, converge, and setup with empty state hash" do
380
+ driver.expects(:create).with(Hash.new)
381
+ driver.expects(:converge).
382
+ with { |state| state[:last_action] == "create" }
383
+ driver.expects(:setup).
384
+ with { |state| state[:last_action] == "converge" }
385
+
386
+ instance.setup
387
+ end
388
+
389
+ it "writes the state file with last_action" do
390
+ instance.setup
125
391
 
126
- def combo(suite_attrs, platform_attrs)
127
- opts[:suite] = Kitchen::Suite.new(
128
- :name => 'suite', :run_list => [], :attributes => suite_attrs
129
- ).extend(Kitchen::Suite::Cheflike)
130
- opts[:platform] = Kitchen::Platform.new(
131
- :name => 'platform', :attributes => platform_attrs
132
- ).extend(Kitchen::Platform::Cheflike)
133
- Kitchen::Instance.new(opts).extend(Kitchen::Instance::Cheflike)
392
+ state_file.read[:last_action].must_equal "setup"
393
+ end
394
+
395
+ it "logs the action start" do
396
+ instance.setup
397
+
398
+ logger_io.string.must_match regex_for("Setting up #{instance.to_str}")
399
+ end
400
+
401
+ it "logs the action finish" do
402
+ instance.setup
403
+
404
+ logger_io.string.
405
+ must_match regex_for("Finished setting up #{instance.to_str}")
406
+ end
134
407
  end
135
408
 
136
- it "merges suite and platform hashes together" do
137
- combo(
138
- { :suite => { :s1 => 'sv1' } },
139
- { :suite => { :p1 => 'pv1' }, :platform => 'pp' }
140
- ).attributes.must_equal({
141
- :suite => { :s1 => 'sv1', :p1 => 'pv1' },
142
- :platform => 'pp'
143
- })
409
+ describe "with last action of create" do
410
+
411
+ before { state_file.write({ :last_action => "create"}) }
412
+
413
+ it "calls Driver#converge and setup with state hash" do
414
+ driver.expects(:converge).
415
+ with { |state| state[:last_action] == "create" }
416
+ driver.expects(:setup).
417
+ with { |state| state[:last_action] == "converge" }
418
+
419
+ instance.setup
420
+ end
421
+
422
+ it "writes the state file with last_action" do
423
+ instance.setup
424
+
425
+ state_file.read[:last_action].must_equal "setup"
426
+ end
144
427
  end
145
428
 
146
- it "merges suite values over platform values" do
147
- combo(
148
- { :common => { :c1 => 'xxx' } },
149
- { :common => { :c1 => 'cv1', :c2 => 'cv2' } },
150
- ).attributes.must_equal({
151
- :common => { :c1 => 'xxx', :c2 => 'cv2' }
152
- })
429
+ describe "with last action of converge" do
430
+
431
+ before { state_file.write({ :last_action => "converge"}) }
432
+
433
+ it "calls Driver#setup with state hash" do
434
+ driver.expects(:setup).
435
+ with { |state| state[:last_action] == "converge" }
436
+
437
+ instance.setup
438
+ end
439
+
440
+ it "writes the state file with last_action" do
441
+ instance.setup
442
+
443
+ state_file.read[:last_action].must_equal "setup"
444
+ end
445
+ end
446
+
447
+ describe "with last action of setup" do
448
+
449
+ before { state_file.write({ :last_action => "setup"}) }
450
+
451
+ it "calls Driver#setup with state hash" do
452
+ driver.expects(:setup).
453
+ with { |state| state[:last_action] == "setup" }
454
+
455
+ instance.setup
456
+ end
457
+
458
+ it "writes the state file with last_action" do
459
+ instance.setup
460
+
461
+ state_file.read[:last_action].must_equal "setup"
462
+ end
153
463
  end
154
464
  end
155
465
 
156
- it "#dna combines attributes with the run_list" do
157
- instance.extend(Kitchen::Instance::Cheflike)
158
- instance.platform.extend(Kitchen::Platform::Cheflike)
159
- instance.suite.extend(Kitchen::Suite::Cheflike)
466
+ describe "#verify" do
467
+
468
+ describe "with no state" do
469
+
470
+ it "calls Driver#create, converge, setup, and verify with empty state hash" do
471
+ driver.expects(:create).with(Hash.new)
472
+ driver.expects(:converge).
473
+ with { |state| state[:last_action] == "create" }
474
+ driver.expects(:setup).
475
+ with { |state| state[:last_action] == "converge" }
476
+ driver.expects(:verify).
477
+ with { |state| state[:last_action] == "setup" }
478
+
479
+ instance.verify
480
+ end
481
+
482
+ it "writes the state file with last_action" do
483
+ instance.verify
484
+
485
+ state_file.read[:last_action].must_equal "verify"
486
+ end
487
+
488
+ it "logs the action start" do
489
+ instance.verify
490
+
491
+ logger_io.string.must_match regex_for("Verifying #{instance.to_str}")
492
+ end
493
+
494
+ it "logs the action finish" do
495
+ instance.verify
496
+
497
+ logger_io.string.
498
+ must_match regex_for("Finished verifying #{instance.to_str}")
499
+ end
500
+ end
501
+
502
+ describe "with last of create" do
503
+
504
+ before { state_file.write({ :last_action => "create"}) }
505
+
506
+ it "calls Driver#converge, setup, and verify with state hash" do
507
+ driver.expects(:converge).
508
+ with { |state| state[:last_action] == "create" }
509
+ driver.expects(:setup).
510
+ with { |state| state[:last_action] == "converge" }
511
+ driver.expects(:verify).
512
+ with { |state| state[:last_action] == "setup" }
513
+
514
+ instance.verify
515
+ end
516
+
517
+ it "writes the state file with last_action" do
518
+ instance.verify
519
+
520
+ state_file.read[:last_action].must_equal "verify"
521
+ end
522
+ end
160
523
 
161
- instance.dna.must_equal({ :s => 'ss', :p => 'pp',
162
- :run_list => ['platform_list', 'suite_list'] })
524
+ describe "with last of converge" do
525
+
526
+ before { state_file.write({ :last_action => "converge"}) }
527
+
528
+ it "calls Driver#setup, and verify with state hash" do
529
+ driver.expects(:setup).
530
+ with { |state| state[:last_action] == "converge" }
531
+ driver.expects(:verify).
532
+ with { |state| state[:last_action] == "setup" }
533
+
534
+ instance.verify
535
+ end
536
+
537
+ it "writes the state file with last_action" do
538
+ instance.verify
539
+
540
+ state_file.read[:last_action].must_equal "verify"
541
+ end
542
+ end
543
+
544
+ describe "with last of setup" do
545
+
546
+ before { state_file.write({ :last_action => "setup"}) }
547
+
548
+ it "calls Driver#verify with state hash" do
549
+ driver.expects(:verify).
550
+ with { |state| state[:last_action] == "setup" }
551
+
552
+ instance.verify
553
+ end
554
+
555
+ it "writes the state file with last_action" do
556
+ instance.verify
557
+
558
+ state_file.read[:last_action].must_equal "verify"
559
+ end
560
+ end
561
+
562
+ describe "with last of verify" do
563
+
564
+ before { state_file.write({ :last_action => "verify"}) }
565
+
566
+ it "calls Driver#verify with state hash" do
567
+ driver.expects(:verify).
568
+ with { |state| state[:last_action] == "verify" }
569
+
570
+ instance.verify
571
+ end
572
+
573
+ it "writes the state file with last_action" do
574
+ instance.verify
575
+
576
+ state_file.read[:last_action].must_equal "verify"
577
+ end
578
+ end
579
+ end
580
+
581
+ describe "#destroy" do
582
+
583
+ describe "with no state" do
584
+
585
+ it "calls Driver#destroy with empty state hash" do
586
+ driver.expects(:destroy).with(Hash.new)
587
+
588
+ instance.destroy
589
+ end
590
+
591
+ it "destroys the state file" do
592
+ state_file.expects(:destroy)
593
+
594
+ instance.destroy
595
+ end
596
+
597
+ it "logs the action start" do
598
+ instance.destroy
599
+
600
+ logger_io.string.
601
+ must_match regex_for("Destroying #{instance.to_str}")
602
+ end
603
+
604
+ it "logs the create finish" do
605
+ instance.destroy
606
+
607
+ logger_io.string.
608
+ must_match regex_for("Finished destroying #{instance.to_str}")
609
+ end
610
+ end
611
+
612
+ [:create, :converge, :setup, :verify].each do |action|
613
+
614
+ describe "with last_action of #{action}" do
615
+
616
+ before { state_file.write({ :last_action => action}) }
617
+
618
+ it "calls Driver#create with state hash" do
619
+ driver.expects(:destroy).
620
+ with { |state| state[:last_action] == action }
621
+
622
+ instance.destroy
623
+ end
624
+
625
+ it "destroys the state file" do
626
+ state_file.expects(:destroy)
627
+
628
+ instance.destroy
629
+ end
630
+ end
631
+ end
632
+ end
633
+
634
+ describe "#test" do
635
+
636
+ describe "with no state" do
637
+
638
+ it "calls Driver#destroy, create, converge, setup, verify, destroy" do
639
+ driver.expects(:destroy)
640
+ driver.expects(:create)
641
+ driver.expects(:converge)
642
+ driver.expects(:setup)
643
+ driver.expects(:verify)
644
+ driver.expects(:destroy)
645
+
646
+ instance.test
647
+ end
648
+
649
+ it "logs the action start" do
650
+ instance.test
651
+
652
+ logger_io.string.must_match regex_for("Testing #{instance.to_str}")
653
+ end
654
+
655
+ it "logs the action finish" do
656
+ instance.test
657
+
658
+ logger_io.string.
659
+ must_match regex_for("Finished testing #{instance.to_str}")
660
+ end
661
+ end
662
+
663
+ [:create, :converge, :setup, :verify].each do |action|
664
+
665
+ describe "with last action of #{action}" do
666
+
667
+ before { state_file.write({ :last_action => action}) }
668
+
669
+ it "calls Driver#destroy, create, converge, setup, verify, destroy" do
670
+ driver.expects(:destroy)
671
+ driver.expects(:create)
672
+ driver.expects(:converge)
673
+ driver.expects(:setup)
674
+ driver.expects(:verify)
675
+ driver.expects(:destroy)
676
+
677
+ instance.test
678
+ end
679
+ end
680
+ end
681
+
682
+ describe "with destroy mode of never" do
683
+
684
+ it "calls Driver#destroy, create, converge, setup, verify" do
685
+ driver.expects(:destroy).once
686
+ driver.expects(:create)
687
+ driver.expects(:converge)
688
+ driver.expects(:setup)
689
+ driver.expects(:verify)
690
+
691
+ instance.test(:never)
692
+ end
693
+ end
694
+
695
+ describe "with destroy mode of always" do
696
+
697
+ it "calls Driver#destroy at even when action fails" do
698
+ driver.stubs(:converge).raises(Kitchen::ActionFailed)
699
+
700
+ driver.expects(:destroy)
701
+ driver.expects(:create)
702
+ driver.expects(:converge)
703
+ driver.expects(:destroy)
704
+
705
+ instance.test(:always)
706
+ end
707
+ end
708
+
709
+ describe "with destroy mode of passing" do
710
+
711
+ it "doesn't call Driver#destroy at when action fails" do
712
+ skip "figure this one out"
713
+ driver.stubs(:create).raises(Kitchen::ActionFailed, "death")
714
+
715
+ driver.expects(:destroy)
716
+ driver.expects(:create)
717
+
718
+ instance.test(:passing)
719
+ end
720
+ end
721
+ end
722
+
723
+ [:create, :converge, :setup, :verify, :test].each do |action|
724
+
725
+ describe "#{action} on driver crash with ActionFailed" do
726
+
727
+ before do
728
+ driver.stubs(:create).raises(Kitchen::ActionFailed, "death")
729
+ end
730
+
731
+ it "write the state file with last action" do
732
+ begin
733
+ instance.public_send(action)
734
+ rescue Kitchen::Error => e
735
+ end
736
+
737
+ state_file.read[:last_action].must_be_nil
738
+ end
739
+
740
+ it "raises an InstanceFailure" do
741
+ proc { instance.public_send(action) }.
742
+ must_raise Kitchen::InstanceFailure
743
+ end
744
+
745
+ it "populates the InstanceFailure message" do
746
+ begin
747
+ instance.public_send(action)
748
+ rescue Kitchen::Error => e
749
+ e.message.must_match regex_for(
750
+ "Create failed on instance #{instance.to_str}")
751
+ end
752
+ end
753
+
754
+ it "logs the failure" do
755
+ begin
756
+ instance.public_send(action)
757
+ rescue Kitchen::Error => e
758
+ end
759
+
760
+ logger_io.string.must_match regex_for(
761
+ "Create failed on instance #{instance.to_str}")
762
+ end
763
+ end
764
+
765
+ describe "on driver crash with unexpected exception class" do
766
+
767
+ before do
768
+ driver.stubs(:create).raises(RuntimeError, "watwat")
769
+ end
770
+
771
+ it "write the state file with last action" do
772
+ begin
773
+ instance.public_send(action)
774
+ rescue Kitchen::Error => e
775
+ end
776
+
777
+ state_file.read[:last_action].must_be_nil
778
+ end
779
+
780
+ it "raises an ActionFailed" do
781
+ proc { instance.public_send(action) }.
782
+ must_raise Kitchen::ActionFailed
783
+ end
784
+
785
+ it "populates the ActionFailed message" do
786
+ begin
787
+ instance.public_send(action)
788
+ rescue Kitchen::Error => e
789
+ e.message.must_match regex_for(
790
+ "Failed to complete #create action: [watwat]")
791
+ end
792
+ end
793
+
794
+ it "logs the failure" do
795
+ begin
796
+ instance.public_send(action)
797
+ rescue Kitchen::Error => e
798
+ end
799
+
800
+ logger_io.string.must_match regex_for(
801
+ "Create failed on instance #{instance.to_str}")
802
+ end
803
+ end
163
804
  end
805
+
806
+ describe "crashes preserve last action for desired verify action" do
807
+
808
+ before do
809
+ driver.stubs(:verify).raises(Kitchen::ActionFailed, "death")
810
+ end
811
+
812
+ [:create, :converge, :setup].each do |action|
813
+
814
+ it "for last state #{action}" do
815
+ state_file.write({ :last_action => action.to_s })
816
+ begin
817
+ instance.verify
818
+ rescue Kitchen::Error => e
819
+ end
820
+
821
+ state_file.read[:last_action].must_equal "setup"
822
+ end
823
+ end
824
+
825
+ it "for last state verify" do
826
+ state_file.write({ :last_action => "verify" })
827
+ begin
828
+ instance.verify
829
+ rescue Kitchen::Error => e
830
+ end
831
+
832
+ state_file.read[:last_action].must_equal "verify"
833
+ end
834
+ end
835
+
836
+ describe "on drivers with serial actions" do
837
+
838
+ let(:driver) { SerialDummyDriver.new({}) }
839
+
840
+ it "runs in a synchronized block for serial actions" do
841
+ instance.test
842
+
843
+ driver.action_in_mutex[:create].must_equal true
844
+ driver.action_in_mutex[:converge].must_equal false
845
+ driver.action_in_mutex[:setup].must_equal false
846
+ driver.action_in_mutex[:verify].must_equal true
847
+ driver.action_in_mutex[:destroy].must_equal true
848
+ end
849
+ end
850
+ end
851
+
852
+ describe Kitchen::Instance::FSM do
853
+
854
+ let(:fsm) { Kitchen::Instance::FSM }
855
+
856
+ describe ".actions" do
857
+
858
+ it "passing nils returns destroy" do
859
+ fsm.actions(nil, nil).must_equal [:destroy]
860
+ end
861
+
862
+ it "accepts a string for desired argument" do
863
+ fsm.actions(nil, "create").must_equal [:create]
864
+ end
865
+
866
+ it "accepts a symbol for desired argument" do
867
+ fsm.actions(nil, :create).must_equal [:create]
868
+ end
869
+
870
+ it "starting from no state to create returns create" do
871
+ fsm.actions(nil, :create).must_equal [:create]
872
+ end
873
+
874
+ it "starting from :create to create returns create" do
875
+ fsm.actions(:create, :create).must_equal [:create]
876
+ end
877
+
878
+ it "starting from no state to converge returns create, converge" do
879
+ fsm.actions(nil, :converge).must_equal [:create, :converge]
880
+ end
881
+
882
+ it "starting from create to converge returns converge" do
883
+ fsm.actions(:create, :converge).must_equal [:converge]
884
+ end
885
+
886
+ it "starting from converge to converge returns converge" do
887
+ fsm.actions(:converge, :converge).must_equal [:converge]
888
+ end
889
+
890
+ it "starting from no state to setup returns create, converge, setup" do
891
+ fsm.actions(nil, :setup).must_equal [:create, :converge, :setup]
892
+ end
893
+
894
+ it "starting from create to setup returns converge, setup" do
895
+ fsm.actions(:create, :setup).must_equal [:converge, :setup]
896
+ end
897
+
898
+ it "starting from converge to setup returns setup" do
899
+ fsm.actions(:converge, :setup).must_equal [:setup]
900
+ end
901
+
902
+ it "starting from setup to setup return setup" do
903
+ fsm.actions(:setup, :setup).must_equal [:setup]
904
+ end
905
+
906
+ it "starting from no state to verify returns create, converge, setup, verify" do
907
+ fsm.actions(nil, :verify).must_equal [:create, :converge, :setup, :verify]
908
+ end
909
+
910
+ it "starting from create to verify returns converge, setup, verify" do
911
+ fsm.actions(:create, :verify).must_equal [:converge, :setup, :verify]
912
+ end
913
+
914
+ it "starting from converge to verify returns setup, verify" do
915
+ fsm.actions(:converge, :verify).must_equal [:setup, :verify]
916
+ end
917
+
918
+ it "starting from setup to verify returns verify" do
919
+ fsm.actions(:setup, :verify).must_equal [:verify]
920
+ end
921
+
922
+ it "starting from verify to verify returns verify" do
923
+ fsm.actions(:verify, :verify).must_equal [:verify]
924
+ end
925
+
926
+ [:verify, :setup, :converge].each do |s|
927
+ it "starting from #{s} to create returns create" do
928
+ fsm.actions(s, :create).must_equal [:create]
929
+ end
930
+ end
931
+
932
+ [:verify, :setup].each do |s|
933
+ it "starting from #{s} to converge returns converge" do
934
+ fsm.actions(s, :converge).must_equal [:converge]
935
+ end
936
+ end
937
+
938
+ it "starting from verify to setup returns setup" do
939
+ fsm.actions(:verify, :setup).must_equal [:setup]
940
+ end
941
+ end
942
+ end
943
+
944
+ def regex_for(string)
945
+ Regexp.new(Regexp.escape(string))
164
946
  end
165
947
  end