test-kitchen 0.7.0 → 1.0.0.alpha.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (95) hide show
  1. data/.gitignore +20 -0
  2. data/.travis.yml +11 -0
  3. data/.yardopts +3 -0
  4. data/Gemfile +13 -0
  5. data/Guardfile +11 -0
  6. data/LICENSE +15 -0
  7. data/README.md +131 -0
  8. data/Rakefile +69 -0
  9. data/bin/kitchen +9 -4
  10. data/features/cli.feature +17 -0
  11. data/features/cli_init.feature +156 -0
  12. data/features/support/env.rb +14 -0
  13. data/lib/kitchen/busser.rb +166 -0
  14. data/lib/kitchen/chef_data_uploader.rb +156 -0
  15. data/lib/kitchen/cli.rb +540 -0
  16. data/lib/kitchen/collection.rb +55 -0
  17. data/lib/kitchen/color.rb +46 -0
  18. data/lib/kitchen/config.rb +223 -0
  19. data/lib/kitchen/driver/base.rb +180 -0
  20. data/lib/kitchen/driver/dummy.rb +81 -0
  21. data/lib/kitchen/driver/ssh_base.rb +192 -0
  22. data/lib/kitchen/driver.rb +42 -0
  23. data/lib/kitchen/errors.rb +52 -0
  24. data/lib/kitchen/instance.rb +327 -0
  25. data/lib/kitchen/instance_actor.rb +42 -0
  26. data/lib/kitchen/loader/yaml.rb +105 -0
  27. data/lib/kitchen/logger.rb +145 -0
  28. data/{cookbooks/test-kitchen/libraries/helpers.rb → lib/kitchen/logging.rb} +13 -9
  29. data/lib/kitchen/manager.rb +45 -0
  30. data/lib/kitchen/metadata_chopper.rb +52 -0
  31. data/lib/kitchen/platform.rb +61 -0
  32. data/lib/kitchen/rake_tasks.rb +59 -0
  33. data/lib/kitchen/shell_out.rb +65 -0
  34. data/lib/kitchen/state_file.rb +88 -0
  35. data/lib/kitchen/suite.rb +76 -0
  36. data/lib/kitchen/thor_tasks.rb +62 -0
  37. data/lib/kitchen/util.rb +79 -0
  38. data/{cookbooks/test-kitchen/recipes/erlang.rb → lib/kitchen/version.rb} +9 -6
  39. data/lib/kitchen.rb +98 -0
  40. data/lib/vendor/hash_recursive_merge.rb +74 -0
  41. data/spec/kitchen/collection_spec.rb +80 -0
  42. data/spec/kitchen/color_spec.rb +54 -0
  43. data/spec/kitchen/config_spec.rb +201 -0
  44. data/spec/kitchen/driver/dummy_spec.rb +191 -0
  45. data/spec/kitchen/instance_spec.rb +162 -0
  46. data/spec/kitchen/loader/yaml_spec.rb +243 -0
  47. data/spec/kitchen/platform_spec.rb +48 -0
  48. data/spec/kitchen/state_file_spec.rb +122 -0
  49. data/spec/kitchen/suite_spec.rb +64 -0
  50. data/spec/spec_helper.rb +47 -0
  51. data/templates/plugin/driver.rb.erb +23 -0
  52. data/templates/plugin/license_apachev2.erb +15 -0
  53. data/templates/plugin/license_gplv2.erb +18 -0
  54. data/templates/plugin/license_gplv3.erb +16 -0
  55. data/templates/plugin/license_mit.erb +22 -0
  56. data/templates/plugin/license_reserved.erb +5 -0
  57. data/templates/plugin/version.rb.erb +12 -0
  58. data/test-kitchen.gemspec +44 -0
  59. metadata +290 -82
  60. data/config/Cheffile +0 -47
  61. data/config/Kitchenfile +0 -39
  62. data/config/Vagrantfile +0 -114
  63. data/cookbooks/test-kitchen/attributes/default.rb +0 -25
  64. data/cookbooks/test-kitchen/metadata.rb +0 -27
  65. data/cookbooks/test-kitchen/recipes/chef.rb +0 -19
  66. data/cookbooks/test-kitchen/recipes/compat.rb +0 -39
  67. data/cookbooks/test-kitchen/recipes/default.rb +0 -51
  68. data/cookbooks/test-kitchen/recipes/ruby.rb +0 -29
  69. data/lib/test-kitchen/cli/destroy.rb +0 -36
  70. data/lib/test-kitchen/cli/init.rb +0 -37
  71. data/lib/test-kitchen/cli/platform_list.rb +0 -37
  72. data/lib/test-kitchen/cli/project_info.rb +0 -44
  73. data/lib/test-kitchen/cli/ssh.rb +0 -36
  74. data/lib/test-kitchen/cli/status.rb +0 -36
  75. data/lib/test-kitchen/cli/test.rb +0 -68
  76. data/lib/test-kitchen/cli.rb +0 -282
  77. data/lib/test-kitchen/dsl.rb +0 -63
  78. data/lib/test-kitchen/environment.rb +0 -166
  79. data/lib/test-kitchen/platform.rb +0 -79
  80. data/lib/test-kitchen/project/base.rb +0 -159
  81. data/lib/test-kitchen/project/cookbook.rb +0 -97
  82. data/lib/test-kitchen/project/cookbook_copy.rb +0 -58
  83. data/lib/test-kitchen/project/ruby.rb +0 -37
  84. data/lib/test-kitchen/project/supported_platforms.rb +0 -75
  85. data/lib/test-kitchen/project.rb +0 -23
  86. data/lib/test-kitchen/runner/base.rb +0 -154
  87. data/lib/test-kitchen/runner/openstack/dsl.rb +0 -39
  88. data/lib/test-kitchen/runner/openstack/environment.rb +0 -141
  89. data/lib/test-kitchen/runner/openstack.rb +0 -147
  90. data/lib/test-kitchen/runner/vagrant.rb +0 -95
  91. data/lib/test-kitchen/runner.rb +0 -21
  92. data/lib/test-kitchen/scaffold.rb +0 -88
  93. data/lib/test-kitchen/ui.rb +0 -73
  94. data/lib/test-kitchen/version.rb +0 -21
  95. data/lib/test-kitchen.rb +0 -34
