test-kitchen 1.2.1 → 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (114) hide show
  1. checksums.yaml +4 -4
  2. data/.cane +1 -1
  3. data/.rubocop.yml +3 -0
  4. data/.travis.yml +20 -9
  5. data/CHANGELOG.md +219 -108
  6. data/Gemfile +10 -6
  7. data/Guardfile +38 -9
  8. data/README.md +11 -1
  9. data/Rakefile +21 -37
  10. data/bin/kitchen +4 -4
  11. data/features/kitchen_action_commands.feature +161 -0
  12. data/features/kitchen_console_command.feature +34 -0
  13. data/features/kitchen_diagnose_command.feature +64 -0
  14. data/features/kitchen_init_command.feature +29 -17
  15. data/features/kitchen_list_command.feature +2 -2
  16. data/features/kitchen_login_command.feature +56 -0
  17. data/features/{sink_command.feature → kitchen_sink_command.feature} +0 -0
  18. data/features/kitchen_test_command.feature +88 -0
  19. data/features/step_definitions/gem_steps.rb +8 -6
  20. data/features/step_definitions/git_steps.rb +4 -2
  21. data/features/step_definitions/output_steps.rb +5 -0
  22. data/features/support/env.rb +12 -9
  23. data/lib/kitchen.rb +60 -38
  24. data/lib/kitchen/base64_stream.rb +55 -0
  25. data/lib/kitchen/busser.rb +124 -58
  26. data/lib/kitchen/cli.rb +121 -38
  27. data/lib/kitchen/collection.rb +3 -3
  28. data/lib/kitchen/color.rb +4 -4
  29. data/lib/kitchen/command.rb +78 -11
  30. data/lib/kitchen/command/action.rb +3 -2
  31. data/lib/kitchen/command/console.rb +12 -5
  32. data/lib/kitchen/command/diagnose.rb +17 -3
  33. data/lib/kitchen/command/driver_discover.rb +26 -7
  34. data/lib/kitchen/command/exec.rb +41 -0
  35. data/lib/kitchen/command/list.rb +44 -14
  36. data/lib/kitchen/command/login.rb +2 -1
  37. data/lib/kitchen/command/sink.rb +2 -1
  38. data/lib/kitchen/command/test.rb +5 -4
  39. data/lib/kitchen/config.rb +146 -14
  40. data/lib/kitchen/configurable.rb +314 -0
  41. data/lib/kitchen/data_munger.rb +522 -18
  42. data/lib/kitchen/diagnostic.rb +43 -4
  43. data/lib/kitchen/driver.rb +4 -4
  44. data/lib/kitchen/driver/base.rb +80 -115
  45. data/lib/kitchen/driver/dummy.rb +34 -6
  46. data/lib/kitchen/driver/proxy.rb +14 -3
  47. data/lib/kitchen/driver/ssh_base.rb +61 -7
  48. data/lib/kitchen/errors.rb +109 -9
  49. data/lib/kitchen/generator/driver_create.rb +39 -5
  50. data/lib/kitchen/generator/init.rb +130 -45
  51. data/lib/kitchen/instance.rb +162 -28
  52. data/lib/kitchen/lazy_hash.rb +79 -7
  53. data/lib/kitchen/loader/yaml.rb +159 -27
  54. data/lib/kitchen/logger.rb +267 -21
  55. data/lib/kitchen/logging.rb +30 -3
  56. data/lib/kitchen/login_command.rb +11 -2
  57. data/lib/kitchen/metadata_chopper.rb +2 -2
  58. data/lib/kitchen/provisioner.rb +4 -4
  59. data/lib/kitchen/provisioner/base.rb +107 -103
  60. data/lib/kitchen/provisioner/chef/berkshelf.rb +36 -8
  61. data/lib/kitchen/provisioner/chef/librarian.rb +40 -11
  62. data/lib/kitchen/provisioner/chef_base.rb +206 -167
  63. data/lib/kitchen/provisioner/chef_solo.rb +25 -7
  64. data/lib/kitchen/provisioner/chef_zero.rb +105 -29
  65. data/lib/kitchen/provisioner/dummy.rb +1 -1
  66. data/lib/kitchen/provisioner/shell.rb +21 -6
  67. data/lib/kitchen/rake_tasks.rb +8 -3
  68. data/lib/kitchen/shell_out.rb +15 -18
  69. data/lib/kitchen/ssh.rb +122 -27
  70. data/lib/kitchen/state_file.rb +24 -7
  71. data/lib/kitchen/thor_tasks.rb +9 -4
  72. data/lib/kitchen/util.rb +43 -118
  73. data/lib/kitchen/version.rb +1 -1
  74. data/lib/vendor/hash_recursive_merge.rb +10 -2
  75. data/spec/kitchen/base64_stream_spec.rb +77 -0
  76. data/spec/kitchen/busser_spec.rb +490 -0
  77. data/spec/kitchen/collection_spec.rb +10 -10
  78. data/spec/kitchen/color_spec.rb +2 -2
  79. data/spec/kitchen/config_spec.rb +234 -62
  80. data/spec/kitchen/configurable_spec.rb +490 -0
  81. data/spec/kitchen/data_munger_spec.rb +1070 -862
  82. data/spec/kitchen/diagnostic_spec.rb +79 -0
  83. data/spec/kitchen/driver/base_spec.rb +80 -85
  84. data/spec/kitchen/driver/dummy_spec.rb +43 -14
  85. data/spec/kitchen/driver/proxy_spec.rb +134 -0
  86. data/spec/kitchen/driver/ssh_base_spec.rb +644 -0
  87. data/spec/kitchen/driver_spec.rb +15 -15
  88. data/spec/kitchen/errors_spec.rb +309 -0
  89. data/spec/kitchen/instance_spec.rb +143 -46
  90. data/spec/kitchen/lazy_hash_spec.rb +36 -9
  91. data/spec/kitchen/loader/yaml_spec.rb +237 -226
  92. data/spec/kitchen/logger_spec.rb +419 -0
  93. data/spec/kitchen/logging_spec.rb +59 -0
  94. data/spec/kitchen/login_command_spec.rb +49 -0
  95. data/spec/kitchen/metadata_chopper_spec.rb +82 -0
  96. data/spec/kitchen/platform_spec.rb +4 -4
  97. data/spec/kitchen/provisioner/base_spec.rb +65 -125
  98. data/spec/kitchen/provisioner/chef_base_spec.rb +798 -0
  99. data/spec/kitchen/provisioner/chef_solo_spec.rb +316 -0
  100. data/spec/kitchen/provisioner/chef_zero_spec.rb +624 -0
  101. data/spec/kitchen/provisioner/shell_spec.rb +269 -0
  102. data/spec/kitchen/provisioner_spec.rb +6 -6
  103. data/spec/kitchen/shell_out_spec.rb +143 -0
  104. data/spec/kitchen/ssh_spec.rb +683 -0
  105. data/spec/kitchen/state_file_spec.rb +28 -21
  106. data/spec/kitchen/suite_spec.rb +7 -7
  107. data/spec/kitchen/util_spec.rb +68 -10
  108. data/spec/kitchen_spec.rb +107 -0
  109. data/spec/spec_helper.rb +18 -13
  110. data/support/chef-client-zero.rb +10 -9
  111. data/support/chef_helpers.sh +16 -0
  112. data/support/download_helpers.sh +109 -0
  113. data/test-kitchen.gemspec +42 -33
  114. metadata +107 -33
