#!/usr/bin/env rake require "bundler" require "bundler/gem_helper" require "rake/testtask" require "train" require_relative "tasks/spdx" require "fileutils" Bundler::GemHelper.install_tasks name: "inspec-core" Bundler::GemHelper.install_tasks name: "inspec" def prompt(message) print(message) STDIN.gets.chomp end # The docs tasks rely on ruby-progressbar. If we can't load it, then don't # load the docs tasks. This is necessary to allow this Rakefile to work # when the "tests" gem group in the Gemfile has been excluded, such as # during an appbundle-updater run. begin require "ruby-progressbar" require_relative "tasks/docs" rescue LoadError puts "docs tasks are unavailable because the ruby-progressbar gem is not available." end begin require "git" require_relative "tasks/contrib" rescue LoadError puts "contrib tasks are unavailable because the git gem is not available." end task :install do inspec_bin_path = ::File.join(::File.dirname(__FILE__), "inspec-bin") Dir.chdir(inspec_bin_path) sh("rake install") end GLOBS = [ "test/unit/**/*_test.rb", "test/functional/**/*_test.rb", "lib/plugins/inspec-*/test/**/*_test.rb", ].freeze # run tests task default: %w{ test } task test: %w{ test:default } namespace :test do Rake::TestTask.new(:default) do |t| t.libs << "test" t.test_files = Dir[*GLOBS].sort t.warning = !!ENV["W"] t.verbose = !!ENV["V"] # default to off. the test commands are _huge_. t.ruby_opts = ["--dev"] if defined?(JRUBY_VERSION) end task default: [:accept_license] begin require "chefstyle" require "rubocop/rake_task" RuboCop::RakeTask.new(:lint) do |task| task.options += ["--display-cop-names", "--no-color", "--parallel"] end rescue LoadError puts "rubocop is not available. Install the rubocop gem to run the lint tests." end task :list do puts Dir[*GLOBS].sort end task :missing do missing = Dir["test/**/*"] - Dir[*GLOBS] missing.reject! { |f| ! File.file? f } missing.reject! { |f| f =~ %r{test/(integration|cookbooks)} } missing.reject! { |f| f =~ %r{test/fixtures} } missing.reject! { |f| f =~ /test.*helper/ } missing.reject! { |f| f =~ %r{test/docker} } puts missing.sort end # rubocop:disable Style/BlockDelimiters,Layout/ExtraSpacing,Lint/AssignmentInCondition def n_threads_run(n_workers, jobs) queue = Queue.new jobs.each do |job| queue << job end n_workers.times.map { queue << nil # 1 quit value per thread Thread.new do while job = queue.pop # go until quit value yield job end end }.each(&:join) end task :parallel do n = (ENV["K"] || 4).to_i lock = Mutex.new passed = true tests = Dir[*GLOBS].sort tests.concat([nil] * (n - tests.size % n)) unless # square it up tests.size % 4 == 0 jobs = tests.each_slice(n).to_a.transpose t0 = Time.now out = Queue.new n_threads_run n, jobs do |job| job.compact! lock.synchronize do warn "Running #{job.size} files in a thread" end t1 = Time.now cmd = "bundle exec minitest #{job.join " "}" output = `#{cmd} 2>&1` t2 = Time.now - t1 lock.synchronize do if $?.success? warn "Finished #{job.size} files successfully in %d seconds" % [t2] else passed = false warn "Finished #{job.size} files with failures in %d seconds" % [t2] end end out << "#{cmd}\n\n#{output}" end puts "done" puts out.length.times do puts out.shift puts end puts "Ran in %d seconds" % [ Time.now - t0 ] exit 1 unless passed end task parallel: [:accept_license] # given isolated being green, why is this needed? desc "Run test files in multiple threads" task :isolated do require "fileutils" # Only needed for local runs, not CI? FileUtils.rm_rf File.expand_path "~/.inspec" FileUtils.rm_rf File.expand_path "~/.chef" # 3 seems to be the magic number... (tho not by that much) bad, good, n = {}, [], (ENV.delete("K") || 3).to_i t0 = Time.now tests = Dir[*GLOBS].sort n_threads_run n, tests do |path| output = `bundle exec ruby -Ilib -Itest #{path} 2>&1` if $?.success? $stderr.print "." good << path else $stderr.print "x" bad[path] = output end end puts "done" puts "Ran in %d seconds" % [ Time.now - t0 ] unless good.empty? puts puts "# Good tests:" good.sort.each do |path| puts path end end unless bad.empty? puts puts "# Bad tests:" bad.keys.each do |path| puts path end puts puts "# Bad Test Output:" bad.each do |path, output| puts puts "# #{path}:" puts output end exit 1 end end task :accept_license do FileUtils.mkdir_p(File.join(Dir.home, ".chef", "accepted_licenses")) # If the user has not accepted the license, touch the acceptance # file, but also touch a marker that it is only for testing. unless File.exist?(File.join(Dir.home, ".chef", "accepted_licenses", "inspec")) puts "\n\nTemporarily accepting Chef user license for the duration of testing...\n" FileUtils.touch(File.join(Dir.home, ".chef", "accepted_licenses", "inspec")) FileUtils.touch(File.join(Dir.home, ".chef", "accepted_licenses", "inspec.for_testing")) end # Regardless of what happens, when this process exits, check for cleanup. at_exit do if File.exist?(File.join(Dir.home, ".chef", "accepted_licenses", "inspec.for_testing")) puts "\n\nRemoving temporary Chef user license acceptance file that was placed for test duration.\n" FileUtils.rm_f(File.join(Dir.home, ".chef", "accepted_licenses", "inspec")) FileUtils.rm_f(File.join(Dir.home, ".chef", "accepted_licenses", "inspec.for_testing")) end end end Rake::TestTask.new(:functional) do |t| t.libs << "test" t.test_files = Dir.glob([ "test/functional/**/*_test.rb", "lib/plugins/inspec-*/test/functional/**/*_test.rb", ]) t.warning = !!ENV["W"] t.verbose = !!ENV["V"] # default to off. the test commands are _huge_. t.ruby_opts = ["--dev"] if defined?(JRUBY_VERSION) end # Inject a prerequisite task task functional: [:accept_license] Rake::TestTask.new(:unit) do |t| t.libs << "test" t.test_files = Dir.glob([ "test/unit/**/*_test.rb", "lib/plugins/inspec-*/test/unit/**/*_test.rb", ]) t.warning = !!ENV["W"] t.verbose = !!ENV["V"] # default to off. the test commands are _huge_. t.ruby_opts = ["--dev"] if defined?(JRUBY_VERSION) end # Inject a prerequisite task task unit: [:accept_license] task :kitchen, [:os] do |task, args| concurrency = ENV["CONCURRENCY"] || 1 os = args[:os] || ENV["OS"] || "" ENV["DOCKER"] = "true" if ENV["docker"].nil? sh("bundle exec kitchen test -c #{concurrency} #{os}") end # Inject a prerequisite task task kitchen: [:accept_license] task :ssh, [:target] do |_t, args| tests_path = File.join(File.dirname(__FILE__), "test", "integration", "test", "integration", "default") key_files = ENV["key_files"] || File.join(ENV["HOME"], ".ssh", "id_rsa") sh_cmd = "bin/inspec exec #{tests_path}/" sh_cmd += ENV["test"] ? "#{ENV["test"]}_spec.rb" : "*" sh_cmd += " --sudo" unless args[:target].split("@")[0] == "root" sh_cmd += " -t ssh://#{args[:target]}" sh_cmd += " --key_files=#{key_files}" sh_cmd += " --format=#{ENV["format"]}" if ENV["format"] sh("sh", "-c", sh_cmd) end end # Print the current version of this gem or update it. # # @param [Type] target the new version you want to set, or nil if you only want to show def inspec_version(target = nil) path = "lib/inspec/version.rb" require_relative path.sub(/.rb$/, "") nu_version = target.nil? ? "" : " -> #{target}" puts "Inspec: #{Inspec::VERSION}#{nu_version}" unless target.nil? raw = File.read(path) nu = raw.sub(/VERSION.*/, "VERSION = '#{target}'.freeze") File.write(path, nu) load(path) end end # Check if a command is available # # @param [Type] x the command you are interested in # @param [Type] msg the message to display if the command is missing def require_command(x, msg = nil) return if system("command -v #{x} || exit 1") msg ||= "Please install it first!" puts "\033[31;1mCan't find command #{x.inspect}. #{msg}\033[0m" exit 1 end # Check if a required environment variable has been set # # @param [String] x the variable you are interested in # @param [String] msg the message you want to display if the variable is missing def require_env(x, msg = nil) exists = `env | grep "^#{x}="` return unless exists.empty? puts "\033[31;1mCan't find environment variable #{x.inspect}. #{msg}\033[0m" exit 1 end # Check the requirements for running an update of this repository. def check_update_requirements require_command "git" end # Show the current version of this gem. desc "Show the version of this gem" task :version do inspec_version end