test-kitchen 1.0.0.beta.4 → 1.0.0.rc.1

Sign up to get free protection for your applications and to get access to all the features.
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