@@ -16,16 +16,16 @@
16
16
  # See the License for the specific language governing permissions and
17
17
  # limitations under the License.
18
18
 
19
- require_relative '../spec_helper'
20
- require 'ostruct'
19
+ require_relative "../spec_helper"
20
+ require "ostruct"
21
21
 
22
- require 'kitchen/collection'
22
+ require "kitchen/collection"
23
23
 
24
24
  describe Kitchen::Collection do
25
25
 
26
26
  let(:collection) do
27
27
  Kitchen::Collection.new([
28
- obj('one'), obj('two', 'a'), obj('two', 'b'), obj('three')
28
+ obj("one"), obj("two", "a"), obj("two", "b"), obj("three")
29
29
  ])
30
30
  end
31
31
 
@@ -36,15 +36,15 @@ describe Kitchen::Collection do
36
36
  describe "#get" do
37
37
 
38
38
  it "returns a single object by its name" do
39
- collection.get('three').must_equal obj('three')
39
+ collection.get("three").must_equal obj("three")
40
40
  end
41
41
 
42
42
  it "returns the first occurance of an object by its name" do
43
- collection.get('two').must_equal obj('two', 'a')
43
+ collection.get("two").must_equal obj("two", "a")
44
44
  end
45
45
 
46
46
  it "returns nil if an object cannot be found by its name" do
47
- collection.get('nope').must_be_nil
47
+ collection.get("nope").must_be_nil
48
48
  end
49
49
  end
50
50
 
@@ -53,8 +53,8 @@ describe Kitchen::Collection do
53
53
  it "returns a Collection of objects whose name matches the regex" do
54
54
  result = collection.get_all(/(one|three)/)
55
55
  result.size.must_equal 2
56
- result[0].must_equal obj('one')
57
- result[1].must_equal obj('three')
56
+ result[0].must_equal obj("one")
57
+ result[1].must_equal obj("three")
58
58
  result.get_all(/one/).size.must_equal 1
59
59
  end
60
60
 
@@ -68,7 +68,7 @@ describe Kitchen::Collection do
68
68
  describe "#as_name" do
69
69
 
70
70
  it "returns an Array of names as strings" do
71
- collection.as_names.must_equal %w{one two two three}
71
+ collection.as_names.must_equal %w[one two two three]
72
72
  end
73
73
  end
