puppetlabs-onceover 5.0.3 → 5.0.5

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 (34) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +4 -0
  3. data/.rubocop_todo.yml +35 -15
  4. data/CHANGELOG.md +398 -3
  5. data/Gemfile +21 -1
  6. data/lib/puppetlabs-onceover/cli/plugins.rb +3 -3
  7. data/lib/puppetlabs-onceover/controlrepo.rb +8 -7
  8. data/lib/puppetlabs-onceover/rake_tasks.rb +2 -2
  9. data/lib/puppetlabs-onceover/vendored_modules.rb +29 -32
  10. data/lib/puppetlabs-onceover/version.rb +1 -1
  11. data/spec/fixtures/controlrepos/deploy_test/Puppetfile +3 -0
  12. data/spec/fixtures/controlrepos/deploy_test/a_dir/nested_file.txt +1 -0
  13. data/spec/fixtures/controlrepos/deploy_test/a_symlink +1 -0
  14. data/spec/fixtures/controlrepos/deploy_test/environment.conf +1 -0
  15. data/spec/fixtures/controlrepos/deploy_test/regular_file.txt +1 -0
  16. data/spec/puppetlabs-onceover/beaker_spec.rb +293 -0
  17. data/spec/puppetlabs-onceover/class_spec.rb +89 -0
  18. data/spec/puppetlabs-onceover/cli/run_spec.rb +80 -0
  19. data/spec/puppetlabs-onceover/cli/show_spec.rb +67 -0
  20. data/spec/puppetlabs-onceover/cli_spec.rb +79 -0
  21. data/spec/puppetlabs-onceover/controlrepo_spec.rb +787 -5
  22. data/spec/puppetlabs-onceover/deploy_spec.rb +246 -0
  23. data/spec/puppetlabs-onceover/group_spec.rb +121 -0
  24. data/spec/puppetlabs-onceover/logger_spec.rb +30 -0
  25. data/spec/puppetlabs-onceover/node_spec.rb +124 -0
  26. data/spec/puppetlabs-onceover/rake_tasks_spec.rb +179 -0
  27. data/spec/puppetlabs-onceover/rspec/formatters_spec.rb +361 -0
  28. data/spec/puppetlabs-onceover/runner_spec.rb +307 -0
  29. data/spec/puppetlabs-onceover/test_spec.rb +177 -0
  30. data/spec/puppetlabs-onceover/testconfig_spec.rb +451 -0
  31. data/spec/puppetlabs-onceover/vendored_modules_spec.rb +293 -0
  32. data/spec/puppetlabs-onceover/version_spec.rb +10 -0
  33. data/spec/spec_helper.rb +8 -0
  34. metadata +23 -2
@@ -672,14 +672,15 @@ class PuppetlabsOnceover
672
672
  end
673
673
 
674
674
  def find_classname(filename)
675
- file = File.new(filename, "r")
676
- while (line = file.gets)
677
- begin
678
- if line =~ /^class (\w+(?:::\w+)*)/
679
- return $1
675
+ File.open(filename, "r") do |file|
676
+ while (line = file.gets)
677
+ begin
678
+ if line =~ /^class (\w+(?:::\w+)*)/
679
+ return $1
680
+ end
681
+ rescue ArgumentError => e
682
+ logger.error "ignoring invalid line in file: #{filename} (#{e.message}) - line: '#{line}'"
680
683
  end
681
- rescue ArgumentError => e
682
- logger.error "ignoring invalid line in file: #{filename} (#{e.message}) - line: '#{line}'"
683
684
  end
684
685
  end
685
686
  return nil
@@ -40,7 +40,7 @@ task :generate_onceover_yaml do
40
40
  repo = PuppetlabsOnceover::Controlrepo.new
41
41
  template_dir = File.expand_path('../../templates', File.dirname(__FILE__))
42
42
  onceover_yaml_template = File.read(File.expand_path('./controlrepo.yaml.erb', template_dir))
43
- puts ERB.new(onceover_yaml_template, nil, '-').result(binding)
43
+ puts ERB.new(onceover_yaml_template, trim_mode: '-').result(binding)
44
44
  end
45
45
 
46
46
  task :generate_nodesets do
@@ -81,7 +81,7 @@ task :generate_nodesets do
81
81
  # Use an ERB template
82
82
  template_dir = File.expand_path('../../templates', File.dirname(__FILE__))
83
83
  fixtures_template = File.read(File.expand_path('./nodeset.yaml.erb', template_dir))
