puppetlabs_spec_helper 2.13.1 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'rspec/core/rake_task'
2
4
 
3
5
  module PuppetlabsSpecHelper; end
@@ -10,9 +12,7 @@ module PuppetlabsSpecHelper::Tasks::BeakerHelpers
10
12
 
11
13
  # cache the repositories and return a hash object
12
14
  def repositories
13
- unless @repositories
14
- @repositories = fixtures('repositories')
15
- end
15
+ @repositories ||= fixtures('repositories')
16
16
  @repositories
17
17
  end
18
18
 
@@ -20,6 +20,7 @@ module PuppetlabsSpecHelper::Tasks::BeakerHelpers
20
20
  # @return [Array<String>]
21
21
  def beaker_node_sets
22
22
  return @beaker_nodes if @beaker_nodes
23
+
23
24
  @beaker_nodes = Dir['spec/acceptance/nodesets/*.yml'].sort.map do |node_set|
24
25
  node_set.slice!('.yml')
25
26
  File.basename(node_set)
@@ -50,7 +51,7 @@ module PuppetlabsSpecHelper::Tasks::BeakerHelpers
50
51
  end
51
52
  end
52
53
  end
53
- include PuppetlabsSpecHelper::Tasks::BeakerHelpers
54
+ include PuppetlabsSpecHelper::Tasks::BeakerHelpers # legacy support code # rubocop:disable Style/MixinUsage
54
55
 
55
56
  desc 'Run beaker acceptance tests'
56
57
  RSpec::Core::RakeTask.new(:beaker) do |t|
@@ -58,24 +59,26 @@ RSpec::Core::RakeTask.new(:beaker) do |t|
58
59
  end
59
60
 
60
61
  class SetupBeaker
61
- def self.setup_beaker(t)
62
- t.rspec_opts = ['--color']
63
- t.pattern = 'spec/acceptance'
62
+ def self.setup_beaker(task)
63
+ task.rspec_opts = []
64
+ task.pattern = 'spec/acceptance'
64
65
  # TEST_TIERS env variable is a comma separated list of tiers to run. e.g. low, medium, high
65
66
  if ENV['TEST_TIERS']
66
67
  test_tiers = ENV['TEST_TIERS'].split(',')
67
68
  test_tiers_allowed = ENV.fetch('TEST_TIERS_ALLOWED', 'low,medium,high').split(',')
68
69
  raise 'TEST_TIERS env variable must have at least 1 tier specified. Either low, medium or high or one of the tiers listed in TEST_TIERS_ALLOWED (comma separated).' if test_tiers.count == 0
70
+
69
71
  test_tiers.each do |tier|
70
72
  tier_to_add = tier.strip.downcase
71
73
  raise "#{tier_to_add} not a valid test tier." unless test_tiers_allowed.include?(tier_to_add)
74
+
72
75
  tiers = "--tag tier_#{tier_to_add}"
73
- t.rspec_opts.push(tiers)
76
+ task.rspec_opts.push(tiers)
74
77
  end
75
78
  else
76
79
  puts 'TEST_TIERS env variable not defined. Defaulting to run all tests.'
77
80
  end
78
- t
81
+ task
79
82
  end
80
83
  end
81
84
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'pathspec'
2
4
 
3
5
  module PuppetlabsSpecHelper; end
@@ -33,7 +35,7 @@ class PuppetlabsSpecHelper::Tasks::CheckSymlinks
33
35
  end
34
36
 
35
37
  def ignored?(path)
36
- path = path.to_s + '/' if File.directory?(path)
38
+ path = "#{path}/" if File.directory?(path)
37
39
 
38
40
  !ignore_pathspec.match_paths([path], Dir.pwd).empty?
39
41
  end
@@ -42,6 +44,7 @@ class PuppetlabsSpecHelper::Tasks::CheckSymlinks
42
44
  @ignore_pathspec ||= PathSpec.new(DEFAULT_IGNORED).tap do |pathspec|
43
45
  IGNORE_LIST_FILES.each do |f|
44
46
  next unless File.file?(f) && File.readable?(f)
47
+
45
48
  File.open(f, 'r') { |fd| pathspec.add(fd) }
46
49
  end
47
50
  end
@@ -1,15 +1,19 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'yaml'
2
4
  require 'open3'
3
5
  require 'json'
4
6
 
5
7
  module PuppetlabsSpecHelper; end