74
74
 
@@ -16,9 +16,9 @@
16
16
  # See the License for the specific language governing permissions and
17
17
  # limitations under the License.
18
18
 
19
- require_relative '../spec_helper'
19
+ require_relative "../spec_helper"
20
20
 
21
- require 'kitchen/color'
21
+ require "kitchen/color"
22
22
 
23
23
  describe Kitchen::Color do
24
24
 
@@ -16,30 +16,28 @@
16
16
  # See the License for the specific language governing permissions and
17
17
  # limitations under the License.
18
18
 
19
- require_relative '../spec_helper'
20
-
21
- require 'kitchen'
22
- require 'kitchen/logging'
23
- require 'kitchen/collection'
24
- require 'kitchen/config'
25
- require 'kitchen/driver'
26
- require 'kitchen/instance'
27
- require 'kitchen/platform'
28
- require 'kitchen/provisioner'
29
- require 'kitchen/suite'
30
- require 'kitchen/util'
19
+ require_relative "../spec_helper"
20
+
21
+ require "kitchen"
22
+ require "kitchen/logging"
23
+ require "kitchen/collection"
24
+ require "kitchen/config"
25
+ require "kitchen/driver"
26
+ require "kitchen/instance"
27
+ require "kitchen/platform"
28
+ require "kitchen/provisioner"
29
+ require "kitchen/suite"
30
+ require "kitchen/util"
31
31
 
32
32
  module Kitchen
33
33
 
34
34
  class DummyLoader
35
35
 
36
+ attr_writer :data
37
+
36
38
  def read
37
39
  @data || Hash.new
38
40
  end
39
-
40
- def data=(hash)
41
- @data = hash
42
- end
43
41
  end
44
42
  end
45
43
 
@@ -49,9 +47,25 @@ describe Kitchen::Config do
49
47
  let(:config) { Kitchen::Config.new(opts) }
50
48
 
51
49
  let(:opts) do
52
- { :loader => loader, :kitchen_root => "/tmp/that/place",
53
- :log_root => "/tmp/logs", :test_base_path => "/testing/yo",
54
- :log_level => :debug }
50
+ {
51
+ :loader => loader,
52
+ :kitchen_root => "/tmp/that/place",
53
+ :log_root => "/tmp/logs",
54
+ :test_base_path => "/testing/yo",
55
+ :log_level => :debug
56
+ }
57
+ end
58
+
59
+ let(:default_kitchen_config) do
60
+ {
61
+ :defaults => {
62
+ :driver => "dummy",
63
+ :provisioner => "chef_solo"
64
+ },
65
+ :kitchen_root => "/tmp/that/place",
66
+ :test_base_path => "/testing/yo",
67
+ :log_level => :debug
68
+ }
55
69
  end
56
70
 
57
71
  describe "#loader" do
@@ -121,75 +135,233 @@ describe Kitchen::Config do
121
135
 
122
136
  describe "#platforms" do
123
137
 
124
- it "returns an array of platforms" do
125
- stub_data!({
126
- :platforms => [
127
- { :name => 'one' },
128
- { :name => 'two' }
129
- ]
130
- })
138
+ before do
139
+ Kitchen::DataMunger.stubs(:new).returns(munger)
140
+ Kitchen::Platform.stubs(:new).returns("platform")
141
+ end
142
+
143
+ let(:munger) do
144
+ stub(
145
+ :platform_data => [{ :one => "a" }, { :two => "b" }]
146
+ )
147
+ end
148
+
149
+ it "loader loads data" do
150
+ loader.expects(:read).returns(Hash.new)
151
+
152
+ config.platforms
153
+ end
154
+
155
+ it "constucts a munger with loader data and defaults" do
156
+ loader.stubs(:read).returns("datum")
157
+
158
+ Kitchen::DataMunger.expects(:new).with("datum", default_kitchen_config).
159
+ returns(munger)
160
+
161
+ config.platforms
162
+ end
163
+
164
+ it "platform_data is called on munger" do
165
+ munger.expects(:platform_data).returns([])
166
+
167
+ config.platforms
168
+ end
169
+
170
+ it "contructs Platform objects" do
171
+ Kitchen::Platform.expects(:new).with(:one => "a")
172
+ Kitchen::Platform.expects(:new).with(:two => "b")
131
173
 
132
- config.platforms.size.must_equal 2
133
- config.platforms[0].name.must_equal 'one'
134
- config.platforms[1].name.must_equal 'two'
174
+ config.platforms
135
175
  end
136
176
 
137
- it "returns an empty Array if no platforms are given" do
138
- stub_data!({})
177
+ it "returns a Collection of platforms" do
178
+ Kitchen::Platform.stubs(:new).
179
+ with(:one => "a").returns(stub(:name => "one"))
180
+ Kitchen::Platform.stubs(:new).
181
+ with(:two => "b").returns(stub(:name => "two"))
139
182
 
