Refactor reporter infrastructure

This commit is contained in:
Markus Schirp 2013-01-15 23:46:05 +01:00
parent bbc91b9457
commit d41d7eddb0
21 changed files with 519 additions and 490 deletions

View file

@ -38,6 +38,8 @@ require 'mutant/helper'
require 'mutant/random'
require 'mutant/mutator'
require 'mutant/mutation'
require 'mutant/mutation/evil'
require 'mutant/mutation/neutral'
require 'mutant/mutation/filter'
require 'mutant/mutation/filter/code'
require 'mutant/mutation/filter/whitelist'
@ -97,7 +99,9 @@ require 'mutant/killer'
require 'mutant/killer/static'
require 'mutant/killer/rspec'
require 'mutant/killer/forking'
require 'mutant/killer/forked'
require 'mutant/strategy'
require 'mutant/strategy/static'
require 'mutant/strategy/method_expansion'
require 'mutant/strategy/rspec'
require 'mutant/strategy/rspec/dm2'

View file

@ -1,20 +1,35 @@
module Mutant
# Abstract base class for mutant killers
class Killer
include Adamantium::Flat, AbstractType
include Adamantium::Flat, AbstractType, Equalizer.new(:strategy, :mutation, :killed?)
# Test for kill failure
#
# @return [true]
# returns true when mutant was killed
# when mutant was killed
#
# @return [false]
# returns false otherwise
# otherwise
#
# @api private
#
def fail?
!@killed
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 runtime of killer
@ -25,14 +40,14 @@ module Mutant
#
attr_reader :runtime
# Return original source
# Return configuration
#
# @return [String]
# @return [Configuration]
#
# @api private
#
def original_source
mutation.original_source
def configuration
strategy.configuration
end
# Return mutated source
@ -45,14 +60,6 @@ module Mutant
mutation.source
end
# Return strategy
#
# @return [Strategy]
#
# @api private
#
attr_reader :strategy
# Return name of killer
#
# @return [String]
@ -63,6 +70,14 @@ module Mutant
self::TYPE
end
# Return strategy
#
# @return [Strategy]
#
# @api private
#
attr_reader :strategy
# Return identification
#
# @return [String]

View file

@ -0,0 +1,58 @@
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
# Return killer type
#
# @return [String]
#
# @api private
#
def type
@killer.type
end
private
# Run killer
#
# @return [true]
# if mutant was killed
#
# @return [false]
# otherwise
#
# @api private
#
def run
fork do
begin
killer = @killer.new(strategy, mutation)
Kernel.exit(killer.fail? ? 1 : 0)
rescue
Kernel.exit(1)
end
end
status = Process.wait2.last
status.exitstatus.zero?
end
end
end
end

View file

@ -1,59 +1,6 @@
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
# Return killer type
#
# @return [String]
#
# @api private
#
def type
@killer.type
end
private
# Run killer
#
# @return [true]
# if mutant was killed
#
# @return [false]
# otherwise
#
# @api private
#
def run
fork do
begin
killer = @killer.new(strategy, mutation)
Kernel.exit(killer.fail? ? 1 : 0)
rescue
Kernel.exit(1)
end
end
status = Process.wait2.last
status.exitstatus.zero?
end
end
# A killer that executes other killer in forked environemnts
class Forking < self
include Equalizer.new(:killer)
@ -93,6 +40,5 @@ module Mutant
end
end
end
end

View file

@ -2,21 +2,11 @@ module Mutant
class Killer
# Runner for rspec tests
class Rspec < self
TYPE = 'rspec'.freeze
private
# Initialize rspec runner
#
# @return [undefined]
#
# @api private
#
def initialize(*)
@error_stream, @output_stream = StringIO.new, StringIO.new
super
end
# Run rspec test
#
# @return [true]

View file

