test-kitchen 1.2.1 → 1.3.0

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 (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