140
- config.platforms.must_equal []
183
+ config.platforms.as_names.must_equal %w[one two]
141
184
  end
142
185
  end
143
186
 
144
187
  describe "#suites" do
145
188
 
146
- it "returns an array of suites" do
147
- stub_data!({
148
- :suites => [
149
- { :name => 'one' },
150
- { :name => 'two' }
151
- ]
152
- })
189
+ before do
190
+ Kitchen::DataMunger.stubs(:new).returns(munger)
191
+ Kitchen::Suite.stubs(:new).returns("suite")
192
+ end
193
+
194
+ let(:munger) do
195
+ stub(
196
+ :suite_data => [{ :one => "a" }, { :two => "b" }]
197
+ )
198
+ end
199
+
200
+ it "loader loads data" do
201
+ loader.expects(:read).returns(Hash.new)
202
+
203
+ config.suites
204
+ end
205
+
206
+ it "constucts a munger with loader data and defaults" do
207
+ loader.stubs(:read).returns("datum")
208
+
209
+ Kitchen::DataMunger.expects(:new).with("datum", default_kitchen_config).
210
+ returns(munger)
211
+
212
+ config.suites
213
+ end
214
+
215
+ it "platform_data is called on munger" do
216
+ munger.expects(:suite_data).returns([])
153
217
 
154
- config.suites.size.must_equal 2
155
- config.suites[0].name.must_equal 'one'
156
- config.suites[1].name.must_equal 'two'
218
+ config.suites
157
219
  end
158
220
 
159
- it "returns an empty Array if no suites are given" do
160
- stub_data!({})
221
+ it "contructs Suite objects" do
222
+ Kitchen::Suite.expects(:new).with(:one => "a")
223
+ Kitchen::Suite.expects(:new).with(:two => "b")
161
224
 
162
- config.suites.must_equal []
225
+ config.suites
226
+ end
227
+
228
+ it "returns a Collection of suites" do
229
+ Kitchen::Suite.stubs(:new).
230
+ with(:one => "a").returns(stub(:name => "one"))
231
+ Kitchen::Suite.stubs(:new).
232
+ with(:two => "b").returns(stub(:name => "two"))
233
+
234
+ config.suites.as_names.must_equal %w[one two]
163
235
  end
164
236
  end
165
237
 
166
238
  describe "#instances" do
167
239
 
168
- it "returns an empty Array if no suites and platforms are given" do
169
- stub_data!({})
240
+ let(:platforms) do
241
+ [stub(:name => "unax")]
242
+ end
243
+
244
+ let(:suites) do
245
+ [stub(:name => "tiny", :includes => [], :excludes => [])]
246
+ end
247
+
248
+ let(:munger) do
249
+ stub(
250
+ :busser_data_for => { "junk" => true },
251
+ :driver_data_for => { "junk" => true },
252
+ :provisioner_data_for => { "junk" => true }
253
+ )
254
+ end
255
+
256
+ before do
257
+ Kitchen::Instance.stubs(:new).returns("instance")
258
+ Kitchen::Busser.stubs(:new).returns("busser")
259
+ Kitchen::Driver.stubs(:for_plugin).returns("driver")
260
+ Kitchen::Provisioner.stubs(:for_plugin).returns("provisioner")
261
+ Kitchen::Logger.stubs(:new).returns("logger")
262
+ Kitchen::StateFile.stubs(:new).returns("state_file")
263
+
264
+ Kitchen::DataMunger.stubs(:new).returns(munger)
265
+ config.stubs(:platforms).returns(platforms)
266
+ config.stubs(:suites).returns(suites)
267
+ end
268
+
269
+ it "constructs a Busser object" do
270
+ munger.expects(:busser_data_for).with("tiny", "unax").returns("datum")
271
+ Kitchen::Busser.unstub(:new)
272
+ Kitchen::Busser.expects(:new).with("tiny", "datum")
273
+
274
+ config.instances
275
+ end
276
+
277
+ it "constructs a Driver object" do
278
+ munger.expects(:driver_data_for).with("tiny", "unax").
279
+ returns(:name => "drivey", :datum => "lots")
280
+ Kitchen::Driver.unstub(:for_plugin)
281
+ Kitchen::Driver.expects(:for_plugin).
282
+ with("drivey", :name => "drivey", :datum => "lots")
170
283
 
171
- config.instances.must_equal []
284
+ config.instances
285
+ end
286
+
287
+ it "constructs a Provisioner object" do
288
+ munger.expects(:provisioner_data_for).with("tiny", "unax").
289
+ returns(:name => "provey", :datum => "lots")
290
+ Kitchen::Provisioner.unstub(:for_plugin)
291
+ Kitchen::Provisioner.expects(:for_plugin).
292
+ with("provey", :name => "provey", :datum => "lots")
293
+
294
+ config.instances
172
295
  end
173
296
 