@ -1,7 +1,20 @@
module Mutant
# Represent a mutated node with its subject
class Mutation
include Adamantium::Flat, Equalizer.new(:sha1)
include AbstractType, Adamantium::Flat, Equalizer.new(:sha1)
# Initialize mutation object
#
# @param [Subject] subject
# @param [Rubinius::Node::AST] node
#
# @return [undefined]
#
# @api private
#
def initialize(subject, node)
@subject, @node = subject, node
end
# Return mutation subject
#
@ -30,6 +43,20 @@ module Mutant
end
memoize :root
# Test if killer is successful
#
# @param [Killer] killer
#
# @return [true]
# if killer is successful
#
# @return [false]
# otherwise
#
# @api private
#
abstract_method :success?
# Insert mutated node
#
# @return [self]
@ -95,46 +122,5 @@ module Mutant
subject.source
end
private
# Initialize mutation object
#
# @param [Subject] subject
# @param [Rubinius::Node::AST] node
#
# @return [undefined]
#
# @api private
#
def initialize(subject, node)
@subject, @node = subject, node
end
# Noop mutation
class Noop < self
# Initialize object
#
# @param [Subject] subject
#
# @return [undefined]
#
# @api private
#
def initialize(subject)
super(subject, subject.node)
end
# Return identification
#
# @return [String]
#
# @api private
#
def identification
"noop:#{super}"
end
memoize :identification
end
end
end

View file

@ -0,0 +1,35 @@
module Mutant
class Mutation
# Evul mutation
class Evil < self
# Return identification
#
# @return [String]
#
# @api private
#
def identification
"evil:#{super}"
end
memoize :identification
# Test if killer is successful
#
# @param [Killer] killer
#
# @return [true]
# if killer killed mutation
#
# @return [false]
# otherwise
#
# @api private
#
def success?(killer)
killer.killed?
end
end
end
end

View file

@ -0,0 +1,35 @@
module Mutant
class Mutation
# Neutral mutation
class Neutral < self
# Return identification
#
# @return [String]
#
# @api private
#
def identification
"noop:#{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
end
end

View file

@ -1,7 +1,18 @@
module Mutant
# Abstract reporter
class Reporter
include Adamantium::Flat, AbstractType
include Adamantium::Flat, AbstractType, Equalizer.new(:stats)
# Initialize reporter
#
# @param [Config] config
#
# @api private
#
def initialize(config)
@stats = Stats.new
@config = config
end
# Report subject
#
@ -11,7 +22,10 @@ module Mutant
#
# @api private
#
abstract_method :subject
def subject(subject)
stats.count_subject
self
end
# Report mutation
#
@ -21,17 +35,9 @@ module Mutant
#
# @api private
#
abstract_method :mutation
# Report notice
#
# @param [String] notice
#
# @return [self]
#
# @api private
#
abstract_method :notice
def mutation(mutation)
self
end
# Report killer
#
@ -41,44 +47,55 @@ module Mutant
#
# @api private
#
abstract_method :killer
def report_killer(killer)
stats.count_killer(killer)
# Report config
#
# @param [Mutant::Config] config
#
# @return [self]
#
# @api private
#
abstract_method :config
# Return output stream
#
# @return [IO]
#
# @api private
#
abstract_method :output_stream
# Return error stream
#
# @return [IO]
#
# @api private
#
abstract_method :error_stream
private
# Initialize reporter
#
# @param [Config] config
#
# @api private
#
def initialize(config)
@config = config
self
end
# Test for running in debug mode
#
# @return [true]
# if running in debug mode
#
# @return [false]
# otherwise
#
# @api private
#
def debug?
config.debug?
end
# Return stats
#
# @return [Reporter::Stats]
#
# @api private
#
attr_reader :stats
# Return config
#
# @return [Config]
#
# @api private
#
attr_reader :config
# Test if errors are present
#
# @return [true]
# if errors are present
#
# @return [false]
# otherwise
#
# @api private
#
def errors?
stats.errors?
end
end
end

View file

