diff --git a/lib/puppetlabs_spec_helper/rake_tasks.rb b/lib/puppetlabs_spec_helper/rake_tasks.rb index efba5661..ff2d9832 100644 --- a/lib/puppetlabs_spec_helper/rake_tasks.rb +++ b/lib/puppetlabs_spec_helper/rake_tasks.rb @@ -8,6 +8,7 @@ require 'puppetlabs_spec_helper/version' require 'puppetlabs_spec_helper/tasks/fixtures' require 'puppetlabs_spec_helper/tasks/check_symlinks' +require 'puppetlabs_spec_helper/tasks/spec_described' require 'English' # optional gems @@ -252,6 +253,41 @@ print new_version end +describe_problems = %w[missing unknown] + +desc "Check to ensure defined puppet code has been described in spec\n(defaults: coverage=100)" +task :spec_described, [:coverage] do |_task, args| + args.with_defaults(coverage: '100') + + colorize = RSpec::Core::Formatters::ConsoleCodes + described = PuppetlabsSpecHelper::Tasks::SpecDescribed.new + result = described.check + + puts "Spec described coverage: #{colorize.wrap(format('%3.1f%%', result[:percent]), described.coverage_color(result[:percent], args[:coverage], warn: 1))}" + + if result[:have] < result[:want] || !result[:unknown].values.flatten.empty? + (result[:code].keys | result[:missing].keys).each do |res_type| + want_type = (result[:code][res_type]&.size || 0) + miss_type = (result[:missing][res_type]&.size || 0) + percent_type = (want_type - miss_type) / want_type.to_f * 100.0 + color = described.coverage_color(percent_type, args[:coverage]) + puts " * #{RSpec::Core::Formatters::Helpers.pluralize(want_type, res_type)}: #{colorize.wrap(format('%3.1f%%', percent_type), color)}" + + describe_problems.each do |problem| + what = result[:"#{problem}"] + next if what[res_type].nil? || what[res_type].empty? + + puts " #{problem}:" + what[res_type].each do |r| + info = " in #{result[:test_files][r]}" if result[:test_files][r] + puts " - #{r}#{info}" + end + end + end + end + abort if result[:percent] < args[:coverage].to_f +end + desc 'Runs all necessary checks on a module in preparation for a release' task :release_checks do Rake::Task[:lint].invoke diff --git a/lib/puppetlabs_spec_helper/tasks/spec_described.rb b/lib/puppetlabs_spec_helper/tasks/spec_described.rb new file mode 100644 index 00000000..7f3ef267 --- /dev/null +++ b/lib/puppetlabs_spec_helper/tasks/spec_described.rb @@ -0,0 +1,80 @@ +# frozen_string_literal: true + +module PuppetlabsSpecHelper + module Tasks + # Helper + class SpecDescribed + def hash_diff(first, second) + first.merge(first) { |ka, va| va.reject { |v| second[ka]&.include?(v) } } + end + + def coverage_color(percent, required = 100, warn: 0.5) + if percent >= required.to_f + :green + elsif percent < required.to_f * warn.to_f + :red + else + :yellow + end + end + + def check + code = {} + code_files = {} + Dir.glob('{functions,manifests,types}/**/*.pp') do |fn| + res_type = res_title = nil + File.foreach(fn) do |line| + if line =~ /^\s*(class|function|define|type|function)\s*([^={\s]+)/ + res_type = Regexp.last_match(1) + res_title = Regexp.last_match(2) + res_type = 'type_alias' if res_type == 'type' + code[res_type] ||= [] + break + end + end + if res_type + code[res_type] << res_title if res_type + code_files[res_title] = fn + end + end + Dir.glob('lib/puppet/functions/**/*.rb') do |fn| + File.foreach(fn) do |line| + if line =~ /^\s*Puppet::Functions\.create_function\(:?['"]?([^']+)['"]?\)/ + code['function'] ||= [] + code['function'] << Regexp.last_match(1) + code_files[Regexp.last_match(1)] = fn + end + end + end + + test = {} + test_files = {} + Dir.glob('spec/{classes,defines,functions,type_aliases}/**/*rb') do |fn| + resource_type = fn.split(File::SEPARATOR)[1].match(/(class|function|define|type_alias)/).captures[0] + test[resource_type] ||= [] + File.foreach(fn) do |line| + if (m = line.match(/^describe ["']([^'"\s]+)/)) + test[resource_type] << m.captures[0] + test_files[m.captures[0]] = fn + end + end + end + + results = { + code: code, + code_files: code_files, + test: test, + test_files: test_files, + missing: hash_diff(code, test), + unknown: hash_diff(test, code), + want: code.values.flatten.size + } + + results[:have] = results[:want] - results[:missing].values.flatten.size + results[:percent] = results[:have] / results[:want].to_f * 100 + + results + end + end + end +end