@@ -0,0 +1,201 @@
1
+ # -*- encoding: utf-8 -*-
2
+ #
3
+ # Author:: Fletcher Nichol (<fnichol@nichol.ca>)
4
+ #
5
+ # Copyright (C) 2012, 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
+
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/suite'
29
+ require 'kitchen/util'
30
+
31
+ module Kitchen
32
+
33
+ class DummyLoader
34
+
35
+ def read
36
+ @data || Hash.new
37
+ end
38
+
39
+ def data=(hash)
40
+ @data = hash
41
+ end
42
+ end
43
+ end
44
+
45
+ describe Kitchen::Config do
46
+
47
+ let(:loader) { Kitchen::DummyLoader.new }
48
+ let(:config) { Kitchen::Config.new(:loader => loader) }
49
+
50
+ before do
51
+ FakeFS.activate!
52
+ FileUtils.mkdir_p("/tmp")
53
+ end
54
+
55
+ after do
56
+ FakeFS.deactivate!
57
+ FakeFS::FileSystem.clear
58
+ end
59
+
60
+ describe "#platforms" do
61
+
62
+ it "returns platforms loaded from a kitchen.yml" do
63
+ stub_data!({ :platforms => [{ :name => 'one' }, { :name => 'two' }] })
64
+
65
+ config.platforms.size.must_equal 2
66
+ config.platforms[0].name.must_equal 'one'
67
+ config.platforms[1].name.must_equal 'two'
68
+ end
69
+
70
+ it "returns an empty Array if no platforms are given" do
71
+ stub_data!({})
72
+
73
+ config.platforms.must_equal []
74
+ end
75
+ end
76
+
77
+ describe "#suites" do
78
+
79
+ it "returns suites loaded from a kitchen.yml" do
80
+ stub_data!({ :suites => [
81
+ { :name => 'one', :run_list => [] },
82
+ { :name => 'two', :run_list => [] },
83
+ ] })
84
+
85
+ config.suites.size.must_equal 2
86
+ config.suites[0].name.must_equal 'one'
87
+ config.suites[1].name.must_equal 'two'
88
+ end
89
+
90
+ it "returns an empty Array if no suites are given" do
91
+ stub_data!({})
92
+
93
+ config.suites.must_equal []
94
+ end
95
+
96
+ it "returns a suite with nil for data_bags_path by default" do
97
+ stub_data!({ :suites => [{ :name => 'one', :run_list => [] }] })
98
+ config.suites.first.data_bags_path.must_be_nil
99
+ end
100
+
101
+ it "retuns a suite with a common data_bags_path set" do
102
+ stub_data!({ :suites => [{ :name => 'one', :run_list => [] }] })
103
+ config.test_base_path = "/tmp/base"
104
+ FileUtils.mkdir_p "/tmp/base/data_bags"
105
+
106
+ config.suites.first.data_bags_path.must_equal "/tmp/base/data_bags"
107
+ end
108
+
109
+ it "retuns a suite with a suite-specific data_bags_path set" do
110
+ stub_data!({ :suites => [{ :name => 'cool', :run_list => [] }] })
111
+ config.test_base_path = "/tmp/base"
112
+ FileUtils.mkdir_p "/tmp/base/cool/data_bags"
113
+ config.suites.first.data_bags_path.must_equal "/tmp/base/cool/data_bags"
114
+ end
115
+
116
+ it "returns a suite with nil for roles_path by default" do
117
+ stub_data!({ :suites => [{ :name => 'one', :run_list => [] }] })
118
+ config.suites.first.roles_path.must_be_nil
119
+ end
120
+
121
+ it "returns a suite with a common roles_path set" do
122
+ stub_data!({ :suites => [{ :name => 'one', :run_list => [] }] })
123
+ config.test_base_path = "/tmp/base"
124
+ FileUtils.mkdir_p "/tmp/base/roles"
125
+ config.suites.first.roles_path.must_equal "/tmp/base/roles"
126
+ end
127
+
128
+ it "returns a suite with a suite-specific roles_path set" do
129
+ stub_data!({ :suites => [{ :name => 'mysuite', :run_list => [] }] })
130
+ config.test_base_path = "/tmp/base"
131
+ FileUtils.mkdir_p "/tmp/base/mysuite/roles"
132
+ config.suites.first.roles_path.must_equal "/tmp/base/mysuite/roles"
133
+ end
134
+ end
135
+
136
+ describe "#instances" do
137
+
138
+ it "returns instances loaded from a kitchen.yml" do
139
+ stub_data!({
140
+ :platforms => [
141
+ { :name => 'p1' },
142
+ { :name => 'p2' },
143
+ ],
144
+ :suites => [
145
+ { :name => 's1', :run_list => [] },
146
+ { :name => 's2', :run_list => [] },
147
+ { :name => 's3', :run_list => [], :excludes => ['p1'] }
148
+ ]
149
+ })
150
+ config.instances.size.must_equal 5
151
+ config.instances.map { |i| i.name }.must_equal %w{s1-p1 s1-p2 s2-p1 s2-p2 s3-p2}
152
+ end
153
+
154
+ it "returns an instance containing a driver instance" do
155
+ stub_data!({
156
+ :platforms => [{ :name => 'platform', :driver_plugin => 'dummy' }],
157
+ :suites => [{ :name => 'suite', :run_list => [] }]
158
+ })
159
+ config.instances.first.driver.must_be_instance_of Kitchen::Driver::Dummy
160
+ end
161
+
162
+ it "returns an instance with a driver initialized with kitchen_root" do
163
+ config.kitchen_root = "/tmp"
164
+ stub_data!({
165
+ :platforms => [{ :name => 'platform', :driver_plugin => 'dummy' }],
166
+ :suites => [{ :name => 'suite', :run_list => [] }]
167
+ })
168
+ config.instances.first.driver[:kitchen_root].must_equal "/tmp"
169
+ end
170
+
171
+ it "returns an instance with a driver initialized with passed in config" do
172
+ stub_data!({
173
+ :platforms => [
174
+ { :name => 'platform', :driver_plugin => 'dummy',
175
+ :driver_config => { :foo => 'bar' }
176
+ }
177
+ ],
178
+ :suites => [{ :name => 'suite', :run_list => [] }]
179
+ })
180
+ config.instances.first.driver[:foo].must_equal "bar"
181
+ end
182
+ end
183
+
184
+ describe "#log_level" do
185
+
186
+ it "returns a default log_level of info" do
187
+ config.log_level.must_equal :info
188
+ end
189
+
190
+ it "returns an overridden log_level" do
191
+ config.log_level = :error
192
+ config.log_level.must_equal :error
193
+ end
194
+ end
195
+
196
+ private
197
+
198
+ def stub_data!(hash)
199
+ loader.data = hash
200
+ end
201
+ end
@@ -0,0 +1,191 @@
1
+ # -*- encoding: utf-8 -*-
2
+ #
3
+ # Author:: Fletcher Nichol (<fnichol@nichol.ca>)
4
+ #
5
+ # Copyright (C) 2012, 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 'logger'
21
+ require 'stringio'
22
+
23
+ require 'kitchen/driver/dummy'
24
+
25
+ describe Kitchen::Driver::Dummy do
26
+
27
+ let(:logged_output) { StringIO.new }
28
+ let(:logger) { Logger.new(logged_output) }
29
+ let(:config) { Hash.new }
30
+ let(:state) { Hash.new }
31
+
32
+ let(:instance) do
33
+ stub(:name => "coolbeans", :logger => logger, :to_str => "instance")
34
+ end
35
+
36
+ let(:driver) do
37
+ d = Kitchen::Driver::Dummy.new(config)
38
+ d.instance = instance
39
+ d
40
+ end
41
+
42
+ describe "default_config" do
43
+
44
+ it "sets :sleep to 0 by default" do
45
+ driver[:sleep].must_equal 0
46
+ end
47
+
48
+ it "sets :random_failure to false by default" do
49
+ driver[:random_failure].must_equal false
50
+ end
51
+ end
52
+
53
+ describe "#create" do
54
+
55
+ it "sets :my_id to a unique value as an example" do
56
+ driver.create(state)
57
+
58
+ state[:my_id].must_match /^coolbeans-/
59
+ end
60
+
61
+ it "calls sleep if :sleep value is greater than 0" do
62
+ config[:sleep] = 12.5
63
+ driver.expects(:sleep).with(12.5).returns(true)
64
+
65
+ driver.create(state)
66
+ end
67
+
68
+ it "randomly raises ActionFailed if :random_failure is set" do
69
+ config[:random_failure] = true
70
+ driver.stubs(:randomly_fail?).returns(true)
71
+
72
+ proc { driver.create(state) }.must_raise Kitchen::ActionFailed
73
+ end
74
+
75
+ it "will only raise ActionFailed if :random_failure is set" do
76
+ config[:random_failure] = true
77
+
78
+ begin
79
+ driver.create(state)
80
+ rescue Kitchen::ActionFailed => ex
81
+ # If exception is anything other than Kitchen::ActionFailed, this spec
82
+ # will fail
83
+ end
84
+ end
85
+
86
+ it "logs a create event to INFO" do
87
+ driver.create(state)
88
+
89
+ logged_output.string.must_match /^.+ INFO .+ \[Dummy\] Create on .+$/
90
+ end
91
+ end
92
+
93
+ describe "#converge" do
94
+
95
+ it "calls sleep if :sleep value is greater than 0" do
96
+ config[:sleep] = 12.5
97
+ driver.expects(:sleep).with(12.5).returns(true)
98
+
99
+ driver.create(state)
100
+ end
101
+
102
+ it "randomly raises ActionFailed if :random_failure is set" do
103
+ config[:random_failure] = true
104
+ driver.stubs(:randomly_fail?).returns(true)
105
+
106
+ proc { driver.converge(state) }.must_raise Kitchen::ActionFailed
107
+ end
108
+
109
+ it "logs a converge event to INFO" do
110
+ driver.converge(state)
111
+
112
+ logged_output.string.must_match /^.+ INFO .+ \[Dummy\] Converge on .+$/
113
+ end
114
+ end
115
+
116
+ describe "#setup" do
117
+
118
+ it "calls sleep if :sleep value is greater than 0" do
119
+ config[:sleep] = 12.5
120
+ driver.expects(:sleep).with(12.5).returns(true)
121
+
122
+ driver.setup(state)
123
+ end
124
+
125
+ it "randomly raises ActionFailed if :random_failure is set" do
126
+ config[:random_failure] = true
127
+ driver.stubs(:randomly_fail?).returns(true)
128
+
129
+ proc { driver.setup(state) }.must_raise Kitchen::ActionFailed
130
+ end
131
+
132
+ it "logs a setup event to INFO" do
133
+ driver.setup(state)
134
+
135
+ logged_output.string.must_match /^.+ INFO .+ \[Dummy\] Setup on .+$/
136
+ end
137
+ end
138
+
139
+ describe "#verify" do
140
+
141
+ it "calls sleep if :sleep value is greater than 0" do
142
+ config[:sleep] = 12.5
143
+ driver.expects(:sleep).with(12.5).returns(true)
144
+
145
+ driver.verify(state)
146
+ end
147
+
148
+ it "randomly raises ActionFailed if :random_failure is set" do
149
+ config[:random_failure] = true
150
+ driver.stubs(:randomly_fail?).returns(true)
151
+
152
+ proc { driver.verify(state) }.must_raise Kitchen::ActionFailed
153
+ end
154
+
155
+ it "logs a verify event to INFO" do
156
+ driver.verify(state)
157
+
158
+ logged_output.string.must_match /^.+ INFO .+ \[Dummy\] Verify on .+$/
159
+ end
160
+ end
161
+
162
+ describe "#destroy" do
163
+
164
+ it "removes :my_id from the state hash" do
165
+ state[:my_id] = "90210"
166
+ driver.destroy(state)
167
+
168
+ state[:my_id].must_be_nil
169
+ end
170
+
171
+ it "calls sleep if :sleep value is greater than 0" do
172
+ config[:sleep] = 12.5
173
+ driver.expects(:sleep).with(12.5).returns(true)
174
+
175
+ driver.verify(state)
176
+ end
177
+
178
+ it "randomly raises ActionFailed if :random_failure is set" do
179
+ config[:random_failure] = true
180
+ driver.stubs(:randomly_fail?).returns(true)
181
+
182
+ proc { driver.destroy(state) }.must_raise Kitchen::ActionFailed
183
+ end
184
+
185
+ it "logs a destroy event to INFO" do
186
+ driver.destroy(state)
187
+
188
+ logged_output.string.must_match /^.+ INFO .+ \[Dummy\] Destroy on .+$/
189
+ end
190
+ end
191
+ end
@@ -0,0 +1,162 @@
1
+ # -*- encoding: utf-8 -*-
2
+ #
3
+ # Author:: Fletcher Nichol (<fnichol@nichol.ca>)
4
+ #
5
+ # Copyright (C) 2012, 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 'logger'
21
+ require 'stringio'
22
+
23
+ require 'kitchen/logging'
24
+ require 'kitchen/instance'
25
+ require 'kitchen/driver'
26
+ require 'kitchen/driver/dummy'
27
+ require 'kitchen/platform'
28
+ require 'kitchen/suite'
29
+
30
+ describe Kitchen::Instance do
31
+
32
+ let(:suite) do
33
+ Kitchen::Suite.new({ :name => 'suite',
34
+ :run_list => 'suite_list', :attributes => { :s => 'ss' } })
35
+ end
36
+
37
+ let(:platform) do
38
+ Kitchen::Platform.new({ :name => 'platform',
39
+ :run_list => 'platform_list', :attributes => { :p => 'pp' } })
40
+ end
41
+
42
+ let(:driver) { Kitchen::Driver::Dummy.new({}) }
43
+
44
+ let(:opts) do
45
+ { :suite => suite, :platform => platform, :driver => driver }
46
+ end
47
+
48
+ let(:instance) { Kitchen::Instance.new(opts) }
49
+
50
+ before do
51
+ Celluloid.logger = Logger.new(StringIO.new)
52
+ end
53
+
54
+ it "raises an ArgumentError if suite is missing" do
55
+ opts.delete(:suite)
56
+ proc { Kitchen::Instance.new(opts) }.must_raise Kitchen::ClientError
57
+ end
58
+
59
+ it "raises an ArgumentError if platform is missing" do
60
+ opts.delete(:platform)
61
+ proc { Kitchen::Instance.new(opts) }.must_raise Kitchen::ClientError
62
+ end
63
+
64
+ it "returns suite" do
65
+ instance.suite.must_equal suite
66
+ end
67
+
68
+ it "returns platform" do
69
+ instance.platform.must_equal platform
70
+ end
71
+
72
+ describe "#name" do
73
+
74
+ def combo(suite_name, platform_name)
75
+ opts[:suite] = Kitchen::Suite.new(
76
+ :name => suite_name, :run_list => []
77
+ )
78
+ opts[:platform] = Kitchen::Platform.new(
79
+ :name => platform_name
80
+ )
81
+ Kitchen::Instance.new(opts)
82
+ end
83
+
84
+ it "combines the suite and platform names with a dash" do
85
+ combo('suite', 'platform').name.must_equal "suite-platform"
86
+ end
87
+
88
+ it "squashes periods" do
89
+ combo('suite.ness', 'platform').name.must_equal "suiteness-platform"
90
+ combo('suite', 'platform.s').name.must_equal "suite-platforms"
91
+ combo('s.s.', '.p.p').name.must_equal "ss-pp"
92
+ end
93
+
94
+ it "transforms underscores to dashes" do
95
+ combo('suite_ness', 'platform').name.must_equal "suite-ness-platform"
96
+ combo('suite', 'platform-s').name.must_equal "suite-platform-s"
97
+ combo('_s__s_', 'pp_').name.must_equal "-s--s--pp-"
98
+ end
99
+ end
100
+
101
+ describe "#run_list" do
102
+
103
+ def combo(suite_list, platform_list)
104
+ opts[:suite] = Kitchen::Suite.new(
105
+ :name => 'suite', :run_list => suite_list
106
+ )
107
+ opts[:platform] = Kitchen::Platform.new(
108
+ :name => 'platform', :run_list => platform_list
109
+ )
110
+ Kitchen::Instance.new(opts)
111
+ end
112
+
113
+ it "combines the platform then suite run_lists" do
114
+ combo(%w{s1 s2}, %w{p1 p2}).run_list.must_equal %w{p1 p2 s1 s2}
115
+ end
116
+
117
+ it "uses the suite run_list only when platform run_list is empty" do
118
+ combo(%w{sa sb}, nil).run_list.must_equal %w{sa sb}
119
+ end
120
+
121
+ it "returns an emtpy Array if both run_lists are empty" do
122
+ combo([], nil).run_list.must_equal []
123
+ end
124
+ end
125
+
126
+ describe "#attributes" do
127
+
128
+ def combo(suite_attrs, platform_attrs)
129
+ opts[:suite] = Kitchen::Suite.new(
130
+ :name => 'suite', :run_list => [], :attributes => suite_attrs
131
+ )
132
+ opts[:platform] = Kitchen::Platform.new(
133
+ :name => 'platform', :attributes => platform_attrs
134
+ )
135
+ Kitchen::Instance.new(opts)
136
+ end
137
+
138
+ it "merges suite and platform hashes together" do
139
+ combo(
140
+ { :suite => { :s1 => 'sv1' } },
141
+ { :suite => { :p1 => 'pv1' }, :platform => 'pp' }
142
+ ).attributes.must_equal({
143
+ :suite => { :s1 => 'sv1', :p1 => 'pv1' },
144
+ :platform => 'pp'
145
+ })
146
+ end
147
+
148
+ it "merges suite values over platform values" do
149
+ combo(
150
+ { :common => { :c1 => 'xxx' } },
151
+ { :common => { :c1 => 'cv1', :c2 => 'cv2' } },
152
+ ).attributes.must_equal({
153
+ :common => { :c1 => 'xxx', :c2 => 'cv2' }
154
+ })
155
+ end
156
+ end
157
+
158
+ it "#dna combines attributes with the run_list" do
159
+ instance.dna.must_equal({ :s => 'ss', :p => 'pp',
160
+ :run_list => ['platform_list', 'suite_list'] })
161
+ end
162
+ end