@ -2,9 +2,21 @@ module Mutant
class Reporter
# Reporter that reports in human readable format
class CLI < self
include Equalizer.new(:io)
# Reporter subject
# Initialize reporter
#
# @param [Config] config
#
# @return [undefined]
#
# @api private
#
def initialize(config)
super
@io = $stdout
end
# Reporte subject
#
# @param [Subject] subject
#
@ -13,7 +25,7 @@ module Mutant
# @api private
#
def subject(subject)
stats.subject
super
puts("Subject: #{subject.identification}")
end
@ -24,7 +36,7 @@ module Mutant
# @api private
#
def error_stream
@config.debug? ? io : StringIO.new
debug? ? io : StringIO.new
end
# Return output stream
@ -34,7 +46,7 @@ module Mutant
# @api private
#
def output_stream
@config.debug? ? io : StringIO.new
debug? ? io : StringIO.new
end
# Report mutation
@ -46,7 +58,9 @@ module Mutant
# @api private
#
def mutation(mutation)
if @config.debug?
super
if debug?
colorized_diff(mutation)
end
@ -61,96 +75,37 @@ module Mutant
#
# @api private
#
def config(config)
puts 'Mutant configuration:'
puts "Matcher: #{config.matcher.inspect}"
puts "Filter: #{config.filter.inspect}"
puts "Strategy: #{config.strategy.inspect}"
def print_config
message = []
message << 'Mutant configuration:'
message << "Matcher: #{config.matcher.inspect}"
message << "Filter: #{config.filter.inspect}"
message << "Strategy: #{config.strategy.inspect}"
puts message.join("\n")
end
# Report noop
#
# @param [Killer] killer
#
# @return [self]
#
# @api private
#
def noop(killer)
color, word =
if killer.fail?
[Color::GREEN, 'Alive']
else
[Color::RED, 'Killed']
end
## Reporter killer
##
## @param [Killer] killer
##
## @return [self]
##
## @api private
##
#def killer(killer)
# super
print_killer(color, word, killer)
# status = killer.killed? ? 'Killed' : 'Alive'
# color = killer.success? ? Color::GREEN : Color::RED
unless killer.fail?
puts(killer.mutation.source)
stats.noop_fail(killer)
end
# puts(colorize(color, "#{status}: #{killer.identification} (%02.2fs)" % killer.runtime))
self
end
# unless killer.success?
# colorized_diff(killer.mutation)
# end
# Reporter killer
#
# @param [Killer] killer
#
# @return [self]
#
# @api private
#
def killer(killer)
stats.killer(killer)
color, word =
if killer.fail?
[Color::RED, 'Alive']
else
[Color::GREEN, 'Killed']
end
print_killer(color, word, killer)
if killer.fail?
colorized_diff(killer.mutation)
end
self
end
# Report errors
#
# @param [Enumerable<Killer>] errors
#
# @api private
#
# @return [self]
#
def errors(errors)
errors.each do |error|
failure(error)
end
puts
puts "subjects: #{stats.subjects}"
puts "mutations: #{stats.mutations}"
puts "noop_fails: #{stats.noop_fails}"
puts "kills: #{stats.kills}"
puts "alive: #{stats.alive}"
puts "mtime: %02.2fs" % stats.time
puts "rtime: %02.2fs" % stats.runtime
end
# Return IO stream
#
# @return [IO]
#
# @api private
#
attr_reader :io
# self
#end
# Return stats
#
@ -162,33 +117,13 @@ module Mutant
private
# Initialize reporter
# Return IO stream
#
# @param [Config] config
#
# @return [undefined]
# @return [IO]
#
# @api private
#
def initialize(config)
super
@io = $stdout
@stats = Stats.new
end
# Report failure on killer
#
# @param [Killer] killer
#
# @return [undefined]
#
# @api private
#
def failure(killer)
puts(colorize(Color::RED, "!!! Mutant alive: #{killer.identification} !!!"))
colorized_diff(killer.mutation)
puts("Took: (%02.2fs)" % killer.runtime)
end
attr_reader :io
# Test for colored output
#
@ -241,7 +176,7 @@ module Mutant
# @api private
#
def colorized_diff(mutation)
if mutation.kind_of?(Mutation::Noop)
if mutation.kind_of?(Mutation::Neutral)
puts mutation.original_source
return
end
@ -252,27 +187,13 @@ module Mutant
# FIXME remove this branch before release
if diff.empty?
raise "Unable to create a diff, so ast mutation or to_source has an error!"
raise 'Unable to create a diff, so ast mutation or to_source has an error!'
end
puts(diff)
self
end
# Print killer
#
# @param [Color] color
# @param [String] word
# @param [Killer] killer
#
# @return [undefined]
#
# @api private
#
def print_killer(color, word, killer)
puts(colorize(color, "#{word}: #{killer.identification} (%02.2fs)" % killer.runtime))
end
# Test for output to tty
#
# @return [true]
@ -287,6 +208,7 @@ module Mutant
@io.respond_to?(:tty?) && @io.tty?
end
memoize :tty?
end
end
end

View file

