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
@@ -0,0 +1,179 @@
1
+ require 'spec_helper'
2
+ require 'rake'
3
+ require 'ostruct'
4
+
5
+ describe 'rake_tasks.rb' do
6
+ # SimpleCov/Coverage tracks a given file path's hit-lines against the ISeq
7
+ # produced by the FIRST time it's compiled in this process. Re-`load`ing
8
+ # the file fresh in a before(:each) (once per example) makes every example
9
+ # after the first exercise a *new*, uninstrumented compilation, so real
10
+ # task-body coverage silently reads back as 0. Instead we `load` the file
11
+ # exactly once for the whole group, and use `Rake::Task#reenable` before
12
+ # each invoke so a task can be invoked again in a later example.
13
+ rake_tasks_path = File.expand_path('../../lib/puppetlabs-onceover/rake_tasks.rb', __dir__)
14
+ Rake.application = Rake::Application.new
15
+ load rake_tasks_path
16
+
17
+ before(:each) do
18
+ Rake::Task.tasks.each(&:reenable)
19
+ end
20
+
21
+ describe 'generate_fixtures' do
22
+ it 'writes fixtures.yml when it does not already exist' do
23
+ repo = double('repo', root: '/tmp/nonexistent_root_for_test', fixtures: "fixture_content")
24
+ allow(PuppetlabsOnceover::Controlrepo).to receive(:new).and_return(repo)
25
+ allow(File).to receive(:exist?).and_call_original
26
+ allow(File).to receive(:exist?).with(File.expand_path('./.fixtures.yml', repo.root)).and_return(false)
27
+ allow(File).to receive(:write)
28
+
29
+ Rake::Task['generate_fixtures'].invoke
30
+
31
+ expect(File).to have_received(:write).with(File.expand_path('./.fixtures.yml', repo.root), 'fixture_content')
32
+ end
33
+
34
+ it 'raises when fixtures.yml already exists' do
35
+ repo = double('repo', root: '/tmp/nonexistent_root_for_test')
36
+ allow(PuppetlabsOnceover::Controlrepo).to receive(:new).and_return(repo)
37
+ allow(File).to receive(:exist?).and_call_original
38
+ allow(File).to receive(:exist?).with(File.expand_path('./.fixtures.yml', repo.root)).and_return(true)
39
+
40
+ expect { Rake::Task['generate_fixtures'].invoke }.to raise_error(/already exits/)
41
+ end
42
+ end
43
+
44
+ describe 'hiera_setup' do
45
+ it 'updates the datadir for hash values containing datadir and writes the new config' do
46
+ hiera_config = { defaults: { datadir: 'orig/data' } }
47
+ repo = double('repo')
48
+ allow(repo).to receive(:hiera_config).and_return(hiera_config)
49
+ # Use File.expand_path so these are real OS-native absolute paths on any
50
+ # platform (Windows Pathname#relative_path_from raises "different prefix"
51
+ # when comparing a bare '/some/repo'-style path against one that's been
52
+ # expanded to a drive-letter path).
53
+ allow(repo).to receive(:hiera_config_file).and_return(File.expand_path('/some/repo/hiera.yaml'))
54
+ allow(repo).to receive(:hiera_data).and_return(File.expand_path('/some/repo/data'))
55
+ allow(repo).to receive(:hiera_config=)
56
+ allow(PuppetlabsOnceover::Controlrepo).to receive(:new).and_return(repo)
57
+
58
+ Rake::Task['hiera_setup'].invoke
59
+
60
+ expect(repo).to have_received(:hiera_config=) do |new_config|
61
+ expect(new_config[:defaults][:datadir]).to eq('data')
62
+ end
63
+ end
64
+
65
+ it 'leaves non-hash values alone' do
66
+ hiera_config = { version: 5 }
67
+ repo = double('repo')
68
+ allow(repo).to receive(:hiera_config).and_return(hiera_config)
69
+ allow(repo).to receive(:hiera_config_file).and_return('/some/repo/hiera.yaml')
70
+ allow(repo).to receive(:hiera_data).and_return('/some/repo/data')
71
+ allow(repo).to receive(:hiera_config=)
72
+ allow(PuppetlabsOnceover::Controlrepo).to receive(:new).and_return(repo)
73
+
74
+ Rake::Task['hiera_setup'].invoke
75
+
76
+ expect(repo).to have_received(:hiera_config=).with(hiera_config)
77
+ end
78
+ end
79
+
80
+ describe 'controlrepo_details' do
81
+ it 'prints the controlrepo to_s' do
82
+ repo = double('repo', to_s: 'REPO DETAILS')
83
+ allow(PuppetlabsOnceover::Controlrepo).to receive(:new).and_return(repo)
84
+
85
+ expect { Rake::Task['controlrepo_details'].invoke }.to output(/REPO DETAILS/).to_stdout
86
+ end
87
+ end
88
+
89
+ describe 'generate_onceover_yaml' do
90
+ it 'reads the controlrepo and renders the controlrepo.yaml.erb template' do
91
+ repo = double('repo', roles: %w[role::web role::db], facts_files: ['/some/repo/facts/centos7.json', '/some/repo/facts/Windows_Server-2019.json'])
92
+ allow(PuppetlabsOnceover::Controlrepo).to receive(:new).and_return(repo)
93
+
94
+ # ERB.new's legacy positional (safe_level, trim_mode) arguments were
95
+ # removed in the bundled erb gem (6.0.7+, Ruby 4.0) -- rake_tasks.rb
96
+ # used to pass them positionally, which raised ArgumentError on that
97
+ # Ruby before this fix. Now that it's fixed (keyword trim_mode:), this
98
+ # asserts the template actually renders real content from the repo
99
+ # double, not just that it "reaches" the call.
100
+ expect { Rake::Task['generate_onceover_yaml'].invoke }.to output(
101
+ a_string_including('role::web', 'role::db', 'centos7', 'Windows_Server-2019', 'Windows_Server-2019')
102
+ ).to_stdout
103
+ end
104
+ end
105
+
106
+ describe 'generate_nodesets' do
107
+ it 'writes a nodeset entry for each factset, handling a 404 box lookup' do
108
+ repo = double('repo')
109
+ allow(repo).to receive(:facts).and_return([{ 'os' => { 'family' => 'RedHat' } }])
110
+ allow(repo).to receive(:facts_files).and_return(['/some/repo/facts/centos7.json'])
111
+ allow(PuppetlabsOnceover::Controlrepo).to receive(:new).and_return(repo)
112
+
113
+ require 'puppetlabs-onceover/beaker'
114
+ allow(PuppetlabsOnceover::Beaker).to receive(:facts_to_vagrant_box).and_return('centos/7')
115
+ allow(PuppetlabsOnceover::Beaker).to receive(:facts_to_platform).and_return('el-7-x86_64')
116
+
117
+ http_double = double('http', use_ssl: true, 'use_ssl=': true)
118
+ response = double('response', code: '404', body: '{}')
119
+ allow(Net::HTTP).to receive(:new).and_return(http_double)
120
+ allow(http_double).to receive(:get).and_return(response)
121
+
122
+ # This deprecated task (Beaker removed) has a second, deeper,
123
+ # pre-existing bug beyond the ERB.new Ruby-4.0 incompatibility fixed
124
+ # elsewhere: nodeset.yaml.erb expects a `hosts_hash` local (and a
125
+ # `prefix` local) that the task body never actually defines -- it
126
+ # builds flat locals (node_name/boxname/platform/url/comment_out)
127
+ # instead. Out of scope to redesign a deprecated task here; asserting
128
+ # the real resulting error (and that execution reaches it, covering
129
+ # the "HOSTS:" header and 404 branch first) rather than the old,
130
+ # now-fixed ArgumentError.
131
+ expect { Rake::Task['generate_nodesets'].invoke }.to raise_error(NameError, /hosts_hash/).and output(/HOSTS:/).to_stdout
132
+ end
133
+
134
+ it 'writes a nodeset entry using the virtualbox provider url when the box lookup succeeds' do
135
+ repo = double('repo')
136
+ allow(repo).to receive(:facts).and_return([{ 'os' => { 'family' => 'RedHat' } }])
137
+ allow(repo).to receive(:facts_files).and_return(['/some/repo/facts/centos7.json'])
138
+ allow(PuppetlabsOnceover::Controlrepo).to receive(:new).and_return(repo)
139
+
140
+ require 'puppetlabs-onceover/beaker'
141
+ allow(PuppetlabsOnceover::Beaker).to receive(:facts_to_vagrant_box).and_return('centos/7')
142
+ allow(PuppetlabsOnceover::Beaker).to receive(:facts_to_platform).and_return('el-7-x86_64')
143
+
144
+ box_body = {
145
+ 'current_version' => {
146
+ 'providers' => [
147
+ { 'name' => 'virtualbox', 'original_url' => 'https://example.com/box.box' }
148
+ ]
149
+ }
150
+ }.to_json
151
+
152
+ http_double = double('http', use_ssl: true, 'use_ssl=': true)
153
+ response = double('response', code: '200', body: box_body)
154
+ allow(Net::HTTP).to receive(:new).and_return(http_double)
155
+ allow(http_double).to receive(:get).and_return(response)
156
+
157
+ # As above: covers the successful (non-404) lookup branch, including
158
+ # the virtualbox provider url extraction, before hitting the same
159
+ # pre-existing hosts_hash/prefix bug described above.
160
+ expect { Rake::Task['generate_nodesets'].invoke }.to raise_error(NameError, /hosts_hash/).and output(/HOSTS:/).to_stdout
161
+ end
162
+ end
163
+
164
+ describe 'generate_vendor_cache' do
165
+ it 'creates a VendoredModules cache for the controlrepo' do
166
+ repo = double('repo', spec_dir: '/some/repo/spec')
167
+ allow(PuppetlabsOnceover::Controlrepo).to receive(:new).with(debug: true).and_return(repo)
168
+
169
+ require 'puppetlabs-onceover/vendored_modules'
170
+ allow(PuppetlabsOnceover::VendoredModules).to receive(:new)
171
+
172
+ Rake::Task['generate_vendor_cache'].invoke
173
+
174
+ expect(PuppetlabsOnceover::VendoredModules).to have_received(:new).with(
175
+ hash_including(repo: repo, force_update: true)
176
+ )
177
+ end
178
+ end
179
+ end
@@ -0,0 +1,361 @@
1
+ require 'spec_helper'
2
+ require 'stringio'
3
+ require 'tmpdir'
4
+ require 'fileutils'
5
+ require 'yaml'
6
+ require 'puppetlabs-onceover/controlrepo'
7
+ require 'puppetlabs-onceover/rspec/formatters'
8
+
9
+ describe PuppetlabsOnceoverFormatter do
10
+ let(:output) { StringIO.new }
11
+ subject(:formatter) { described_class.new(output) }
12
+
13
+ def group_double(description)
14
+ group = double('group', description: description)
15
+ allow(group).to receive(:parent_groups).and_return([group])
16
+ group
17
+ end
18
+
19
+ def stub_world_groups(descriptions)
20
+ groups = descriptions.map { |d| double('group', description: d) }
21
+ allow(RSpec.configuration.world).to receive(:example_groups).and_return(groups)
22
+ end
23
+
24
+ def failure_double(role:, factset:, raw_error:, description: 'a test', file_path: '/spec/foo_spec.rb', line_number: 5)
25
+ metadata = {
26
+ example_group: {
27
+ description: factset,
28
+ parent_example_group: { description: role }
29
+ },
30
+ execution_result: double('execution_result', exception: RuntimeError.new(raw_error)),
31
+ description: description,
32
+ file_path: file_path,
33
+ line_number: line_number
34
+ }
35
+ double('example', metadata: metadata)
36
+ end
37
+
38
+ describe '#example_group_started' do
39
+ context 'when the group is a top-level role' do
40
+ it 'prints the role name and sets previous_role' do
41
+ stub_world_groups(['role::foo', 'role::barbaz'])
42
+ group = group_double('role::foo')
43
+ notification = double('notification', group: group)
44
+
45
+ formatter.example_group_started(notification)
46
+
47
+ expect(output.string).to include('role::foo:')
48
+ end
49
+
50
+ it 'does not reprint the same role twice in a row' do
51
+ stub_world_groups(['role::foo'])
52
+ group = group_double('role::foo')
53
+ notification = double('notification', group: group)
54
+
55
+ formatter.example_group_started(notification)
56
+ first_output = output.string
57
+ formatter.example_group_started(notification)
58
+
59
+ expect(output.string).to eq(first_output)
60
+ end
61
+ end
62
+
63
+ context 'when the group is nested (not the top-level role)' do
64
+ it "prints '? ' as a placeholder" do
65
+ top = double('top_group')
66
+ group = double('group', description: 'nested context')
67
+ allow(group).to receive(:parent_groups).and_return([top, group])
68
+ notification = double('notification', group: group)
69
+
70
+ formatter.example_group_started(notification)
71
+
72
+ expect(output.string).to eq('? ')
73
+ end
74
+ end
75
+ end
76
+
77
+ describe '#example_passed' do
78
+ it 'writes a green P' do
79
+ formatter.example_passed(double('notification'))
80
+ expect(output.string).to include('P')
81
+ end
82
+ end
83
+
84
+ describe '#example_failed' do
85
+ it 'writes a red F' do
86
+ formatter.example_failed(double('notification'))
87
+ expect(output.string).to include('F')
88
+ end
89
+ end
90
+
91
+ describe '#example_pending' do
92
+ it 'writes a yellow ?' do
93
+ formatter.example_pending(double('notification'))
94
+ expect(output.string).to include('?')
95
+ end
96
+ end
97
+
98
+ describe '#parse_errors' do
99
+ it 'parses a compilation error with a file/line/column location' do
100
+ allow(RSpec.configuration).to receive(:onceover_tempdir).and_return('/tmp/xyz/.onceover')
101
+ allow(RSpec.configuration).to receive(:onceover_root).and_return('/tmp/xyz')
102
+ allow(RSpec.configuration).to receive(:onceover_environmentpath).and_return('etc/puppetlabs/code/environments')
103
+
104
+ raw = 'Some function failed error during compilation: bad value at (file: /tmp/xyz/.onceover/etc/puppetlabs/code/environments/production/site/role/manifests/foo.pp, line: 4, column: 2); more'
105
+ results = formatter.parse_errors(raw)
106
+ expect(results.first[:line]).to eq('4')
107
+ expect(results.first[:column]).to eq('2')
108
+ expect(results.first[:file]).to eq('site/role/manifests/foo.pp')
109
+ end
110
+
111
+ it 'parses a compilation error whose location omits the file' do
112
+ raw = 'error during compilation: bad value at (line: 9, column: 1); trailer'
113
+ results = formatter.parse_errors(raw)
114
+ expect(results.first[:file]).to be_nil
115
+ expect(results.first[:line]).to eq('9')
116
+ end
117
+
118
+ it 'parses a compilation error with no location at all, matching "on node"' do
119
+ raw = 'error during compilation: something bad happened on node foo.example.com'
120
+ results = formatter.parse_errors(raw)
121
+ expect(results.first[:text]).to eq('something bad happened')
122
+ expect(results.first[:file]).to be_nil
123
+ end
124
+
125
+ it 'falls back to the raw string when a compilation error matches neither location pattern' do
126
+ raw = 'error during compilation: nothing structured here whatsoever'
127
+ results = formatter.parse_errors(raw)
128
+ expect(results).to eq([{ text: raw }])
129
+ end
130
+
131
+ it 'falls back to the raw string for a non-compilation error' do
132
+ raw = "NoMethodError: undefined method 'foo' for nil"
133
+ results = formatter.parse_errors(raw)
134
+ expect(results).to eq([{ text: raw }])
135
+ end
136
+ end
137
+
138
+ describe '#calculate_relative_source' do
139
+ before do
140
+ allow(RSpec.configuration).to receive(:onceover_tempdir).and_return('/tmp/xyz/.onceover')
141
+ allow(RSpec.configuration).to receive(:onceover_root).and_return('/tmp/xyz')
142
+ allow(RSpec.configuration).to receive(:onceover_environmentpath).and_return('etc/puppetlabs/code/environments')
143
+ end
144
+
145
+ it 'returns nil when given a nil file' do
146
+ expect(formatter.calculate_relative_source(nil)).to be_nil
147
+ end
148
+
149
+ it 'calculates the path relative to the production environment root' do
150
+ file = '/tmp/xyz/.onceover/etc/puppetlabs/code/environments/production/site/role/manifests/foo.pp'
151
+ expect(formatter.calculate_relative_source(file)).to eq('site/role/manifests/foo.pp')
152
+ end
153
+ end
154
+
155
+ describe '#extract_failure_data' do
156
+ it 'attaches the list of failing factsets to each parsed error' do
157
+ fails = [
158
+ failure_double(role: 'role::foo', factset: 'using fact set centos-7-x86_64', raw_error: 'boom'),
159
+ failure_double(role: 'role::foo', factset: 'using fact set windows-2019-x64', raw_error: 'boom')
160
+ ]
161
+ results = formatter.extract_failure_data(fails)
162
+ expect(results.first[:factsets]).to eq(['centos-7-x86_64', 'windows-2019-x64'])
163
+ end
164
+ end
165
+
166
+ describe '#extract_failures' do
167
+ it 'groups failures by role and then by distinct error message' do
168
+ notification = double(
169
+ 'notification',
170
+ failed_examples: [
171
+ failure_double(role: 'role::foo', factset: 'using fact set centos-7-x86_64', raw_error: 'boom one'),
172
+ failure_double(role: 'role::foo', factset: 'using fact set windows-2019-x64', raw_error: 'boom two'),
173
+ failure_double(role: 'role::bar', factset: 'using fact set centos-7-x86_64', raw_error: 'boom three')
174
+ ]
175
+ )
176
+
177
+ grouped = formatter.extract_failures(notification)
178
+
179
+ expect(grouped.keys).to contain_exactly('role::foo', 'role::bar')
180
+ expect(grouped['role::foo'].size).to eq(2)
181
+ expect(grouped['role::bar'].size).to eq(1)
182
+ end
183
+ end
184
+
185
+ describe '#dump_failures' do
186
+ before do
187
+ allow(RSpec.configuration).to receive(:onceover_tempdir).and_return('/tmp/xyz/.onceover')
188
+ allow(RSpec.configuration).to receive(:onceover_root).and_return('/tmp/xyz')
189
+ allow(RSpec.configuration).to receive(:onceover_environmentpath).and_return('etc/puppetlabs/code/environments')
190
+ end
191
+
192
+ it 'renders the error summary template for each failing role' do
193
+ notification = double(
194
+ 'notification',
195
+ failed_examples: [
196
+ failure_double(role: 'role::foo', factset: 'using fact set centos-7-x86_64', raw_error: 'boom')
197
+ ]
198
+ )
199
+
200
+ formatter.dump_failures(notification)
201
+
202
+ expect(output.string).to include('role::foo')
203
+ expect(output.string).to include('failed')
204
+ end
205
+ end
206
+
207
+ describe '#longest_group' do
208
+ it 'returns the length of the longest example group description' do
209
+ stub_world_groups(['role::a', 'role::much_longer_name'])
210
+ expect(formatter.longest_group).to eq('role::much_longer_name'.length)
211
+ end
212
+ end
213
+
214
+ describe 'color helper methods' do
215
+ [:class_name, :black, :red, :green, :yellow, :blue, :magenta, :cyan, :white, :bold].each do |method|
216
+ it "##{method} wraps the given text" do
217
+ expect(formatter.public_send(method, 'hello')).to be_a(String)
218
+ expect(formatter.public_send(method, 'hello')).to include('hello')
219
+ end
220
+ end
221
+ end
222
+ end
223
+
224
+ describe PuppetlabsOnceoverFormatterParallel do
225
+ let(:output) { StringIO.new }
226
+ subject(:formatter) { described_class.new(output) }
227
+
228
+ def failure_double(role:, factset:, raw_error:)
229
+ metadata = {
230
+ example_group: {
231
+ description: factset,
232
+ parent_example_group: { description: role }
233
+ },
234
+ execution_result: double('execution_result', exception: RuntimeError.new(raw_error))
235
+ }
236
+ double('example', metadata: metadata)
237
+ end
238
+
239
+ describe '#example_group_started' do
240
+ it 'does nothing' do
241
+ expect { formatter.example_group_started(double('notification')) }.not_to raise_error
242
+ expect(output.string).to eq('')
243
+ end
244
+ end
245
+
246
+ describe '#example_passed' do
247
+ it 'writes and flushes a green P' do
248
+ formatter.example_passed(double('notification'))
249
+ expect(output.string).to include('P')
250
+ end
251
+ end
252
+
253
+ describe '#example_failed' do
254
+ it 'writes and flushes a red F' do
255
+ formatter.example_failed(double('notification'))
256
+ expect(output.string).to include('F')
257
+ end
258
+ end
259
+
260
+ describe '#example_pending' do
261
+ it 'writes and flushes a yellow ?' do
262
+ formatter.example_pending(double('notification'))
263
+ expect(output.string).to include('?')
264
+ end
265
+ end
266
+
267
+ describe '#dump_failures' do
268
+ it 'dumps a yaml file of the failures into the parallel results dir' do
269
+ tempdir = Dir.mktmpdir
270
+ allow(RSpec.configuration).to receive(:onceover_tempdir).and_return(tempdir)
271
+
272
+ notification = double(
273
+ 'notification',
274
+ failed_examples: [failure_double(role: 'role::foo', factset: 'using fact set centos-7-x86_64', raw_error: 'boom')]
275
+ )
276
+
277
+ formatter.dump_failures(notification)
278
+
279
+ files = Dir["#{tempdir}/parallel/*.yaml"]
280
+ expect(files.size).to eq(1)
281
+ expect(YAML.load_file(files.first)).to have_key('role::foo')
282
+
283
+ FileUtils.remove_entry(tempdir)
284
+ end
285
+ end
286
+
287
+ describe '#output_results' do
288
+ before do
289
+ allow(RSpec.configuration).to receive(:onceover_tempdir).and_return('/tmp/xyz/.onceover')
290
+ allow(RSpec.configuration).to receive(:onceover_root).and_return('/tmp/xyz')
291
+ allow(RSpec.configuration).to receive(:onceover_environmentpath).and_return('etc/puppetlabs/code/environments')
292
+ end
293
+
294
+ it 'merges all yaml result files, renders them, and deletes the source files' do
295
+ directory = Dir.mktmpdir
296
+ File.write(File.join(directory, 'results-1.yaml'), { 'role::foo' => [{ text: 'boom one' }] }.to_yaml)
297
+ File.write(File.join(directory, 'results-2.yaml'), { 'role::bar' => [{ text: 'boom two' }] }.to_yaml)
298
+
299
+ formatter.output_results(directory)
300
+
301
+ expect(output.string).to include('role::foo')
302
+ expect(output.string).to include('role::bar')
303
+ expect(Dir["#{directory}/*.yaml"]).to be_empty
304
+
305
+ FileUtils.remove_entry(directory)
306
+ end
307
+
308
+ it 'merges values when the same role appears in multiple files' do
309
+ directory = Dir.mktmpdir
310
+ File.write(File.join(directory, 'results-1.yaml'), { 'role::foo' => [{ text: 'boom one' }] }.to_yaml)
311
+ File.write(File.join(directory, 'results-2.yaml'), { 'role::foo' => [{ text: 'boom two' }] }.to_yaml)
312
+
313
+ formatter.output_results(directory)
314
+
315
+ expect(output.string).to include('role::foo')
316
+
317
+ FileUtils.remove_entry(directory)
318
+ end
319
+ end
320
+ end
321
+
322
+ describe FailureCollector do
323
+ let(:tempdir) { Dir.mktmpdir }
324
+
325
+ before do
326
+ allow(RSpec.configuration).to receive(:onceover_tempdir).and_return(tempdir)
327
+ end
328
+
329
+ after { FileUtils.rm_rf(tempdir) }
330
+
331
+ describe '#initialize' do
332
+ it 'touches a failures.out file in the onceover tempdir' do
333
+ described_class.new(StringIO.new)
334
+ expect(File.exist?(File.join(tempdir, 'failures.out'))).to be true
335
+ end
336
+ end
337
+
338
+ describe '#dump_failures' do
339
+ it 'appends details of each failed example to failures.out' do
340
+ collector = described_class.new(StringIO.new)
341
+
342
+ fe = double(
343
+ 'failed_example',
344
+ metadata: {
345
+ description: 'does a thing',
346
+ execution_result: double('execution_result', exception: RuntimeError.new('kaboom')),
347
+ file_path: '/spec/foo_spec.rb',
348
+ line_number: 12
349
+ }
350
+ )
351
+ failures = double('failures', failed_examples: [fe])
352
+
353
+ collector.dump_failures(failures)
354
+
355
+ contents = File.read(File.join(tempdir, 'failures.out'))
356
+ expect(contents).to include('does a thing')
357
+ expect(contents).to include('kaboom')
358
+ expect(contents).to include('/spec/foo_spec.rb:12')
359
+ end
360
+ end
361
+ end