84
- puts ERB.new(fixtures_template, nil, '-').result(binding)
84
+ puts ERB.new(fixtures_template, trim_mode: '-').result(binding)
85
85
  end
86
86
  end
87
87
 
@@ -1,6 +1,5 @@
1
1
  require 'puppet/version'
2
- require 'net/http'
3
- require 'uri'
2
+ require 'git'
4
3
  require 'multi_json'
5
4
  require 'r10k/module_loader/puppetfile'
6
5
  require 'puppetlabs-onceover/logger'
@@ -69,16 +68,18 @@ class PuppetlabsOnceover
69
68
  # any puppet version this gem tests against). Neither is needed: the
70
69
  # module list is stable (see CORE_MODULES) and each module's own repo
71
70
  # is the authoritative source for its own latest version.
72
- # https://docs.github.com/en/rest/repos/repos#list-repository-tags
71
+ #
72
+ # Tags are read via the git protocol (git ls-remote) rather than
73
+ # GitHub's REST API: the REST API's unauthenticated rate limit
74
+ # (60 req/hr) is shared across every GitHub-hosted Actions runner IP,
75
+ # so 10 sequential lookups per test run reliably exhausted it in CI.
76
+ # git's smart-HTTP protocol has no equivalent per-hour cap.
73
77
  @vendored_references = CORE_MODULES.map do |mod_name|
74
- tags = query_or_cache(
75
- "https://api.github.com/repos/puppetlabs/puppetlabs-#{mod_name}/tags",
76
- nil,
77
- component_cache(mod_name),
78
- )
78
+ repo_url = "https://github.com/puppetlabs/puppetlabs-#{mod_name}.git"
79
+ tags = query_or_cache(repo_url, component_cache(mod_name))
79
80
  latest_tag = tags.first['name']
80
81
  {
81
- 'url' => "https://github.com/puppetlabs/puppetlabs-#{mod_name}.git",
82
+ 'url' => repo_url,
82
83
  'ref' => "refs/tags/#{latest_tag}"
83
84
  }
84
85
  end
@@ -153,40 +154,36 @@ class PuppetlabsOnceover
153
154
  end
154
155
  end
155
156
 
156
- # Return json from a query whom caches, or from the cache to avoid spamming github
157
- def query_or_cache(url, params, filepath)
157
+ # Return tag data from a query whom caches, or from the cache to avoid
158
+ # re-querying the remote on every run
159
+ def query_or_cache(repo_url, filepath)
158
160
  if (File.exist? filepath) && (@force_update == false)
159
161
  logger.debug "Using cache: #{filepath}"
160
162
  json = read_json_dump(filepath)
161
163
  else
162
- logger.debug "Making GET request to: #{url}"
163
- json = github_get(url, params)
164
+ logger.debug "Listing tags for: #{repo_url}"
165
+ json = remote_tags(repo_url)
164
166
  logger.debug "Caching response to: #{filepath}"
165
167
  write_json_dump(filepath, json)
166
168
  end
167
169
  json
168
170
  end
169
171
 
170
- # Given a github url and optional query parameters, return the parsed json body
171
- def github_get(url, params)
172
- uri = URI.parse(url)
173
- uri.query = URI.encode_www_form(params) if params
174
- http = Net::HTTP.new(uri.host, uri.port)
175
- http.use_ssl = (uri.scheme == 'https')
176
- request = Net::HTTP::Get.new(uri.request_uri)
177
- request['Accept'] = 'application/vnd.github.raw+json'
178
- request['X-GitHub-Api-Version'] = '2022-11-28'
179
- response = http.request(request)
180
-
181
- case response
182
- when Net::HTTPOK # 200
183
- MultiJson.load(response.body)
184
- else
185
- # Expose the ratelimit response headers
186
- # https://docs.github.com/en/rest/using-the-rest-api/rate-limits-for-the-rest-api?apiVersion=2022-11-28#checking-the-status-of-your-rate-limit
187
- ratelimit_headers = response.to_hash.select { |k, _v| k =~ /x-ratelimit.*/ }
188
- raise "#{response.code} #{response.message} #{ratelimit_headers}"
172
+ # Given a git remote url, return its tags as [{ 'name' => tag }, ...],
173
+ # sorted newest version first (matches the shape/ordering the old
174
+ # GitHub REST tags API returned, so callers can keep using .first).
175
+ def remote_tags(repo_url)
176
+ refs = Git.ls_remote(repo_url, refs: true)['tags'] || {}
177
+ versions = refs.keys.filter_map do |name|
178
+ next if name.end_with?('^{}') # annotated-tag dereference entries
179
+
180
+ begin
181
+ [Gem::Version.new(name.delete_prefix('v')), name]
182
+ rescue ArgumentError
183
+ nil # skip non-version-shaped tags
184
+ end
189
185
  end