@ -4,45 +4,48 @@ module Mutant
# Stats gathered while reporter is running
class Stats
# Return subject count
#
# @return [Fixnum]
#
# @api private
#
attr_reader :subjects
# A counter with fail counts
class Counter
include Equalizer.new(:count, :fails)
# Return mutation count
#
# @return [Fixnum]
#
# @api private
#
attr_reader :mutations
attr_reader :count
# Return skip count
#
# @return [Fixnum]
#
# @api private
#
attr_reader :noop_fails
# Return fail count
#
# @return [Fixnum]
#
# @api private
#
attr_reader :fails
# Return kill count
#
# @return [Fixnum]
#
# @api private
#
attr_reader :kills
# Initialize object
#
# @return [undefined]
#
# @api private
#
def initialize
@count = @fails = 0
end
# Return mutation runtime
#
# @return [Float]
#
# @api private
#
attr_reader :time
# Count killer
#
# @param [Killer] killer
#
# @return [self]
#
# @api private
#
def handle(killer)
@count += 1
unless killer.success?
@fails += 1
end
self
end
end
include Equalizer.new(:start, :counts, :killers)
# Initialize object
#
@ -51,8 +54,76 @@ module Mutant
# @api private
#
def initialize
@start = Time.now
@noop_fails = @subjects = @mutations = @kills = @time = 0
@start = start
@counts = Hash.new(0)
@killers = {}
end
protected
# Return counts
#
# @return [Hash]
#
# @api private
#
attr_reader :counts
# Return start time
#
# @return [Time]
#
# @api private
#
attr_reader :start
# Return killers
#
# @return [Hash]
#
# @api private
#
attr_reader :killers
public
# Count subject
#
# @return [self]
#
# @api private
#
def count_subject
@counts[:subject] += 1
end
# Count killer
#
# @param [Killer] killer
#
# @return [self]
#
# @api private
#
def count_killer(killer)
counter = @killers[killer.mutation.class] ||= Counter.new
counter.handle(killer)
self
end
# Test for errors
#
# @return [true]
# if there are errors
#
# @return [false]
# otherwise
#
def errors?
@killers.values.inject(0) do |count, counter|
p counter, count
count += counter.fails
end.nonzero?
end
# Return runtime in seconds
@ -65,56 +136,6 @@ module Mutant
Time.now - @start
end
# Count subject
#
# @return [self]
#
# @api private
#
def subject
@subjects +=1
self
end
# Return number of mutants alive
#
# @return [Fixnum]
#
# @api private
#
def alive
@mutations - @kills
end
# Count noop mutation fail
#
# @param [Killer] killer
#
# @return [self]
#
# @api private
#
def noop_fail(killer)
@noop_fails += 1
@time += killer.runtime
self
end
# Count killer
#
# @param [Killer] killer
#
# @return [self]
#
# @api private
#
def killer(killer)
@mutations +=1
@kills +=1 unless killer.fail?
@time += killer.runtime
self
end
end
end
end

View file

@ -4,14 +4,6 @@ module Mutant
include Adamantium::Flat
extend MethodObject
# Return killers with errors
#
# @return [Enumerable<Killer>]
#
# @api private
#
attr_reader :errors
# Test for failure
#
# @return [true]
@ -23,7 +15,7 @@ module Mutant
# @api private
#
def fail?
!errors.empty?
reporter.errors?
end
# Return config
@ -45,12 +37,18 @@ module Mutant
# @api private
#
def initialize(config)
@config, @errors = config, []
util_reporter = reporter
util_reporter.config(config)
@config = config
run
util_reporter.errors(@errors)
end
# Return strategy
#
# @return [Strategy]
#
# @api private
#
def strategy
config.strategy
end
# Return reporter
@ -70,10 +68,14 @@ module Mutant
# @api private
#
def run
reporter.print_config
util = strategy
util.setup
config.matcher.each do |subject|
reporter.subject(subject)
run_subject(subject)
end
util.teardown
end
# Run mutation killers on subject
@ -85,7 +87,7 @@ module Mutant
# @api private
#
def run_subject(subject)
return unless noop(subject)
return unless test_noop(subject)
subject.each do |mutation|
next unless config.filter.match?(mutation)
reporter.mutation(mutation)
@ -93,26 +95,22 @@ module Mutant
end
end
# Test for noop mutation
#
# @param [Subject] subject
# Test noop mutation
#
# @return [true]
# if noop mutation is okay
# if noop mutation is alive
#
# @return [false]
# otherwise
#
# @api private
#
def noop(subject)
killer = killer(subject.noop)
reporter.noop(killer)
unless killer.fail?
@errors << killer
def test_noop(subject)
noop = subject.noop
unless kill(noop)
reporter.noop(noop)
return false
end
true
end
@ -121,7 +119,7 @@ module Mutant
# @param [Mutation] mutation
#
# @return [true]
# if killer was unsuccessful
# if killer was successful
#
# @return [false]
# otherwise
@ -130,11 +128,8 @@ module Mutant
#
def kill(mutation)
killer = killer(mutation)
reporter.killer(killer)
if killer.fail?
@errors << killer
end
reporter.report_killer(killer)
killer.success?
end
# Return killer for mutation
@ -144,7 +139,7 @@ module Mutant
# @api private
#
def killer(mutation)
config.strategy.kill(mutation)
strategy.kill(mutation)
end
end
end

