Move isolation into config object

Also add Isolation::None for testing.
This commit is contained in:
Markus Schirp 2014-07-06 02:11:31 +00:00
parent 473aeace90
commit dec1a39eb5
6 changed files with 167 additions and 24 deletions

View file

@ -218,6 +218,7 @@ module Mutant
matcher_config: Matcher::Config::DEFAULT,
includes: [],
requires: [],
isolation: Mutant::Isolation::Fork,
reporter: Reporter::CLI.new($stdout),
zombie: false,
expected_coverage: 100.0

View file

@ -8,6 +8,7 @@ module Mutant
:includes,
:requires,
:reporter,
:isolation,
:fail_fast,
:zombie,
:expected_coverage

View file

@ -3,25 +3,49 @@ module Mutant
module Isolation
Error = Class.new(RuntimeError)
# Call block in isolation
#
# 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 [Object]
#
# @raise [Error]
#
# @api private
#
def self.call(&block)
Parallel.map([block], in_processes: 1) do
module None
# Call block in isolation
#
# @return [Object]
#
# @raise [Error]
# if block terminates abnormal
#
# @api private
#
def self.call(&block)
block.call
end.first
rescue Parallel::DeadWorker => exception
fail Error, exception
rescue => exception
fail Error, exception
end
end
module Fork
# Call block in isolation
#
# 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 [Object]
# returns block execution result
#
# @raise [Error]
# if block terminates abnormal
#
# @api private
#
def self.call(&block)
Parallel.map([block], in_processes: 1) do
block.call
end.first
rescue Parallel::DeadWorker => exception
fail Error, exception
end
end # Fork
end # Isolator
end # Mutant

View file

@ -121,10 +121,10 @@ module Mutant
# @api private
#
def run_mutation_test(mutation, test)
Isolation.call do
config.isolation.call do
mutation.insert
test.run
end.update(test: test, mutation: mutation)
end
rescue Isolation::Error
Result::Test.new(
test: test,

View file

@ -1,13 +1,36 @@
require 'spec_helper'
describe Mutant::Isolation do
describe Mutant::Isolation::None do
before do
@initial = 1
end
describe '.run' do
let(:object) { described_class }
it 'isolates global effects from process' do
expect(defined?(::TestConstant)).to be(nil)
object.call { ::TestConstant = 1 }
expect(defined?(::TestConstant)).to be(nil)
it 'does not isolate side effects' do
object.call { @initial = 2 }
expect(@initial).to be(2)
end
it 'return block value' do
expect(object.call { :foo }).to be(:foo)
end
end
end
describe Mutant::Isolation::Fork do
before do
@initial = 1
end
describe '.run' do
let(:object) { described_class }
it 'does isolate side effects' do
object.call { @initial = 2 }
expect(@initial).to be(1)
end
it 'return block value' do

View file

@ -0,0 +1,94 @@
require 'spec_helper'
describe Mutant::Runner do
let(:object) { described_class.new(env) }
let(:reporter) { Mutant::Reporter::Trace.new }
let(:config) { Mutant::Config::DEFAULT.update(reporter: reporter) }
let(:subjects) { [subject_a, subject_b] }
class Double
include Concord.new(:name, :attributes)
def self.new(name, attributes = {})
super
end
def method_missing(name, *arguments)
super unless attributes.key?(name)
fail "Arguments provided for #{name}" if arguments.any?
attributes.fetch(name)
end
end
let(:subject_a) { Double.new('Subject A', mutations: mutations_a, tests: subject_a_tests) }
let(:subject_b) { Double.new('Subject B', mutations: mutations_b) }
let(:subject_a_tests) { [test_a1, test_a2] }
let(:env) do
subjects = self.subjects
Class.new(Mutant::Env) do
define_method(:subjects) { subjects }
end.new(config)
end
let(:mutations_a) { [mutation_a1, mutation_a2] }
let(:mutations_b) { [] }
let(:mutation_a1) { Double.new('Mutation A1') }
let(:mutation_a2) { Double.new('Mutation A2') }
let(:test_a1) { Double.new('Test A1') }
let(:test_a2) { Double.new('Test A2') }
let(:test_report_a1) { Double.new('Test Report A1') }
before do
allow(mutation_a1).to receive(:subject).and_return(subject_a)
allow(mutation_a1).to receive(:insert)
allow(mutation_a2).to receive(:subject).and_return(subject_a)
allow(mutation_a2).to receive(:insert)
allow(test_a1).to receive(:run).and_return(test_report_a1)
allow(mutation_a1).to receive(:killed_by?).with(test_report_a1).and_return(true)
allow(mutation_a2).to receive(:killed_by?).with(test_report_a1).and_return(true)
end
before do
time = Time.at(0)
allow(Time).to receive(:now).and_return(time)
end
describe '#result' do
subject { object.result }
its(:env) { should be(env) }
its(:subject_results) { should eql(expected_subject_results) }
let(:expected_subject_results) do
[
Mutant::Result::Subject.new(
subject: subject_a,
mutation_results: [
Mutant::Result::Mutation.new(
mutation: mutation_a1,
runtime: 0.0,
test_results: [test_report_a1]
),
Mutant::Result::Mutation.new(
mutation: mutation_a2,
runtime: 0.0,
test_results: [test_report_a1]
)
],
runtime: 0.0
),
Mutant::Result::Subject.new(
subject: subject_b,
mutation_results: [],
runtime: 0.0
)
]
end
end
end