186
+ versions.sort_by(&:first).reverse.map { |_version, name| { 'name' => name } }
190
187
  end
191
188
 
192
189
  # Returns parsed json of file
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class PuppetlabsOnceover
4
- VERSION = '5.0.3'
4
+ VERSION = '5.0.5'
5
5
  end
@@ -0,0 +1,3 @@
1
+ forge "https://forge.puppet.com"
2
+
3
+ mod "puppetlabs/stdlib", '4.11.0'
@@ -0,0 +1 @@
1
+ regular_file.txt
@@ -0,0 +1 @@
1
+ modulepath = site:modules:$basemodulepath
@@ -0,0 +1,293 @@
1
+ require 'spec_helper'
2
+ require 'puppetlabs-onceover/beaker'
3
+
4
+ # The `beaker`/`beaker-rspec` gems are intentionally NOT in this project's Gemfile
5
+ # (removing that dependency is the whole point of this fork -- see CAT-2770 et al).
6
+ # beaker.rb's deprecated, still-shippped `deploy_controlrepo_on`/`provision_and_test`/
7
+ # `host_create` methods `require 'beaker-rspec'` / `require 'beaker/network_manager'`
8
+ # and reference `::Beaker::NetworkManager` inline. To cover those code paths without
9
+ # pulling the real (large, unmaintained-for-this-repo's-purposes) beaker gems back in,
10
+ # stand in a minimal fake `::Beaker::NetworkManager` and make the specific `require`
11
+ # calls those methods make into no-ops.
12
+ module ::Beaker
13
+ unless defined?(::Beaker::NetworkManager)
14
+ class NetworkManager
15
+ end
16
+ end
17
+ end
18
+
19
+ describe "PuppetlabsOnceover::Beaker" do
20
+ before(:each) do
21
+ allow(Kernel).to receive(:warn)
22
+ allow_any_instance_of(Object).to receive(:require).and_wrap_original do |original, name|
23
+ %w[beaker-rspec beaker/network_manager].include?(name) || original.call(name)
24
+ end
25
+ end
26
+
27
+ describe ".facts_to_vagrant_box" do
28
+ it "maps Ubuntu facts to a puppetlabs vagrant box name" do
29
+ facts = { 'os' => { 'distro' => { 'id' => 'Ubuntu', 'release' => { 'major' => '20.04' } }, 'architecture' => 'x86_64' } }
30
+ expect(PuppetlabsOnceover::Beaker.facts_to_vagrant_box(facts)).to eq('puppetlabs/ubuntu-20.04-64-puppet')
31
+ end
32
+
33
+ it "maps Debian facts to a puppetlabs vagrant box name" do
34
+ facts = { 'os' => { 'distro' => { 'id' => 'Debian', 'release' => { 'full' => '10.9' } }, 'architecture' => 'x86_64' } }
35
+ expect(PuppetlabsOnceover::Beaker.facts_to_vagrant_box(facts)).to eq('puppetlabs/Debian-10.9-64-puppet')
36
+ end
37
+
38
+ it "maps RedHat family facts to a centos vagrant box name" do
39
+ facts = { 'os' => { 'family' => 'RedHat', 'release' => { 'major' => '7', 'minor' => '9' }, 'architecture' => 'x86_64' } }
40
+ expect(PuppetlabsOnceover::Beaker.facts_to_vagrant_box(facts)).to eq('puppetlabs/centos-7.9-64-puppet')
41
+ end
42
+
43
+ it "returns UNKNOWN when the OS cannot be determined" do
44
+ facts = { 'os' => { 'family' => 'SomeWeirdOS', 'architecture' => 'x86_64' } }
45
+ expect(PuppetlabsOnceover::Beaker.facts_to_vagrant_box(facts)).to eq('UNKNOWN')
46
+ end
47
+
48
+ it "detects 32-bit architectures" do
49
+ facts = { 'os' => { 'family' => 'RedHat', 'release' => { 'major' => '6', 'minor' => '0' } }, 'architecture' => 'i386' }
50
+ expect(PuppetlabsOnceover::Beaker.facts_to_vagrant_box(facts)).to eq('puppetlabs/centos-6.0-32-puppet')
51
+ end
52
+
53
+ it "recurses over an array of factsets" do
54
+ facts = [
55
+ { 'os' => { 'family' => 'RedHat', 'release' => { 'major' => '7', 'minor' => '0' }, 'architecture' => 'x86_64' } },
56
+ { 'os' => { 'family' => 'SomeWeirdOS', 'architecture' => 'x86_64' } }
57
+ ]
58
+ result = PuppetlabsOnceover::Beaker.facts_to_vagrant_box(facts)
59
+ expect(result).to eq(['puppetlabs/centos-7.0-64-puppet', 'UNKNOWN'])
60
+ end
61
+
62
+ it "does not blow up when the facts hash does not have the expected 'os' shape" do
63
+ expect(PuppetlabsOnceover::Beaker.facts_to_vagrant_box({ 'architecture' => 'x86_64' })).to eq('UNKNOWN')
64
+ end
65
+ end
66
+
67
+ describe ".facts_to_platform" do
68
+ it "maps RedHat family facts to an 'el' platform string" do
69
+ facts = { 'os' => { 'family' => 'RedHat', 'release' => { 'major' => '7' }, 'architecture' => 'x86_64' } }
70
+ expect(PuppetlabsOnceover::Beaker.facts_to_platform(facts)).to eq('el-7-64')
71
+ end
72
+
73
+ it "maps Ubuntu facts to an 'ubuntu' platform string" do
74
+ facts = { 'os' => { 'family' => 'Debian', 'distro' => { 'id' => 'Ubuntu', 'release' => { 'major' => '20.04' } }, 'architecture' => 'x86_64' } }
75
+ expect(PuppetlabsOnceover::Beaker.facts_to_platform(facts)).to eq('ubuntu-20.04-64')
76
+ end
77
+
78
+ it "maps Debian facts to a 'debian' platform string" do
79
+ facts = { 'os' => { 'family' => 'Debian', 'distro' => { 'id' => 'Debian', 'release' => { 'full' => '10.9' } }, 'architecture' => 'x86_64' } }
80
+ expect(PuppetlabsOnceover::Beaker.facts_to_platform(facts)).to eq('debian-10.9-64')
81
+ end
82
+
83
+ it "detects 32-bit architectures" do
84
+ facts = { 'os' => { 'family' => 'RedHat', 'release' => { 'major' => '6' }, 'architecture' => 'i386' } }
85
+ expect(PuppetlabsOnceover::Beaker.facts_to_platform(facts)).to eq('el-6-32')
86
+ end
87
+
88
+ it "produces a nil-nil platform string when nothing matches" do
89
+ facts = { 'os' => { 'family' => 'SomeWeirdOS', 'architecture' => 'x86_64' } }
90
+ expect(PuppetlabsOnceover::Beaker.facts_to_platform(facts)).to eq('--64')
91
+ end
92
+
93
+ it "recurses over an array of factsets" do
94
+ facts = [
95
+ { 'os' => { 'family' => 'RedHat', 'release' => { 'major' => '7' }, 'architecture' => 'x86_64' } },
96
+ { 'os' => { 'family' => 'RedHat', 'release' => { 'major' => '8' }, 'architecture' => 'x86_64' } }
97
+ ]
98
+ expect(PuppetlabsOnceover::Beaker.facts_to_platform(facts)).to eq(['el-7-64', 'el-8-64'])
99
+ end
100
+ end
101
+
102
+ describe ".deploy_controlrepo_on" do
103
+ it "installs r10k, git, copies the r10k config, and deploys via r10k on a single host" do
104
+ host = double('host')
105
+ repo = double('repo', r10k_config_file: '/tmp/r10k.yaml')
106
+
107
+ allow(PuppetlabsOnceover::Beaker).to receive(:require).and_return(true)
108
+ allow(host).to receive(:is_a?).with(Array).and_return(false)
109
+ allow(PuppetlabsOnceover::Beaker).to receive(:install_r10k_on)
110
+ allow(host).to receive(:install_package).with('git')
111
+ allow(PuppetlabsOnceover::Beaker).to receive(:scp_to)
112
+ allow(PuppetlabsOnceover::Beaker).to receive(:r10k_deploy)
113
+
114
+ PuppetlabsOnceover::Beaker.deploy_controlrepo_on(host, repo)
115
+
116
+ expect(PuppetlabsOnceover::Beaker).to have_received(:install_r10k_on).with(host)
117
+ expect(host).to have_received(:install_package).with('git')
118
+ expect(PuppetlabsOnceover::Beaker).to have_received(:scp_to).with(host, '/tmp/r10k.yaml', '/tmp/r10k.yaml')
119
+ expect(PuppetlabsOnceover::Beaker).to have_received(:r10k_deploy).with(host, { puppetfile: true, configfile: '/tmp/r10k.yaml' })
120
+ end
121
+
122
+ it "recurses when given an array of hosts (accepted quirk: iterates `hosts`, an undefined local, not the `host` array param)" do
123
+ host_array = double('host_array')
124
+ allow(host_array).to receive(:is_a?).with(Array).and_return(true)
125
+ repo = double('repo', r10k_config_file: '/tmp/r10k.yaml')
126
+
127
+ allow(PuppetlabsOnceover::Beaker).to receive(:install_r10k_on)
128
+ allow(host_array).to receive(:install_package)
129
+ allow(PuppetlabsOnceover::Beaker).to receive(:scp_to)
130
+ allow(PuppetlabsOnceover::Beaker).to receive(:r10k_deploy)
131
+
132
+ # `hosts.each { ... }` inside the `if host.is_a?(Array)` branch references an
133
+ # undefined local `hosts` (not the `host` param) -- a real bug in the source.
134
+ # It raises NameError immediately, so the array-recursion branch never actually
135
+ # recurses; we assert the crash rather than pretending it works.
136
+ expect { PuppetlabsOnceover::Beaker.deploy_controlrepo_on(host_array, repo) }.to raise_error(NameError, /hosts/)
137
+ end
138
+ end
139
+
140
+ describe ".provision_and_test" do
141
+ # provision_and_test's 4th positional param defaults to `PuppetlabsOnceover::Controlrepo.new`
142
+ # -- Ruby evaluates that default for any omitted arg before the method body (and its own
143
+ # raise checks) ever runs, and a real `Controlrepo.new` throws outside an actual controlrepo.
144
+ # Always pass an explicit (unused) repo double as the 4th arg to avoid tripping that default.
145
+ let(:unused_repo) { double('repo') }
146
+
147
+ it "raises when given an array of hosts" do
148
+ expect do
149
+ PuppetlabsOnceover::Beaker.provision_and_test([double('host')], 'role::example', {}, unused_repo)
150
+ end.to raise_error(RuntimeError, /must be a single host object/)
151
+ end
152
+
153
+ it "raises when puppet_class is not a String" do
154
+ expect do
155
+ PuppetlabsOnceover::Beaker.provision_and_test(double('host'), :not_a_string, {}, unused_repo)
156
+ end.to raise_error(RuntimeError, /must be a single Class/)
157
+ end
158
+
159
+ it "provisions (when host is not up), applies the manifest, checks idempotency, and cleans up" do
160
+ host = double('host', up?: false)
161
+ network_manager = double('network_manager')
162
+ allow(network_manager).to receive(:instance_variable_set)
163
+ allow(network_manager).to receive(:provision)
164
+ allow(network_manager).to receive(:proxy_package_manager)
165
+ allow(network_manager).to receive(:validate)
166
+ allow(network_manager).to receive(:configure)
167
+ allow(network_manager).to receive(:cleanup)
168
+ allow(Beaker::NetworkManager).to receive(:new).and_return(network_manager)
169
+
170
+ apply_calls = []
171
+ allow(PuppetlabsOnceover::Beaker).to receive(:apply_manifest_on) do |h, manifest, opts|
172
+ apply_calls << [h, manifest, opts]
173
+ end
174
+ allow(PuppetlabsOnceover::Beaker).to receive(:options).and_return({})
175
+ allow(PuppetlabsOnceover::Beaker).to receive(:logger).and_return(double('logger'))
176
+
177
+ PuppetlabsOnceover::Beaker.provision_and_test(host, 'role::example', {}, unused_repo)
178
+
179
+ expect(network_manager).to have_received(:provision)
180
+ expect(network_manager).to have_received(:cleanup)
181
+ expect(apply_calls.map { |c| c[2] }).to include({ catch_failures: true }, { catch_changes: true })
182
+ end
183
+
184
+ it "skips provisioning when the host is already up, and skips idempotency check when disabled" do
185
+ host = double('host', up?: true)
186
+ network_manager = double('network_manager')
187
+ allow(network_manager).to receive(:instance_variable_set)
188
+ allow(network_manager).to receive(:cleanup)
189
+ allow(Beaker::NetworkManager).to receive(:new).and_return(network_manager)
190
+
191
+ apply_calls = []
192
+ allow(PuppetlabsOnceover::Beaker).to receive(:apply_manifest_on) do |h, manifest, opts|
193
+ apply_calls << [h, manifest, opts]
194
+ end
195
+ allow(PuppetlabsOnceover::Beaker).to receive(:options).and_return({})
196
+ allow(PuppetlabsOnceover::Beaker).to receive(:logger).and_return(double('logger'))
197
+
198
+ PuppetlabsOnceover::Beaker.provision_and_test(host, 'role::example', { check_idempotency: false, runs_before_idempotency: 2 }, unused_repo)
199
+
200
+ expect(apply_calls.size).to eq(2)
201
+ expect(apply_calls.map { |c| c[2] }).to eq([{ catch_failures: true }, { catch_failures: true }])
202
+ end
203
+ end
204
+
205
+ describe ".match_indentation" do
206
+ it "sets the logger's line_prefix based on the scoped_id depth" do
207
+ test = double('test', metadata: { scoped_id: '1:2:3' })
208
+ logger = double('logger')
209
+ expect(logger).to receive(:line_prefix=).with(' ')
210
+ PuppetlabsOnceover::Beaker.match_indentation(test, logger)
211
+ end
212
+
213
+ it "uses no prefix for a top-level scoped_id" do
214
+ test = double('test', metadata: { scoped_id: '1' })
215
+ logger = double('logger')
216
+ expect(logger).to receive(:line_prefix=).with('')
217
+ PuppetlabsOnceover::Beaker.match_indentation(test, logger)
218
+ end
219
+ end
220
+
221
+ describe ".host_create" do
222
+ it "builds a single host via a NetworkManager, wires up a down! method, and returns it" do
223
+ nodes = { HOSTS: { 'centos7' => { platform: 'el-7-x86_64' }, 'other' => { platform: 'ubuntu' } }, other_opt: 'value' }
224
+
225
+ host_double = double('host')
226
+ allow(host_double).to receive(:instance_variable_set)
227
+ allow(host_double).to receive(:define_singleton_method)
228
+
229
+ network_manager = double('network_manager')
230
+ allow(network_manager).to receive(:provision)
231
+ allow(network_manager).to receive(:proxy_package_manager)
232
+ allow(network_manager).to receive(:validate)
233
+ allow(network_manager).to receive(:configure)
234
+ allow(network_manager).to receive(:instance_variable_get).with(:@hosts).and_return([host_double])
235
+ allow(Beaker::NetworkManager).to receive(:new).and_return(network_manager)
236
+ allow(PuppetlabsOnceover::Beaker).to receive(:logger).and_return(double('logger'))
237
+ allow(PuppetlabsOnceover::Beaker).to receive(:hosts).and_return([host_double])
238
+
239
+ result = PuppetlabsOnceover::Beaker.host_create('centos7', nodes)
240
+
241
+ expect(result).to eq(host_double)
242
+ expect(Beaker::NetworkManager).to have_received(:new) do |opts, _logger|
243
+ expect(opts[:HOSTS]).to eq({ 'centos7' => { platform: 'el-7-x86_64' } })
244
+ expect(opts[:other_opt]).to eq('value')
245
+ end
246
+ end
247
+
248
+ it "supports the default_proc string/symbol key fallback on current_opts" do
249
+ nodes = { HOSTS: { 'centos7' => {} } }
250
+ captured_opts = nil
251
+
252
+ host_double = double('host')
253
+ allow(host_double).to receive(:instance_variable_set)
254
+ allow(host_double).to receive(:define_singleton_method)
255
+
256
+ network_manager = double('network_manager')
257
+ allow(network_manager).to receive(:provision)
258
+ allow(network_manager).to receive(:proxy_package_manager)
259
+ allow(network_manager).to receive(:validate)
260
+ allow(network_manager).to receive(:configure)
261
+ allow(network_manager).to receive(:instance_variable_get).with(:@hosts).and_return([host_double])
262
+ allow(Beaker::NetworkManager).to receive(:new) do |opts, _logger|
263
+ captured_opts = opts
264
+ network_manager
265
+ end
266
+ allow(PuppetlabsOnceover::Beaker).to receive(:logger).and_return(double('logger'))
267
+ allow(PuppetlabsOnceover::Beaker).to receive(:hosts).and_return([host_double])
268
+
269
+ PuppetlabsOnceover::Beaker.host_create('centos7', nodes)
270
+
271
+ expect(captured_opts[:HOSTS]).to eq({ 'centos7' => {} })
272
+ expect(captured_opts['HOSTS']).to eq({ 'centos7' => {} })
273
+ end
274
+
275
+ it "raises if the network manager created more than one host" do
276
+ nodes = { HOSTS: { 'centos7' => {} } }
277
+
278
+ network_manager = double('network_manager')
279
+ allow(network_manager).to receive(:provision)
280
+ allow(network_manager).to receive(:proxy_package_manager)
281
+ allow(network_manager).to receive(:validate)
282
+ allow(network_manager).to receive(:configure)
283
+ allow(network_manager).to receive(:instance_variable_get).with(:@hosts).and_return([double('a'), double('b')])
284
+ allow(Beaker::NetworkManager).to receive(:new).and_return(network_manager)
285
+ allow(PuppetlabsOnceover::Beaker).to receive(:logger).and_return(double('logger'))
286
+ allow(PuppetlabsOnceover::Beaker).to receive(:hosts).and_return([double('a'), double('b')])
287
+
288
+ expect do
289
+ PuppetlabsOnceover::Beaker.host_create('centos7', nodes)
290
+ end.to raise_error(RuntimeError, /too many machines/)
291
+ end
292
+ end
293
+ end
@@ -0,0 +1,89 @@
1
+ require 'spec_helper'
2
+ require 'puppetlabs-onceover/class'
3
+ require 'puppetlabs-onceover/controlrepo'
4
+
5
+ describe "PuppetlabsOnceover::Class" do
6
+ before(:each) do
7
+ PuppetlabsOnceover::Class.class_variable_set(:@@all, [])
8
+ # Silence the info/warn logging noise from the shared $logger global
9
+ logger.level = :fatal
10
+ end
11
+
12
+ describe ".name_is_regexp?" do
13
+ it "returns true for a /.../ delimited string" do
14
+ expect(PuppetlabsOnceover::Class.name_is_regexp?('/role::/')).to eq(true)
15
+ end
16
+
17
+ it "returns false for a plain string" do
18
+ expect(PuppetlabsOnceover::Class.name_is_regexp?('role::webserver')).to eq(false)
19
+ end
20
+
21
+ it "returns false for a string that only starts with /" do
22
+ expect(PuppetlabsOnceover::Class.name_is_regexp?('/role::webserver')).to eq(false)
23
+ end
24
+ end
25
+
26
+ describe ".string_to_regexp" do
27
+ it "converts a /.../ delimited string into a Regexp" do
28
+ expect(PuppetlabsOnceover::Class.string_to_regexp('/role::/')).to eq(Regexp.new('role::'))
29
+ end
30
+
31
+ it "raises when the string is not regexp-shaped" do
32
+ expect { PuppetlabsOnceover::Class.string_to_regexp('role::webserver') }.to raise_error(
33
+ RuntimeError, /does not start and end with/
34
+ )
35
+ end
36
+ end
37
+
38
+ describe "#initialize" do
39
+ it "sets the name and adds itself to .all for a plain class name" do
40
+ cls = PuppetlabsOnceover::Class.new('role::webserver')
41
+ expect(cls.name).to eq('role::webserver')
42
+ expect(PuppetlabsOnceover::Class.all).to eq([cls])
43
+ end
44
+
45
+ context "when given a regexp name" do
46
+ before(:each) do
47
+ PuppetlabsOnceover::Controlrepo.new(path: 'spec/fixtures/controlrepos/function_mocking')
48
+ end
49
+
50
+ it "expands into one Class object per matching controlrepo class, and does not add the regexp itself" do
51
+ PuppetlabsOnceover::Class.new('/role::/')
52
+ names = PuppetlabsOnceover::Class.all.map(&:name)
53
+ expect(names).not_to be_empty
54
+ expect(names).to all(match(/role::/))
55
+ end
56
+
57
+ it "creates no objects when nothing matches" do
58
+ PuppetlabsOnceover::Class.new('/nothing_matches_this_prefix_xyz::/')
59
+ expect(PuppetlabsOnceover::Class.all).to eq([])
60
+ end
61
+ end
62
+ end
63
+
64
+ describe ".find" do
65
+ it "finds an exact match by name" do
66
+ cls = PuppetlabsOnceover::Class.new('role::webserver')
67
+ expect(PuppetlabsOnceover::Class.find('role::webserver')).to eq(cls)
68
+ end
69
+
70
+ it "returns nil (and warns) when nothing matches" do
71
+ expect(PuppetlabsOnceover::Class.find('role::does_not_exist')).to be_nil
72
+ end
73
+
74
+ it "finds all matches when given a regexp name" do
75
+ a = PuppetlabsOnceover::Class.new('role::a')
76
+ b = PuppetlabsOnceover::Class.new('profile::b')
77
+ expect(PuppetlabsOnceover::Class.find('/role::/')).to eq([a])
78
+ expect(PuppetlabsOnceover::Class.find('/role::/')).not_to include(b)
79
+ end
80
+ end
81
+
82
+ describe ".all" do
83
+ it "returns every class created so far" do
84
+ a = PuppetlabsOnceover::Class.new('role::a')
85
+ b = PuppetlabsOnceover::Class.new('role::b')
86
+ expect(PuppetlabsOnceover::Class.all).to eq([a, b])
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,80 @@
1
+ require 'spec_helper'
2
+ require 'puppetlabs-onceover/cli/run'
3
+
4
+ describe PuppetlabsOnceover::CLI::Run do
5
+ describe '.command' do
6
+ it 'builds a memoized Cri::Command named run' do
7
+ cmd = described_class.command
8
+ expect(cmd).to be_a(Cri::Command)
9
+ expect(cmd.name).to eq('run')
10
+ expect(described_class.command).to equal(cmd)
11
+ end
12
+
13
+ it 'prints help and exits 0 when run directly' do
14
+ allow_any_instance_of(Object).to receive(:exit)
15
+ expect { described_class.command.run([]) }.to output(/Runs either the spec or acceptance tests/).to_stdout
16
+ end
17
+
18
+ it 'registers the spec and acceptance subcommands' do
19
+ names = described_class.command.subcommands.map(&:name)
20
+ expect(names).to include('spec', 'acceptance')
21
+ end
22
+ end
23
+
24
+ describe PuppetlabsOnceover::CLI::Run::Spec do
25
+ describe '.command' do
26
+ it 'builds a memoized Cri::Command named spec' do
27
+ cmd = described_class.command
28
+ expect(cmd).to be_a(Cri::Command)
29
+ expect(cmd.name).to eq('spec')
30
+ expect(described_class.command).to equal(cmd)
31
+ end
32
+
33
+ it 'deploys locally then prepares and runs the spec suite' do
34
+ repo = double('repo', onceover_yaml: '/some/repo/.onceover.yaml')
35
+ config = double('config')
36
+ deploy = double('deploy', deploy_local: nil)
37
+ runner = double('runner', prepare!: nil, run_spec!: nil)
38
+
39
+ allow(PuppetlabsOnceover::Controlrepo).to receive(:new).and_return(repo)
40
+ allow(PuppetlabsOnceover::TestConfig).to receive(:new).and_return(config)
41
+ allow(PuppetlabsOnceover::Deploy).to receive(:new).and_return(deploy)
42
+ allow(PuppetlabsOnceover::Runner).to receive(:new).and_return(runner)
43
+
44
+ described_class.command.run([])
45
+
46
+ expect(deploy).to have_received(:deploy_local).with(repo, anything)
47
+ expect(PuppetlabsOnceover::Runner).to have_received(:new).with(repo, config, :spec)
48
+ expect(runner).to have_received(:prepare!)
49
+ expect(runner).to have_received(:run_spec!)
50
+ end
51
+ end
52
+ end
53
+
54
+ describe PuppetlabsOnceover::CLI::Run::Acceptance do
55
+ describe '.command' do
56
+ it 'builds a memoized Cri::Command named acceptance' do
57
+ cmd = described_class.command
58
+ expect(cmd).to be_a(Cri::Command)
59
+ expect(cmd.name).to eq('acceptance')
60
+ expect(described_class.command).to equal(cmd)
61
+ end
62
+
63
+ it 'warns about deprecation then prepares and runs the acceptance suite' do
64
+ repo = double('repo', onceover_yaml: '/some/repo/.onceover.yaml')
65
+ config = double('config')
66
+ runner = double('runner', prepare!: nil, run_acceptance!: nil)
67
+
68
+ allow(PuppetlabsOnceover::Controlrepo).to receive(:new).and_return(repo)
69
+ allow(PuppetlabsOnceover::TestConfig).to receive(:new).and_return(config)
70
+ allow(PuppetlabsOnceover::Runner).to receive(:new).and_return(runner)
71
+
72
+ expect { described_class.command.run([]) }.to output(/DEPRECATION/).to_stderr
73
+
74
+ expect(PuppetlabsOnceover::Runner).to have_received(:new).with(repo, config, :acceptance)
75
+ expect(runner).to have_received(:prepare!)
76
+ expect(runner).to have_received(:run_acceptance!)
77
+ end
78
+ end
79
+ end
80
+ end