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.
- checksums.yaml +4 -4
- data/.cane +1 -1
- data/.rubocop.yml +3 -0
- data/.travis.yml +20 -9
- data/CHANGELOG.md +219 -108
- data/Gemfile +10 -6
- data/Guardfile +38 -9
- data/README.md +11 -1
- data/Rakefile +21 -37
- data/bin/kitchen +4 -4
- data/features/kitchen_action_commands.feature +161 -0
- data/features/kitchen_console_command.feature +34 -0
- data/features/kitchen_diagnose_command.feature +64 -0
- data/features/kitchen_init_command.feature +29 -17
- data/features/kitchen_list_command.feature +2 -2
- data/features/kitchen_login_command.feature +56 -0
- data/features/{sink_command.feature → kitchen_sink_command.feature} +0 -0
- data/features/kitchen_test_command.feature +88 -0
- data/features/step_definitions/gem_steps.rb +8 -6
- data/features/step_definitions/git_steps.rb +4 -2
- data/features/step_definitions/output_steps.rb +5 -0
- data/features/support/env.rb +12 -9
- data/lib/kitchen.rb +60 -38
- data/lib/kitchen/base64_stream.rb +55 -0
- data/lib/kitchen/busser.rb +124 -58
- data/lib/kitchen/cli.rb +121 -38
- data/lib/kitchen/collection.rb +3 -3
- data/lib/kitchen/color.rb +4 -4
- data/lib/kitchen/command.rb +78 -11
- data/lib/kitchen/command/action.rb +3 -2
- data/lib/kitchen/command/console.rb +12 -5
- data/lib/kitchen/command/diagnose.rb +17 -3
- data/lib/kitchen/command/driver_discover.rb +26 -7
- data/lib/kitchen/command/exec.rb +41 -0
- data/lib/kitchen/command/list.rb +44 -14
- data/lib/kitchen/command/login.rb +2 -1
- data/lib/kitchen/command/sink.rb +2 -1
- data/lib/kitchen/command/test.rb +5 -4
- data/lib/kitchen/config.rb +146 -14
- data/lib/kitchen/configurable.rb +314 -0
- data/lib/kitchen/data_munger.rb +522 -18
- data/lib/kitchen/diagnostic.rb +43 -4
- data/lib/kitchen/driver.rb +4 -4
- data/lib/kitchen/driver/base.rb +80 -115
- data/lib/kitchen/driver/dummy.rb +34 -6
- data/lib/kitchen/driver/proxy.rb +14 -3
- data/lib/kitchen/driver/ssh_base.rb +61 -7
- data/lib/kitchen/errors.rb +109 -9
- data/lib/kitchen/generator/driver_create.rb +39 -5
- data/lib/kitchen/generator/init.rb +130 -45
- data/lib/kitchen/instance.rb +162 -28
- data/lib/kitchen/lazy_hash.rb +79 -7
- data/lib/kitchen/loader/yaml.rb +159 -27
- data/lib/kitchen/logger.rb +267 -21
- data/lib/kitchen/logging.rb +30 -3
- data/lib/kitchen/login_command.rb +11 -2
- data/lib/kitchen/metadata_chopper.rb +2 -2
- data/lib/kitchen/provisioner.rb +4 -4
- data/lib/kitchen/provisioner/base.rb +107 -103
- data/lib/kitchen/provisioner/chef/berkshelf.rb +36 -8
- data/lib/kitchen/provisioner/chef/librarian.rb +40 -11
- data/lib/kitchen/provisioner/chef_base.rb +206 -167
- data/lib/kitchen/provisioner/chef_solo.rb +25 -7
- data/lib/kitchen/provisioner/chef_zero.rb +105 -29
- data/lib/kitchen/provisioner/dummy.rb +1 -1
- data/lib/kitchen/provisioner/shell.rb +21 -6
- data/lib/kitchen/rake_tasks.rb +8 -3
- data/lib/kitchen/shell_out.rb +15 -18
- data/lib/kitchen/ssh.rb +122 -27
- data/lib/kitchen/state_file.rb +24 -7
- data/lib/kitchen/thor_tasks.rb +9 -4
- data/lib/kitchen/util.rb +43 -118
- data/lib/kitchen/version.rb +1 -1
- data/lib/vendor/hash_recursive_merge.rb +10 -2
- data/spec/kitchen/base64_stream_spec.rb +77 -0
- data/spec/kitchen/busser_spec.rb +490 -0
- data/spec/kitchen/collection_spec.rb +10 -10
- data/spec/kitchen/color_spec.rb +2 -2
- data/spec/kitchen/config_spec.rb +234 -62
- data/spec/kitchen/configurable_spec.rb +490 -0
- data/spec/kitchen/data_munger_spec.rb +1070 -862
- data/spec/kitchen/diagnostic_spec.rb +79 -0
- data/spec/kitchen/driver/base_spec.rb +80 -85
- data/spec/kitchen/driver/dummy_spec.rb +43 -14
- data/spec/kitchen/driver/proxy_spec.rb +134 -0
- data/spec/kitchen/driver/ssh_base_spec.rb +644 -0
- data/spec/kitchen/driver_spec.rb +15 -15
- data/spec/kitchen/errors_spec.rb +309 -0
- data/spec/kitchen/instance_spec.rb +143 -46
- data/spec/kitchen/lazy_hash_spec.rb +36 -9
- data/spec/kitchen/loader/yaml_spec.rb +237 -226
- data/spec/kitchen/logger_spec.rb +419 -0
- data/spec/kitchen/logging_spec.rb +59 -0
- data/spec/kitchen/login_command_spec.rb +49 -0
- data/spec/kitchen/metadata_chopper_spec.rb +82 -0
- data/spec/kitchen/platform_spec.rb +4 -4
- data/spec/kitchen/provisioner/base_spec.rb +65 -125
- data/spec/kitchen/provisioner/chef_base_spec.rb +798 -0
- data/spec/kitchen/provisioner/chef_solo_spec.rb +316 -0
- data/spec/kitchen/provisioner/chef_zero_spec.rb +624 -0
- data/spec/kitchen/provisioner/shell_spec.rb +269 -0
- data/spec/kitchen/provisioner_spec.rb +6 -6
- data/spec/kitchen/shell_out_spec.rb +143 -0
- data/spec/kitchen/ssh_spec.rb +683 -0
- data/spec/kitchen/state_file_spec.rb +28 -21
- data/spec/kitchen/suite_spec.rb +7 -7
- data/spec/kitchen/util_spec.rb +68 -10
- data/spec/kitchen_spec.rb +107 -0
- data/spec/spec_helper.rb +18 -13
- data/support/chef-client-zero.rb +10 -9
- data/support/chef_helpers.sh +16 -0
- data/support/download_helpers.sh +109 -0
- data/test-kitchen.gemspec +42 -33
- 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
|
20
|
-
require
|
19
|
+
require_relative "../spec_helper"
|
20
|
+
require "ostruct"
|
21
21
|
|
22
|
-
require
|
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(
|
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(
|
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(
|
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(
|
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(
|
57
|
-
result[1].must_equal obj(
|
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
|
71
|
+
collection.as_names.must_equal %w[one two two three]
|
72
72
|
end
|
73
73
|
end
|
74
74
|
|
data/spec/kitchen/color_spec.rb
CHANGED
@@ -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
|
19
|
+
require_relative "../spec_helper"
|
20
20
|
|
21
|
-
require
|
21
|
+
require "kitchen/color"
|
22
22
|
|
23
23
|
describe Kitchen::Color do
|
24
24
|
|
data/spec/kitchen/config_spec.rb
CHANGED
@@ -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
|
20
|
-
|
21
|
-
require
|
22
|
-
require
|
23
|
-
require
|
24
|
-
require
|
25
|
-
require
|
26
|
-
require
|
27
|
-
require
|
28
|
-
require
|
29
|
-
require
|
30
|
-
require
|
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
|
-
{
|
53
|
-
:
|
54
|
-
:
|
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
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
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
|
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
|
138
|
-
|
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
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
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
|
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 "
|
160
|
-
|
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
|
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
|
-
|
169
|
-
|
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
|
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 "
|
175
|
-
|
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
|
-
|
178
|
-
|
179
|
-
|
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
|
-
|
193
|
-
|
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
|