View file

@ -24,24 +24,24 @@ module Mutant
@config = config
end
# Return output stream
# Perform setup
#
# @return [IO]
# @return [self]
#
# @api private
#
def output_stream
config.reporter.output_stream
def setup
self
end
# Return error stream
# Perform teardown
#
# @return [IO]
# @return [self]
#
# @api private
#
def error_stream
config.reporter.error_stream
def teardown
self
end
# Kill mutation
@ -65,21 +65,5 @@ module Mutant
def killer
self.class::KILLER
end
# Static strategies
class Static < self
include Equalizer.new
# Always fail to kill strategy
class Fail < self
KILLER = Killer::Static::Fail
end
# Always succeed to kill strategy
class Success < self
KILLER = Killer::Static::Success
end
end
end
end

View file

@ -0,0 +1,18 @@
module Mutant
class Strategy
# Static strategies
class Static < self
# Always fail to kill strategy
class Fail < self
KILLER = Killer::Static::Fail
end
# Always succeed to kill strategy
class Success < self
KILLER = Killer::Static::Success
end
end
end
end

View file

@ -32,7 +32,7 @@ module Mutant
def each
return to_enum unless block_given?
Mutator.each(node) do |mutant|
yield Mutation.new(self, mutant)
yield Mutation::Evil.new(self, mutant)
end
self
@ -45,7 +45,7 @@ module Mutant
# @api private
#
def noop
Mutation::Noop.new(self)
Mutation::Neutral.new(self, node)
end
memoize :noop

View file

@ -74,7 +74,7 @@ module Mutant
# @api private
#
def subtype
"#{context.name}.#{node.name}"
"#{context.identification}.#{node.body.name}"
end
end
end

View file

@ -0,0 +1,9 @@
require 'ice_nine'
module IceNine
class Freezer
class RSpec < NoFreeze
end
end
end

View file

@ -16,6 +16,11 @@ end
describe Mutant::CLI, '.new' do
let(:object) { described_class }
let(:time) { Time.now }
before do
Time.stub(:now => time)
end
# Defaults
let(:expected_filter) { Mutant::Mutation::Filter::ALL }

View file

@ -1,39 +0,0 @@
require 'spec_helper'
describe Mutant::Killer,'#fail?' do
subject { object.fail? }
let(:object) { class_under_test.new(strategy, mutation) }
let(:strategy) { mock('Strategy') }
let(:mutation) { mock('Mutation') }
before do
mutation.stub(:insert)
mutation.stub(:reset)
end
let(:class_under_test) do
kill_state = self.kill_state
Class.new(described_class) do
define_method :run do
kill_state
end
end
end
context 'when mutant was killed' do
let(:kill_state) { true }
it_should_behave_like 'an idempotent method'
it { should be(false) }
end
context 'when mutant was NOT killed' do
let(:kill_state) { false }
it_should_behave_like 'an idempotent method'
it { should be(true) }
end
end

View file

@ -20,14 +20,14 @@ describe Mutant::Killer::Rspec, '.new' do
context 'when run exits zero' do
let(:exit_status) { 0 }
its(:fail?) { should be(true) }
its(:killed?) { should be(false) }
it { should be_a(described_class) }
end
context 'when run exits nonzero' do
let(:exit_status) { 1 }
its(:fail?) { should be(false) }
its(:killed?) { should be(true) }
it { should be_a(described_class) }
end
end

View file

@ -0,0 +1,28 @@
require 'spec_helper'
describe Mutant::Killer, '#success?' do
subject { object.success? }
let(:object) { class_under_test.new(strategy, mutation) }
let(:strategy) { mock('Strategy') }
let(:mutation) { mock('Mutation', :success? => kill_state) }
let(:kill_state) { mock('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