6
8
  module PuppetlabsSpecHelper::Tasks; end
9
+
7
10
  module PuppetlabsSpecHelper::Tasks::FixtureHelpers
8
11
  # This is a helper for the self-symlink entry of fixtures.yml
9
12
  def source_dir
10
13
  Dir.pwd
11
14
  end
12
15
 
16
+ # @return [String] - the name of current module
13
17
  def module_name
14
18
  raise ArgumentError unless File.file?('metadata.json') && File.readable?('metadata.json')
15
19
 
@@ -23,16 +27,48 @@ module PuppetlabsSpecHelper::Tasks::FixtureHelpers
23
27
  File.basename(Dir.pwd).split('-').last
24
28
  end
25
29
 
26
- # cache the repositories and return a hash object
30
+ def module_version(path)
31
+ metadata_path = File.join(path, 'metadata.json')
32
+ raise ArgumentError unless File.file?(metadata_path) && File.readable?(metadata_path)
33
+
34
+ metadata = JSON.parse(File.read(metadata_path))
35
+ metadata.fetch('version', nil) || '0.0.1'
36
+ rescue JSON::ParserError, ArgumentError
37
+ logger.warn "Failed to find module version at path #{path}"
38
+ '0.0.1'
39
+ end
40
+
41
+ # @return [Hash] - returns a hash of all the fixture repositories
42
+ # @example
43
+ # {"puppetlabs-stdlib"=>{"target"=>"https://gitlab.com/puppetlabs/puppet-stdlib.git",
44
+ # "ref"=>nil, "branch"=>"master", "scm"=>nil,
45
+ # }}
27
46
  def repositories
28
- unless @repositories
29
- @repositories = fixtures('repositories')
30
- end
31
- @repositories
47
+ @repositories ||= fixtures('repositories') || {}
48
+ end
49
+
50
+ # @return [Hash] - returns a hash of all the fixture forge modules
51
+ # @example
52
+ # {"puppetlabs-stdlib"=>{"target"=>"spec/fixtures/modules/stdlib",
53
+ # "ref"=>nil, "branch"=>nil, "scm"=>nil,
54
+ # "flags"=>"--module_repository=https://myforge.example.com/", "subdir"=>nil}}
55
+ def forge_modules
56
+ @forge_modules ||= fixtures('forge_modules') || {}
57
+ end
58
+
59
+ # @return [Hash] - a hash of symlinks specified in the fixtures file
60
+ def symlinks
61
+ @symlinks ||= fixtures('symlinks') || {}
32
62
  end
33
63
 
64
+ # @return [Hash] - returns a hash with the module name and the source directory
34
65
  def auto_symlink
35
- { module_name => '#{source_dir}' }
66
+ { module_name => "\#{source_dir}" }
67
+ end
68
+
69
+ # @return [Boolean] - true if the os is a windows system
70
+ def windows?
71
+ !!File::ALT_SEPARATOR
36
72
  end
37
73
 
38
74
  def fixtures(category)
@@ -77,7 +113,6 @@ module PuppetlabsSpecHelper::Tasks::FixtureHelpers
77
113
 
78
114
  result = {}
79
115
  if fixtures.include?(category) && !fixtures[category].nil?
80
-
81
116
  defaults = { 'target' => 'spec/fixtures/modules' }
82
117
 
83
118
  # load defaults from the `.fixtures.yml` `defaults` section
@@ -94,28 +129,39 @@ module PuppetlabsSpecHelper::Tasks::FixtureHelpers
94
129
  end
95
130
  # there should be a warning or something if it's not a hash...
96
131
  next unless opts.instance_of?(Hash)
132
+
97
133
  # merge our options into the defaults to get the
98
134
  # final option list
99
135
  opts = defaults.merge(opts)
100
136
 
101
137
  next unless include_repo?(opts['puppet_version'])
102
138
 
103
- real_target = eval('"' + opts['target'] + '"')
104
- real_source = eval('"' + opts['repo'] + '"')
139
+ real_target = eval("\"#{opts['target']}\"", binding, __FILE__, __LINE__) # evaluating target reference in this context (see auto_symlink)
140
+ real_source = eval("\"#{opts['repo']}\"", binding, __FILE__, __LINE__) # evaluating repo reference in this context (see auto_symlink)
105
141
 