174
- it "returns an array of instances" do
175
- skip "much more needed here"
297
+ it "constructs a Logger object" do
298
+ Kitchen::Logger.unstub(:new)
299
+ Kitchen::Logger.expects(:new).with(
300
+ :stdout => STDOUT,
301
+ :color => :cyan,
302
+ :logdev => "/tmp/logs/tiny-unax.log",
303
+ :level => 0,
304
+ :progname => "tiny-unax"
305
+ )
306
+
307
+ config.instances
308
+ end
176
309
 
177
- stub_data!({
178
- :platforms => [
179
- { :name => "p1" },
180
- { :name => "p2" }
181
- ],
182
- :suites => [
183
- { :name => 's1' },
184
- { :name => 's2' }
185
- ]
186
- })
310
+ it "constructs a StateFile object" do
311
+ Kitchen::StateFile.unstub(:new)
312
+ Kitchen::StateFile.expects(:new).with("/tmp/that/place", "tiny-unax")
187
313
 
188
314
  config.instances
189
315
  end
316
+
317
+ it "constructs an Instance object from all built objects" do
318
+ Kitchen::Instance.unstub(:new)
319
+ Kitchen::Instance.expects(:new).with(
320
+ :busser => "busser",
321
+ :driver => "driver",
322
+ :logger => "logger",
323
+ :suite => suites.first,
324
+ :platform => platforms.first,
325
+ :provisioner => "provisioner",
326
+ :state_file => "state_file"
327
+ )
328
+ config.instances
329
+ end
190
330
  end
191
331
 
192
- def stub_data!(hash)
193
- loader.data = hash
332
+ describe "using Suite#includes" do
333
+
334
+ it "selects only platforms in a suite's includes array" do
335
+ config.stubs(:platforms).returns([
336
+ stub(:name => "good"),
337
+ stub(:name => "nope"),
338
+ stub(:name => "one")
339
+ ])
340
+ config.stubs(:suites).returns([
341
+ stub(:name => "selecta", :includes => %w[good one], :excludes => []),
342
+ stub(:name => "allem", :includes => [], :excludes => [])
343
+ ])
344
+
345
+ config.instances.as_names.must_equal [
346
+ "selecta-good", "selecta-one", "allem-good", "allem-nope", "allem-one"]
347
+ end
348
+ end
349
+
350
+ describe "using Suite#excludes" do
351
+
352
+ it "selects only platforms in a suite's includes array" do
353
+ config.stubs(:platforms).returns([
354
+ stub(:name => "good"),
355
+ stub(:name => "nope"),
356
+ stub(:name => "one")
357
+ ])
358
+ config.stubs(:suites).returns([
359
+ stub(:name => "selecta", :includes => [], :excludes => ["nope"]),
360
+ stub(:name => "allem", :includes => [], :excludes => [])
361
+ ])
362
+
363
+ config.instances.as_names.must_equal [
364
+ "selecta-good", "selecta-one", "allem-good", "allem-nope", "allem-one"]
365
+ end
194
366
  end
195
367
  end
