tasks/spec/mutate.rake in cli-forge-0.0.0 vs tasks/spec/mutate.rake in cli-forge-0.1.0

- old
+ new

@@ -1,32 +1,23 @@ namespace :spec do - def mutant_supported? - return false unless RUBY_VERSION.start_with?("1.9") - - begin - return false unless RUBY_ENGINE == "ruby" || RUBY_ENGINE == "rbx" - rescue NameError - return false - end - - true - end - desc "Runs tests with code mutation" task :mutate, [:focus_on] do |t, args| next unless mutant_supported? require "cli_forge" require "mutant" + require "mutant/constants" + require "mutant/mutation/filter/regexp" # You can focus on a particular symbol/method by passing it to the task: # rake spec:mutate[AutoloadConvention#const_missing], for example. if args.focus_on - matcher = Mutant::Matcher.from_string("::CLIForge::#{args.focus_on}") + matcher = matcher_for_filter(args.focus_on) + # Otherwise we're doing a full mutation suite else - matcher = Mutant::Matcher::ObjectSpace.new(/\ACLIForge(::|#|\.).+\Z/) + matcher = all_matcher end # Mutant doesn't have a public scripting API yet; so we're cheating. config = {} def config.method_missing(sym) @@ -35,16 +26,132 @@ config.merge!( :strategy => Mutant::Strategy::Rspec::DM2.new(config), :killer => Mutant::Killer::Rspec, :matcher => matcher, - :filter => Mutant::Mutation::Filter::ALL, + :filter => filter_for_specs, :reporter => Mutant::Reporter::CLI.new(config) ) ENV["MUTATION"] = "yes" if Mutant::Runner.run(config).fail? exit 1 end + end + + def mutant_supported? + return false unless RUBY_VERSION.start_with?("1.9") + # TODO: Rubinius crashes under mutant: + # https://github.com/rubinius/rubinius/issues/2186 + return false if RUBY_ENGINE == "rbx" + + begin + return false unless RUBY_ENGINE == "ruby" || RUBY_ENGINE == "rbx" + rescue NameError + return false + end + + true + end + + # Also preloads the related constants + def matcher_for_filter(filter) + # Method on CLIForge? + if filter.start_with?(".") || filter.start_with?("#") + matcher = Mutant::Matcher.from_string("::CLIForge#{filter}") + + # Or regular constant? + else + matcher = Mutant::Matcher.from_string("::CLIForge::#{filter}") + # Force that symbol to load + const = CLIForge + filter[/^[^#\.]+/].split("::").each do |const_name| + const = const.const_get(const_name.to_sym) + end + end + + matcher + end + + def all_matcher + # Force everything to load + Dir["#{PROJECT_ROOT}/lib/cli_forge/**/*.rb"].each do |path| + require path[/lib.(cli_forge..+)\.rb$/, 1] + end + + unit_folders = Dir["spec/unit/**/*/"].map { |f| f[/^spec.unit.(.+)./, 1] } + unit_constants = unit_folders.map do |folder| + folder.gsub(File::SEPARATOR, "::").gsub("_", "").downcase + end + + Mutant::Matcher::ObjectSpace.new(/^CLIForge/) + end + + def spec_filter_klass + # Avoid loading w/ rake + filter_klass = Class.new(Mutant::Mutation::Filter) + filter_klass.class_eval do + def initialize(regexp) + @regexp = regexp + end + + def match?(mutation) + @regexp =~ mutation.subject.matcher.identification + end + end + + filter_klass + end + + def filter_for_specs + # Only run mutations for methods that we have specs for. Basic code + # coverage can cover simple cases. + spec_symbols = Dir["#{PROJECT_ROOT}/spec/unit/**/*_spec.rb"].map { |path| + path_to_symbol(path[/spec.unit.(.+)_spec\.rb$/, 1]) + } + safe_symbols = spec_symbols.map { |s| Regexp.escape(s) } + + spec_filter_klass.new(/^(#{safe_symbols.join("|")})$/i) + end + + # convert a spec path back to a (case insensitive) symbol + def path_to_symbol(spec_path) + parts = spec_path.split(/[\/\\]/) + method = symbolicate_method_name(parts.pop) + if parts.last == "class_methods" + method = ".#{method}" + parts.pop + else + method = "##{method}" + end + + parts.join("::").gsub("_", "") + method + end + + # https://github.com/mbj/mutant/blob/master/lib/mutant/constants.rb + def postfix_expansions + # @postfix_expansions ||= Mutant::METHOD_POSTFIX_EXPANSIONS.invert + @postfix_expansions ||= { + '_predicate' => '?', + '_writer' => '=', + '_bang' => '!' + } + end + + def operator_expansions + @operator_expansions ||= Mutant::OPERATOR_EXPANSIONS.invert + end + + def symbolicate_method_name(method_name) + operator = operator_expansions[method_name.to_sym] + return operator.to_s if operator + + postfix_expansions.each do |postfix, symbol| + if method_name.end_with? postfix + method_name = method_name[0...-postfix.size] + symbol.to_s + end + end + + method_name end end