106
- result[real_source] = {
142
+ result[real_source] = validate_fixture_hash!(
107
143
  'target' => File.join(real_target, fixture),
108
- 'ref' => opts['ref'],
144
+ 'ref' => opts['ref'] || opts['tag'],
109
145
  'branch' => opts['branch'],
110
146
  'scm' => opts['scm'],
111
147
  'flags' => opts['flags'],
112
148
  'subdir' => opts['subdir'],
113
- }
149
+ )
114
150
  end
115
151
  end
116
152
  result
117
153
  end
118
154
 
155
+ def validate_fixture_hash!(hash)
156
+ # Can only validate git based scm
157
+ return hash unless hash['scm'] == 'git'
158
+
159
+ # Forward slashes in the ref aren't allowed. And is probably a branch name.
160
+ raise ArgumentError, "The ref for #{hash['target']} is invalid (Contains a forward slash). If this is a branch name, please use the 'branch' setting instead." if hash['ref'] =~ %r{/}
161
+
162
+ hash
163
+ end
164
+
119
165
  def include_repo?(version_range)
120
166
  if version_range && defined?(SemanticPuppet)
121
167
  puppet_spec = Gem::Specification.find_by_name('puppet')
@@ -149,6 +195,7 @@ module PuppetlabsSpecHelper::Tasks::FixtureHelpers
149
195
  unless File.exist?(target)
150
196
  raise "Failed to clone #{scm} repository #{remote} into #{target}"
151
197
  end
198
+
152
199
  result
153
200
  end
154
201
 
@@ -196,7 +243,7 @@ module PuppetlabsSpecHelper::Tasks::FixtureHelpers
196
243
  end
197
244
 
198
245
  def git_remote_url(target)
199
- output, status = Open3.capture2e('git', '-C', target, 'remote', 'get-url', 'origin')
246
+ output, status = Open3.capture2e('git', '--git-dir', File.join(target, '.git'), 'ls-remote', '--get-url', 'origin')
200
247
  status.success? ? output.strip : nil
201
248
  end
202
249
 
@@ -219,7 +266,7 @@ module PuppetlabsSpecHelper::Tasks::FixtureHelpers
219
266
  else
220
267
  Logger::INFO
221
268
  end
222
- @logger = Logger.new(STDERR)
269
+ @logger = Logger.new($stderr)
223
270
  @logger.level = level
224
271
  end
225
272
  @logger
@@ -229,12 +276,13 @@ module PuppetlabsSpecHelper::Tasks::FixtureHelpers
229
276
  # The problem with the relative path is that PMT doesn't expand the path properly and so passing in a relative path here
230
277
  # becomes something like C:\somewhere\backslashes/spec/fixtures/work-dir on Windows, and then PMT barfs itself.
231
278
  # This has been reported as https://tickets.puppetlabs.com/browse/PUP-4884
232
- File.expand_path((ENV['MODULE_WORKING_DIR']) ? ENV['MODULE_WORKING_DIR'] : 'spec/fixtures/work-dir')
279
+ File.expand_path(ENV['MODULE_WORKING_DIR'] || 'spec/fixtures/work-dir')
233
280
  end
234
281
 
235
282
  # returns the current thread count that is currently active
236
283
  # a status of false or nil means the thread completed
237
284
  # so when anything else we count that as a active thread
285
+ # @return [Integer] - current thread count
238
286
  def current_thread_count(items)
239
287
  active_threads = items.find_all do |_item, opts|
240
288
  if opts[:thread]
@@ -247,79 +295,47 @@ module PuppetlabsSpecHelper::Tasks::FixtureHelpers
247
295
  active_threads.count
248
296
  end
249
297
 
250
- # returns the max_thread_count
251
- # because we may want to limit ssh or https connections
298
+ # @summary Set a limit on the amount threads used, defaults to 10
299
+ # MAX_FIXTURE_THREAD_COUNT can be used to set this limit
300
+ # @return [Integer] - returns the max_thread_count
252
301
  def max_thread_limit
253
- unless @max_thread_limit
254
- # the default thread count is 10 but can be
255
- # raised by using environment variable MAX_FIXTURE_THREAD_COUNT
256
- @max_thread_limit = if ENV['MAX_FIXTURE_THREAD_COUNT'].to_i > 0
257
- ENV['MAX_FIXTURE_THREAD_COUNT'].to_i
258
- else
259
- 10 # the default
260
- end
261
- end
262
- @max_thread_limit
302
+ @max_thread_limit ||= (ENV['MAX_FIXTURE_THREAD_COUNT'] || 10).to_i
263
303
  end
