Merge branch 'spec-output'
This commit is contained in:
commit
e7158acb17
63 changed files with 1472 additions and 1345 deletions
|
@ -1,7 +1,7 @@
|
|||
---
|
||||
machine:
|
||||
ruby:
|
||||
version: 2.0.0
|
||||
version: 2.1.2
|
||||
test:
|
||||
override:
|
||||
- bundle exec rake ci
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
---
|
||||
threshold: 18
|
||||
total_score: 839
|
||||
total_score: 812
|
||||
|
|
|
@ -10,6 +10,5 @@ ignore_subjects:
|
|||
- Mutant::Reporter*
|
||||
- Mutant::CLI*
|
||||
- Mutant.singleton_subclass_instance
|
||||
- Mutant.symbolset
|
||||
# Executing this has undefined behavior with the zombifier
|
||||
- Mutant.zombify
|
||||
|
|
|
@ -51,9 +51,11 @@ NestedIterators:
|
|||
- Mutant#self.singleton_subclass_instance
|
||||
- Mutant::CLI#parse
|
||||
- Mutant::Mutator::Util::Array::Element#dispatch
|
||||
- Mutant::Reporter::CLI::Printer::Config::Runner#generic_stats
|
||||
- Mutant::Reporter::CLI::Report::Config#generic_stats
|
||||
- Mutant::RequireHighjack#infect
|
||||
- Mutant::RequireHighjack#desinfect
|
||||
- Mutant::Reporter::CLI::Registry#included
|
||||
- Mutant::Strategy#tests
|
||||
- Parser::Lexer#self.new
|
||||
max_allowed_nesting: 1
|
||||
ignore_iterators: []
|
||||
|
@ -63,6 +65,7 @@ RepeatedConditional:
|
|||
enabled: true
|
||||
exclude:
|
||||
- Mutant::Mutator
|
||||
- Mutant::Rspec::Strategy
|
||||
- Mutant::Reporter::CLI
|
||||
max_ifs: 1
|
||||
TooManyInstanceVariables:
|
||||
|
@ -84,12 +87,14 @@ TooManyStatements:
|
|||
enabled: true
|
||||
exclude:
|
||||
- Mutant#self.singleton_subclass_instance
|
||||
- Mutant#self.isolate
|
||||
- Mutant::Rspec::Killer#run
|
||||
- Mutant::Reporter::CLI#colorized_diff
|
||||
- Mutant::Reporter::CLI::Printer::Config::Runner#run
|
||||
- Mutant::Runner#dispatch
|
||||
- Mutant::Reporter::CLI::Report::Config#run
|
||||
- Mutant::Runner#visit_collection
|
||||
- Mutant::Zombifier::File#self.find
|
||||
- Mutant::RequireHighjack#infect
|
||||
- Mutant::Reporter::CLI::Registry#included
|
||||
# How mutant does CLI parsing is shit
|
||||
- Mutant::CLI#parse
|
||||
- Mutant::CLI#initialize
|
||||
|
|
|
@ -99,6 +99,10 @@ IndentationWidth:
|
|||
EmptyLinesAroundBody:
|
||||
Enabled: false
|
||||
|
||||
# I test this style for a while
|
||||
LambdaCall:
|
||||
Enabled: false
|
||||
|
||||
# I like my style more
|
||||
AccessModifierIndentation:
|
||||
Enabled: false
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
# encoding: UTF-8
|
||||
|
||||
require 'rspec'
|
||||
|
||||
require 'mutant/rspec'
|
||||
require 'mutant/rspec/killer'
|
||||
require 'mutant/rspec/strategy'
|
||||
require 'mutant/rspec/test'
|
||||
|
|
|
@ -37,18 +37,42 @@ module Mutant
|
|||
self
|
||||
end
|
||||
|
||||
# Return a frozen set of symbols from string enumerable
|
||||
IsolationError = Class.new(RuntimeError)
|
||||
|
||||
# Call block in isolation
|
||||
#
|
||||
# @param [Enumerable<String>]
|
||||
# This isolation implements the fork strategy.
|
||||
# Future strategies will probably use a process pool that can
|
||||
# handle multiple mutation kills, in-isolation at once.
|
||||
#
|
||||
# @return [Set<Symbol>]
|
||||
# @return [Object]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def self.symbolset(strings)
|
||||
strings.map(&:to_sym).to_set.freeze
|
||||
def self.isolate(&block)
|
||||
reader, writer = IO.pipe
|
||||
|
||||
pid = fork do
|
||||
reader.close
|
||||
writer.write(Marshal.dump(block.call))
|
||||
end
|
||||
|
||||
writer.close
|
||||
|
||||
begin
|
||||
data = Marshal.load(reader.read)
|
||||
rescue ArgumentError
|
||||
raise IsolationError, 'Childprocess wrote un-unmarshallable data'
|
||||
end
|
||||
|
||||
status = Process.waitpid2(pid).last
|
||||
|
||||
unless status.exitstatus.zero?
|
||||
raise IsolationError, "Childprocess exited with nonzero exit status: #{status.exitstatus}"
|
||||
end
|
||||
|
||||
data
|
||||
end
|
||||
private_class_method :symbolset
|
||||
|
||||
# Define instance of subclassed superclass as constant
|
||||
#
|
||||
|
@ -78,6 +102,7 @@ end # Mutant
|
|||
|
||||
require 'mutant/version'
|
||||
require 'mutant/cache'
|
||||
require 'mutant/delegator'
|
||||
require 'mutant/node_helpers'
|
||||
require 'mutant/warning_filter'
|
||||
require 'mutant/warning_expectation'
|
||||
|
@ -162,27 +187,33 @@ require 'mutant/matcher/scope'
|
|||
require 'mutant/matcher/filter'
|
||||
require 'mutant/matcher/null'
|
||||
require 'mutant/killer'
|
||||
require 'mutant/killer/static'
|
||||
require 'mutant/killer/forking'
|
||||
require 'mutant/killer/forked'
|
||||
require 'mutant/test'
|
||||
require 'mutant/strategy'
|
||||
require 'mutant/runner'
|
||||
require 'mutant/runner/config'
|
||||
require 'mutant/runner/subject'
|
||||
require 'mutant/runner/mutation'
|
||||
require 'mutant/runner/killer'
|
||||
require 'mutant/cli'
|
||||
require 'mutant/cli/classifier'
|
||||
require 'mutant/cli/classifier/namespace'
|
||||
require 'mutant/cli/classifier/method'
|
||||
require 'mutant/color'
|
||||
require 'mutant/differ'
|
||||
require 'mutant/diff'
|
||||
require 'mutant/reporter'
|
||||
require 'mutant/reporter/null'
|
||||
require 'mutant/reporter/trace'
|
||||
require 'mutant/reporter/cli'
|
||||
require 'mutant/reporter/cli/registry'
|
||||
require 'mutant/reporter/cli/printer'
|
||||
require 'mutant/reporter/cli/printer/config'
|
||||
require 'mutant/reporter/cli/printer/subject'
|
||||
require 'mutant/reporter/cli/printer/killer'
|
||||
require 'mutant/reporter/cli/printer/mutation'
|
||||
require 'mutant/reporter/cli/report'
|
||||
require 'mutant/reporter/cli/report/config'
|
||||
require 'mutant/reporter/cli/report/subject'
|
||||
require 'mutant/reporter/cli/report/mutation'
|
||||
require 'mutant/reporter/cli/progress'
|
||||
require 'mutant/reporter/cli/progress/config'
|
||||
require 'mutant/reporter/cli/progress/subject'
|
||||
require 'mutant/reporter/cli/progress/mutation'
|
||||
require 'mutant/reporter/cli/progress/noop'
|
||||
require 'mutant/zombifier'
|
||||
require 'mutant/zombifier/file'
|
||||
|
|
|
@ -2,23 +2,23 @@
|
|||
|
||||
module Mutant
|
||||
|
||||
symbolset = ->(strings) { strings.map(&:to_sym).to_set.freeze }
|
||||
|
||||
# Set of nodes that cannot be on the LHS of an assignment
|
||||
NOT_ASSIGNABLE = symbolset %w(
|
||||
int float str dstr class module self
|
||||
)
|
||||
NOT_ASSIGNABLE = symbolset.(%w(int float str dstr class module self))
|
||||
|
||||
# Set of op-assign types
|
||||
OP_ASSIGN = symbolset %w(or_asgn and_asgn op_asgn)
|
||||
OP_ASSIGN = symbolset.call(%w(or_asgn and_asgn op_asgn))
|
||||
# Set of node types that are not valid when emitted standalone
|
||||
NOT_STANDALONE = symbolset %w( splat restarg block_pass)
|
||||
INDEX_OPERATORS = symbolset %w([] []=)
|
||||
UNARY_METHOD_OPERATORS = symbolset %w(~@ +@ -@ !)
|
||||
NOT_STANDALONE = symbolset.(%w( splat restarg block_pass))
|
||||
INDEX_OPERATORS = symbolset.(%w([] []=))
|
||||
UNARY_METHOD_OPERATORS = symbolset.(%w(~@ +@ -@ !))
|
||||
|
||||
# Operators ruby implementeds as methods
|
||||
METHOD_OPERATORS = symbolset %w(
|
||||
METHOD_OPERATORS = symbolset.(%w(
|
||||
<=> === []= [] <= >= == !~ != =~ <<
|
||||
>> ** * % / | ^ & < > + - ~@ +@ -@ !
|
||||
)
|
||||
))
|
||||
|
||||
BINARY_METHOD_OPERATORS = (
|
||||
METHOD_OPERATORS - (INDEX_OPERATORS + UNARY_METHOD_OPERATORS)
|
||||
|
@ -32,10 +32,10 @@ module Mutant
|
|||
#
|
||||
# not - 1.8 only, mutant does not support 1.8
|
||||
#
|
||||
NODE_BLACKLIST = symbolset %w(not)
|
||||
NODE_BLACKLIST = symbolset.(%w(not))
|
||||
|
||||
# Nodes that are NOT generated by parser but used by mutant / unparser.
|
||||
NODE_EXTRA = symbolset %w(empty)
|
||||
NODE_EXTRA = symbolset.(%w(empty))
|
||||
|
||||
NODE_TYPES = ((Parser::Meta::NODE_TYPES + NODE_EXTRA) - NODE_BLACKLIST).to_set.freeze
|
||||
|
||||
|
|
49
lib/mutant/delegator.rb
Normal file
49
lib/mutant/delegator.rb
Normal file
|
@ -0,0 +1,49 @@
|
|||
module Mutant
|
||||
module Delegator
|
||||
module ClassMethods
|
||||
|
||||
private
|
||||
# Create delegators to object
|
||||
#
|
||||
# @return [undefined]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def delegate(*names)
|
||||
names.each do |name|
|
||||
define_delegator(name)
|
||||
end
|
||||
end
|
||||
|
||||
# Create delegator to object
|
||||
#
|
||||
# @param [Symbol] name
|
||||
#
|
||||
# @return [undefined]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def define_delegator(name)
|
||||
define_method(name) do
|
||||
object.public_send(name)
|
||||
end
|
||||
private name
|
||||
end
|
||||
|
||||
end # ClassMethods
|
||||
|
||||
# Hook called when module is included
|
||||
#
|
||||
# @param [Class,Module] host
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def self.included(host)
|
||||
super
|
||||
host.class_eval do
|
||||
extend ClassMethods
|
||||
end
|
||||
end
|
||||
|
||||
end # Delegator
|
||||
end # Mutant
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
module Mutant
|
||||
# Class to create diffs from source code
|
||||
class Differ
|
||||
class Diff
|
||||
include Adamantium::Flat, Concord.new(:old, :new)
|
||||
|
||||
# Return source diff
|
||||
|
@ -20,7 +20,7 @@ module Mutant
|
|||
when 0
|
||||
nil
|
||||
when 1
|
||||
Diff::LCS::Hunk.new(old, new, diffs.first, max_length, 0)
|
||||
::Diff::LCS::Hunk.new(old, new, diffs.first, max_length, 0)
|
||||
.diff(:unified) << "\n"
|
||||
else
|
||||
$stderr.puts(
|
||||
|
@ -55,7 +55,7 @@ module Mutant
|
|||
# @param [String] old
|
||||
# @param [String] new
|
||||
#
|
||||
# @return [Differ]
|
||||
# @return [Diff]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
|
@ -85,7 +85,7 @@ module Mutant
|
|||
# @api private
|
||||
#
|
||||
def diffs
|
||||
Diff::LCS.diff(old, new)
|
||||
::Diff::LCS.diff(old, new)
|
||||
end
|
||||
memoize :diffs
|
||||
|
||||
|
@ -118,5 +118,5 @@ module Mutant
|
|||
end.format(line)
|
||||
end
|
||||
|
||||
end # Differ
|
||||
end # Diff
|
||||
end # Mutant
|
|
@ -1,123 +1,46 @@
|
|||
# encoding: utf-8
|
||||
|
||||
module Mutant
|
||||
# Abstract base class for mutant killers
|
||||
# Mutation killer
|
||||
class Killer
|
||||
include Adamantium::Flat, AbstractType
|
||||
include Equalizer.new(:strategy, :mutation, :killed?)
|
||||
include Adamantium::Flat, Anima.new(:test, :mutation)
|
||||
|
||||
# Return strategy
|
||||
#
|
||||
# @return [Strategy]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
attr_reader :strategy
|
||||
# Report object for kill results
|
||||
class Report
|
||||
include Anima.new(
|
||||
:killer,
|
||||
:test_report
|
||||
)
|
||||
|
||||
# Return mutation to kill
|
||||
#
|
||||
# @return [Mutation]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
attr_reader :mutation
|
||||
|
||||
# Initialize killer object
|
||||
#
|
||||
# @param [Strategy] strategy
|
||||
# @param [Mutation] mutation
|
||||
#
|
||||
# @return [undefined]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def initialize(strategy, mutation)
|
||||
@strategy, @mutation = strategy, mutation
|
||||
@killed = run
|
||||
end
|
||||
|
||||
# Test for kill failure
|
||||
#
|
||||
# @return [true]
|
||||
# when killer succeeded
|
||||
#
|
||||
# @return [false]
|
||||
# otherwise
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def success?
|
||||
mutation.success?(self)
|
||||
end
|
||||
memoize :success?
|
||||
|
||||
# Test if mutant was killed
|
||||
#
|
||||
# @return [true]
|
||||
# if mutant was killed
|
||||
#
|
||||
# @return [false]
|
||||
# otherwise
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def killed?
|
||||
@killed
|
||||
end
|
||||
|
||||
# Return mutated source
|
||||
#
|
||||
# @return [String]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def mutation_source
|
||||
mutation.source
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Return subject
|
||||
#
|
||||
# @return [Subject]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def subject
|
||||
mutation.subject
|
||||
end
|
||||
|
||||
# Run killer
|
||||
#
|
||||
# @return [true]
|
||||
# when mutant was killed
|
||||
#
|
||||
# @return [false]
|
||||
# otherwise
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
abstract_method :run
|
||||
|
||||
# Null killer that never kills a mutation
|
||||
class Null < self
|
||||
|
||||
private
|
||||
|
||||
# Run killer
|
||||
# Test if kill was successful
|
||||
#
|
||||
# @return [true]
|
||||
# when mutant was killed
|
||||
#
|
||||
# @return [false]
|
||||
# otherwise
|
||||
# @return [Boolean]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def run
|
||||
false
|
||||
def success?
|
||||
killer.mutation.should_fail?.equal?(test_report.failed?)
|
||||
end
|
||||
|
||||
end # Report
|
||||
|
||||
# Return killer report
|
||||
#
|
||||
# @return [Killer::Report]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def run
|
||||
test_report = Mutant.isolate do
|
||||
mutation.insert
|
||||
test.run
|
||||
end
|
||||
|
||||
Report.new(
|
||||
killer: self,
|
||||
test_report: test_report.update(test: test)
|
||||
)
|
||||
end
|
||||
|
||||
end # Killer
|
||||
end # Mutant
|
||||
|
|
|
@ -1,46 +0,0 @@
|
|||
# encoding: utf-8
|
||||
|
||||
module Mutant
|
||||
class Killer
|
||||
|
||||
# Killer that executes other killer in forked environment
|
||||
class Forked < self
|
||||
|
||||
# Initialize object
|
||||
#
|
||||
# @param [Killer] killer
|
||||
# @param [Strategy] strategy
|
||||
# @param [Mutation] mutation
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def initialize(killer, strategy, mutation)
|
||||
@killer = killer
|
||||
super(strategy, mutation)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Run killer
|
||||
#
|
||||
# @return [true]
|
||||
# if mutant was killed
|
||||
#
|
||||
# @return [false]
|
||||
# otherwise
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def run
|
||||
pid = fork do
|
||||
killer = @killer.new(strategy, mutation)
|
||||
exit(killer.killed? ? CLI::EXIT_SUCCESS : CLI::EXIT_FAILURE)
|
||||
end
|
||||
|
||||
status = Process.wait2(pid).last
|
||||
status.exited? && status.success?
|
||||
end
|
||||
|
||||
end # Forked
|
||||
end # Killer
|
||||
end # Mutant
|
|
@ -1,46 +0,0 @@
|
|||
# encoding: utf-8
|
||||
|
||||
module Mutant
|
||||
class Killer
|
||||
|
||||
# A killer that executes other killer in forked environemnts
|
||||
class Forking < self
|
||||
include Equalizer.new(:killer)
|
||||
|
||||
# Return killer
|
||||
#
|
||||
# @return [Killer]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
attr_reader :killer
|
||||
|
||||
# Initalize killer
|
||||
#
|
||||
# @param [Killer] killer
|
||||
# the killer that will be used
|
||||
#
|
||||
# @return [undefined]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def initialize(killer)
|
||||
@killer = killer
|
||||
end
|
||||
|
||||
# Return killer instance
|
||||
#
|
||||
# @param [Strategy] strategy
|
||||
# @param [Mutation] mutation
|
||||
#
|
||||
# @return [Killer::Forked]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def new(strategy, mutation)
|
||||
Forked.new(killer, strategy, mutation)
|
||||
end
|
||||
|
||||
end # Forking
|
||||
end # Killer
|
||||
end # Mutant
|
|
@ -1,34 +0,0 @@
|
|||
# encoding: utf-8
|
||||
|
||||
module Mutant
|
||||
class Killer
|
||||
# Abstract base class for killer with static result
|
||||
class Static < self
|
||||
|
||||
# Return result
|
||||
#
|
||||
# @return [true]
|
||||
# if mutation was killed
|
||||
#
|
||||
# @return [false]
|
||||
# otherwise
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def run
|
||||
self.class::RESULT
|
||||
end
|
||||
|
||||
# Killer that is always successful
|
||||
class Success < self
|
||||
RESULT = true
|
||||
end # Success
|
||||
|
||||
# Killer that always fails
|
||||
class Fail < self
|
||||
RESULT = false
|
||||
end # Fail
|
||||
|
||||
end # Static
|
||||
end # Killer
|
||||
end # Mutant
|
|
@ -6,6 +6,9 @@ module Mutant
|
|||
include AbstractType, Adamantium::Flat
|
||||
include Concord::Public.new(:subject, :node)
|
||||
|
||||
CODE_DELIMITER = "\0".freeze
|
||||
CODE_RANGE = (0..4).freeze
|
||||
|
||||
# Return mutated root node
|
||||
#
|
||||
# @return [Parser::AST::Node]
|
||||
|
@ -54,8 +57,9 @@ module Mutant
|
|||
# @api private
|
||||
#
|
||||
def identification
|
||||
"#{subject.identification}:#{code}"
|
||||
"#{self.class::SYMBOL}:#{subject.identification}:#{code}"
|
||||
end
|
||||
memoize :identification
|
||||
|
||||
# Return mutation code
|
||||
#
|
||||
|
@ -64,7 +68,7 @@ module Mutant
|
|||
# @api private
|
||||
#
|
||||
def code
|
||||
sha1[0..4]
|
||||
sha1[CODE_RANGE]
|
||||
end
|
||||
memoize :code
|
||||
|
||||
|
@ -89,6 +93,16 @@ module Mutant
|
|||
subject.source
|
||||
end
|
||||
|
||||
# Test if test should fail under mutation
|
||||
#
|
||||
# @return [Boolean]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def should_fail?
|
||||
self.class::SHOULD_FAIL
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Return sha1 sum of source and subject identification
|
||||
|
@ -98,7 +112,7 @@ module Mutant
|
|||
# @api private
|
||||
#
|
||||
def sha1
|
||||
Digest::SHA1.hexdigest(subject.identification + 0.chr + source)
|
||||
Digest::SHA1.hexdigest(subject.identification + CODE_DELIMITER + source)
|
||||
end
|
||||
memoize :sha1
|
||||
|
||||
|
|
|
@ -5,16 +5,8 @@ module Mutant
|
|||
# Evul mutation
|
||||
class Evil < self
|
||||
|
||||
# Return identification
|
||||
#
|
||||
# @return [String]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def identification
|
||||
"evil:#{super}"
|
||||
end
|
||||
memoize :identification
|
||||
SHOULD_FAIL = true
|
||||
SYMBOL = 'evil'.freeze
|
||||
|
||||
# Test if killer is successful
|
||||
#
|
||||
|
|
|
@ -5,41 +5,15 @@ module Mutant
|
|||
# Neutral mutation
|
||||
class Neutral < self
|
||||
|
||||
SYMBOL = 'neutral'
|
||||
SYMBOL = 'neutral'.freeze
|
||||
SHOULD_FAIL = false
|
||||
|
||||
# Noop mutation, special case of neutral
|
||||
class Noop < self
|
||||
|
||||
SYMBOL = 'noop'
|
||||
SYMBOL = 'noop'.freeze
|
||||
|
||||
end
|
||||
|
||||
# Return identification
|
||||
#
|
||||
# @return [String]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def identification
|
||||
"#{self.class::SYMBOL}:#{super}"
|
||||
end
|
||||
memoize :identification
|
||||
|
||||
# Test if killer is successful
|
||||
#
|
||||
# @param [Killer] killer
|
||||
#
|
||||
# @return [true]
|
||||
# if killer did NOT killed mutation
|
||||
#
|
||||
# @return [false]
|
||||
# otherwise
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def success?(killer)
|
||||
!killer.killed?
|
||||
end
|
||||
end # Noop
|
||||
|
||||
end # Neutral
|
||||
end # Mutation
|
||||
|
|
|
@ -13,7 +13,7 @@ module Mutant
|
|||
# @api private
|
||||
#
|
||||
def s(type, *children)
|
||||
::Parser::AST::Node.new(type, children)
|
||||
Parser::AST::Node.new(type, children)
|
||||
end
|
||||
module_function :s
|
||||
|
||||
|
@ -21,8 +21,6 @@ module Mutant
|
|||
s(:send, s(:float, 0.0), :/, s(:float, 0.0))
|
||||
INFINITY =
|
||||
s(:send, s(:float, 1.0), :/, s(:float, 0.0))
|
||||
NEW_OBJECT =
|
||||
s(:send, s(:const, s(:cbase), :Object), :new)
|
||||
NEGATIVE_INFINITY =
|
||||
s(:send, s(:float, -1.0), :/, s(:float, 0.0))
|
||||
|
||||
|
|
|
@ -15,5 +15,15 @@ module Mutant
|
|||
#
|
||||
abstract_method :report
|
||||
|
||||
# Report progress on object
|
||||
#
|
||||
# @param [Object] object
|
||||
#
|
||||
# @return [self]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
abstract_method :progress
|
||||
|
||||
end # Reporter
|
||||
end # Mutant
|
||||
|
|
|
@ -4,10 +4,23 @@ module Mutant
|
|||
class Reporter
|
||||
# Reporter that reports in human readable format
|
||||
class CLI < self
|
||||
include Concord::Public.new(:output)
|
||||
include Concord.new(:output)
|
||||
|
||||
NL = "\n".freeze
|
||||
|
||||
# Report progress object
|
||||
#
|
||||
# @param [Object] object
|
||||
#
|
||||
# @return [self]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def progress(object)
|
||||
Progress.run(output, object)
|
||||
self
|
||||
end
|
||||
|
||||
# Report object
|
||||
#
|
||||
# @param [Object] object
|
||||
|
@ -17,7 +30,7 @@ module Mutant
|
|||
# @api private
|
||||
#
|
||||
def report(object)
|
||||
Printer.visit(object, output)
|
||||
Report.run(output, object)
|
||||
self
|
||||
end
|
||||
|
||||
|
|
|
@ -6,69 +6,19 @@ module Mutant
|
|||
|
||||
# CLI runner status printer base class
|
||||
class Printer
|
||||
include AbstractType, Adamantium::Flat, Concord.new(:object, :output)
|
||||
include AbstractType, Delegator, Adamantium::Flat, Concord.new(:output, :object)
|
||||
|
||||
REGISTRY = {}
|
||||
|
||||
# Create delegators to object
|
||||
# Run printer on object to output
|
||||
#
|
||||
# @return [undefined]
|
||||
# @param [IO] output
|
||||
# @param [Object] object
|
||||
#
|
||||
# @api private
|
||||
# @return [self]
|
||||
#
|
||||
def self.delegate(*names)
|
||||
names.each do |name|
|
||||
define_delegator(name)
|
||||
end
|
||||
end
|
||||
private_class_method :delegate
|
||||
|
||||
# Create delegator to object
|
||||
#
|
||||
# @param [Symbol] name
|
||||
#
|
||||
# @return [undefined]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def self.define_delegator(name)
|
||||
define_method(name) do
|
||||
object.public_send(name)
|
||||
end
|
||||
private name
|
||||
end
|
||||
private_class_method :define_delegator
|
||||
|
||||
# Registre handler for class
|
||||
#
|
||||
# @param [Class] klass
|
||||
#
|
||||
# @return [undefined]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def self.handle(klass)
|
||||
REGISTRY[klass] = self
|
||||
end
|
||||
|
||||
# Finalize CLI reporter
|
||||
#
|
||||
# @return [undefined]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def self.finalize
|
||||
REGISTRY.freeze
|
||||
end
|
||||
|
||||
# Build printer
|
||||
#
|
||||
# @return [Printer]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def self.build(*args)
|
||||
new(*args)
|
||||
def self.run(output, object)
|
||||
handler = lookup(object.class)
|
||||
handler.new(output, object).run
|
||||
self
|
||||
end
|
||||
|
||||
# Run printer
|
||||
|
@ -77,49 +27,6 @@ module Mutant
|
|||
#
|
||||
# @api private
|
||||
#
|
||||
def self.run(*args)
|
||||
build(*args).run
|
||||
self
|
||||
end
|
||||
|
||||
# Visit object
|
||||
#
|
||||
# @param [Object] object
|
||||
# @param [IO] output
|
||||
#
|
||||
# @return [undefined]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def self.visit(object, output)
|
||||
printer = lookup(object.class)
|
||||
printer.run(object, output)
|
||||
end
|
||||
|
||||
# Lookup printer class
|
||||
#
|
||||
# @param [Class] klass
|
||||
#
|
||||
# @return [Class:Printer]
|
||||
# if found
|
||||
#
|
||||
# @raise [RuntimeError]
|
||||
# otherwise
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def self.lookup(klass)
|
||||
current = klass
|
||||
until current == Object
|
||||
if REGISTRY.key?(current)
|
||||
return REGISTRY.fetch(current)
|
||||
end
|
||||
current = current.superclass
|
||||
end
|
||||
raise "No printer for: #{klass}"
|
||||
end
|
||||
private_class_method :lookup
|
||||
|
||||
abstract_method :run
|
||||
|
||||
private
|
||||
|
@ -130,7 +37,7 @@ module Mutant
|
|||
#
|
||||
# @api private
|
||||
#
|
||||
def color
|
||||
def status_color
|
||||
success? ? Color::GREEN : Color::RED
|
||||
end
|
||||
|
||||
|
@ -143,7 +50,7 @@ module Mutant
|
|||
# @api private
|
||||
#
|
||||
def visit(object)
|
||||
self.class.visit(object, output)
|
||||
self.class.run(output, object)
|
||||
end
|
||||
|
||||
# Print an info line to output
|
||||
|
@ -163,7 +70,7 @@ module Mutant
|
|||
# @api private
|
||||
#
|
||||
def status(string, *arguments)
|
||||
puts(colorize(color, sprintf(string, *arguments)))
|
||||
puts(colorize(status_color, sprintf(string, *arguments)))
|
||||
end
|
||||
|
||||
# Print a line to output
|
||||
|
|
|
@ -1,154 +0,0 @@
|
|||
# encoding: utf-8
|
||||
|
||||
module Mutant
|
||||
class Reporter
|
||||
class CLI
|
||||
class Printer
|
||||
|
||||
# Printer for configuration
|
||||
class Config < self
|
||||
|
||||
handle(Mutant::Config)
|
||||
|
||||
delegate :matcher, :strategy, :expected_coverage
|
||||
|
||||
# Report configuration
|
||||
#
|
||||
# @param [Mutant::Config] config
|
||||
#
|
||||
# @return [self]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def run
|
||||
info 'Mutant configuration:'
|
||||
info 'Matcher: %s', matcher.inspect
|
||||
info 'Strategy: %s', strategy.inspect
|
||||
info 'Expect Coverage: %02f%%', expected_coverage.inspect
|
||||
self
|
||||
end
|
||||
|
||||
# Config results printer
|
||||
class Runner < self
|
||||
|
||||
handle(Mutant::Runner::Config)
|
||||
|
||||
delegate(
|
||||
:amount_kills, :amount_mutations, :amount_kils,
|
||||
:coverage, :subjects, :failed_subjects, :runtime, :mutations
|
||||
)
|
||||
|
||||
# Run printer
|
||||
#
|
||||
# @return [self]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def run
|
||||
print_mutations
|
||||
info 'Subjects: %s', amount_subjects
|
||||
info 'Mutations: %s', amount_mutations
|
||||
info 'Kills: %s', amount_kills
|
||||
info 'Alive: %s', amount_alive
|
||||
info 'Runtime: %0.2fs', runtime
|
||||
info 'Killtime: %0.2fs', killtime
|
||||
info 'Overhead: %0.2f%%', overhead
|
||||
status 'Coverage: %0.2f%%', coverage
|
||||
status 'Expected: %0.2f%%', object.config.expected_coverage
|
||||
print_generic_stats
|
||||
self
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Print generic stats
|
||||
#
|
||||
# @return [undefined]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def print_generic_stats
|
||||
stats = generic_stats.to_a.sort_by(&:last)
|
||||
return if stats.empty?
|
||||
info('Nodes handled by generic mutator (type:occurrences):')
|
||||
stats.reverse_each do |type, amount|
|
||||
info('%-10s: %d', type, amount)
|
||||
end
|
||||
end
|
||||
|
||||
# Return stats for nodes handled by generic mutator
|
||||
#
|
||||
# @return [Hash<Symbo, Fixnum>]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def generic_stats
|
||||
subjects.each_with_object(Hash.new(0)) do |runner, stats|
|
||||
Walker.run(runner.subject.node) do |node|
|
||||
if Mutator::Registry.lookup(node) == Mutator::Node::Generic
|
||||
stats[node.type] += 1
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Return amount of subjects
|
||||
#
|
||||
# @return [Fixnum]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def amount_subjects
|
||||
subjects.length
|
||||
end
|
||||
|
||||
# Print mutations
|
||||
#
|
||||
# @return [undefined]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def print_mutations
|
||||
failed_subjects.each do |subject|
|
||||
Subject::Runner::Details.run(subject, output)
|
||||
end
|
||||
end
|
||||
|
||||
# Return amount of time in killers
|
||||
#
|
||||
# @return [Float]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def killtime
|
||||
mutations.map(&:runtime).inject(0, :+)
|
||||
end
|
||||
memoize :killtime
|
||||
|
||||
# Return mutant overhead
|
||||
#
|
||||
# @return [Float]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def overhead
|
||||
return 0 if runtime.zero?
|
||||
Rational(runtime - killtime, runtime) * 100
|
||||
end
|
||||
|
||||
# Return amount of alive mutations
|
||||
#
|
||||
# @return [Fixnum]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def amount_alive
|
||||
object.amount_mutations - amount_kills
|
||||
end
|
||||
|
||||
end # Runner
|
||||
end # Config
|
||||
end # Printer
|
||||
end # CLI
|
||||
end # Reporter
|
||||
end # Mutant
|
|
@ -1,103 +0,0 @@
|
|||
# encoding: utf-8
|
||||
|
||||
module Mutant
|
||||
class Reporter
|
||||
class CLI
|
||||
class Printer
|
||||
# Mutation printer
|
||||
class Mutation < self
|
||||
|
||||
handle(Runner::Mutation)
|
||||
|
||||
# Build printer
|
||||
#
|
||||
# @param [Runner::Mutation] runner
|
||||
# @param [IO] output
|
||||
#
|
||||
# @return [Printer::Mutation]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def self.build(runner, output)
|
||||
mutation = runner.mutation
|
||||
lookup(mutation.class).new(runner, output)
|
||||
end
|
||||
|
||||
# Run mutation printer
|
||||
#
|
||||
# @return [undefined]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def run
|
||||
status('%s', mutation.identification)
|
||||
puts(details)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Return mutation
|
||||
#
|
||||
# @return [Mutation]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def mutation
|
||||
object.mutation
|
||||
end
|
||||
|
||||
# Reporter for noop mutations
|
||||
class Noop < self
|
||||
|
||||
handle(Mutant::Mutation::Neutral::Noop)
|
||||
|
||||
MESSAGE = [
|
||||
'Parsed subject AST:',
|
||||
'%s',
|
||||
'Unparsed source:',
|
||||
'%s'
|
||||
].join("\n")
|
||||
|
||||
private
|
||||
|
||||
# Return details
|
||||
#
|
||||
# @return [String]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def details
|
||||
sprintf(
|
||||
MESSAGE,
|
||||
mutation.subject.node.inspect,
|
||||
mutation.original_source
|
||||
)
|
||||
end
|
||||
|
||||
end # Noop
|
||||
|
||||
# Reporter for neutral and evil mutations
|
||||
class Diff < self
|
||||
|
||||
handle(Mutant::Mutation::Neutral)
|
||||
handle(Mutant::Mutation::Evil)
|
||||
|
||||
# Return diff
|
||||
#
|
||||
# @return [String]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def details
|
||||
original, current = mutation.original_source, mutation.source
|
||||
differ = Differ.build(original, current)
|
||||
color? ? differ.colorized_diff : differ.diff
|
||||
end
|
||||
|
||||
end # Evil
|
||||
|
||||
end # Mutantion
|
||||
end # Printer
|
||||
end # CLI
|
||||
end # Reporter
|
||||
end # Mutant
|
|
@ -1,150 +0,0 @@
|
|||
# encoding: utf-8
|
||||
|
||||
module Mutant
|
||||
class Reporter
|
||||
class CLI
|
||||
class Printer
|
||||
|
||||
# Subject results printer
|
||||
class Subject < self
|
||||
|
||||
handle(Mutant::Subject)
|
||||
|
||||
# Run subject results printer
|
||||
#
|
||||
# @return [undefined]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def run
|
||||
info('%s', object.identification)
|
||||
end
|
||||
|
||||
# Printer for subject runners
|
||||
class Runner < self
|
||||
|
||||
handle(Mutant::Runner::Subject)
|
||||
|
||||
# Run printer
|
||||
#
|
||||
# @return [undefined]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def run
|
||||
print_progress_bar_finish
|
||||
print_stats
|
||||
self
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Return mutation time on subject
|
||||
#
|
||||
# @return [Float]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def time
|
||||
mutations.map(&:runtime).inject(0, :+)
|
||||
end
|
||||
|
||||
# Return subject
|
||||
#
|
||||
# @return [Subject]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def subject
|
||||
object.subject
|
||||
end
|
||||
|
||||
FORMAT = '(%02d/%02d) %3d%% - %0.02fs'.freeze
|
||||
|
||||
# Print stats
|
||||
#
|
||||
# @return [undefned
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def print_stats
|
||||
status(FORMAT, amount_kills, amount_mutations, coverage, time)
|
||||
end
|
||||
|
||||
# Print progress bar finish
|
||||
#
|
||||
# @return [undefined]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def print_progress_bar_finish
|
||||
puts unless amount_mutations.zero?
|
||||
end
|
||||
|
||||
# Return kills
|
||||
#
|
||||
# @return [Fixnum]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def amount_kills
|
||||
fails = object.failed_mutations
|
||||
fails = fails.length
|
||||
amount_mutations - fails
|
||||
end
|
||||
|
||||
# Return amount of mutations
|
||||
#
|
||||
# @return [Array<Mutation>]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def amount_mutations
|
||||
mutations.length
|
||||
end
|
||||
|
||||
# Return mutations
|
||||
#
|
||||
# @return [Array<Mutation>]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def mutations
|
||||
object.mutations
|
||||
end
|
||||
|
||||
# Return suject coverage
|
||||
#
|
||||
# @return [Float]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def coverage
|
||||
return 0 if amount_mutations.zero?
|
||||
Rational(amount_kills, amount_mutations) * 100
|
||||
end
|
||||
|
||||
# Detailed subject printer
|
||||
class Details < self
|
||||
|
||||
# Run subject details printer
|
||||
#
|
||||
# @return [undefined]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def run
|
||||
puts(subject.identification)
|
||||
object.failed_mutations.each do |mutation|
|
||||
visit(mutation)
|
||||
end
|
||||
print_stats
|
||||
end
|
||||
|
||||
end # Details
|
||||
end # Runner
|
||||
end # Subject
|
||||
end # Printer
|
||||
end # CLI
|
||||
end # Reporter
|
||||
end # Mutant
|
12
lib/mutant/reporter/cli/progress.rb
Normal file
12
lib/mutant/reporter/cli/progress.rb
Normal file
|
@ -0,0 +1,12 @@
|
|||
# encoding: utf-8
|
||||
|
||||
module Mutant
|
||||
class Reporter
|
||||
class CLI
|
||||
# Abstract base class for process printers
|
||||
class Progress < Printer
|
||||
include AbstractType, Registry.new
|
||||
end # Progress
|
||||
end # CLI
|
||||
end # Reporter
|
||||
end # Mutant
|
32
lib/mutant/reporter/cli/progress/config.rb
Normal file
32
lib/mutant/reporter/cli/progress/config.rb
Normal file
|
@ -0,0 +1,32 @@
|
|||
module Mutant
|
||||
class Reporter
|
||||
class CLI
|
||||
class Progress
|
||||
# Progress printer for configuration
|
||||
class Config < self
|
||||
|
||||
handle(Mutant::Config)
|
||||
|
||||
delegate :matcher, :strategy, :expected_coverage
|
||||
|
||||
# Report configuration
|
||||
#
|
||||
# @param [Mutant::Config] config
|
||||
#
|
||||
# @return [self]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def run
|
||||
info 'Mutant configuration:'
|
||||
info 'Matcher: %s', matcher.inspect
|
||||
info 'Strategy: %s', strategy.inspect
|
||||
info 'Expect Coverage: %02f%%', expected_coverage.inspect
|
||||
self
|
||||
end
|
||||
|
||||
end # Progress
|
||||
end # Printer
|
||||
end # CLI
|
||||
end # Reporter
|
||||
end # Mutant
|
|
@ -1,14 +1,12 @@
|
|||
# encoding: utf-8
|
||||
|
||||
module Mutant
|
||||
class Reporter
|
||||
class CLI
|
||||
class Printer
|
||||
class Progress
|
||||
|
||||
# Printer for killer results
|
||||
class Killer < self
|
||||
# Mutation progress reporter
|
||||
class Mutation < self
|
||||
|
||||
handle(Mutant::Killer)
|
||||
handle(Runner::Mutation)
|
||||
|
||||
SUCCESS = '.'.freeze
|
||||
FAILURE = 'F'.freeze
|
||||
|
@ -20,11 +18,7 @@ module Mutant
|
|||
# @api private
|
||||
#
|
||||
def run
|
||||
if success?
|
||||
char(SUCCESS, Color::GREEN)
|
||||
else
|
||||
char(FAILURE, Color::RED)
|
||||
end
|
||||
char(success? ? SUCCESS : FAILURE)
|
||||
end
|
||||
|
||||
private
|
||||
|
@ -32,19 +26,18 @@ module Mutant
|
|||
# Write colorized char
|
||||
#
|
||||
# @param [String] char
|
||||
# @param [Color]
|
||||
#
|
||||
# @return [undefined]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def char(char, color)
|
||||
output.write(colorize(color, char))
|
||||
def char(char)
|
||||
output.write(colorize(status_color, char))
|
||||
output.flush
|
||||
end
|
||||
|
||||
end # Killer
|
||||
end # Printer
|
||||
end # Mutation
|
||||
end # Progress
|
||||
end # CLI
|
||||
end # Reporter
|
||||
end # Mutant
|
22
lib/mutant/reporter/cli/progress/noop.rb
Normal file
22
lib/mutant/reporter/cli/progress/noop.rb
Normal file
|
@ -0,0 +1,22 @@
|
|||
module Mutant
|
||||
class Reporter
|
||||
class CLI
|
||||
class Progress
|
||||
# Noop CLI progress reporter
|
||||
class Noop < self
|
||||
|
||||
handle(Mutant::Mutation)
|
||||
|
||||
# Noop progress report
|
||||
#
|
||||
# @return [self]
|
||||
#
|
||||
def run
|
||||
self
|
||||
end
|
||||
|
||||
end # Noop
|
||||
end # Progress
|
||||
end # CLI
|
||||
end # Reporter
|
||||
end # Mutant
|
118
lib/mutant/reporter/cli/progress/subject.rb
Normal file
118
lib/mutant/reporter/cli/progress/subject.rb
Normal file
|
@ -0,0 +1,118 @@
|
|||
module Mutant
|
||||
class Reporter
|
||||
class CLI
|
||||
class Progress
|
||||
# Subject results printer
|
||||
class Subject < self
|
||||
|
||||
handle(Mutant::Subject)
|
||||
|
||||
# Run subject results printer
|
||||
#
|
||||
# @return [undefined]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def run
|
||||
puts(object.identification)
|
||||
end
|
||||
|
||||
end # Subject
|
||||
|
||||
# Reporter for subject runners
|
||||
class SubjectRunner < self
|
||||
|
||||
FORMAT = '(%02d/%02d) %3d%% - %0.02fs'.freeze
|
||||
|
||||
handle(Mutant::Runner::Subject)
|
||||
|
||||
# Run printer
|
||||
#
|
||||
# @return [undefined]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def run
|
||||
print_progress_bar_finish
|
||||
print_stats
|
||||
self
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Return mutation time on subject
|
||||
#
|
||||
# @return [Float]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def time
|
||||
mutations.map(&:runtime).inject(0, :+)
|
||||
end
|
||||
|
||||
# Print stats
|
||||
#
|
||||
# @return [undefined]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def print_stats
|
||||
status(FORMAT, amount_kills, amount_mutations, coverage, time)
|
||||
end
|
||||
|
||||
# Print progress bar finish
|
||||
#
|
||||
# @return [undefined]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def print_progress_bar_finish
|
||||
puts unless amount_mutations.zero?
|
||||
end
|
||||
|
||||
# Return kills
|
||||
#
|
||||
# @return [Fixnum]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def amount_kills
|
||||
amount_mutations - object.failed_mutations.length
|
||||
end
|
||||
|
||||
# Return amount of mutations
|
||||
#
|
||||
# @return [Array<Mutation>]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def amount_mutations
|
||||
mutations.length
|
||||
end
|
||||
|
||||
# Return mutations
|
||||
#
|
||||
# @return [Array<Mutation>]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def mutations
|
||||
object.mutations
|
||||
end
|
||||
|
||||
# Return subject coverage
|
||||
#
|
||||
# @return [Float]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def coverage
|
||||
return 0 if amount_mutations.zero?
|
||||
Rational(amount_kills, amount_mutations) * 100
|
||||
end
|
||||
|
||||
end # Runner
|
||||
end # Progress
|
||||
end # CLI
|
||||
end # Reporter
|
||||
end # Mutant
|
77
lib/mutant/reporter/cli/registry.rb
Normal file
77
lib/mutant/reporter/cli/registry.rb
Normal file
|
@ -0,0 +1,77 @@
|
|||
module Mutant
|
||||
class Reporter
|
||||
class CLI
|
||||
# Mixin to generate registry semantics
|
||||
class Registry < Module
|
||||
include Concord.new(:registry)
|
||||
|
||||
# Return new registry
|
||||
#
|
||||
# @return [Registry]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def self.new
|
||||
super({})
|
||||
end
|
||||
|
||||
# Register handler for class
|
||||
#
|
||||
# @param [Class] klass
|
||||
#
|
||||
# @return [self]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def handle(subject, handler)
|
||||
raise "Duplicate registration of #{subject}" if registry.key?(subject)
|
||||
registry[subject] = handler
|
||||
self
|
||||
end
|
||||
|
||||
# Lookup handler
|
||||
#
|
||||
# @param [Class] subject
|
||||
#
|
||||
# @return [Object]
|
||||
# if found
|
||||
#
|
||||
# @raise [RuntimeError]
|
||||
# otherwise
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def lookup(subject)
|
||||
current = subject
|
||||
until current == Object
|
||||
if registry.key?(current)
|
||||
return registry.fetch(current)
|
||||
end
|
||||
current = current.superclass
|
||||
end
|
||||
raise "No printer for: #{subject}"
|
||||
end
|
||||
|
||||
# Hook called when module is included
|
||||
#
|
||||
# @param [Class,Module] host
|
||||
#
|
||||
# @return [undefined]
|
||||
#
|
||||
def included(host)
|
||||
object = self
|
||||
host.class_eval do
|
||||
define_singleton_method(:lookup, &object.method(:lookup))
|
||||
private_class_method :lookup
|
||||
|
||||
define_singleton_method(:handle) do |subject|
|
||||
object.handle(subject, self)
|
||||
end
|
||||
private_class_method :handle
|
||||
end
|
||||
end
|
||||
|
||||
end # Registry
|
||||
end # CLI
|
||||
end # Reporter
|
||||
end # Mutant
|
12
lib/mutant/reporter/cli/report.rb
Normal file
12
lib/mutant/reporter/cli/report.rb
Normal file
|
@ -0,0 +1,12 @@
|
|||
# encoding: utf-8
|
||||
|
||||
module Mutant
|
||||
class Reporter
|
||||
class CLI
|
||||
# Abstract base class for process printers
|
||||
class Report < Printer
|
||||
include AbstractType, Registry.new
|
||||
end # Report
|
||||
end # CLI
|
||||
end # Reporter
|
||||
end # Mutant
|
118
lib/mutant/reporter/cli/report/config.rb
Normal file
118
lib/mutant/reporter/cli/report/config.rb
Normal file
|
@ -0,0 +1,118 @@
|
|||
# encoding: utf-8
|
||||
|
||||
module Mutant
|
||||
class Reporter
|
||||
class CLI
|
||||
class Report
|
||||
|
||||
# Printer for configuration
|
||||
class Config < self
|
||||
|
||||
handle(Mutant::Runner::Config)
|
||||
|
||||
delegate(
|
||||
:amount_kills, :amount_mutations, :amount_kils,
|
||||
:coverage, :subjects, :failed_subjects, :runtime, :mutations
|
||||
)
|
||||
|
||||
# Run printer
|
||||
#
|
||||
# @return [self]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def run
|
||||
failed_subjects.each(&method(:visit))
|
||||
info 'Subjects: %s', amount_subjects
|
||||
info 'Mutations: %s', amount_mutations
|
||||
info 'Kills: %s', amount_kills
|
||||
info 'Alive: %s', amount_alive
|
||||
info 'Runtime: %0.2fs', runtime
|
||||
info 'Killtime: %0.2fs', killtime
|
||||
info 'Overhead: %0.2f%%', overhead
|
||||
status 'Coverage: %0.2f%%', coverage
|
||||
status 'Expected: %0.2f%%', object.config.expected_coverage
|
||||
print_generic_stats
|
||||
self
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Print generic stats
|
||||
#
|
||||
# @return [undefined]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def print_generic_stats
|
||||
stats = generic_stats.to_a.sort_by(&:last)
|
||||
return if stats.empty?
|
||||
info('Nodes handled by generic mutator (type:occurrences):')
|
||||
stats.reverse_each do |type, amount|
|
||||
info('%-10s: %d', type, amount)
|
||||
end
|
||||
end
|
||||
|
||||
# Return stats for nodes handled by generic mutator
|
||||
#
|
||||
# @return [Hash<Symbo, Fixnum>]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def generic_stats
|
||||
subjects.each_with_object(Hash.new(0)) do |runner, stats|
|
||||
Walker.run(runner.subject.node) do |node|
|
||||
if Mutator::Registry.lookup(node) == Mutator::Node::Generic
|
||||
stats[node.type] += 1
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Return amount of subjects
|
||||
#
|
||||
# @return [Fixnum]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def amount_subjects
|
||||
subjects.length
|
||||
end
|
||||
|
||||
# Return amount of time in killers
|
||||
#
|
||||
# @return [Float]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def killtime
|
||||
mutations.map(&:runtime).inject(0, :+)
|
||||
end
|
||||
memoize :killtime
|
||||
|
||||
# Return mutant overhead
|
||||
#
|
||||
# @return [Float]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def overhead
|
||||
return 0 if runtime.zero?
|
||||
Rational(runtime - killtime, runtime) * 100
|
||||
end
|
||||
|
||||
# Return amount of alive mutations
|
||||
#
|
||||
# @return [Fixnum]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def amount_alive
|
||||
object.amount_mutations - amount_kills
|
||||
end
|
||||
|
||||
end # Config
|
||||
end # Report
|
||||
end # CLI
|
||||
end # Reporter
|
||||
end # Mutant
|
88
lib/mutant/reporter/cli/report/mutation.rb
Normal file
88
lib/mutant/reporter/cli/report/mutation.rb
Normal file
|
@ -0,0 +1,88 @@
|
|||
# encoding: utf-8
|
||||
|
||||
module Mutant
|
||||
class Reporter
|
||||
class CLI
|
||||
class Report
|
||||
|
||||
# Reporter for mutations
|
||||
class Mutation < self
|
||||
|
||||
# Run report printer
|
||||
#
|
||||
# @return [self]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def run
|
||||
puts(object.identification)
|
||||
puts(details)
|
||||
self
|
||||
end
|
||||
|
||||
# Reporter for noop mutations
|
||||
class Noop < self
|
||||
handle(Mutant::Mutation::Neutral::Noop)
|
||||
|
||||
MESSAGE = [
|
||||
'Parsed subject AST:',
|
||||
'%s',
|
||||
'Unparsed source:',
|
||||
'%s'
|
||||
].join("\n").freeze
|
||||
|
||||
private
|
||||
|
||||
# Return details
|
||||
#
|
||||
# @return [self]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def details
|
||||
MESSAGE % [object.subject.node.inspect, object.original_source]
|
||||
end
|
||||
|
||||
end # Noop
|
||||
|
||||
# Reporter for mutations producing a diff
|
||||
class Diff < self
|
||||
handle(Mutant::Mutation::Evil)
|
||||
handle(Mutant::Mutation::Neutral)
|
||||
|
||||
private
|
||||
|
||||
# Run report printer
|
||||
#
|
||||
# @return [self]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def details
|
||||
original, current = object.original_source, object.source
|
||||
diff = Mutant::Diff.build(original, current)
|
||||
color? ? diff.colorized_diff : diff.diff
|
||||
end
|
||||
|
||||
end # Diff
|
||||
end # Mutation
|
||||
|
||||
# Subject report printer
|
||||
class MutationRunner < self
|
||||
handle(Mutant::Runner::Mutation)
|
||||
|
||||
# Run report printer
|
||||
#
|
||||
# @return [self]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def run
|
||||
visit(object.mutation)
|
||||
end
|
||||
|
||||
end # Mutation
|
||||
end # Report
|
||||
end # CLI
|
||||
end # Reporter
|
||||
end # Mutant
|
33
lib/mutant/reporter/cli/report/subject.rb
Normal file
33
lib/mutant/reporter/cli/report/subject.rb
Normal file
|
@ -0,0 +1,33 @@
|
|||
# encoding: utf-8
|
||||
|
||||
module Mutant
|
||||
class Reporter
|
||||
class CLI
|
||||
class Report
|
||||
|
||||
# Subject report printer
|
||||
class Subject < self
|
||||
handle(Mutant::Runner::Subject)
|
||||
|
||||
delegate :subject, :failed_mutations
|
||||
|
||||
# Run report printer
|
||||
#
|
||||
# @return [self]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def run
|
||||
status(subject.identification)
|
||||
object.tests.each do |test|
|
||||
puts("- #{test.identification}")
|
||||
end
|
||||
object.failed_mutations.each(&method(:visit))
|
||||
self
|
||||
end
|
||||
|
||||
end # Subject
|
||||
end # Report
|
||||
end # CLI
|
||||
end # Reporter
|
||||
end # Mutant
|
|
@ -5,6 +5,7 @@ module Mutant
|
|||
|
||||
# Null reporter
|
||||
class Null < self
|
||||
include Equalizer.new
|
||||
|
||||
# Report object
|
||||
#
|
||||
|
@ -18,6 +19,18 @@ module Mutant
|
|||
self
|
||||
end
|
||||
|
||||
# Report progress on object
|
||||
#
|
||||
# @param [Object] _object
|
||||
#
|
||||
# @return [self]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def progress(_object)
|
||||
self
|
||||
end
|
||||
|
||||
end # Null
|
||||
end # Reporter
|
||||
end # Mutant
|
||||
|
|
41
lib/mutant/reporter/trace.rb
Normal file
41
lib/mutant/reporter/trace.rb
Normal file
|
@ -0,0 +1,41 @@
|
|||
module Mutant
|
||||
class Reporter
|
||||
# Reporter to trace report calls, used as a spec adapter
|
||||
class Trace
|
||||
include Concord::Public.new(:progress_calls, :report_calls)
|
||||
|
||||
# Return new trace reporter
|
||||
#
|
||||
# @return [Tracer]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def self.new
|
||||
super([], [])
|
||||
end
|
||||
|
||||
# Report object
|
||||
#
|
||||
# @param [Object] object
|
||||
#
|
||||
# @return [self]
|
||||
#
|
||||
def report(object)
|
||||
report_calls << object
|
||||
self
|
||||
end
|
||||
|
||||
# Report new progress on object
|
||||
#
|
||||
# @param [Object] object
|
||||
#
|
||||
# @return [self]
|
||||
#
|
||||
def progress(object)
|
||||
progress_calls << object
|
||||
self
|
||||
end
|
||||
|
||||
end # Tracker
|
||||
end # reporter
|
||||
end # Mutant
|
|
@ -1,7 +1,5 @@
|
|||
# encoding: UTF-8
|
||||
|
||||
require 'rspec'
|
||||
|
||||
module Mutant
|
||||
# Rspec integration namespace
|
||||
module Rspec
|
||||
|
|
|
@ -1,104 +0,0 @@
|
|||
# encoding: utf-8
|
||||
|
||||
module Mutant
|
||||
module Rspec
|
||||
# Runner for rspec tests
|
||||
class Killer < Mutant::Killer
|
||||
|
||||
private
|
||||
|
||||
# Run rspec test
|
||||
#
|
||||
# @return [true]
|
||||
# when test is NOT successful
|
||||
#
|
||||
# @return [false]
|
||||
# otherwise
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def run
|
||||
mutation.insert
|
||||
|
||||
if example_groups.nil? || example_groups.empty?
|
||||
$stderr.puts("No rspec example groups found for: #{match_prefixes.join(', ')}")
|
||||
return false
|
||||
end
|
||||
|
||||
example_groups.each do |group|
|
||||
return true unless group.run(reporter)
|
||||
end
|
||||
|
||||
false
|
||||
end
|
||||
|
||||
# Return match prefixes
|
||||
#
|
||||
# @return [Enumerble<String>]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def match_prefixes
|
||||
subject.match_prefixes
|
||||
end
|
||||
|
||||
# Return example groups
|
||||
#
|
||||
# @return [Array<RSpec::Example>]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def example_groups
|
||||
match_prefixes.each do |match_expression|
|
||||
example_groups = find_with(match_expression)
|
||||
return example_groups unless example_groups.empty?
|
||||
end
|
||||
|
||||
nil
|
||||
end
|
||||
memoize :example_groups
|
||||
|
||||
# Return example groups that match expression
|
||||
#
|
||||
# @param [String] match_expression
|
||||
#
|
||||
# @return [Enumerable<String>]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def find_with(match_expression)
|
||||
all_example_groups.select do |example_group|
|
||||
example_group.description.start_with?(match_expression)
|
||||
end
|
||||
end
|
||||
|
||||
# Return all example groups
|
||||
#
|
||||
# @return [Enumerable<RSpec::Example>]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def all_example_groups
|
||||
strategy.example_groups
|
||||
end
|
||||
|
||||
# Choose and memoize RSpec reporter
|
||||
#
|
||||
# @return [RSpec::Core::Reporter]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def reporter
|
||||
reporter_class = RSpec::Core::Reporter
|
||||
|
||||
if strategy.rspec2?
|
||||
reporter_class.new
|
||||
else
|
||||
reporter_class.new(strategy.configuration)
|
||||
end
|
||||
end
|
||||
memoize :reporter, freezer: :noop
|
||||
|
||||
end # Killer
|
||||
end # Rspec
|
||||
end # Mutant
|
|
@ -2,13 +2,12 @@
|
|||
|
||||
module Mutant
|
||||
module Rspec
|
||||
|
||||
# Rspec killer strategy
|
||||
class Strategy < Mutant::Strategy
|
||||
|
||||
register 'rspec'
|
||||
|
||||
KILLER = Killer::Forking.new(Rspec::Killer)
|
||||
|
||||
# Setup rspec strategy
|
||||
#
|
||||
# @return [self]
|
||||
|
@ -25,6 +24,34 @@ module Mutant
|
|||
end
|
||||
memoize :setup
|
||||
|
||||
# Return new reporter
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def reporter
|
||||
reporter_class = RSpec::Core::Reporter
|
||||
|
||||
if rspec2?
|
||||
reporter_class.new
|
||||
else
|
||||
reporter_class.new(configuration)
|
||||
end
|
||||
end
|
||||
|
||||
# Detect RSpec 2
|
||||
#
|
||||
# @return [true]
|
||||
# when RSpec 2
|
||||
#
|
||||
# @return [false]
|
||||
# otherwise
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def rspec2?
|
||||
RSpec::Core::Version::STRING.start_with?('2.')
|
||||
end
|
||||
|
||||
# Return configuration
|
||||
#
|
||||
# @return [RSpec::Core::Configuration]
|
||||
|
@ -46,20 +73,6 @@ module Mutant
|
|||
world.example_groups
|
||||
end
|
||||
|
||||
# Detect RSpec 2
|
||||
#
|
||||
# @return [true]
|
||||
# when RSpec 2
|
||||
#
|
||||
# @return [false]
|
||||
# otherwise
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def rspec2?
|
||||
RSpec::Core::Version::STRING.start_with?('2.')
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Return world
|
||||
|
@ -73,6 +86,18 @@ module Mutant
|
|||
end
|
||||
memoize :world, freezer: :noop
|
||||
|
||||
# Return all available tests
|
||||
#
|
||||
# @return [Enumerable<Test>]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def all_tests
|
||||
example_groups.map do |example_group|
|
||||
Test.new(self, example_group)
|
||||
end
|
||||
end
|
||||
|
||||
# Return options
|
||||
#
|
||||
# @return [RSpec::Core::ConfigurationOptions]
|
||||
|
|
37
lib/mutant/rspec/test.rb
Normal file
37
lib/mutant/rspec/test.rb
Normal file
|
@ -0,0 +1,37 @@
|
|||
module Mutant
|
||||
module Rspec
|
||||
# Rspec test abstraction
|
||||
class Test < Mutant::Test
|
||||
include Concord.new(:strategy, :example_group)
|
||||
|
||||
PREFIX = :rspec
|
||||
|
||||
# Return subject identification
|
||||
#
|
||||
# @return [String]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def subject_identification
|
||||
example_group.description
|
||||
end
|
||||
memoize :subject_identification
|
||||
|
||||
# Run test, return report
|
||||
#
|
||||
# @return [String]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def run
|
||||
flag = example_group.run(strategy.reporter)
|
||||
Report.new(
|
||||
test: self,
|
||||
output: '',
|
||||
success: flag
|
||||
)
|
||||
end
|
||||
|
||||
end # Test
|
||||
end # Rspec
|
||||
end # Mutant
|
|
@ -48,9 +48,9 @@ module Mutant
|
|||
#
|
||||
# @api private
|
||||
#
|
||||
def self.run(config, object)
|
||||
def self.run(config, object, *arguments)
|
||||
handler = lookup(object.class)
|
||||
handler.new(config, object)
|
||||
handler.new(config, object, *arguments)
|
||||
end
|
||||
|
||||
# Return config
|
||||
|
@ -101,16 +101,6 @@ module Mutant
|
|||
(@end || Time.now) - @start
|
||||
end
|
||||
|
||||
# Return reporter
|
||||
#
|
||||
# @return [Reporter]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def reporter
|
||||
config.reporter
|
||||
end
|
||||
|
||||
# Test if runner is successful
|
||||
#
|
||||
# @return [true]
|
||||
|
@ -133,7 +123,7 @@ module Mutant
|
|||
#
|
||||
abstract_method :run
|
||||
|
||||
# Return reporter
|
||||
# Run reporter on object
|
||||
#
|
||||
# @param [Object] object
|
||||
#
|
||||
|
@ -141,20 +131,32 @@ module Mutant
|
|||
#
|
||||
# @api private
|
||||
#
|
||||
def report(object)
|
||||
reporter.report(object)
|
||||
def progress(object)
|
||||
reporter.progress(object)
|
||||
end
|
||||
|
||||
# Perform dispatch
|
||||
# Return reporter
|
||||
#
|
||||
# @return [Reporter]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def reporter
|
||||
config.reporter
|
||||
end
|
||||
|
||||
# Perform dispatch on multiple inputs
|
||||
#
|
||||
# @param [Enumerable<Object>] input
|
||||
#
|
||||
# @return [Enumerable<Runner>]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def dispatch(input)
|
||||
def visit_collection(input, *arguments)
|
||||
collection = []
|
||||
input.each do |object|
|
||||
runner = visit(object)
|
||||
runner = visit(object, *arguments)
|
||||
collection << runner
|
||||
@stop = runner.stop?
|
||||
break if @stop
|
||||
|
@ -170,8 +172,8 @@ module Mutant
|
|||
#
|
||||
# @api private
|
||||
#
|
||||
def visit(object)
|
||||
Runner.run(config, object)
|
||||
def visit(object, *arguments)
|
||||
Runner.run(config, object, *arguments)
|
||||
end
|
||||
|
||||
end # Runner
|
||||
|
|
|
@ -5,6 +5,9 @@ module Mutant
|
|||
# Runner for object config
|
||||
class Config < self
|
||||
|
||||
# The expected coverage precision
|
||||
COVERAGE_PRECISION = 1
|
||||
|
||||
register Mutant::Config
|
||||
|
||||
# Run runner for object
|
||||
|
@ -40,8 +43,6 @@ module Mutant
|
|||
end
|
||||
memoize :failed_subjects
|
||||
|
||||
COVERAGE_PRECISION = 1
|
||||
|
||||
# Test if run was successful
|
||||
#
|
||||
# @return [true]
|
||||
|
@ -122,7 +123,7 @@ module Mutant
|
|||
def run_subjects
|
||||
strategy = self.strategy
|
||||
strategy.setup
|
||||
@subjects = dispatch(config.subjects)
|
||||
@subjects = visit_collection(config.subjects)
|
||||
strategy.teardown
|
||||
end
|
||||
|
||||
|
@ -133,10 +134,10 @@ module Mutant
|
|||
# @api private
|
||||
#
|
||||
def run
|
||||
report(config)
|
||||
progress(config)
|
||||
run_subjects
|
||||
@end = Time.now
|
||||
report(self)
|
||||
reporter.report(self)
|
||||
end
|
||||
|
||||
end # Config
|
||||
|
|
51
lib/mutant/runner/killer.rb
Normal file
51
lib/mutant/runner/killer.rb
Normal file
|
@ -0,0 +1,51 @@
|
|||
module Mutant
|
||||
class Runner
|
||||
# Killer runner
|
||||
class Killer < self
|
||||
include Equalizer.new(:config, :killer)
|
||||
|
||||
register Mutant::Killer
|
||||
|
||||
# Return killer
|
||||
#
|
||||
# @return [Killer]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
attr_reader :killer
|
||||
protected :killer
|
||||
|
||||
# Test if killer ran successfully
|
||||
#
|
||||
# @return [Boolean]
|
||||
#
|
||||
# @api private
|
||||
def success?
|
||||
@report.success?
|
||||
end
|
||||
|
||||
# Initialize object
|
||||
#
|
||||
# @param [Config] config
|
||||
# @param [Mutation] mutation
|
||||
#
|
||||
# @return [undefined]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def initialize(config, killer)
|
||||
@killer = killer
|
||||
super(config)
|
||||
end
|
||||
|
||||
# Run killer
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def run
|
||||
@report = killer.run
|
||||
end
|
||||
|
||||
end # Killer
|
||||
end # Runner
|
||||
end # Mutant
|
|
@ -4,7 +4,7 @@ module Mutant
|
|||
class Runner
|
||||
# Mutation runner
|
||||
class Mutation < self
|
||||
include Equalizer.new(:config, :mutation)
|
||||
include Equalizer.new(:config, :mutation, :tests)
|
||||
|
||||
register Mutant::Mutation
|
||||
|
||||
|
@ -16,26 +16,28 @@ module Mutant
|
|||
#
|
||||
attr_reader :mutation
|
||||
|
||||
# Return killer instance
|
||||
# Return killers
|
||||
#
|
||||
# @return [Killer]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
attr_reader :killer
|
||||
attr_reader :killers
|
||||
|
||||
# Initialize object
|
||||
#
|
||||
# @param [Config] config
|
||||
# @param [Mutation] mutation
|
||||
# @param [Enumerable<Test>] tests
|
||||
#
|
||||
# @return [undefined]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def initialize(config, mutation)
|
||||
@mutation = mutation
|
||||
def initialize(config, mutation, tests)
|
||||
@mutation, @tests = mutation, tests
|
||||
super(config)
|
||||
@stop = config.fail_fast && !success?
|
||||
end
|
||||
|
||||
# Test if mutation was handeled successfully
|
||||
|
@ -49,7 +51,7 @@ module Mutant
|
|||
# @api private
|
||||
#
|
||||
def success?
|
||||
mutation.success?(killer)
|
||||
killers.any?(&:success?)
|
||||
end
|
||||
|
||||
private
|
||||
|
@ -61,9 +63,14 @@ module Mutant
|
|||
# @api private
|
||||
#
|
||||
def run
|
||||
@killer = config.strategy.kill(mutation)
|
||||
report(killer)
|
||||
@stop = config.fail_fast && !killer.success?
|
||||
progress(mutation)
|
||||
@killers = @tests.map do |test|
|
||||
Mutant::Killer.new(
|
||||
mutation: mutation,
|
||||
test: test
|
||||
)
|
||||
end.map(&method(:visit))
|
||||
progress(self)
|
||||
end
|
||||
|
||||
end # Mutation
|
||||
|
|
|
@ -63,6 +63,17 @@ module Mutant
|
|||
failed_mutations.empty?
|
||||
end
|
||||
|
||||
# Return tests used to kill mutations on this subject
|
||||
#
|
||||
# @return [Enumerable<Test>]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def tests
|
||||
config.strategy.tests(subject)
|
||||
end
|
||||
memoize :tests
|
||||
|
||||
private
|
||||
|
||||
# Perform operation
|
||||
|
@ -72,10 +83,9 @@ module Mutant
|
|||
# @api private
|
||||
#
|
||||
def run
|
||||
subject = self.subject
|
||||
report(subject)
|
||||
@mutations = dispatch(subject.mutations)
|
||||
report(self)
|
||||
progress(subject)
|
||||
@mutations = visit_collection(subject.mutations, tests)
|
||||
progress(self)
|
||||
end
|
||||
|
||||
end # Subject
|
||||
|
|
|
@ -54,39 +54,53 @@ module Mutant
|
|||
self
|
||||
end
|
||||
|
||||
# Kill mutation
|
||||
# Return all available tests by strategy
|
||||
#
|
||||
# @param [Mutation] mutation
|
||||
#
|
||||
# @return [Killer]
|
||||
# @return [Enumerable<Test>]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def kill(mutation)
|
||||
killer.new(self, mutation)
|
||||
abstract_method :all_tests
|
||||
|
||||
# Return tests for mutation
|
||||
#
|
||||
# TODO: This logic is now centralized but still fucked.
|
||||
#
|
||||
# @param [Mutation] mutation
|
||||
#
|
||||
# @return [Enumerable<Test>]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def tests(subject)
|
||||
subject.match_prefixes.map do |match_expression|
|
||||
tests = all_tests.select do |test|
|
||||
test.subject_identification.start_with?(match_expression)
|
||||
end
|
||||
return tests if tests.any?
|
||||
end
|
||||
|
||||
[]
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Return killer
|
||||
#
|
||||
# @return [Class:Killer]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def killer
|
||||
self.class::KILLER
|
||||
end
|
||||
|
||||
# Null strategy that never kills a mutation
|
||||
class Null < self
|
||||
|
||||
register 'null'
|
||||
register('null')
|
||||
|
||||
KILLER = Killer::Null
|
||||
# Return all tests
|
||||
#
|
||||
# @return [Enumerable<Test>]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def all_tests
|
||||
EMPTY_ARRAY
|
||||
end
|
||||
|
||||
end # Null
|
||||
|
||||
end # Strategy
|
||||
|
||||
end # Mutant
|
||||
|
|
|
@ -68,7 +68,7 @@ module Mutant
|
|||
#
|
||||
def prepare
|
||||
scope.send(:memoized_methods).instance_variable_get(:@memory).delete(name)
|
||||
scope.send(:undef_method, name)
|
||||
super
|
||||
self
|
||||
end
|
||||
|
||||
|
|
86
lib/mutant/test.rb
Normal file
86
lib/mutant/test.rb
Normal file
|
@ -0,0 +1,86 @@
|
|||
module Mutant
|
||||
# Abstract base class for test that might kill a mutation
|
||||
class Test
|
||||
include AbstractType, Adamantium::Flat
|
||||
|
||||
# Object to report test status
|
||||
class Report
|
||||
include Adamantium::Flat, Anima::Update, Anima.new(
|
||||
:test,
|
||||
:output,
|
||||
:success
|
||||
)
|
||||
|
||||
alias_method :success?, :success
|
||||
|
||||
# Test if test failed
|
||||
#
|
||||
# @return [Boolean]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def failed?
|
||||
!success?
|
||||
end
|
||||
|
||||
# Return marshallable data
|
||||
#
|
||||
# NOTE:
|
||||
#
|
||||
# The test is intentionally NOT part of the mashalled data.
|
||||
# In rspec the example group cannot deterministically being marshalled, because
|
||||
# they reference a crazy mix of IO objects, global objects etc.
|
||||
#
|
||||
# @return [Array]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def marshal_dump
|
||||
[@output, @success]
|
||||
end
|
||||
|
||||
# Load marshalled data
|
||||
#
|
||||
# @param [Array] arry
|
||||
#
|
||||
# @return [undefined]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def marshal_load(array)
|
||||
@output, @success = array
|
||||
end
|
||||
|
||||
end # Report
|
||||
|
||||
# Run tests
|
||||
#
|
||||
# @return [Test::Result]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
abstract_method :run
|
||||
|
||||
# Return test identification
|
||||
#
|
||||
# @return [String]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def identification
|
||||
"#{self.class::PREFIX}:#{subject_identification}"
|
||||
end
|
||||
memoize :identification
|
||||
|
||||
# Return subject identification
|
||||
#
|
||||
# This method is used for current mutants primitive test selection.
|
||||
#
|
||||
# @return [String]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
abstract_method :subject_identification
|
||||
|
||||
end # Test
|
||||
end # Mutant
|
18
spec/integration/mutant/null_spec.rb
Normal file
18
spec/integration/mutant/null_spec.rb
Normal file
|
@ -0,0 +1,18 @@
|
|||
# encoding: utf-8
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
describe 'null integration' do
|
||||
|
||||
let(:base_cmd) { 'bundle exec mutant -I lib --require test_app "::TestApp*"' }
|
||||
|
||||
around do |example|
|
||||
Dir.chdir(TestApp.root) do
|
||||
example.run
|
||||
end
|
||||
end
|
||||
|
||||
specify 'it allows to kill mutations' do
|
||||
expect(Kernel.system(base_cmd)).to be(false)
|
||||
end
|
||||
end
|
|
@ -10,7 +10,7 @@ describe 'rspec integration' do
|
|||
around do |example|
|
||||
Bundler.with_clean_env do
|
||||
Dir.chdir(TestApp.root) do
|
||||
Kernel.system("bundle install --gemfile=#{gemfile}")
|
||||
Kernel.system("bundle install --gemfile=#{gemfile}") || fail('Bundle install failed!')
|
||||
ENV['BUNDLE_GEMFILE'] = gemfile
|
||||
example.run
|
||||
end
|
||||
|
|
|
@ -2,11 +2,9 @@
|
|||
|
||||
if ENV['COVERAGE'] == 'true'
|
||||
require 'simplecov'
|
||||
require 'coveralls'
|
||||
|
||||
SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter[
|
||||
SimpleCov::Formatter::HTMLFormatter,
|
||||
Coveralls::SimpleCov::Formatter
|
||||
]
|
||||
|
||||
SimpleCov.start do
|
||||
|
|
162
spec/unit/mutant/diff_spec.rb
Normal file
162
spec/unit/mutant/diff_spec.rb
Normal file
|
@ -0,0 +1,162 @@
|
|||
# encoding: utf-8
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
describe Mutant::Diff do
|
||||
let(:object) { described_class }
|
||||
|
||||
describe '.build' do
|
||||
|
||||
subject { object.build(old_string, new_string) }
|
||||
|
||||
let(:old_string) { "foo\nbar" }
|
||||
let(:new_string) { "bar\nbaz" }
|
||||
|
||||
it { should eql(Mutant::Diff.new(%w(foo bar), %w(bar baz))) }
|
||||
|
||||
end
|
||||
|
||||
describe '.colorize_line' do
|
||||
let(:object) { described_class }
|
||||
|
||||
subject { object.colorize_line(line) }
|
||||
|
||||
context 'line beginning with "+"' do
|
||||
let(:line) { '+line' }
|
||||
|
||||
it { should eql(Mutant::Color::GREEN.format(line)) }
|
||||
end
|
||||
|
||||
context 'line beginning with "-"' do
|
||||
let(:line) { '-line' }
|
||||
|
||||
it { should eql(Mutant::Color::RED.format(line)) }
|
||||
end
|
||||
|
||||
context 'line beginning in other char' do
|
||||
let(:line) { ' line' }
|
||||
|
||||
it { should eql(line) }
|
||||
end
|
||||
end
|
||||
|
||||
describe '#diff' do
|
||||
let(:object) { described_class.new(old, new) }
|
||||
|
||||
subject { object.diff }
|
||||
|
||||
context 'when there is a diff at begin of hunk' do
|
||||
let(:old) { %w(foo bar) }
|
||||
let(:new) { %w(baz bar) }
|
||||
|
||||
let(:expectation) do
|
||||
strip_indent(<<-STR)
|
||||
@@ -1,3 +1,3 @@
|
||||
-foo
|
||||
+baz
|
||||
bar
|
||||
STR
|
||||
end
|
||||
|
||||
it { should eql(expectation) }
|
||||
|
||||
it_should_behave_like 'an idempotent method'
|
||||
end
|
||||
|
||||
context 'when there is a diff NOT at begin of hunk' do
|
||||
let(:old) { %w(foo bar) }
|
||||
let(:new) { %w(foo baz bar) }
|
||||
|
||||
let(:expectation) do
|
||||
strip_indent(<<-STR)
|
||||
@@ -1,3 +1,4 @@
|
||||
foo
|
||||
+baz
|
||||
bar
|
||||
STR
|
||||
end
|
||||
|
||||
it { should eql(expectation) }
|
||||
|
||||
it_should_behave_like 'an idempotent method'
|
||||
end
|
||||
|
||||
context 'when the diff has a long context at begin' do
|
||||
let(:old) { %w(foo bar baz boz a b c) }
|
||||
let(:new) { %w(foo bar baz boz a b c other) }
|
||||
|
||||
let(:expectation) do
|
||||
strip_indent(<<-STR)
|
||||
@@ -1,8 +1,9 @@
|
||||
foo
|
||||
bar
|
||||
baz
|
||||
boz
|
||||
a
|
||||
b
|
||||
c
|
||||
+other
|
||||
STR
|
||||
end
|
||||
|
||||
it { should eql(expectation) }
|
||||
|
||||
it_should_behave_like 'an idempotent method'
|
||||
end
|
||||
|
||||
context 'when the diff has a long context at end, deleting' do
|
||||
let(:old) { %w(other foo bar baz boz a b c) }
|
||||
let(:new) { %w(foo bar baz boz a b c) }
|
||||
|
||||
let(:expectation) do
|
||||
strip_indent(<<-STR)
|
||||
@@ -1,9 +1,8 @@
|
||||
-other
|
||||
foo
|
||||
bar
|
||||
baz
|
||||
boz
|
||||
a
|
||||
b
|
||||
c
|
||||
STR
|
||||
end
|
||||
|
||||
it { should eql(expectation) }
|
||||
|
||||
it_should_behave_like 'an idempotent method'
|
||||
end
|
||||
|
||||
context 'when the diff has a long context at end, inserting' do
|
||||
let(:old) { %w(foo bar baz boz a b c) }
|
||||
let(:new) { %w(other foo bar baz boz a b c) }
|
||||
|
||||
let(:expectation) do
|
||||
strip_indent(<<-STR)
|
||||
@@ -1,8 +1,9 @@
|
||||
+other
|
||||
foo
|
||||
bar
|
||||
baz
|
||||
boz
|
||||
a
|
||||
b
|
||||
c
|
||||
STR
|
||||
end
|
||||
|
||||
it { should eql(expectation) }
|
||||
|
||||
it_should_behave_like 'an idempotent method'
|
||||
end
|
||||
|
||||
context 'when there is no diff' do
|
||||
let(:old) { '' }
|
||||
let(:new) { '' }
|
||||
|
||||
it { should be(nil) }
|
||||
|
||||
it_should_behave_like 'an idempotent method'
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,123 +0,0 @@
|
|||
# encoding: utf-8
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
describe Mutant::Differ, '#diff' do
|
||||
let(:object) { described_class.new(old, new) }
|
||||
|
||||
subject { object.diff }
|
||||
|
||||
context 'when there is a diff at begin of hunk' do
|
||||
let(:old) { %w(foo bar) }
|
||||
let(:new) { %w(baz bar) }
|
||||
|
||||
let(:expectation) do
|
||||
strip_indent(<<-STR)
|
||||
@@ -1,3 +1,3 @@
|
||||
-foo
|
||||
+baz
|
||||
bar
|
||||
STR
|
||||
end
|
||||
|
||||
it { should eql(expectation) }
|
||||
|
||||
it_should_behave_like 'an idempotent method'
|
||||
end
|
||||
|
||||
context 'when there is a diff NOT at begin of hunk' do
|
||||
let(:old) { %w(foo bar) }
|
||||
let(:new) { %w(foo baz bar) }
|
||||
|
||||
let(:expectation) do
|
||||
strip_indent(<<-STR)
|
||||
@@ -1,3 +1,4 @@
|
||||
foo
|
||||
+baz
|
||||
bar
|
||||
STR
|
||||
end
|
||||
|
||||
it { should eql(expectation) }
|
||||
|
||||
it_should_behave_like 'an idempotent method'
|
||||
end
|
||||
|
||||
context 'when the diff has a long context at begin' do
|
||||
let(:old) { %w(foo bar baz boz a b c) }
|
||||
let(:new) { %w(foo bar baz boz a b c other) }
|
||||
|
||||
let(:expectation) do
|
||||
strip_indent(<<-STR)
|
||||
@@ -1,8 +1,9 @@
|
||||
foo
|
||||
bar
|
||||
baz
|
||||
boz
|
||||
a
|
||||
b
|
||||
c
|
||||
+other
|
||||
STR
|
||||
end
|
||||
|
||||
it { should eql(expectation) }
|
||||
|
||||
it_should_behave_like 'an idempotent method'
|
||||
end
|
||||
|
||||
context 'when the diff has a long context at end, deleting' do
|
||||
let(:old) { %w(other foo bar baz boz a b c) }
|
||||
let(:new) { %w(foo bar baz boz a b c) }
|
||||
|
||||
let(:expectation) do
|
||||
strip_indent(<<-STR)
|
||||
@@ -1,9 +1,8 @@
|
||||
-other
|
||||
foo
|
||||
bar
|
||||
baz
|
||||
boz
|
||||
a
|
||||
b
|
||||
c
|
||||
STR
|
||||
end
|
||||
|
||||
it { should eql(expectation) }
|
||||
|
||||
it_should_behave_like 'an idempotent method'
|
||||
end
|
||||
|
||||
context 'when the diff has a long context at end, inserting' do
|
||||
let(:old) { %w(foo bar baz boz a b c) }
|
||||
let(:new) { %w(other foo bar baz boz a b c) }
|
||||
|
||||
let(:expectation) do
|
||||
strip_indent(<<-STR)
|
||||
@@ -1,8 +1,9 @@
|
||||
+other
|
||||
foo
|
||||
bar
|
||||
baz
|
||||
boz
|
||||
a
|
||||
b
|
||||
c
|
||||
STR
|
||||
end
|
||||
|
||||
it { should eql(expectation) }
|
||||
|
||||
it_should_behave_like 'an idempotent method'
|
||||
end
|
||||
|
||||
context 'when there is no diff' do
|
||||
let(:old) { '' }
|
||||
let(:new) { '' }
|
||||
|
||||
it { should be(nil) }
|
||||
|
||||
it_should_behave_like 'an idempotent method'
|
||||
end
|
||||
end
|
|
@ -1,42 +0,0 @@
|
|||
# encoding: utf-8
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
describe Mutant::Differ do
|
||||
let(:object) { described_class }
|
||||
|
||||
describe '.build' do
|
||||
|
||||
subject { object.build(old_string, new_string) }
|
||||
|
||||
let(:old_string) { "foo\nbar" }
|
||||
let(:new_string) { "bar\nbaz" }
|
||||
|
||||
it { should eql(Mutant::Differ.new(%w(foo bar), %w(bar baz))) }
|
||||
|
||||
end
|
||||
|
||||
describe '.colorize_line' do
|
||||
let(:object) { described_class }
|
||||
|
||||
subject { object.colorize_line(line) }
|
||||
|
||||
context 'line beginning with "+"' do
|
||||
let(:line) { '+line' }
|
||||
|
||||
it { should eql(Mutant::Color::GREEN.format(line)) }
|
||||
end
|
||||
|
||||
context 'line beginning with "-"' do
|
||||
let(:line) { '-line' }
|
||||
|
||||
it { should eql(Mutant::Color::RED.format(line)) }
|
||||
end
|
||||
|
||||
context 'line beginning in other char' do
|
||||
let(:line) { ' line' }
|
||||
|
||||
it { should eql(line) }
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,30 +0,0 @@
|
|||
# encoding: utf-8
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
describe Mutant::Killer, '#success?' do
|
||||
subject { object.success? }
|
||||
|
||||
let(:object) { class_under_test.new(strategy, mutation) }
|
||||
let(:strategy) { double('Strategy') }
|
||||
let(:mutation) { double('Mutation', success?: kill_state) }
|
||||
let(:kill_state) { double('Kill State') }
|
||||
|
||||
before do
|
||||
kill_state.stub(freeze: kill_state, dup: kill_state)
|
||||
end
|
||||
|
||||
let(:class_under_test) do
|
||||
Class.new(described_class) do
|
||||
def run
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it_should_behave_like 'an idempotent method'
|
||||
|
||||
it 'should use kill state to gather success' do
|
||||
mutation.should_receive(:success?).with(object).and_return(kill_state)
|
||||
should be(kill_state)
|
||||
end
|
||||
end
|
|
@ -4,10 +4,13 @@ require 'spec_helper'
|
|||
|
||||
describe Mutant::Mutation do
|
||||
|
||||
let(:class_under_test) { Class.new(described_class) { memoize :identification } }
|
||||
let(:object) { class_under_test.new(mutation_subject, Mutant::NodeHelpers::N_NIL) }
|
||||
let(:mutation_subject) { double('Subject', identification: 'subject', source: 'original') }
|
||||
let(:node) { double('Node') }
|
||||
class TestMutation < Mutant::Mutation
|
||||
SYMBOL = 'test'
|
||||
end
|
||||
|
||||
let(:object) { TestMutation.new(mutation_subject, Mutant::NodeHelpers::N_NIL) }
|
||||
let(:mutation_subject) { double('Subject', identification: 'subject', source: 'original') }
|
||||
let(:node) { double('Node') }
|
||||
|
||||
describe '#code' do
|
||||
subject { object.code }
|
||||
|
@ -37,7 +40,7 @@ describe Mutant::Mutation do
|
|||
|
||||
subject { object.identification }
|
||||
|
||||
it { should eql('subject:8771a') }
|
||||
it { should eql('test:subject:8771a') }
|
||||
|
||||
it_should_behave_like 'an idempotent method'
|
||||
end
|
||||
|
|
11
spec/unit/mutant/reporter/null_spec.rb
Normal file
11
spec/unit/mutant/reporter/null_spec.rb
Normal file
|
@ -0,0 +1,11 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe Mutant::Reporter::Null do
|
||||
let(:object) { described_class.new }
|
||||
|
||||
describe '#report' do
|
||||
subject { object.report(double('some input')) }
|
||||
|
||||
it_should_behave_like 'a command method'
|
||||
end
|
||||
end
|
|
@ -1,57 +0,0 @@
|
|||
# encoding: utf-8
|
||||
|
||||
require 'spec_helper'
|
||||
require 'mutant-rspec'
|
||||
|
||||
describe Mutant::Rspec::Killer, '.new' do
|
||||
|
||||
before do
|
||||
pending 'dactivated'
|
||||
end
|
||||
|
||||
subject { object.new(strategy, mutation) }
|
||||
|
||||
let(:context) { double('Context') }
|
||||
let(:mutation_subject) { double('Mutation Subject') }
|
||||
|
||||
let(:object) { described_class }
|
||||
|
||||
let(:mutation) do
|
||||
double(
|
||||
'Mutation',
|
||||
subject: mutation_subject,
|
||||
should_survive?: false
|
||||
)
|
||||
end
|
||||
|
||||
let(:strategy) do
|
||||
double(
|
||||
'Strategy',
|
||||
spec_files: ['foo'],
|
||||
error_stream: $stderr,
|
||||
output_stream: $stdout
|
||||
)
|
||||
end
|
||||
|
||||
before do
|
||||
mutation.stub(:insert)
|
||||
mutation.stub(:reset)
|
||||
RSpec::Core::Runner.stub(run: exit_status)
|
||||
end
|
||||
|
||||
context 'when run exits zero' do
|
||||
let(:exit_status) { 0 }
|
||||
|
||||
it { expect(subject.killed?).to be(false) }
|
||||
|
||||
it { should be_a(described_class) }
|
||||
end
|
||||
|
||||
context 'when run exits nonzero' do
|
||||
let(:exit_status) { 1 }
|
||||
|
||||
it { expect(subject.killed?).to be(true) }
|
||||
|
||||
it { should be_a(described_class) }
|
||||
end
|
||||
end
|
|
@ -18,15 +18,14 @@ describe Mutant::Runner::Config do
|
|||
)
|
||||
end
|
||||
|
||||
let(:fail_fast) { false }
|
||||
let(:expected_coverage) { 100.0 }
|
||||
let(:reporter) { double('Reporter') }
|
||||
let(:strategy) { double('Strategy') }
|
||||
let(:subject_a) { double('Subject A') }
|
||||
let(:subject_b) { double('Subject B') }
|
||||
let(:fail_fast) { false }
|
||||
let(:expected_coverage) { 100.0 }
|
||||
let(:reporter) { Mutant::Reporter::Trace.new }
|
||||
let(:strategy) { double('Strategy') }
|
||||
let(:subject_a) { double('Subject A') }
|
||||
let(:subject_b) { double('Subject B') }
|
||||
|
||||
before do
|
||||
reporter.stub(report: reporter)
|
||||
strategy.stub(:setup)
|
||||
strategy.stub(:teardown)
|
||||
Mutant::Runner.stub(:run).with(config, subject_a).and_return(runner_a)
|
||||
|
|
|
@ -1,44 +0,0 @@
|
|||
# encoding: utf-8
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
describe Mutant::Runner::Mutation, '#killer' do
|
||||
let(:object) { described_class.run(config, mutation) }
|
||||
|
||||
let(:config) do
|
||||
double(
|
||||
'Config',
|
||||
fail_fast: fail_fast,
|
||||
reporter: reporter,
|
||||
strategy: strategy
|
||||
)
|
||||
end
|
||||
|
||||
let(:reporter) { double('Reporter') }
|
||||
let(:mutation) { double('Mutation', class: Mutant::Mutation) }
|
||||
let(:strategy) { double('Strategy') }
|
||||
let(:killer) { double('Killer', success?: success) }
|
||||
let(:fail_fast) { false }
|
||||
let(:success) { false }
|
||||
|
||||
subject { object.killer }
|
||||
|
||||
before do
|
||||
reporter.stub(report: reporter)
|
||||
strategy.stub(kill: killer)
|
||||
end
|
||||
|
||||
it 'should call configuration to identify strategy' do
|
||||
config.should_receive(:strategy).with(no_args).and_return(strategy)
|
||||
should be(killer)
|
||||
end
|
||||
|
||||
it 'should run killer' do
|
||||
strategy.should_receive(:kill).with(mutation).and_return(killer)
|
||||
should be(killer)
|
||||
end
|
||||
|
||||
it { should be(killer) }
|
||||
|
||||
it_should_behave_like 'an idempotent method'
|
||||
end
|
102
spec/unit/mutant/runner/mutation_spec.rb
Normal file
102
spec/unit/mutant/runner/mutation_spec.rb
Normal file
|
@ -0,0 +1,102 @@
|
|||
# encoding: utf-8
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
describe Mutant::Runner::Mutation do
|
||||
let(:object) { described_class.new(config, mutation, tests) }
|
||||
|
||||
let(:reporter) { double('Reporter') }
|
||||
let(:mutation) { double('Mutation', class: Mutant::Mutation) }
|
||||
let(:strategy) { double('Strategy') }
|
||||
let(:killer_a) { Mutant::Killer.new(test: test_a, mutation: mutation) }
|
||||
let(:killer_b) { Mutant::Killer.new(test: test_b, mutation: mutation) }
|
||||
let(:runner_a) { double('Runner A', success?: success_a, stop?: stop_a) }
|
||||
let(:runner_b) { double('Runner B', success?: success_b, stop?: stop_b) }
|
||||
let(:runners) { [runner_a, runner_b] }
|
||||
let(:killers) { [killer_a, killer_b] }
|
||||
let(:fail_fast) { false }
|
||||
let(:success_a) { true }
|
||||
let(:success_b) { true }
|
||||
let(:stop_a) { false }
|
||||
let(:stop_b) { false }
|
||||
let(:test_a) { double('test a') }
|
||||
let(:test_b) { double('test b') }
|
||||
let(:tests) { [test_a, test_b] }
|
||||
|
||||
before do
|
||||
expect(Mutant::Runner).to receive(:run).with(config, killer_a).and_return(runner_a)
|
||||
expect(Mutant::Runner).to receive(:run).with(config, killer_b).and_return(runner_b)
|
||||
end
|
||||
|
||||
let(:config) do
|
||||
double(
|
||||
'Config',
|
||||
fail_fast: fail_fast,
|
||||
reporter: reporter,
|
||||
strategy: strategy
|
||||
)
|
||||
end
|
||||
|
||||
before do
|
||||
reporter.stub(progress: reporter)
|
||||
strategy.stub(killers: killers)
|
||||
end
|
||||
|
||||
describe '#stop?' do
|
||||
subject { object.stop? }
|
||||
|
||||
context 'when fail fast is false' do
|
||||
it { should be(false) }
|
||||
end
|
||||
|
||||
context 'when fail fast is true' do
|
||||
let(:fail_fast) { true }
|
||||
|
||||
context 'when all killers are successful' do
|
||||
it { should be(false) }
|
||||
end
|
||||
|
||||
context 'when one killer is NOT successful' do
|
||||
let(:success_b) { false }
|
||||
it { should be(false) }
|
||||
end
|
||||
|
||||
context 'when all killer are NOT successful' do
|
||||
let(:success_b) { false }
|
||||
let(:success_a) { false }
|
||||
|
||||
it { should be(true) }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#success?' do
|
||||
subject { object.success? }
|
||||
|
||||
context 'when all killers are successful' do
|
||||
it { should be(true) }
|
||||
end
|
||||
|
||||
context 'when one killer is not successful' do
|
||||
let(:success_b) { false }
|
||||
|
||||
it { should be(true) }
|
||||
end
|
||||
|
||||
context 'when all killer are not successful' do
|
||||
let(:success_a) { false }
|
||||
let(:success_b) { false }
|
||||
|
||||
it { should be(false) }
|
||||
end
|
||||
end
|
||||
|
||||
describe '#killers' do
|
||||
subject { object.killers }
|
||||
|
||||
|
||||
it { should eql(runners) }
|
||||
|
||||
it_should_behave_like 'an idempotent method'
|
||||
end
|
||||
end
|
|
@ -15,10 +15,11 @@ describe Mutant::Runner::Subject, '#success?' do
|
|||
)
|
||||
end
|
||||
|
||||
let(:reporter) { double('Reporter') }
|
||||
let(:config) { double('Config', reporter: reporter) }
|
||||
let(:mutation_a) { double('Mutation A') }
|
||||
let(:mutation_b) { double('Mutation B') }
|
||||
let(:reporter) { Mutant::Reporter::Trace.new }
|
||||
let(:config) { double('Config', reporter: reporter, strategy: strategy) }
|
||||
let(:mutation_a) { double('Mutation A') }
|
||||
let(:mutation_b) { double('Mutation B') }
|
||||
let(:strategy) { double('Strategy') }
|
||||
|
||||
let(:runner_a) do
|
||||
double('Runner A', success?: success_a, stop?: stop_a)
|
||||
|
@ -28,10 +29,12 @@ describe Mutant::Runner::Subject, '#success?' do
|
|||
double('Runner B', success?: success_b, stop?: stop_b)
|
||||
end
|
||||
|
||||
let(:tests) { [double('test a'), double('test b')] }
|
||||
|
||||
before do
|
||||
reporter.stub(report: reporter)
|
||||
Mutant::Runner.stub(:run).with(config, mutation_a).and_return(runner_a)
|
||||
Mutant::Runner.stub(:run).with(config, mutation_b).and_return(runner_b)
|
||||
expect(strategy).to receive(:tests).with(mutation_subject).and_return(tests)
|
||||
expect(Mutant::Runner).to receive(:run).with(config, mutation_a, tests).and_return(runner_a)
|
||||
expect(Mutant::Runner).to receive(:run).with(config, mutation_b, tests).and_return(runner_b)
|
||||
end
|
||||
|
||||
context 'with failing mutations' do
|
||||
|
|
|
@ -38,4 +38,57 @@ describe Mutant do
|
|||
expect(inspect).to be_frozen
|
||||
end
|
||||
end
|
||||
|
||||
describe '.isolate' do
|
||||
let(:object) { described_class }
|
||||
|
||||
let(:expected_return) { :foo }
|
||||
|
||||
subject { object.isolate(&block) }
|
||||
|
||||
def redirect_stderr
|
||||
$stderr = File.open('/dev/null')
|
||||
end
|
||||
|
||||
unless ENV['COVERAGE']
|
||||
context 'when block returns mashallable data, and process exists zero' do
|
||||
let(:block) do
|
||||
lambda do
|
||||
:data_from_child_process
|
||||
end
|
||||
end
|
||||
|
||||
it { should eql(:data_from_child_process) }
|
||||
end
|
||||
end
|
||||
|
||||
context 'when block does return marshallable data' do
|
||||
let(:block) do
|
||||
lambda do
|
||||
redirect_stderr
|
||||
$stderr # not mashallable, nothing written to pipe and raised exceptions in child
|
||||
end
|
||||
end
|
||||
|
||||
it 'raises an exception' do
|
||||
expect { subject }.to raise_error(Mutant::IsolationError, 'Childprocess wrote un-unmarshallable data')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when block does return marshallable data, but process exits with nonzero exitstatus' do
|
||||
let(:block) do
|
||||
lambda do
|
||||
redirect_stderr
|
||||
at_exit do
|
||||
raise
|
||||
end
|
||||
:foo
|
||||
end
|
||||
end
|
||||
|
||||
it 'raises an exception' do
|
||||
expect { subject }.to raise_error(Mutant::IsolationError, 'Childprocess exited with nonzero exit status: 1')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in a new issue