Fine grained rspec integration

* Flattens the rspec example groups on filtering to remove redundant
  work being done on executing subtrees of already executed example
  groups
* Uses test batching to kill one mutation per isolation that results in
  a significant speedup.
* Drop rspec 2 support.

[closes #256]
This commit is contained in:
Markus Schirp 2014-11-27 16:34:08 +00:00
parent 8cbb5e288a
commit 40c337ce5b
22 changed files with 246 additions and 656 deletions

View file

@ -1,3 +1,3 @@
---
threshold: 18
total_score: 1179
total_score: 1143

View file

@ -27,9 +27,7 @@ FeatureEnvy:
exclude:
- Mutant::Env#scope_name
- Mutant::Diff#minimized_hunks
- Mutant::Integration::Rspec#run
- Mutant::Integration::Rspec::Rspec2#full_description
- Mutant::Integration::Rspec::Rspec3#full_description
- Mutant::Integration::Rspec#parse_example
- Mutant::Matcher::Method::Instance#match?
- Mutant::Matcher::Method::Singleton#receiver?
- Mutant::Mutator::Node#children_indices
@ -56,7 +54,6 @@ NestedIterators:
exclude:
- Mutant#self.singleton_subclass_instance
- Mutant::CLI#parse
- Mutant::Integration::Rspec#run
- Mutant::Isolation::Fork#self.call
- Mutant::Mutator::Util::Array::Element#dispatch
- Mutant::Mutator::Node::Resbody#mutate_captures
@ -93,7 +90,6 @@ TooManyMethods:
TooManyStatements:
enabled: true
exclude:
- Mutant::Integration::Rspec#run
- Mutant::Isolation::Fork#self.call
- Mutant::Reporter::CLI::Printer::EnvProgress#run
- Mutant::Reporter::CLI::Printer::Config#run
@ -113,9 +109,7 @@ UncommunicativeMethodName:
accept: []
UncommunicativeModuleName:
enabled: true
exclude:
- Rspec2
- Rspec3
exclude: []
reject:
- !ruby/regexp /^.$/
- !ruby/regexp /[0-9]$/
@ -144,11 +138,7 @@ UtilityFunction:
exclude:
- Mutant::AST::Sexp#s
- Mutant::CLI#reporter
- Mutant::Integration::Rspec#configuration
- Mutant::Integration::Rspec#options
- Mutant::Integration::Rspec::Rspec2#full_description
- Mutant::Integration::Rspec::Rspec2#new_reporter
- Mutant::Integration::Rspec::Rspec3#full_description
- Mutant::Meta::Example::Verification#format_mutation
- Mutant::Integration::Rspec#parse_example
- Mutant::Meta::Example::Verification#format_mutation # False positive, its a utility
- Mutant::Reporter::CLI::Format::Progressive#new_buffer
max_helper_calls: 0

View file

@ -16,7 +16,7 @@ module Mutant
# @api private
#
def self.lookup(name)
REGISTRY.fetch(name).build
REGISTRY.fetch(name).new
end
# Register integration
@ -44,6 +44,16 @@ module Mutant
self
end
# Return test result for tests
#
# @param [Enumerable<Test>] tests
#
# @return [Result::Test]
#
# @api private
#
abstract_method :call
# Return all available tests by integration
#
# @return [Enumerable<Test>]

View file

@ -5,24 +5,25 @@ module Mutant
class Integration
# Shared parts of rspec2/3 integration
class Rspec < self
include AbstractType
ALL = Mutant::Expression.parse('*')
EXPRESSION_DELIMITER = ' '.freeze
LOCATION_DELIMITER = ':'.freeze
EXIT_SUCCESS = 0
CLI_OPTIONS = IceNine.deep_freeze(%w[spec --fail-fast])
register 'rspec'
RSPEC_2_VERSION_PREFIX = '2.'.freeze
# Return integration compatible to currently loaded rspec
# Initialize rspec integration
#
# @return [Integration]
# @return [undefined]
#
# @api private
#
def self.build
if RSpec::Core::Version::STRING.start_with?(RSPEC_2_VERSION_PREFIX)
Rspec2.new
else
Rspec3.new
end
def initialize
@output = StringIO.new
@runner = RSpec::Core::Runner.new(RSpec::Core::ConfigurationOptions.new(CLI_OPTIONS))
@world = RSpec.world
end
# Setup rspec integration
@ -32,15 +33,14 @@ module Mutant
# @api private
#
def setup
options.configure(configuration)
configuration.load_spec_files
@runner.setup($stderr, @output)
self
end
memoize :setup
# Return report for test
#
# @param [Rspec::Test] test
# @param [Enumerable<Mutant::Test>] tests
#
# @return [Test::Result]
#
@ -48,25 +48,17 @@ module Mutant
#
# rubocop:disable MethodLength
#
def run(test)
output = StringIO.new
failed = false
def call(tests)
examples = tests.map(&all_tests_index.method(:fetch)).to_set
filter_examples(&examples.method(:include?))
start = Time.now
reporter = new_reporter(output)
reporter.report(1) do
example_group_index.fetch(test.expression.syntax).each do |example_group|
next if example_group.run(reporter)
failed = true
break
end
end
output.rewind
passed = @runner.run_specs(RSpec.world.ordered_example_groups).equal?(EXIT_SUCCESS)
@output.rewind
Result::Test.new(
test: nil,
mutation: nil,
output: output.read,
tests: nil,
output: @output.read,
runtime: Time.now - start,
passed: !failed
passed: passed
)
end
@ -77,136 +69,69 @@ module Mutant
# @api private
#
def all_tests
example_group_index.keys.each_with_object([]) do |full_description, aggregate|
expression = Expression.try_parse(full_description) or next
aggregate << Test.new(self, expression)
end
all_tests_index.keys
end
memoize :all_tests
private
# Return all example groups
# Return all tests index
#
# @return [Hash<String, RSpec::Core::ExampleGroup]
# @return [Hash<Test, RSpec::Core::Example]
#
# @api private
#
def example_group_index
index = Hash.new { |hash, key| hash[key] = [] }
RSpec.world.example_groups.flat_map(&:descendants).each do |example_group|
full_description = full_description(example_group)
index[full_description] << example_group
def all_tests_index
all_examples.each_with_object({}) do |example, index|
index[parse_example(example)] = example
end
index
end
memoize :example_group_index
memoize :all_tests_index
# Return configuration
# Parse example into test
#
# @return [RSpec::Core::Configuration]
# @param [RSpec::Core::Example]
#
# @return [Test]
#
# @api private
#
def configuration
RSpec::Core::Configuration.new
end
memoize :configuration, freezer: :noop
def parse_example(example)
metadata = example.metadata
location = metadata.fetch(:location)
full_description = metadata.fetch(:full_description)
expression = Expression.try_parse(full_description.split(EXPRESSION_DELIMITER, 2).first) || ALL
# Return options
Test.new(
id: "rspec:#{location} / #{full_description}",
expression: expression
)
end
# Return all examples
#
# @return [RSpec::Core::ConfigurationOptions]
# @return [Array<String, RSpec::Core::Example]
#
# @api private
#
def options
RSpec::Core::ConfigurationOptions.new(%w[--fail-fast spec])
def all_examples
@world.example_groups.flat_map(&:descendants).flat_map(&:examples)
end
memoize :options, freezer: :noop
# Rspec2 integration
class Rspec2 < self
register 'rspec'
private
# Return options
#
# @return [RSpec::Core::ConfigurationOptions]
#
# @api private
#
def options
super.tap(&:parse_options)
# Filter examples
#
# @param [#call] predicate
#
# @return [undefined]
#
# @api private
#
def filter_examples(&predicate)
@world.filtered_examples.each_value do |examples|
examples.keep_if(&predicate)
end
end
# Return full description of example group
#
# @param [RSpec::Core::ExampleGroup] example_group
#
# @return [String]
#
# @api private
#
def full_description(example_group)
example_group.metadata.fetch(:example_group).fetch(:full_description)
end
# Return new reporter
#
# @param [StringIO] output
#
# @return [RSpec::Core::Reporter]
#
# @api private
#
def new_reporter(output)
formatter = RSpec::Core::Formatters::BaseTextFormatter.new(output)
RSpec::Core::Reporter.new(formatter)
end
end # Rspec2
# Rspec 3 integration
class Rspec3 < self
private
# Return full description for example group
#
# @param [RSpec::Core::ExampleGroup] example_group
#
# @return [String]
#
# @api private
#
def full_description(example_group)
example_group.metadata.fetch(:full_description)
end
# Return new reporter
#
# @param [StringIO] output
#
# @return [RSpec::Core::Reporter]
#
# @api private
#
def new_reporter(output)
formatter = RSpec::Core::Formatters::BaseTextFormatter.new(output)
notifications = RSpec::Core::Formatters::Loader.allocate.send(:notifications_for, formatter.class)
RSpec::Core::Reporter.new(configuration).tap do |reporter|
reporter.register_listener(formatter, *notifications)
end
end
end # Rspec3
end # Rspec
end # Integration
end # Mutant

View file

@ -7,45 +7,30 @@ module Mutant
CODE_DELIMITER = "\0".freeze
CODE_RANGE = (0..4).freeze
# Kill mutation via isolation
# Kill mutation under isolation with integration
#
# @param [Isolation] isolation
# @param [Integration] integration
#
# @return [Result::Mutation]
# @return [Result::Test]
#
# @api private
#
def kill(isolation)
result = Result::Mutation.new(
index: nil,
mutation: self,
test_results: []
def kill(isolation, integration)
start = Time.now
tests = subject.tests
isolation.call do
insert
integration.call(tests)
end.update(tests: tests)
rescue Isolation::Error => error
Result::Test.new(
tests: tests,
output: error.message,
runtime: Time.now - start,
passed: false
)
subject.tests.reduce(result) do |current, test|
return current unless current.continue?
test_result = test.kill(isolation, self)
current.update(
test_results: current.test_results.dup << test_result
)
end
end
# Insert mutated node
#
# FIXME: Cache subject visibility in a better way! Ideally dont mutate it
# implicitly. Also subject.public? should NOT be a public interface it
# is a detail of method mutations.
#
# @return [self]
#
# @api private
#
def insert
subject.public?
subject.prepare
Loader::Eval.call(root, subject)
self
end
# Return identification
@ -99,17 +84,28 @@ module Mutant
#
# @api private
#
abstract_singleton_method :success?
def self.success?(test_result)
self::TEST_PASS_SUCCESS.equal?(test_result.passed)
end
# Test if execution can be continued
private
# Insert mutated node
#
# @return [Boolean]
# FIXME: Cache subject visibility in a better way! Ideally dont mutate it
# implicitly. Also subject.public? should NOT be a public interface it
# is a detail of method mutations.
#
# @return [self]
#
# @api private
#
abstract_singleton_method :continue?
private
def insert
subject.public?
subject.prepare
Loader::Eval.call(root, subject)
self
end
# Return sha1 sum of source and subject identification
#
@ -135,65 +131,24 @@ module Mutant
# Evil mutation that should case mutations to fail tests
class Evil < self
SYMBOL = 'evil'.freeze
# Test if mutation is killed by test reports
#
# @param [Array<Report::Test>] test_reports
#
# @return [Boolean]
#
# @api private
#
def self.success?(test_results)
!test_results.all?(&:passed)
end
# Test if mutation execution can be continued
#
# @return [Boolean]
#
# @api private
#
def self.continue?(test_results)
!success?(test_results)
end
SYMBOL = 'evil'.freeze
TEST_PASS_SUCCESS = false
end # Evil
# Neutral mutation that should not cause mutations to fail tests
class Neutral < self
SYMBOL = 'neutral'.freeze
# Test if mutation is killed by test reports
#
# @param [Array<Report::Test>] test_reports
#
# @return [Boolean]
#
# @api private
#
def self.success?(test_results)
test_results.any? && test_results.all?(&:passed)
end
# Test if mutation execution can be continued
#
# @return [Boolean] _test_results
#
# @api private
#
def self.continue?(_test_results)
true
end
SYMBOL = 'neutral'.freeze
TEST_PASS_SUCCESS = true
end # Neutral
# Noop mutation, special case of neutral
class Noop < Neutral
SYMBOL = 'noop'.freeze
SYMBOL = 'noop'.freeze
TEST_PASS_SUCCESS = true
end # Noop

View file

@ -430,7 +430,7 @@ module Mutant
# Reporter for mutation results
class MutationResult < self
delegate :mutation, :failed_test_results
delegate :mutation, :test_result
DIFF_ERROR_MESSAGE = 'BUG: Mutation NOT resulted in exactly one diff hunk. Please report a reproduction!'.freeze
@ -448,13 +448,13 @@ module Mutant
"%s\n" \
"Unparsed Source:\n" \
"%s\n" \
"Test Reports: %d\n"
"Test Result:\n".freeze
NOOP_MESSAGE =
"---- Noop failure -----\n" \
"No code was inserted. And the test did NOT PASS.\n" \
"This is typically a problem of your specs not passing unmutated.\n" \
"Test Reports: %d\n"
"Test Result:\n".freeze
FOOTER = '-----------------------'.freeze
@ -503,8 +503,8 @@ module Mutant
# @api private
#
def noop_details
info(NOOP_MESSAGE, failed_test_results.length)
visit_failed_test_results
info(NOOP_MESSAGE)
visit_test_result
end
# Neutral details
@ -514,8 +514,8 @@ module Mutant
# @api private
#
def neutral_details
info(NEUTRAL_MESSAGE, mutation.subject.node.inspect, mutation.source, failed_test_results.length)
visit_failed_test_results
info(NEUTRAL_MESSAGE, mutation.subject.node.inspect, mutation.source)
visit_test_result
end
# Visit failed test results
@ -524,8 +524,8 @@ module Mutant
#
# @api private
#
def visit_failed_test_results
visit_collection(TestResult, failed_test_results)
def visit_test_result
visit(TestResult, test_result)
end
end # MutationResult
@ -533,7 +533,7 @@ module Mutant
# Test result reporter
class TestResult < self
delegate :test, :runtime, :mutation
delegate :tests, :runtime
# Run test result reporter
#
@ -542,7 +542,10 @@ module Mutant
# @api private
#
def run
status('- %s / runtime: %s', test.identification, runtime)
status('- %d @ runtime: %s', tests.length, runtime)
tests.each do |test|
puts(" - #{test.identification}")
end
puts('Test Output:')
puts(object.output)
end

View file

@ -148,21 +148,11 @@ module Mutant
# Test result
class Test
include Result, Anima.new(
:test,
:tests,
:output,
:mutation,
:passed,
:runtime
)
# Return killtime
#
# @return [Float]
#
# @api private
#
alias_method :killtime, :runtime
end # Test
# Subject result
@ -258,20 +248,19 @@ module Mutant
# Mutation result
class Mutation
include Result, Anima.new(:mutation, :test_results, :index)
include Result, Anima.new(:mutation, :test_result, :index)
sum :runtime, :test_results
# Return failed test results
# Return runtime
#
# @return [Array<Result::Test>]
# @return [Float]
#
# @api private
#
def failed_test_results
test_results.reject(&:passed)
def runtime
test_result.runtime
end
memoize :failed_test_results
alias_method :killtime, :runtime
# Test if mutation was handled successfully
#
@ -280,22 +269,9 @@ module Mutant
# @api private
#
def success?
mutation.class.success?(test_results)
mutation.class.success?(test_result)
end
# Test if execution on mutation can be stopped
#
# @return [Boolean]
#
# @api private
#
def continue?
mutation.class.continue?(test_results)
end
sum :killtime, :test_results
end # Mutation
end # Result
end # Mutant

View file

@ -79,8 +79,12 @@ module Mutant
# @api private
#
def run_mutation(job)
job.mutation.kill(config.isolation).update(
index: job.index
mutation = job.mutation
test_result = mutation.kill(config.isolation, config.integration)
Result::Mutation.new(
index: job.index,
mutation: mutation,
test_result: test_result
)
end

View file

@ -1,7 +1,7 @@
module Mutant
# Abstract base class for test that might kill a mutation
class Test
include Adamantium::Flat, Concord::Public.new(:integration, :expression)
include Adamantium::Flat, Anima.new(:id, :expression)
# Return test identification
#
@ -9,47 +9,7 @@ module Mutant
#
# @api private
#
def identification
"#{integration.name}:#{expression.syntax}"
end
memoize :identification
# Kill mutation with test under isolation
#
# @param [Isolation] isolation
# @param [Mutation] mutation
#
# @return [Report::Test]
#
# @api private
#
def kill(isolation, mutation)
time = Time.now
isolation.call do
mutation.insert
run
end.update(test: self)
rescue Isolation::Error => exception
Result::Test.new(
test: self,
mutation: mutation,
runtime: Time.now - time,
output: exception.message,
passed: false
)
end
private
# Run test, return report
#
# @return [Report]
#
# @api private
#
def run
integration.run(self)
end
alias_method :identification, :id
end # Test
end # Mutant

View file

@ -38,12 +38,6 @@ RSpec.describe 'rspec integration' do
end
end
context 'RSpec 2' do
let(:gemfile) { 'Gemfile.rspec2' }
it_behaves_like 'rspec integration'
end
context 'RSpec 3.0' do
let(:gemfile) { 'Gemfile.rspec3.0' }

View file

@ -85,57 +85,35 @@ module SharedContext
let(:mutation_a_result) do
Mutant::Result::Mutation.new(
index: 1,
mutation: mutation_a,
test_results: [mutation_a_test_a_result, mutation_a_test_b_result]
index: 1,
mutation: mutation_a,
test_result: mutation_a_test_result
)
end
let(:mutation_b_result) do
Mutant::Result::Mutation.new(
index: 1,
mutation: mutation_a,
test_results: [mutation_b_test_a_result, mutation_b_test_b_result]
index: 1,
mutation: mutation_a,
test_result: mutation_b_test_result
)
end
let(:mutation_a_test_a_result) do
let(:mutation_a_test_result) do
Mutant::Result::Test.new(
mutation: mutation_a,
test: test_a,
tests: [test_a],
passed: false,
runtime: 1.0,
output: 'mutation a test a result output'
output: 'mutation a test result output'
)
end
let(:mutation_a_test_b_result) do
let(:mutation_b_test_result) do
Mutant::Result::Test.new(
mutation: mutation_b,
test: test_b,
tests: [test_a],
passed: false,
runtime: 1.0,
output: 'mutation a test b result output'
)
end
let(:mutation_b_test_a_result) do
Mutant::Result::Test.new(
mutation: mutation_b,
test: test_a,
passed: false,
runtime: 1.0,
output: 'mutation b test a result output'
)
end
let(:mutation_b_test_b_result) do
Mutant::Result::Test.new(
mutation: mutation_b,
test: test_b,
passed: false,
runtime: 1.0,
output: 'mutation b test b result output'
output: 'mutation b test result output'
)
end

View file

@ -160,7 +160,7 @@ Options:
it_should_behave_like 'a cli parser'
let(:expected_integration) { Mutant::Integration::Rspec.build }
let(:expected_integration) { Mutant::Integration::Rspec.new }
end
context 'when integration does NOT exist' do

View file

@ -1,63 +0,0 @@
RSpec.describe Mutant::Mutation::Evil do
let(:object) do
described_class.new(mutation_subject, double('node'))
end
let(:mutation_subject) { double('subject') }
describe '.continue?' do
subject { described_class.continue?(test_results) }
context 'with empty test results' do
let(:test_results) { [] }
it { should be(true) }
end
context 'with single passed test result' do
let(:test_results) { [double('test result', passed: true)] }
it { should be(true) }
end
context 'with failed test result' do
let(:test_results) { [double('test result', passed: false)] }
it { should be(false) }
end
context 'with passed test result and failed test result' do
let(:test_results) { [double('test result', passed: true), double('test result', passed: false)] }
it { should be(false) }
end
end
describe '.success?' do
subject { described_class.success?(test_results) }
context 'with empty test results' do
let(:test_results) { [] }
it { should be(false) }
end
context 'with single passed test result' do
let(:test_results) { [double('test result', passed: true)] }
it { should be(false) }
end
context 'with failed test result' do
let(:test_results) { [double('test result', passed: false)] }
it { should be(true) }
end
context 'with passed test result and failed test result' do
let(:test_results) { [double('test result', passed: true), double('test result', passed: false)] }
it { should be(true) }
end
end
end

View file

@ -1,63 +0,0 @@
RSpec.describe Mutant::Mutation::Neutral do
let(:object) do
described_class.new(mutation_subject, double('node'))
end
let(:mutation_subject) { double('subject') }
describe '.continue?' do
subject { described_class.continue?(test_results) }
context 'with empty test results' do
let(:test_results) { [] }
it { should be(true) }
end
context 'with single passed test result' do
let(:test_results) { [double('test result', passed: true)] }
it { should be(true) }
end
context 'with failed test result' do
let(:test_results) { [double('test result', passed: false)] }
it { should be(true) }
end
context 'with passed test result and failed test result' do
let(:test_results) { [double('test result', passed: true), double('test result', passed: false)] }
it { should be(true) }
end
end
describe '.success?' do
subject { described_class.success?(test_results) }
context 'with empty test results' do
let(:test_results) { [] }
it { should be(false) }
end
context 'with single passed test result' do
let(:test_results) { [double('test result', passed: true)] }
it { should be(true) }
end
context 'with failed test result' do
let(:test_results) { [double('test result', passed: false)] }
it { should be(false) }
end
context 'with passed test result and failed test result' do
let(:test_results) { [double('test result', passed: true), double('test result', passed: false)] }
it { should be(false) }
end
end
end

View file

@ -13,26 +13,48 @@ RSpec.describe Mutant::Mutation do
identification: 'subject',
context: context,
source: 'original',
tests: [test_a, test_b]
tests: tests
)
end
let(:test_a) { double('Test A') }
let(:test_b) { double('Test B') }
let(:tests) { [test_a, test_b] }
describe '#kill' do
let(:isolation) { double('Isolation') }
let(:object) { Mutant::Mutation::Evil.new(mutation_subject, Mutant::AST::Nodes::N_NIL) }
let(:isolation) { Mutant::Isolation::None }
let(:integration) { double('Integration') }
let(:object) { Mutant::Mutation::Evil.new(mutation_subject, Mutant::AST::Nodes::N_NIL) }
let(:wrapped_node) { double('Wrapped Node') }
let(:test_result_a) { double('Test Result A', passed: false) }
subject { object.kill(isolation, integration) }
before do
expect(test_a).to receive(:kill).with(isolation, object).and_return(test_result_a)
allow(Time).to receive(:now).and_return(Time.at(0))
end
subject { object.kill(isolation) }
context 'when isolation does not raise error' do
let(:test_result) { double('Test Result A', passed: false) }
it { should eql(Mutant::Result::Mutation.new(index: nil, mutation: object, test_results: [test_result_a])) }
before do
expect(mutation_subject).to receive(:public?).and_return(true).ordered
expect(mutation_subject).to receive(:prepare).and_return(mutation_subject).ordered
expect(context).to receive(:root).with(s(:nil)).and_return(wrapped_node).ordered
expect(Mutant::Loader::Eval).to receive(:call).with(wrapped_node, mutation_subject).and_return(nil).ordered
expect(integration).to receive(:call).with(tests).and_return(test_result).ordered
expect(test_result).to receive(:update).with(tests: tests).and_return(test_result).ordered
end
it { should eql(test_result) }
end
context 'when isolation does raise error' do
before do
expect(isolation).to receive(:call).and_raise(Mutant::Isolation::Error, 'test-error')
end
it { should eql(Mutant::Result::Test.new(tests: tests, output: 'test-error', passed: false, runtime: 0.0)) }
end
end
describe '#code' do
@ -51,21 +73,6 @@ RSpec.describe Mutant::Mutation do
it_should_behave_like 'an idempotent method'
end
describe '#insert' do
subject { object.insert }
let(:wrapped_node) { double('Wrapped Node') }
before do
expect(mutation_subject).to receive(:public?).ordered.and_return(true)
expect(mutation_subject).to receive(:prepare).ordered
expect(context).to receive(:root).ordered.with(s(:nil)).and_return(wrapped_node)
expect(Mutant::Loader::Eval).to receive(:call).ordered.with(wrapped_node, mutation_subject).and_return(nil)
end
it_should_behave_like 'a command method'
end
describe '#source' do
subject { object.source }

View file

@ -127,8 +127,7 @@ RSpec.describe Mutant::Reporter::CLI do
end
context 'when mutation is NOT successful' do
update(:mutation_a_test_a_result) { { passed: true } }
update(:mutation_a_test_b_result) { { passed: true } }
update(:mutation_a_test_result) { { passed: true } }
it_reports 'F.'
end
end
@ -178,8 +177,8 @@ RSpec.describe Mutant::Reporter::CLI do
Kills: 2
Alive: 0
Runtime: 4.00s
Killtime: 4.00s
Overhead: 0.00%
Killtime: 2.00s
Overhead: 100.00%
Coverage: 100.00%
Expected: 100.00%
Active subjects: 0
@ -190,8 +189,7 @@ RSpec.describe Mutant::Reporter::CLI do
update(:status) { { active_jobs: [job_a].to_set } }
context 'on failure' do
update(:mutation_a_test_a_result) { { passed: true } }
update(:mutation_a_test_b_result) { { passed: true } }
update(:mutation_a_test_result) { { passed: true } }
it_reports(<<-REPORT)
Mutant configuration:
@ -207,15 +205,15 @@ RSpec.describe Mutant::Reporter::CLI do
Kills: 1
Alive: 1
Runtime: 4.00s
Killtime: 4.00s
Overhead: 0.00%
Killtime: 2.00s
Overhead: 100.00%
Coverage: 50.00%
Expected: 100.00%
Active subjects: 1
subject-a mutations: 2
- test-a
F.
(01/02) 50% - killtime: 4.00s runtime: 4.00s overhead: 0.00s
(01/02) 50% - killtime: 2.00s runtime: 2.00s overhead: 0.00s
Active Jobs:
0: evil:subject-a:d27d2
REPORT
@ -236,15 +234,15 @@ RSpec.describe Mutant::Reporter::CLI do
Kills: 2
Alive: 0
Runtime: 4.00s
Killtime: 4.00s
Overhead: 0.00%
Killtime: 2.00s
Overhead: 100.00%
Coverage: 100.00%
Expected: 100.00%
Active subjects: 1
subject-a mutations: 2
- test-a
..
(02/02) 100% - killtime: 4.00s runtime: 4.00s overhead: 0.00s
(02/02) 100% - killtime: 2.00s runtime: 2.00s overhead: 0.00s
Active Jobs:
0: evil:subject-a:d27d2
REPORT
@ -271,16 +269,15 @@ RSpec.describe Mutant::Reporter::CLI do
Kills: 2
Alive: 0
Runtime: 4.00s
Killtime: 4.00s
Overhead: 0.00%
Killtime: 2.00s
Overhead: 100.00%
Coverage: 100.00%
Expected: 100.00%
REPORT
end
context 'and partial coverage' do
update(:mutation_a_test_a_result) { { passed: true } }
update(:mutation_a_test_b_result) { { passed: true } }
update(:mutation_a_test_result) { { passed: true } }
context 'on evil mutation' do
context 'with a diff' do
@ -305,8 +302,8 @@ RSpec.describe Mutant::Reporter::CLI do
Kills: 1
Alive: 1
Runtime: 4.00s
Killtime: 4.00s
Overhead: 0.00%
Killtime: 2.00s
Overhead: 100.00%
Coverage: 50.00%
Expected: 100.00%
REPORT
@ -338,8 +335,8 @@ RSpec.describe Mutant::Reporter::CLI do
Kills: 1
Alive: 1
Runtime: 4.00s
Killtime: 4.00s
Overhead: 0.00%
Killtime: 2.00s
Overhead: 100.00%
Coverage: 50.00%
Expected: 100.00%
REPORT
@ -347,8 +344,7 @@ RSpec.describe Mutant::Reporter::CLI do
end
context 'on neutral mutation' do
update(:mutation_a_test_a_result) { { passed: false } }
update(:mutation_a_test_b_result) { { passed: false } }
update(:mutation_a_test_result) { { passed: false } }
let(:mutation_a) do
Mutant::Mutation::Neutral.new(subject_a, s(:true))
@ -365,13 +361,11 @@ RSpec.describe Mutant::Reporter::CLI do
(true)
Unparsed Source:
true
Test Reports: 2
- test-a / runtime: 1.0
Test Result:
- 1 @ runtime: 1.0
- test-a
Test Output:
mutation a test a result output
- test-b / runtime: 1.0
Test Output:
mutation a test b result output
mutation a test result output
-----------------------
neutral:subject-a:d5318
--- Neutral failure ---
@ -381,13 +375,11 @@ RSpec.describe Mutant::Reporter::CLI do
(true)
Unparsed Source:
true
Test Reports: 2
- test-a / runtime: 1.0
Test Result:
- 1 @ runtime: 1.0
- test-a
Test Output:
mutation b test a result output
- test-b / runtime: 1.0
Test Output:
mutation b test b result output
mutation b test result output
-----------------------
Mutant configuration:
Matcher: #<Mutant::Matcher::Config match_expressions=[] subject_ignores=[] subject_selects=[]>
@ -402,16 +394,15 @@ RSpec.describe Mutant::Reporter::CLI do
Kills: 0
Alive: 2
Runtime: 4.00s
Killtime: 4.00s
Overhead: 0.00%
Killtime: 2.00s
Overhead: 100.00%
Coverage: 0.00%
Expected: 100.00%
REPORT
end
context 'on noop mutation' do
update(:mutation_a_test_a_result) { { passed: false } }
update(:mutation_a_test_b_result) { { passed: false } }
update(:mutation_a_test_result) { { passed: false } }
let(:mutation_a) do
Mutant::Mutation::Noop.new(subject_a, s(:true))
@ -424,25 +415,21 @@ RSpec.describe Mutant::Reporter::CLI do
---- Noop failure -----
No code was inserted. And the test did NOT PASS.
This is typically a problem of your specs not passing unmutated.
Test Reports: 2
- test-a / runtime: 1.0
Test Result:
- 1 @ runtime: 1.0
- test-a
Test Output:
mutation a test a result output
- test-b / runtime: 1.0
Test Output:
mutation a test b result output
mutation a test result output
-----------------------
noop:subject-a:d5318
---- Noop failure -----
No code was inserted. And the test did NOT PASS.
This is typically a problem of your specs not passing unmutated.
Test Reports: 2
- test-a / runtime: 1.0
Test Result:
- 1 @ runtime: 1.0
- test-a
Test Output:
mutation b test a result output
- test-b / runtime: 1.0
Test Output:
mutation b test b result output
mutation b test result output
-----------------------
Mutant configuration:
Matcher: #<Mutant::Matcher::Config match_expressions=[] subject_ignores=[] subject_selects=[]>
@ -457,8 +444,8 @@ RSpec.describe Mutant::Reporter::CLI do
Kills: 0
Alive: 2
Runtime: 4.00s
Killtime: 4.00s
Overhead: 0.00%
Killtime: 2.00s
Overhead: 100.00%
Coverage: 0.00%
Expected: 100.00%
REPORT

View file

@ -1,19 +0,0 @@
RSpec.describe Mutant::Result::Mutation do
let(:object) do
described_class.new(
index: 0,
mutation: double('mutation'),
test_results: double('test results')
)
end
describe '#continue?' do
subject { object.continue? }
it 'forwards calls to mutation' do
return_value = double('return value')
expect(object.mutation.class).to receive(:continue?).with(object.test_results).and_return(return_value)
expect(subject).to be(return_value)
end
end
end

View file

@ -54,9 +54,8 @@ RSpec.describe Mutant::Runner::Master do
end
context 'stop by fail fast trigger first' do
update(:config) { { fail_fast: true } }
update(:mutation_b_test_a_result) { { passed: true } }
update(:mutation_b_test_b_result) { { passed: true } }
update(:config) { { fail_fast: true } }
update(:mutation_b_test_result) { { passed: true } }
before do
message_sequence.add(:master, :ready, worker_a)
@ -82,9 +81,8 @@ RSpec.describe Mutant::Runner::Master do
end
context 'stop by fail fast trigger last' do
update(:config) { { fail_fast: true } }
update(:mutation_a_test_a_result) { { passed: true } }
update(:mutation_a_test_b_result) { { passed: true } }
update(:config) { { fail_fast: true } }
update(:mutation_a_test_result) { { passed: true } }
before do
message_sequence.add(:master, :ready, worker_a)

View file

@ -123,10 +123,9 @@ describe Mutant::Runner::Scheduler do
object.job_result(job_a_result)
end
update(:subject_a_result) { { mutation_results: [mutation_a_result] } }
update(:mutation_a_test_a_result) { { passed: true } }
update(:mutation_a_test_b_result) { { passed: true } }
update(:config) { { fail_fast: true } }
update(:subject_a_result) { { mutation_results: [mutation_a_result] } }
update(:mutation_a_test_result) { { passed: true } }
update(:config) { { fail_fast: true } }
let(:expected_status) do
Mutant::Runner::Status.new(

View file

@ -23,9 +23,10 @@ RSpec.describe Mutant::Runner::Worker do
context 'when receving :job command' do
let(:test_result) { double('Test Result') }
before do
expect(mutation).to receive(:kill).with(config.isolation).and_return(mutation_result).ordered
expect(mutation_result).to receive(:update).with(index: job.index).and_return(mutation_result).ordered
expect(mutation).to receive(:kill).with(config.isolation, config.integration).and_return(test_result).ordered
message_sequence.add(:worker, :job, job)
message_sequence.add(:parent, :result, job_result)
@ -37,10 +38,17 @@ RSpec.describe Mutant::Runner::Worker do
let(:index) { double('Index') }
let(:test_result) { double('Test Result') }
let(:mutation) { double('Mutation') }
let(:mutation_result) { double('Mutation Result') }
let(:job_result) { Mutant::Runner::JobResult.new(job: job, result: mutation_result) }
let(:job) { Mutant::Runner::Job.new(index: index, mutation: mutation) }
let(:mutation_result) do
Mutant::Result::Mutation.new(
mutation: mutation,
index: job.index,
test_result: test_result
)
end
it 'signals ready and status to parent' do
subject
end

View file

@ -1,53 +0,0 @@
RSpec.describe Mutant::Test do
let(:object) { described_class.new(integration, expression) }
let(:integration) { double('Integration', name: 'test-integration') }
let(:expression) { double('Expression', syntax: 'test-syntax') }
let(:report) { double('Report') }
let(:updated_report) { double('Updated Report') }
describe '#identification' do
subject { object.identification }
it { should eql('test-integration:test-syntax') }
end
describe '#kill' do
let(:isolation) { Mutant::Isolation::None }
let(:mutation) { double('Mutation') }
subject { object.kill(isolation, mutation) }
before do
expect(mutation).to receive(:insert)
end
context 'when isolation does not raise' do
before do
expect(report).to receive(:update).with(test: object).and_return(updated_report)
end
it 'runs test via integration' do
expect(integration).to receive(:run).with(object).and_return(report)
expect(subject).to be(updated_report)
end
end
context 'when isolation does raise' do
before do
allow(Time).to receive(:now).and_return(Time.at(0))
end
it 'runs test via integration' do
expect(integration).to receive(:run).with(object).and_raise(Mutant::Isolation::Error, 'fake message')
expect(subject).to eql(Mutant::Result::Test.new(
test: object,
mutation: mutation,
output: 'fake message',
passed: false,
runtime: 0.0
))
end
end
end
end

View file

@ -1,6 +0,0 @@
source 'https://rubygems.org'
gem 'rspec', '~> 2.14.1'
gem 'rspec-core', '~> 2.14.1'
gem 'mutant', path: '../'
gem 'mutant-rspec', path: '../'