264
- end
265
- include PuppetlabsSpecHelper::Tasks::FixtureHelpers
266
304
 
267
- desc 'Create the fixtures directory'
268
- task :spec_prep do
269
- # Ruby only sets File::ALT_SEPARATOR on Windows and Rubys standard library
270
- # uses this to check for Windows
271
- is_windows = !!File::ALT_SEPARATOR
272
- if is_windows
273
- begin
274
- require 'win32/dir'
275
- rescue LoadError
276
- $stderr.puts 'win32-dir gem not installed, falling back to executing mklink directly'
277
- end
278
- end
279
-
280
- # git has a race condition creating that directory, that would lead to aborted clone operations
281
- FileUtils.mkdir_p('spec/fixtures/modules')
282
-
283
- repositories.each do |remote, opts|
284
- scm = 'git'
285
- target = opts['target']
286
- subdir = opts['subdir']
287
- ref = opts['ref']
288
- scm = opts['scm'] if opts['scm']
289
- branch = opts['branch'] if opts['branch']
290
- flags = opts['flags']
291
- # get the current active threads that are alive
292
- count = current_thread_count(repositories)
293
- if count < max_thread_limit
294
- logger.debug "New Thread started for #{remote}"
295
- # start up a new thread and store it in the opts hash
296
- opts[:thread] = Thread.new do
297
- if valid_repo?(scm, target, remote)
298
- update_repo(scm, target)
299
- else
300
- clone_repo(scm, remote, target, subdir, ref, branch, flags)
305
+ # @param items [Hash] - a hash of either repositories or forge modules
306
+ # @param [Block] - the method you wish to use to download the item
307
+ def download_items(items)
308
+ items.each do |remote, opts|
309
+ # get the current active threads that are alive
310
+ count = current_thread_count(items)
311
+ if count < max_thread_limit
312
+ logger.debug "New Thread started for #{remote}"
313
+ # start up a new thread and store it in the opts hash
314
+ opts[:thread] = Thread.new do
315
+ yield(remote, opts)
301
316
  end
302
- revision(scm, target, ref) if ref
303
- remove_subdirectory(target, subdir) if subdir
317
+ else
318
+ # the last thread started should be the longest wait
319
+ item, item_opts = items.find_all { |_i, o| o.key?(:thread) }.last
320
+ logger.debug "Waiting on #{item}"
321
+ item_opts[:thread].join # wait for the thread to finish
322
+ # now that we waited lets try again
323
+ redo
304
324
  end
305
- else
306
- # the last thread started should be the longest wait
307
- item, item_opts = repositories.find_all { |_i, o| o.key?(:thread) }.last
308
- logger.debug "Waiting on #{item}"
309
- item_opts[:thread].join # wait for the thread to finish
310
- # now that we waited lets try again
311
- redo
312
325
  end
326
+ # wait for all the threads to finish
327
+ items.each { |_remote, opts| opts[:thread].join }
313
328
  end
314
329
 
315
- # wait for all the threads to finish
316
- repositories.each { |_remote, opts| opts[:thread].join }
317
-
318
- fixtures('symlinks').each do |target, link|
330
+ # @param target [String] - the target directory
331
+ # @param link [String] - the name of the link you wish to create
332
+ # works on windows and linux
333
+ def setup_symlink(target, link)
319
334
  link = link['target']
320
- next if File.symlink?(link)
335
+ return if File.symlink?(link)
336
+
321
337
  logger.info("Creating symlink from #{link} to #{target}")
322
- if is_windows
338
+ if windows?
323
339
  target = File.join(File.dirname(link), target) unless Pathname.new(target).absolute?
324
340
  if Dir.respond_to?(:create_junction)
325
341
  Dir.create_junction(link, target)
@@ -331,7 +347,35 @@ task :spec_prep do
331
347
  end
332
348
  end
333
349
 