@@ -0,0 +1,490 @@
1
+ # -*- encoding: utf-8 -*-
2
+ #
3
+ # Author:: Fletcher Nichol (<fnichol@nichol.ca>)
4
+ #
5
+ # Copyright (C) 2014, 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
+ require "stringio"
21
+
22
+ require "kitchen/errors"
23
+ require "kitchen/configurable"
24
+
25
+ module Kitchen
26
+
27
+ module Thing
28
+
29
+ class Tiny
30
+
31
+ include Kitchen::Configurable
32
+
33
+ def initialize(config = {})
34
+ init_config(config)
35
+ end
36
+ end
37
+
38
+ class StaticDefaults
39
+
40
+ include Kitchen::Configurable
41
+
42
+ default_config :beans, "kidney"
43
+ default_config :tunables, "flimflam" => "positate"
44
+ default_config :edible, true
45
+ default_config :fetch_command, "curl"
46
+ default_config :success_path, "./success"
47
+ default_config :beans_url do |subject|
48
+ "http://gim.me/#{subject[:beans]}"
49
+ end
50
+ default_config :command do |subject|
51
+ "#{subject[:fetch_command]} #{subject[:beans_url]}"
52
+ end
53
+ default_config :fetch_url do |subject|
54
+ "http://gim.me/beans-for/#{subject.instance.name}"
55
+ end
56
+
57
+ required_config :need_it
58
+ required_config :a_default
59
+ required_config :no_nuts do |attr, value, _subject|
60
+ raise UserError, "NO NUTS FOR #{attr}!" if value == "nuts"
61
+ end
62
+
63
+ expand_path_for :success_path
64
+ expand_path_for :relative_path, false
65
+ expand_path_for :another_path
66
+ expand_path_for :complex_path do |subject|
67
+ subject[:something_else] == "is_set"
68
+ end
69
+
70
+ def initialize(config = {})
71
+ init_config(config)
72
+ end
73
+ end
74
+
75
+ class SubclassDefaults < StaticDefaults
76
+
77
+ default_config :yea, "ya"
78
+ default_config :fetch_command, "wget"
79
+ default_config :fetch_url, "http://no.beans"
80
+
81
+ required_config :a_default do |_attr, value, _subject|
82
+ raise UserError, "Overriding a_default is fun" unless value == "please"
83
+ end
84
+
85
+ expand_path_for :another_path, false
86
+ end
87
+ end
88
+ end
89
+
90
+ describe Kitchen::Configurable do
91
+
92
+ let(:config) { Hash.new }
93
+ let(:instance) { stub(:name => "coolbeans", :to_str => "<instance>") }
94
+
95
+ let(:subject) do
96
+ Kitchen::Thing::Tiny.new(config).finalize_config!(instance)
97
+ end
98
+
99
+ describe "creation and setup" do
100
+
101
+ it "#instance returns its instance" do
102
+ subject.instance.must_equal instance
103
+ end
104
+
105
+ it "#finalize_config! raises ClientError if instance is nil" do
106
+ proc { Kitchen::Thing::Tiny.new({}).finalize_config!(nil) }.
107
+ must_raise(Kitchen::ClientError)
108
+ end
109
+
110
+ it "#finalize_config! returns self for chaining" do
111
+ t = Kitchen::Thing::Tiny.new({})
112
+ t.finalize_config!(instance).must_equal t
113
+ end
114
+ end
115
+
116
+ describe "configuration" do
117
+
118
+ describe "provided from the outside" do
119
+
120
+ it "returns provided config" do
121
+ config[:fruit] = %w[apples oranges]
122
+ config[:cool_enough] = true
123
+
124
+ subject[:fruit].must_equal %w[apples oranges]
125
+ subject[:cool_enough].must_equal true
126
+ end
127
+ end
128
+
129
+ describe "using static default_config statements" do
130
+
131
+ let(:config) do
132
+ { :need_it => true, :a_default => true }
133
+ end
134
+
135
+ let(:subject) do
136
+ Kitchen::Thing::StaticDefaults.new(config).finalize_config!(instance)
137
+ end
138
+
139
+ it "uses defaults" do
140
+ subject[:beans].must_equal "kidney"
141
+ subject[:tunables]["flimflam"].must_equal "positate"
142
+ subject[:edible].must_equal true
143
+ end
144
+
145
+ it "uses provided config over default_config" do
146
+ config[:beans] = "pinto"
147
+ config[:edible] = false
148
+
149
+ subject[:beans].must_equal "pinto"
150
+ subject[:edible].must_equal false
151
+ end
152
+
153
+ it "uses other config values to compute values" do
154
+ subject[:beans_url].must_equal "http://gim.me/kidney"
155
+ subject[:command].must_equal "curl http://gim.me/kidney"
156
+ end
157
+
158
+ it "computed value blocks have access to instance object" do
159
+ subject[:fetch_url].must_equal "http://gim.me/beans-for/coolbeans"
160
+ end
161
+
162
+ it "uses provided config over default_config for computed values" do
163
+ config[:command] = "echo listentome"
164
+ config[:beans] = "pinto"
165
+
166
+ subject[:command].must_equal "echo listentome"
167
+ subject[:beans_url].must_equal "http://gim.me/pinto"
168
+ end
169
+ end
170
+
171
+ describe "using inherited static default_config statements" do
172
+
173
+ let(:config) do
174
+ { :need_it => true, :a_default => "please" }
175
+ end
176
+
177
+ let(:subject) do
178
+ Kitchen::Thing::SubclassDefaults.new(config).finalize_config!(instance)
179
+ end
180
+
181
+ it "contains defaults from superclass" do
182
+ subject[:beans].must_equal "kidney"
183
+ subject[:tunables]["flimflam"].must_equal "positate"
184
+ subject[:edible].must_equal true
185
+ subject[:yea].must_equal "ya"
186
+ end
187
+
188
+ it "uses provided config over default config" do
189
+ config[:beans] = "pinto"
190
+ config[:edible] = false
191
+
192
+ subject[:beans].must_equal "pinto"
193
+ subject[:edible].must_equal false
194
+ subject[:yea].must_equal "ya"
195
+ subject[:beans_url].must_equal "http://gim.me/pinto"
196
+ end
197
+
198
+ it "uses its own default_config over inherited default_config" do
199
+ subject[:fetch_url].must_equal "http://no.beans"
200
+ subject[:command].must_equal "wget http://gim.me/kidney"
201
+ end
202
+ end
203
+
204
+ describe "using static required_config statements" do
205
+
206
+ let(:config) do
207
+ { :a_default => true }
208
+ end
209
+
210
+ let(:subject) do
211
+ Kitchen::Thing::StaticDefaults.new(config).finalize_config!(instance)
212
+ end
213
+
214
+ it "uses a value when provided" do
215
+ config[:need_it] = "okay"
216
+
217
+ subject[:need_it].must_equal "okay"
218
+ end
219
+
220
+ it "without a block, raises a UserError if attr is nil" do
221
+ config[:need_it] = nil
222
+
223
+ begin
224
+ subject
225
+ flunk "UserError must be raised"
226
+ rescue Kitchen::UserError => e
227
+ attr = "Kitchen::Thing::StaticDefaults<instance>#config[:need_it]"
228
+ e.message.must_equal "#{attr} cannot be blank"
229
+ end
230
+ end
231
+
232
+ it "without a block, raises a UserError if attr is an empty string" do
233
+ config[:need_it] = ""
234
+
235
+ begin
236
+ subject
237
+ flunk "UserError must be raised"
238
+ rescue Kitchen::UserError => e
239
+ attr = "Kitchen::Thing::StaticDefaults<instance>#config[:need_it]"
240
+ e.message.must_equal "#{attr} cannot be blank"
241
+ end
242
+ end
243
+
244
+ it "with a block, it is saved and invoked" do
245
+ config[:need_it] = "okay"
246
+ config[:no_nuts] = "nuts"
247
+
248
+ begin
249
+ subject
250
+ flunk "UserError must be raised"
251
+ rescue Kitchen::UserError => e
252
+ e.message.must_equal "NO NUTS FOR no_nuts!"
253
+ end
254
+ end
255
+ end
256
+
257
+ describe "using inherited static require_config statements" do
258
+
259
+ let(:subject) do
260
+ Kitchen::Thing::SubclassDefaults.new(config).finalize_config!(instance)
261
+ end
262
+
263
+ it "contains required config from superclass" do
264
+ config[:a_default] = nil
265
+ config[:need_it] = nil
266
+
267
+ begin
268
+ subject
269
+ flunk "UserError must be raised"
270
+ rescue Kitchen::UserError => e
271
+ attr = "Kitchen::Thing::StaticDefaults<instance>#config[:need_it]"
272
+ e.message.must_equal "#{attr} cannot be blank"
273
+ end
274
+ end
275
+
276
+ it "uses its own require_config over inherited require_config" do
277
+ config[:need_it] = true
278
+ config[:a_default] = nil
279
+
280
+ begin
281
+ subject
282
+ flunk "UserError must be raised"
283
+ rescue Kitchen::UserError => e
284
+ e.message.must_equal "Overriding a_default is fun"
285
+ end
286
+ end
287
+ end
288
+
289
+ describe "using static expand_path_for statements" do
290
+
291
+ let(:config) do
292
+ { :need_it => "a", :a_default => "b", :kitchen_root => "/tmp/yo/self" }
293
+ end
294
+
295
+ let(:subject) do
296
+ Kitchen::Thing::StaticDefaults.new(config).finalize_config!(instance)
297
+ end
298
+
299
+ it "expands a default value" do
300
+ subject[:success_path].must_equal "/tmp/yo/self/success"
301
+ end
302
+
303
+ it "uses provided config over default_config" do
304
+ config[:success_path] = "mine"
305
+
306
+ subject[:success_path].must_equal "/tmp/yo/self/mine"
307
+ end
308
+
309
+ it "leaves a full path expanded" do
310
+ config[:success_path] = "/the/other/one"
311
+
312
+ subject[:success_path].must_equal "/the/other/one"
313
+ end
314
+
315
+ it "doesn't expand path with a falsy expand_path_for value" do
316
+ config[:relative_path] = "./rel"
317
+
318
+ subject[:relative_path].must_equal "./rel"
319
+ end
320
+
321
+ it "expands a path if a lambda returns truthy" do
322
+ config[:something_else] = "is_set"
323
+ config[:complex_path] = "./complex"
324
+
325
+ subject[:complex_path].must_equal "/tmp/yo/self/complex"
326
+ end
327
+ end
328
+
329
+ describe "using inherited static expand_path_for statements" do
330
+
331
+ let(:config) do
332
+ { :need_it => "a", :a_default => "please", :kitchen_root => "/rooty" }
333
+ end
334
+
335
+ let(:subject) do
336
+ Kitchen::Thing::SubclassDefaults.new(config).finalize_config!(instance)
337
+ end
338
+
339
+ it "contains expand_path_for from superclass" do
340
+ subject[:success_path].must_equal "/rooty/success"
341
+ end
342
+
343
+ it "uses its own expand_path_for over inherited expand_path_for" do
344
+ config[:another_path] = "./pp"
345
+
346
+ subject[:another_path].must_equal "./pp"
347
+ end
348
+ end
349
+
350
+ it "#config_keys returns an array of config key names" do
351
+ subject = Kitchen::Thing::Tiny.new(:ice_cream => "dragon")
352
+
353
+ subject.config_keys.sort.must_equal [:ice_cream]
354
+ end
355
+ end
356
+
357
+ describe "#diagnose" do
358
+
359
+ it "returns an empty hash for no config" do
360
+ subject.diagnose.must_equal Hash.new
361
+ end
362
+
363
+ it "returns a hash of config" do
364
+ config[:alpha] = "beta"
365
+ subject.diagnose.must_equal(:alpha => "beta")
366
+ end
367
+
368
+ it "returns a hash with sorted keys" do
369
+ config[:zebra] = true
370
+ config[:elephant] = true
371
+
372
+ subject.diagnose.keys.must_equal [:elephant, :zebra]
373
+ end
374
+ end
375
+
376
+ describe "#calculate_path" do
377
+
378
+ let(:config) do
379
+ { :test_base_path => "/the/basest" }
380
+ end
381
+
382
+ let(:suite) do
383
+ stub(:name => "ultimate")
384
+ end
385
+
386
+ let(:instance) do
387
+ stub(:name => "coolbeans", :to_str => "<instance>", :suite => suite)
388
+ end
389
+
390
+ let(:subject) do
391
+ Kitchen::Thing::Tiny.new(config).finalize_config!(instance)
392
+ end
393
+
394
+ before do
395
+ FakeFS.activate!
396
+ end
397
+
398
+ after do
399
+ FakeFS.deactivate!
400
+ FakeFS::FileSystem.clear
401
+ end
402
+
403
+ describe "for directories" do
404
+
405
+ before do
406
+ FileUtils.mkdir_p(File.join(Dir.pwd, "winner"))
407
+ FileUtils.mkdir_p("/the/basest/winner")
408
+ FileUtils.mkdir_p("/the/basest/ultimate/winner")
409
+ end
410
+
411
+ it "prefers a path containing base path and suite name if it exists" do
412
+ subject.calculate_path("winner").
413
+ must_equal "/the/basest/ultimate/winner"
414
+ end
415
+
416
+ it "prefers a path containing base path if it exists" do
417
+ FileUtils.rm_rf("/the/basest/ultimate/winner")
418
+
419
+ subject.calculate_path("winner").must_equal "/the/basest/winner"
420
+ end
421
+
422
+ it "prefers a path in the current working directory if it exists" do
423
+ FileUtils.rm_rf("/the/basest/ultimate/winner")
424
+ FileUtils.rm_rf("/the/basest/winner")
425
+ pwd_dir = File.join(Dir.pwd, "winner")
426
+
427
+ subject.calculate_path("winner").must_equal pwd_dir
428
+ end
429
+
430
+ it "raises a UserError if test_base_path key is not set" do
431
+ config.delete(:test_base_path)
432
+
433
+ proc { subject.calculate_path("winner") }.must_raise Kitchen::UserError
434
+ end
435
+
436
+ it "uses a custom base path" do
437
+ FileUtils.mkdir_p("/custom/ultimate/winner")
438
+
439
+ subject.calculate_path("winner", :base_path => "/custom").
440
+ must_equal "/custom/ultimate/winner"
441
+ end
442
+ end
443
+
444
+ describe "for files" do
445
+
446
+ before do
447
+ FileUtils.mkdir_p(Dir.pwd)
448
+ FileUtils.touch(File.join(Dir.pwd, "winner"))
449
+ FileUtils.mkdir_p("/the/basest")
450
+ FileUtils.touch(File.join("/the/basest", "winner"))
451
+ FileUtils.mkdir_p("/the/basest/ultimate")
452
+ FileUtils.touch(File.join("/the/basest/ultimate", "winner"))
453
+ end
454
+
455
+ it "prefers a path containing base path and suite name if it exists" do
456
+ subject.calculate_path("winner", :type => :file).
457
+ must_equal "/the/basest/ultimate/winner"
458
+ end
459
+
460
+ it "prefers a path containing base path if it exists" do
461
+ FileUtils.rm_rf("/the/basest/ultimate/winner")
462
+
463
+ subject.calculate_path("winner", :type => :file).
464
+ must_equal "/the/basest/winner"
465
+ end
466
+
467
+ it "prefers a path in the current working directory if it exists" do
468
+ FileUtils.rm_rf("/the/basest/ultimate/winner")
469
+ FileUtils.rm_rf("/the/basest/winner")
470
+ pwd_dir = File.join(Dir.pwd, "winner")
471
+
472
+ subject.calculate_path("winner", :type => :file).must_equal pwd_dir
473
+ end
474
+
475
+ it "raises a UserError if test_base_path key is not set" do
476
+ config.delete(:test_base_path)
477
+
478
+ proc { subject.calculate_path("winner") }.must_raise Kitchen::UserError
479
+ end
480
+
481
+ it "uses a custom base path" do
482
+ FileUtils.mkdir_p("/custom/ultimate")
483
+ FileUtils.touch(File.join("/custom/ultimate", "winner"))
484
+
485
+ subject.calculate_path("winner", :type => :file, :base_path => "/custom").
486
+ must_equal "/custom/ultimate/winner"
487
+ end
488
+ end
489
+ end
490
+ end