334
- fixtures('forge_modules').each do |remote, opts|
350
+ # @return [Boolean] - returns true if the module was downloaded successfully, false otherwise
351
+ # @param [String] - the remote url or namespace/name of the module to download
352
+ # @param [Hash] - list of options such as version, branch, ref
353
+ def download_repository(remote, opts)
354
+ scm = 'git'
355
+ target = opts['target']
356
+ subdir = opts['subdir']
357
+ ref = opts['ref']
358
+ scm = opts['scm'] if opts['scm']
359
+ branch = opts['branch'] if opts['branch']
360
+ flags = opts['flags']
361
+ if valid_repo?(scm, target, remote)
362
+ update_repo(scm, target)
363
+ else
364
+ clone_repo(scm, remote, target, subdir, ref, branch, flags)
365
+ end
366
+ revision(scm, target, ref) if ref
367
+ remove_subdirectory(target, subdir) if subdir
368
+ end
369
+
370
+ # @return [String] - the spec/fixtures/modules directory in the module root folder
371
+ def module_target_dir
372
+ @module_target_dir ||= File.expand_path('spec/fixtures/modules')
373
+ end
374
+
375
+ # @return [Boolean] - returns true if the module was downloaded successfully, false otherwise
376
+ # @param [String] - the remote url or namespace/name of the module to download
377
+ # @param [Hash] - list of options such as version
378
+ def download_module(remote, opts)
335
379
  ref = ''
336
380
  flags = ''
337
381
  if opts.instance_of?(String)
@@ -342,34 +386,60 @@ task :spec_prep do
342
386
  flags = " #{opts['flags']}" if opts['flags']
343
387
  end
344
388
 
345
- next if File.directory?(target)
389
+ return false if File.directory?(target) && (ref.empty? || opts['ref'] == module_version(target))
390
+
391
+ # The PMT cannot handle multi threaded runs due to cache directory collisons
392
+ # so we randomize the directory instead.
393
+ # Does working_dir even need to be passed?
394
+ Dir.mktmpdir do |working_dir|
395
+ command = "puppet module install#{ref}#{flags} --ignore-dependencies" \
396
+ ' --force' \
397
+ " --module_working_dir \"#{working_dir}\"" \
398
+ " --target-dir \"#{module_target_dir}\" \"#{remote}\""
346
399
 
347
- working_dir = module_working_directory
348
- target_dir = File.expand_path('spec/fixtures/modules')
400
+ unless system(command)
401
+ raise "Failed to install module #{remote} to #{module_target_dir}"
402
+ end
403
+ end
404
+ $CHILD_STATUS.success?
405
+ end
406
+ end
349
407
 
350
- command = 'puppet module install' + ref + flags + \
351
- ' --ignore-dependencies' \
352
- ' --force' \
353
- " --module_working_dir \"#{working_dir}\"" \
354
- " --target-dir \"#{target_dir}\" \"#{remote}\""
408
+ include PuppetlabsSpecHelper::Tasks::FixtureHelpers # DSL include # rubocop:disable Style/MixinUsage
355
409
 
356
- unless system(command)
357
- raise "Failed to install module #{remote} to #{target_dir}"
410
+ desc 'Create the fixtures directory'
411
+ task :spec_prep do
412
+ # Ruby only sets File::ALT_SEPARATOR on Windows and Rubys standard library
413
+ # uses this to check for Windows
414
+ if windows?
415
+ begin
416
+ require 'win32/dir'
417
+ rescue LoadError
418
+ warn 'win32-dir gem not installed, falling back to executing mklink directly'
358
419
  end
359
420
  end
360
421
 
422
+ # git has a race condition creating that directory, that would lead to aborted clone operations
423
+ FileUtils.mkdir_p('spec/fixtures/modules')
424
+
425
+ symlinks.each { |target, link| setup_symlink(target, link) }
426
+
427
+ download_items(repositories) { |remote, opts| download_repository(remote, opts) }
428
+
429
+ download_items(forge_modules) { |remote, opts| download_module(remote, opts) }
430
+
361
431
  FileUtils.mkdir_p('spec/fixtures/manifests')
362
432
  FileUtils.touch('spec/fixtures/manifests/site.pp')
363
433
  end
364
434
 
365
435
  desc 'Clean up the fixtures directory'
366
436
  task :spec_clean do
367
- fixtures('repositories').each do |_remote, opts|
437
+ repositories.each do |_remote, opts|
368
438
  target = opts['target']
369
439
  FileUtils.rm_rf(target)
370
440
  end
371
441
 
372
- fixtures('forge_modules').each do |_remote, opts|
442
+ forge_modules.each do |_remote, opts|
373
443
  target = opts['target']
374
444
  FileUtils.rm_rf(target)
375
445
  end