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:
parent
8cbb5e288a
commit
40c337ce5b
22 changed files with 246 additions and 656 deletions
|
@ -1,3 +1,3 @@
|
||||||
---
|
---
|
||||||
threshold: 18
|
threshold: 18
|
||||||
total_score: 1179
|
total_score: 1143
|
||||||
|
|
|
@ -27,9 +27,7 @@ FeatureEnvy:
|
||||||
exclude:
|
exclude:
|
||||||
- Mutant::Env#scope_name
|
- Mutant::Env#scope_name
|
||||||
- Mutant::Diff#minimized_hunks
|
- Mutant::Diff#minimized_hunks
|
||||||
- Mutant::Integration::Rspec#run
|
- Mutant::Integration::Rspec#parse_example
|
||||||
- Mutant::Integration::Rspec::Rspec2#full_description
|
|
||||||
- Mutant::Integration::Rspec::Rspec3#full_description
|
|
||||||
- Mutant::Matcher::Method::Instance#match?
|
- Mutant::Matcher::Method::Instance#match?
|
||||||
- Mutant::Matcher::Method::Singleton#receiver?
|
- Mutant::Matcher::Method::Singleton#receiver?
|
||||||
- Mutant::Mutator::Node#children_indices
|
- Mutant::Mutator::Node#children_indices
|
||||||
|
@ -56,7 +54,6 @@ NestedIterators:
|
||||||
exclude:
|
exclude:
|
||||||
- Mutant#self.singleton_subclass_instance
|
- Mutant#self.singleton_subclass_instance
|
||||||
- Mutant::CLI#parse
|
- Mutant::CLI#parse
|
||||||
- Mutant::Integration::Rspec#run
|
|
||||||
- Mutant::Isolation::Fork#self.call
|
- Mutant::Isolation::Fork#self.call
|
||||||
- Mutant::Mutator::Util::Array::Element#dispatch
|
- Mutant::Mutator::Util::Array::Element#dispatch
|
||||||
- Mutant::Mutator::Node::Resbody#mutate_captures
|
- Mutant::Mutator::Node::Resbody#mutate_captures
|
||||||
|
@ -93,7 +90,6 @@ TooManyMethods:
|
||||||
TooManyStatements:
|
TooManyStatements:
|
||||||
enabled: true
|
enabled: true
|
||||||
exclude:
|
exclude:
|
||||||
- Mutant::Integration::Rspec#run
|
|
||||||
- Mutant::Isolation::Fork#self.call
|
- Mutant::Isolation::Fork#self.call
|
||||||
- Mutant::Reporter::CLI::Printer::EnvProgress#run
|
- Mutant::Reporter::CLI::Printer::EnvProgress#run
|
||||||
- Mutant::Reporter::CLI::Printer::Config#run
|
- Mutant::Reporter::CLI::Printer::Config#run
|
||||||
|
@ -113,9 +109,7 @@ UncommunicativeMethodName:
|
||||||
accept: []
|
accept: []
|
||||||
UncommunicativeModuleName:
|
UncommunicativeModuleName:
|
||||||
enabled: true
|
enabled: true
|
||||||
exclude:
|
exclude: []
|
||||||
- Rspec2
|
|
||||||
- Rspec3
|
|
||||||
reject:
|
reject:
|
||||||
- !ruby/regexp /^.$/
|
- !ruby/regexp /^.$/
|
||||||
- !ruby/regexp /[0-9]$/
|
- !ruby/regexp /[0-9]$/
|
||||||
|
@ -144,11 +138,7 @@ UtilityFunction:
|
||||||
exclude:
|
exclude:
|
||||||
- Mutant::AST::Sexp#s
|
- Mutant::AST::Sexp#s
|
||||||
- Mutant::CLI#reporter
|
- Mutant::CLI#reporter
|
||||||
- Mutant::Integration::Rspec#configuration
|
- Mutant::Integration::Rspec#parse_example
|
||||||
- Mutant::Integration::Rspec#options
|
- Mutant::Meta::Example::Verification#format_mutation # False positive, its a utility
|
||||||
- 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::Reporter::CLI::Format::Progressive#new_buffer
|
- Mutant::Reporter::CLI::Format::Progressive#new_buffer
|
||||||
max_helper_calls: 0
|
max_helper_calls: 0
|
||||||
|
|
|
@ -16,7 +16,7 @@ module Mutant
|
||||||
# @api private
|
# @api private
|
||||||
#
|
#
|
||||||
def self.lookup(name)
|
def self.lookup(name)
|
||||||
REGISTRY.fetch(name).build
|
REGISTRY.fetch(name).new
|
||||||
end
|
end
|
||||||
|
|
||||||
# Register integration
|
# Register integration
|
||||||
|
@ -44,6 +44,16 @@ module Mutant
|
||||||
self
|
self
|
||||||
end
|
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 all available tests by integration
|
||||||
#
|
#
|
||||||
# @return [Enumerable<Test>]
|
# @return [Enumerable<Test>]
|
||||||
|
|
|
@ -5,24 +5,25 @@ module Mutant
|
||||||
class Integration
|
class Integration
|
||||||
# Shared parts of rspec2/3 integration
|
# Shared parts of rspec2/3 integration
|
||||||
class Rspec < self
|
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'
|
register 'rspec'
|
||||||
|
|
||||||
RSPEC_2_VERSION_PREFIX = '2.'.freeze
|
# Initialize rspec integration
|
||||||
|
|
||||||
# Return integration compatible to currently loaded rspec
|
|
||||||
#
|
#
|
||||||
# @return [Integration]
|
# @return [undefined]
|
||||||
#
|
#
|
||||||
# @api private
|
# @api private
|
||||||
#
|
#
|
||||||
def self.build
|
def initialize
|
||||||
if RSpec::Core::Version::STRING.start_with?(RSPEC_2_VERSION_PREFIX)
|
@output = StringIO.new
|
||||||
Rspec2.new
|
@runner = RSpec::Core::Runner.new(RSpec::Core::ConfigurationOptions.new(CLI_OPTIONS))
|
||||||
else
|
@world = RSpec.world
|
||||||
Rspec3.new
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# Setup rspec integration
|
# Setup rspec integration
|
||||||
|
@ -32,15 +33,14 @@ module Mutant
|
||||||
# @api private
|
# @api private
|
||||||
#
|
#
|
||||||
def setup
|
def setup
|
||||||
options.configure(configuration)
|
@runner.setup($stderr, @output)
|
||||||
configuration.load_spec_files
|
|
||||||
self
|
self
|
||||||
end
|
end
|
||||||
memoize :setup
|
memoize :setup
|
||||||
|
|
||||||
# Return report for test
|
# Return report for test
|
||||||
#
|
#
|
||||||
# @param [Rspec::Test] test
|
# @param [Enumerable<Mutant::Test>] tests
|
||||||
#
|
#
|
||||||
# @return [Test::Result]
|
# @return [Test::Result]
|
||||||
#
|
#
|
||||||
|
@ -48,25 +48,17 @@ module Mutant
|
||||||
#
|
#
|
||||||
# rubocop:disable MethodLength
|
# rubocop:disable MethodLength
|
||||||
#
|
#
|
||||||
def run(test)
|
def call(tests)
|
||||||
output = StringIO.new
|
examples = tests.map(&all_tests_index.method(:fetch)).to_set
|
||||||
failed = false
|
filter_examples(&examples.method(:include?))
|
||||||
start = Time.now
|
start = Time.now
|
||||||
reporter = new_reporter(output)
|
passed = @runner.run_specs(RSpec.world.ordered_example_groups).equal?(EXIT_SUCCESS)
|
||||||
reporter.report(1) do
|
@output.rewind
|
||||||
example_group_index.fetch(test.expression.syntax).each do |example_group|
|
|
||||||
next if example_group.run(reporter)
|
|
||||||
failed = true
|
|
||||||
break
|
|
||||||
end
|
|
||||||
end
|
|
||||||
output.rewind
|
|
||||||
Result::Test.new(
|
Result::Test.new(
|
||||||
test: nil,
|
tests: nil,
|
||||||
mutation: nil,
|
output: @output.read,
|
||||||
output: output.read,
|
|
||||||
runtime: Time.now - start,
|
runtime: Time.now - start,
|
||||||
passed: !failed
|
passed: passed
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -77,136 +69,69 @@ module Mutant
|
||||||
# @api private
|
# @api private
|
||||||
#
|
#
|
||||||
def all_tests
|
def all_tests
|
||||||
example_group_index.keys.each_with_object([]) do |full_description, aggregate|
|
all_tests_index.keys
|
||||||
expression = Expression.try_parse(full_description) or next
|
|
||||||
|
|
||||||
aggregate << Test.new(self, expression)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
memoize :all_tests
|
memoize :all_tests
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
# Return all example groups
|
# Return all tests index
|
||||||
#
|
#
|
||||||
# @return [Hash<String, RSpec::Core::ExampleGroup]
|
# @return [Hash<Test, RSpec::Core::Example]
|
||||||
#
|
#
|
||||||
# @api private
|
# @api private
|
||||||
#
|
#
|
||||||
def example_group_index
|
def all_tests_index
|
||||||
index = Hash.new { |hash, key| hash[key] = [] }
|
all_examples.each_with_object({}) do |example, index|
|
||||||
|
index[parse_example(example)] = example
|
||||||
RSpec.world.example_groups.flat_map(&:descendants).each do |example_group|
|
|
||||||
full_description = full_description(example_group)
|
|
||||||
index[full_description] << example_group
|
|
||||||
end
|
end
|
||||||
|
|
||||||
index
|
|
||||||
end
|
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
|
# @api private
|
||||||
#
|
#
|
||||||
def configuration
|
def parse_example(example)
|
||||||
RSpec::Core::Configuration.new
|
metadata = example.metadata
|
||||||
end
|
location = metadata.fetch(:location)
|
||||||
memoize :configuration, freezer: :noop
|
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
|
# @api private
|
||||||
#
|
#
|
||||||
def options
|
def all_examples
|
||||||
RSpec::Core::ConfigurationOptions.new(%w[--fail-fast spec])
|
@world.example_groups.flat_map(&:descendants).flat_map(&:examples)
|
||||||
end
|
end
|
||||||
memoize :options, freezer: :noop
|
|
||||||
|
|
||||||
# Rspec2 integration
|
# Filter examples
|
||||||
class Rspec2 < self
|
#
|
||||||
|
# @param [#call] predicate
|
||||||
register 'rspec'
|
#
|
||||||
|
# @return [undefined]
|
||||||
private
|
#
|
||||||
|
# @api private
|
||||||
# Return options
|
#
|
||||||
#
|
def filter_examples(&predicate)
|
||||||
# @return [RSpec::Core::ConfigurationOptions]
|
@world.filtered_examples.each_value do |examples|
|
||||||
#
|
examples.keep_if(&predicate)
|
||||||
# @api private
|
|
||||||
#
|
|
||||||
def options
|
|
||||||
super.tap(&:parse_options)
|
|
||||||
end
|
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 # Rspec
|
||||||
end # Integration
|
end # Integration
|
||||||
end # Mutant
|
end # Mutant
|
||||||
|
|
|
@ -7,45 +7,30 @@ module Mutant
|
||||||
CODE_DELIMITER = "\0".freeze
|
CODE_DELIMITER = "\0".freeze
|
||||||
CODE_RANGE = (0..4).freeze
|
CODE_RANGE = (0..4).freeze
|
||||||
|
|
||||||
# Kill mutation via isolation
|
# Kill mutation under isolation with integration
|
||||||
#
|
#
|
||||||
# @param [Isolation] isolation
|
# @param [Isolation] isolation
|
||||||
|
# @param [Integration] integration
|
||||||
#
|
#
|
||||||
# @return [Result::Mutation]
|
# @return [Result::Test]
|
||||||
#
|
#
|
||||||
# @api private
|
# @api private
|
||||||
#
|
#
|
||||||
def kill(isolation)
|
def kill(isolation, integration)
|
||||||
result = Result::Mutation.new(
|
start = Time.now
|
||||||
index: nil,
|
tests = subject.tests
|
||||||
mutation: self,
|
|
||||||
test_results: []
|
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
|
end
|
||||||
|
|
||||||
# Return identification
|
# Return identification
|
||||||
|
@ -99,17 +84,28 @@ module Mutant
|
||||||
#
|
#
|
||||||
# @api private
|
# @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
|
# @api private
|
||||||
#
|
#
|
||||||
abstract_singleton_method :continue?
|
def insert
|
||||||
|
subject.public?
|
||||||
private
|
subject.prepare
|
||||||
|
Loader::Eval.call(root, subject)
|
||||||
|
self
|
||||||
|
end
|
||||||
|
|
||||||
# Return sha1 sum of source and subject identification
|
# Return sha1 sum of source and subject identification
|
||||||
#
|
#
|
||||||
|
@ -135,65 +131,24 @@ module Mutant
|
||||||
# Evil mutation that should case mutations to fail tests
|
# Evil mutation that should case mutations to fail tests
|
||||||
class Evil < self
|
class Evil < self
|
||||||
|
|
||||||
SYMBOL = 'evil'.freeze
|
SYMBOL = 'evil'.freeze
|
||||||
|
TEST_PASS_SUCCESS = false
|
||||||
# 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
|
|
||||||
|
|
||||||
end # Evil
|
end # Evil
|
||||||
|
|
||||||
# Neutral mutation that should not cause mutations to fail tests
|
# Neutral mutation that should not cause mutations to fail tests
|
||||||
class Neutral < self
|
class Neutral < self
|
||||||
|
|
||||||
SYMBOL = 'neutral'.freeze
|
SYMBOL = 'neutral'.freeze
|
||||||
|
TEST_PASS_SUCCESS = true
|
||||||
# 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
|
|
||||||
|
|
||||||
end # Neutral
|
end # Neutral
|
||||||
|
|
||||||
# Noop mutation, special case of neutral
|
# Noop mutation, special case of neutral
|
||||||
class Noop < Neutral
|
class Noop < Neutral
|
||||||
|
|
||||||
SYMBOL = 'noop'.freeze
|
SYMBOL = 'noop'.freeze
|
||||||
|
TEST_PASS_SUCCESS = true
|
||||||
|
|
||||||
end # Noop
|
end # Noop
|
||||||
|
|
||||||
|
|
|
@ -430,7 +430,7 @@ module Mutant
|
||||||
# Reporter for mutation results
|
# Reporter for mutation results
|
||||||
class MutationResult < self
|
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
|
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" \
|
"%s\n" \
|
||||||
"Unparsed Source:\n" \
|
"Unparsed Source:\n" \
|
||||||
"%s\n" \
|
"%s\n" \
|
||||||
"Test Reports: %d\n"
|
"Test Result:\n".freeze
|
||||||
|
|
||||||
NOOP_MESSAGE =
|
NOOP_MESSAGE =
|
||||||
"---- Noop failure -----\n" \
|
"---- Noop failure -----\n" \
|
||||||
"No code was inserted. And the test did NOT PASS.\n" \
|
"No code was inserted. And the test did NOT PASS.\n" \
|
||||||
"This is typically a problem of your specs not passing unmutated.\n" \
|
"This is typically a problem of your specs not passing unmutated.\n" \
|
||||||
"Test Reports: %d\n"
|
"Test Result:\n".freeze
|
||||||
|
|
||||||
FOOTER = '-----------------------'.freeze
|
FOOTER = '-----------------------'.freeze
|
||||||
|
|
||||||
|
@ -503,8 +503,8 @@ module Mutant
|
||||||
# @api private
|
# @api private
|
||||||
#
|
#
|
||||||
def noop_details
|
def noop_details
|
||||||
info(NOOP_MESSAGE, failed_test_results.length)
|
info(NOOP_MESSAGE)
|
||||||
visit_failed_test_results
|
visit_test_result
|
||||||
end
|
end
|
||||||
|
|
||||||
# Neutral details
|
# Neutral details
|
||||||
|
@ -514,8 +514,8 @@ module Mutant
|
||||||
# @api private
|
# @api private
|
||||||
#
|
#
|
||||||
def neutral_details
|
def neutral_details
|
||||||
info(NEUTRAL_MESSAGE, mutation.subject.node.inspect, mutation.source, failed_test_results.length)
|
info(NEUTRAL_MESSAGE, mutation.subject.node.inspect, mutation.source)
|
||||||
visit_failed_test_results
|
visit_test_result
|
||||||
end
|
end
|
||||||
|
|
||||||
# Visit failed test results
|
# Visit failed test results
|
||||||
|
@ -524,8 +524,8 @@ module Mutant
|
||||||
#
|
#
|
||||||
# @api private
|
# @api private
|
||||||
#
|
#
|
||||||
def visit_failed_test_results
|
def visit_test_result
|
||||||
visit_collection(TestResult, failed_test_results)
|
visit(TestResult, test_result)
|
||||||
end
|
end
|
||||||
|
|
||||||
end # MutationResult
|
end # MutationResult
|
||||||
|
@ -533,7 +533,7 @@ module Mutant
|
||||||
# Test result reporter
|
# Test result reporter
|
||||||
class TestResult < self
|
class TestResult < self
|
||||||
|
|
||||||
delegate :test, :runtime, :mutation
|
delegate :tests, :runtime
|
||||||
|
|
||||||
# Run test result reporter
|
# Run test result reporter
|
||||||
#
|
#
|
||||||
|
@ -542,7 +542,10 @@ module Mutant
|
||||||
# @api private
|
# @api private
|
||||||
#
|
#
|
||||||
def run
|
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('Test Output:')
|
||||||
puts(object.output)
|
puts(object.output)
|
||||||
end
|
end
|
||||||
|
|
|
@ -148,21 +148,11 @@ module Mutant
|
||||||
# Test result
|
# Test result
|
||||||
class Test
|
class Test
|
||||||
include Result, Anima.new(
|
include Result, Anima.new(
|
||||||
:test,
|
:tests,
|
||||||
:output,
|
:output,
|
||||||
:mutation,
|
|
||||||
:passed,
|
:passed,
|
||||||
:runtime
|
:runtime
|
||||||
)
|
)
|
||||||
|
|
||||||
# Return killtime
|
|
||||||
#
|
|
||||||
# @return [Float]
|
|
||||||
#
|
|
||||||
# @api private
|
|
||||||
#
|
|
||||||
alias_method :killtime, :runtime
|
|
||||||
|
|
||||||
end # Test
|
end # Test
|
||||||
|
|
||||||
# Subject result
|
# Subject result
|
||||||
|
@ -258,20 +248,19 @@ module Mutant
|
||||||
|
|
||||||
# Mutation result
|
# Mutation result
|
||||||
class Mutation
|
class Mutation
|
||||||
include Result, Anima.new(:mutation, :test_results, :index)
|
include Result, Anima.new(:mutation, :test_result, :index)
|
||||||
|
|
||||||
sum :runtime, :test_results
|
# Return runtime
|
||||||
|
|
||||||
# Return failed test results
|
|
||||||
#
|
#
|
||||||
# @return [Array<Result::Test>]
|
# @return [Float]
|
||||||
#
|
#
|
||||||
# @api private
|
# @api private
|
||||||
#
|
#
|
||||||
def failed_test_results
|
def runtime
|
||||||
test_results.reject(&:passed)
|
test_result.runtime
|
||||||
end
|
end
|
||||||
memoize :failed_test_results
|
|
||||||
|
alias_method :killtime, :runtime
|
||||||
|
|
||||||
# Test if mutation was handled successfully
|
# Test if mutation was handled successfully
|
||||||
#
|
#
|
||||||
|
@ -280,22 +269,9 @@ module Mutant
|
||||||
# @api private
|
# @api private
|
||||||
#
|
#
|
||||||
def success?
|
def success?
|
||||||
mutation.class.success?(test_results)
|
mutation.class.success?(test_result)
|
||||||
end
|
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 # Mutation
|
||||||
|
|
||||||
end # Result
|
end # Result
|
||||||
end # Mutant
|
end # Mutant
|
||||||
|
|
|
@ -79,8 +79,12 @@ module Mutant
|
||||||
# @api private
|
# @api private
|
||||||
#
|
#
|
||||||
def run_mutation(job)
|
def run_mutation(job)
|
||||||
job.mutation.kill(config.isolation).update(
|
mutation = job.mutation
|
||||||
index: job.index
|
test_result = mutation.kill(config.isolation, config.integration)
|
||||||
|
Result::Mutation.new(
|
||||||
|
index: job.index,
|
||||||
|
mutation: mutation,
|
||||||
|
test_result: test_result
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
module Mutant
|
module Mutant
|
||||||
# Abstract base class for test that might kill a mutation
|
# Abstract base class for test that might kill a mutation
|
||||||
class Test
|
class Test
|
||||||
include Adamantium::Flat, Concord::Public.new(:integration, :expression)
|
include Adamantium::Flat, Anima.new(:id, :expression)
|
||||||
|
|
||||||
# Return test identification
|
# Return test identification
|
||||||
#
|
#
|
||||||
|
@ -9,47 +9,7 @@ module Mutant
|
||||||
#
|
#
|
||||||
# @api private
|
# @api private
|
||||||
#
|
#
|
||||||
def identification
|
alias_method :identification, :id
|
||||||
"#{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
|
|
||||||
|
|
||||||
end # Test
|
end # Test
|
||||||
end # Mutant
|
end # Mutant
|
||||||
|
|
|
@ -38,12 +38,6 @@ RSpec.describe 'rspec integration' do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'RSpec 2' do
|
|
||||||
let(:gemfile) { 'Gemfile.rspec2' }
|
|
||||||
|
|
||||||
it_behaves_like 'rspec integration'
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'RSpec 3.0' do
|
context 'RSpec 3.0' do
|
||||||
let(:gemfile) { 'Gemfile.rspec3.0' }
|
let(:gemfile) { 'Gemfile.rspec3.0' }
|
||||||
|
|
||||||
|
|
|
@ -85,57 +85,35 @@ module SharedContext
|
||||||
|
|
||||||
let(:mutation_a_result) do
|
let(:mutation_a_result) do
|
||||||
Mutant::Result::Mutation.new(
|
Mutant::Result::Mutation.new(
|
||||||
index: 1,
|
index: 1,
|
||||||
mutation: mutation_a,
|
mutation: mutation_a,
|
||||||
test_results: [mutation_a_test_a_result, mutation_a_test_b_result]
|
test_result: mutation_a_test_result
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
let(:mutation_b_result) do
|
let(:mutation_b_result) do
|
||||||
Mutant::Result::Mutation.new(
|
Mutant::Result::Mutation.new(
|
||||||
index: 1,
|
index: 1,
|
||||||
mutation: mutation_a,
|
mutation: mutation_a,
|
||||||
test_results: [mutation_b_test_a_result, mutation_b_test_b_result]
|
test_result: mutation_b_test_result
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
let(:mutation_a_test_a_result) do
|
let(:mutation_a_test_result) do
|
||||||
Mutant::Result::Test.new(
|
Mutant::Result::Test.new(
|
||||||
mutation: mutation_a,
|
tests: [test_a],
|
||||||
test: test_a,
|
|
||||||
passed: false,
|
passed: false,
|
||||||
runtime: 1.0,
|
runtime: 1.0,
|
||||||
output: 'mutation a test a result output'
|
output: 'mutation a test result output'
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
let(:mutation_a_test_b_result) do
|
let(:mutation_b_test_result) do
|
||||||
Mutant::Result::Test.new(
|
Mutant::Result::Test.new(
|
||||||
mutation: mutation_b,
|
tests: [test_a],
|
||||||
test: test_b,
|
|
||||||
passed: false,
|
passed: false,
|
||||||
runtime: 1.0,
|
runtime: 1.0,
|
||||||
output: 'mutation a test b result output'
|
output: 'mutation b test 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'
|
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -160,7 +160,7 @@ Options:
|
||||||
|
|
||||||
it_should_behave_like 'a cli parser'
|
it_should_behave_like 'a cli parser'
|
||||||
|
|
||||||
let(:expected_integration) { Mutant::Integration::Rspec.build }
|
let(:expected_integration) { Mutant::Integration::Rspec.new }
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when integration does NOT exist' do
|
context 'when integration does NOT exist' do
|
||||||
|
|
|
@ -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
|
|
|
@ -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
|
|
|
@ -13,26 +13,48 @@ RSpec.describe Mutant::Mutation do
|
||||||
identification: 'subject',
|
identification: 'subject',
|
||||||
context: context,
|
context: context,
|
||||||
source: 'original',
|
source: 'original',
|
||||||
tests: [test_a, test_b]
|
tests: tests
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
let(:test_a) { double('Test A') }
|
let(:test_a) { double('Test A') }
|
||||||
let(:test_b) { double('Test B') }
|
let(:test_b) { double('Test B') }
|
||||||
|
let(:tests) { [test_a, test_b] }
|
||||||
|
|
||||||
describe '#kill' do
|
describe '#kill' do
|
||||||
let(:isolation) { double('Isolation') }
|
let(:isolation) { Mutant::Isolation::None }
|
||||||
let(:object) { Mutant::Mutation::Evil.new(mutation_subject, Mutant::AST::Nodes::N_NIL) }
|
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
|
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
|
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
|
end
|
||||||
|
|
||||||
describe '#code' do
|
describe '#code' do
|
||||||
|
@ -51,21 +73,6 @@ RSpec.describe Mutant::Mutation do
|
||||||
it_should_behave_like 'an idempotent method'
|
it_should_behave_like 'an idempotent method'
|
||||||
end
|
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
|
describe '#source' do
|
||||||
subject { object.source }
|
subject { object.source }
|
||||||
|
|
||||||
|
|
|
@ -127,8 +127,7 @@ RSpec.describe Mutant::Reporter::CLI do
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when mutation is NOT successful' do
|
context 'when mutation is NOT successful' do
|
||||||
update(:mutation_a_test_a_result) { { passed: true } }
|
update(:mutation_a_test_result) { { passed: true } }
|
||||||
update(:mutation_a_test_b_result) { { passed: true } }
|
|
||||||
it_reports 'F.'
|
it_reports 'F.'
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -178,8 +177,8 @@ RSpec.describe Mutant::Reporter::CLI do
|
||||||
Kills: 2
|
Kills: 2
|
||||||
Alive: 0
|
Alive: 0
|
||||||
Runtime: 4.00s
|
Runtime: 4.00s
|
||||||
Killtime: 4.00s
|
Killtime: 2.00s
|
||||||
Overhead: 0.00%
|
Overhead: 100.00%
|
||||||
Coverage: 100.00%
|
Coverage: 100.00%
|
||||||
Expected: 100.00%
|
Expected: 100.00%
|
||||||
Active subjects: 0
|
Active subjects: 0
|
||||||
|
@ -190,8 +189,7 @@ RSpec.describe Mutant::Reporter::CLI do
|
||||||
update(:status) { { active_jobs: [job_a].to_set } }
|
update(:status) { { active_jobs: [job_a].to_set } }
|
||||||
|
|
||||||
context 'on failure' do
|
context 'on failure' do
|
||||||
update(:mutation_a_test_a_result) { { passed: true } }
|
update(:mutation_a_test_result) { { passed: true } }
|
||||||
update(:mutation_a_test_b_result) { { passed: true } }
|
|
||||||
|
|
||||||
it_reports(<<-REPORT)
|
it_reports(<<-REPORT)
|
||||||
Mutant configuration:
|
Mutant configuration:
|
||||||
|
@ -207,15 +205,15 @@ RSpec.describe Mutant::Reporter::CLI do
|
||||||
Kills: 1
|
Kills: 1
|
||||||
Alive: 1
|
Alive: 1
|
||||||
Runtime: 4.00s
|
Runtime: 4.00s
|
||||||
Killtime: 4.00s
|
Killtime: 2.00s
|
||||||
Overhead: 0.00%
|
Overhead: 100.00%
|
||||||
Coverage: 50.00%
|
Coverage: 50.00%
|
||||||
Expected: 100.00%
|
Expected: 100.00%
|
||||||
Active subjects: 1
|
Active subjects: 1
|
||||||
subject-a mutations: 2
|
subject-a mutations: 2
|
||||||
- test-a
|
- test-a
|
||||||
F.
|
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:
|
Active Jobs:
|
||||||
0: evil:subject-a:d27d2
|
0: evil:subject-a:d27d2
|
||||||
REPORT
|
REPORT
|
||||||
|
@ -236,15 +234,15 @@ RSpec.describe Mutant::Reporter::CLI do
|
||||||
Kills: 2
|
Kills: 2
|
||||||
Alive: 0
|
Alive: 0
|
||||||
Runtime: 4.00s
|
Runtime: 4.00s
|
||||||
Killtime: 4.00s
|
Killtime: 2.00s
|
||||||
Overhead: 0.00%
|
Overhead: 100.00%
|
||||||
Coverage: 100.00%
|
Coverage: 100.00%
|
||||||
Expected: 100.00%
|
Expected: 100.00%
|
||||||
Active subjects: 1
|
Active subjects: 1
|
||||||
subject-a mutations: 2
|
subject-a mutations: 2
|
||||||
- test-a
|
- 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:
|
Active Jobs:
|
||||||
0: evil:subject-a:d27d2
|
0: evil:subject-a:d27d2
|
||||||
REPORT
|
REPORT
|
||||||
|
@ -271,16 +269,15 @@ RSpec.describe Mutant::Reporter::CLI do
|
||||||
Kills: 2
|
Kills: 2
|
||||||
Alive: 0
|
Alive: 0
|
||||||
Runtime: 4.00s
|
Runtime: 4.00s
|
||||||
Killtime: 4.00s
|
Killtime: 2.00s
|
||||||
Overhead: 0.00%
|
Overhead: 100.00%
|
||||||
Coverage: 100.00%
|
Coverage: 100.00%
|
||||||
Expected: 100.00%
|
Expected: 100.00%
|
||||||
REPORT
|
REPORT
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'and partial coverage' do
|
context 'and partial coverage' do
|
||||||
update(:mutation_a_test_a_result) { { passed: true } }
|
update(:mutation_a_test_result) { { passed: true } }
|
||||||
update(:mutation_a_test_b_result) { { passed: true } }
|
|
||||||
|
|
||||||
context 'on evil mutation' do
|
context 'on evil mutation' do
|
||||||
context 'with a diff' do
|
context 'with a diff' do
|
||||||
|
@ -305,8 +302,8 @@ RSpec.describe Mutant::Reporter::CLI do
|
||||||
Kills: 1
|
Kills: 1
|
||||||
Alive: 1
|
Alive: 1
|
||||||
Runtime: 4.00s
|
Runtime: 4.00s
|
||||||
Killtime: 4.00s
|
Killtime: 2.00s
|
||||||
Overhead: 0.00%
|
Overhead: 100.00%
|
||||||
Coverage: 50.00%
|
Coverage: 50.00%
|
||||||
Expected: 100.00%
|
Expected: 100.00%
|
||||||
REPORT
|
REPORT
|
||||||
|
@ -338,8 +335,8 @@ RSpec.describe Mutant::Reporter::CLI do
|
||||||
Kills: 1
|
Kills: 1
|
||||||
Alive: 1
|
Alive: 1
|
||||||
Runtime: 4.00s
|
Runtime: 4.00s
|
||||||
Killtime: 4.00s
|
Killtime: 2.00s
|
||||||
Overhead: 0.00%
|
Overhead: 100.00%
|
||||||
Coverage: 50.00%
|
Coverage: 50.00%
|
||||||
Expected: 100.00%
|
Expected: 100.00%
|
||||||
REPORT
|
REPORT
|
||||||
|
@ -347,8 +344,7 @@ RSpec.describe Mutant::Reporter::CLI do
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'on neutral mutation' do
|
context 'on neutral mutation' do
|
||||||
update(:mutation_a_test_a_result) { { passed: false } }
|
update(:mutation_a_test_result) { { passed: false } }
|
||||||
update(:mutation_a_test_b_result) { { passed: false } }
|
|
||||||
|
|
||||||
let(:mutation_a) do
|
let(:mutation_a) do
|
||||||
Mutant::Mutation::Neutral.new(subject_a, s(:true))
|
Mutant::Mutation::Neutral.new(subject_a, s(:true))
|
||||||
|
@ -365,13 +361,11 @@ RSpec.describe Mutant::Reporter::CLI do
|
||||||
(true)
|
(true)
|
||||||
Unparsed Source:
|
Unparsed Source:
|
||||||
true
|
true
|
||||||
Test Reports: 2
|
Test Result:
|
||||||
- test-a / runtime: 1.0
|
- 1 @ runtime: 1.0
|
||||||
|
- test-a
|
||||||
Test Output:
|
Test Output:
|
||||||
mutation a test a result output
|
mutation a test result output
|
||||||
- test-b / runtime: 1.0
|
|
||||||
Test Output:
|
|
||||||
mutation a test b result output
|
|
||||||
-----------------------
|
-----------------------
|
||||||
neutral:subject-a:d5318
|
neutral:subject-a:d5318
|
||||||
--- Neutral failure ---
|
--- Neutral failure ---
|
||||||
|
@ -381,13 +375,11 @@ RSpec.describe Mutant::Reporter::CLI do
|
||||||
(true)
|
(true)
|
||||||
Unparsed Source:
|
Unparsed Source:
|
||||||
true
|
true
|
||||||
Test Reports: 2
|
Test Result:
|
||||||
- test-a / runtime: 1.0
|
- 1 @ runtime: 1.0
|
||||||
|
- test-a
|
||||||
Test Output:
|
Test Output:
|
||||||
mutation b test a result output
|
mutation b test result output
|
||||||
- test-b / runtime: 1.0
|
|
||||||
Test Output:
|
|
||||||
mutation b test b result output
|
|
||||||
-----------------------
|
-----------------------
|
||||||
Mutant configuration:
|
Mutant configuration:
|
||||||
Matcher: #<Mutant::Matcher::Config match_expressions=[] subject_ignores=[] subject_selects=[]>
|
Matcher: #<Mutant::Matcher::Config match_expressions=[] subject_ignores=[] subject_selects=[]>
|
||||||
|
@ -402,16 +394,15 @@ RSpec.describe Mutant::Reporter::CLI do
|
||||||
Kills: 0
|
Kills: 0
|
||||||
Alive: 2
|
Alive: 2
|
||||||
Runtime: 4.00s
|
Runtime: 4.00s
|
||||||
Killtime: 4.00s
|
Killtime: 2.00s
|
||||||
Overhead: 0.00%
|
Overhead: 100.00%
|
||||||
Coverage: 0.00%
|
Coverage: 0.00%
|
||||||
Expected: 100.00%
|
Expected: 100.00%
|
||||||
REPORT
|
REPORT
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'on noop mutation' do
|
context 'on noop mutation' do
|
||||||
update(:mutation_a_test_a_result) { { passed: false } }
|
update(:mutation_a_test_result) { { passed: false } }
|
||||||
update(:mutation_a_test_b_result) { { passed: false } }
|
|
||||||
|
|
||||||
let(:mutation_a) do
|
let(:mutation_a) do
|
||||||
Mutant::Mutation::Noop.new(subject_a, s(:true))
|
Mutant::Mutation::Noop.new(subject_a, s(:true))
|
||||||
|
@ -424,25 +415,21 @@ RSpec.describe Mutant::Reporter::CLI do
|
||||||
---- Noop failure -----
|
---- Noop failure -----
|
||||||
No code was inserted. And the test did NOT PASS.
|
No code was inserted. And the test did NOT PASS.
|
||||||
This is typically a problem of your specs not passing unmutated.
|
This is typically a problem of your specs not passing unmutated.
|
||||||
Test Reports: 2
|
Test Result:
|
||||||
- test-a / runtime: 1.0
|
- 1 @ runtime: 1.0
|
||||||
|
- test-a
|
||||||
Test Output:
|
Test Output:
|
||||||
mutation a test a result output
|
mutation a test result output
|
||||||
- test-b / runtime: 1.0
|
|
||||||
Test Output:
|
|
||||||
mutation a test b result output
|
|
||||||
-----------------------
|
-----------------------
|
||||||
noop:subject-a:d5318
|
noop:subject-a:d5318
|
||||||
---- Noop failure -----
|
---- Noop failure -----
|
||||||
No code was inserted. And the test did NOT PASS.
|
No code was inserted. And the test did NOT PASS.
|
||||||
This is typically a problem of your specs not passing unmutated.
|
This is typically a problem of your specs not passing unmutated.
|
||||||
Test Reports: 2
|
Test Result:
|
||||||
- test-a / runtime: 1.0
|
- 1 @ runtime: 1.0
|
||||||
|
- test-a
|
||||||
Test Output:
|
Test Output:
|
||||||
mutation b test a result output
|
mutation b test result output
|
||||||
- test-b / runtime: 1.0
|
|
||||||
Test Output:
|
|
||||||
mutation b test b result output
|
|
||||||
-----------------------
|
-----------------------
|
||||||
Mutant configuration:
|
Mutant configuration:
|
||||||
Matcher: #<Mutant::Matcher::Config match_expressions=[] subject_ignores=[] subject_selects=[]>
|
Matcher: #<Mutant::Matcher::Config match_expressions=[] subject_ignores=[] subject_selects=[]>
|
||||||
|
@ -457,8 +444,8 @@ RSpec.describe Mutant::Reporter::CLI do
|
||||||
Kills: 0
|
Kills: 0
|
||||||
Alive: 2
|
Alive: 2
|
||||||
Runtime: 4.00s
|
Runtime: 4.00s
|
||||||
Killtime: 4.00s
|
Killtime: 2.00s
|
||||||
Overhead: 0.00%
|
Overhead: 100.00%
|
||||||
Coverage: 0.00%
|
Coverage: 0.00%
|
||||||
Expected: 100.00%
|
Expected: 100.00%
|
||||||
REPORT
|
REPORT
|
||||||
|
|
|
@ -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
|
|
|
@ -54,9 +54,8 @@ RSpec.describe Mutant::Runner::Master do
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'stop by fail fast trigger first' do
|
context 'stop by fail fast trigger first' do
|
||||||
update(:config) { { fail_fast: true } }
|
update(:config) { { fail_fast: true } }
|
||||||
update(:mutation_b_test_a_result) { { passed: true } }
|
update(:mutation_b_test_result) { { passed: true } }
|
||||||
update(:mutation_b_test_b_result) { { passed: true } }
|
|
||||||
|
|
||||||
before do
|
before do
|
||||||
message_sequence.add(:master, :ready, worker_a)
|
message_sequence.add(:master, :ready, worker_a)
|
||||||
|
@ -82,9 +81,8 @@ RSpec.describe Mutant::Runner::Master do
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'stop by fail fast trigger last' do
|
context 'stop by fail fast trigger last' do
|
||||||
update(:config) { { fail_fast: true } }
|
update(:config) { { fail_fast: true } }
|
||||||
update(:mutation_a_test_a_result) { { passed: true } }
|
update(:mutation_a_test_result) { { passed: true } }
|
||||||
update(:mutation_a_test_b_result) { { passed: true } }
|
|
||||||
|
|
||||||
before do
|
before do
|
||||||
message_sequence.add(:master, :ready, worker_a)
|
message_sequence.add(:master, :ready, worker_a)
|
||||||
|
|
|
@ -123,10 +123,9 @@ describe Mutant::Runner::Scheduler do
|
||||||
object.job_result(job_a_result)
|
object.job_result(job_a_result)
|
||||||
end
|
end
|
||||||
|
|
||||||
update(:subject_a_result) { { mutation_results: [mutation_a_result] } }
|
update(:subject_a_result) { { mutation_results: [mutation_a_result] } }
|
||||||
update(:mutation_a_test_a_result) { { passed: true } }
|
update(:mutation_a_test_result) { { passed: true } }
|
||||||
update(:mutation_a_test_b_result) { { passed: true } }
|
update(:config) { { fail_fast: true } }
|
||||||
update(:config) { { fail_fast: true } }
|
|
||||||
|
|
||||||
let(:expected_status) do
|
let(:expected_status) do
|
||||||
Mutant::Runner::Status.new(
|
Mutant::Runner::Status.new(
|
||||||
|
|
|
@ -23,9 +23,10 @@ RSpec.describe Mutant::Runner::Worker do
|
||||||
|
|
||||||
context 'when receving :job command' do
|
context 'when receving :job command' do
|
||||||
|
|
||||||
|
let(:test_result) { double('Test Result') }
|
||||||
|
|
||||||
before do
|
before do
|
||||||
expect(mutation).to receive(:kill).with(config.isolation).and_return(mutation_result).ordered
|
expect(mutation).to receive(:kill).with(config.isolation, config.integration).and_return(test_result).ordered
|
||||||
expect(mutation_result).to receive(:update).with(index: job.index).and_return(mutation_result).ordered
|
|
||||||
|
|
||||||
message_sequence.add(:worker, :job, job)
|
message_sequence.add(:worker, :job, job)
|
||||||
message_sequence.add(:parent, :result, job_result)
|
message_sequence.add(:parent, :result, job_result)
|
||||||
|
@ -37,10 +38,17 @@ RSpec.describe Mutant::Runner::Worker do
|
||||||
let(:index) { double('Index') }
|
let(:index) { double('Index') }
|
||||||
let(:test_result) { double('Test Result') }
|
let(:test_result) { double('Test Result') }
|
||||||
let(:mutation) { double('Mutation') }
|
let(:mutation) { double('Mutation') }
|
||||||
let(:mutation_result) { double('Mutation Result') }
|
|
||||||
let(:job_result) { Mutant::Runner::JobResult.new(job: job, result: 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(: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
|
it 'signals ready and status to parent' do
|
||||||
subject
|
subject
|
||||||
end
|
end
|
||||||
|
|
|
@ -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
|
|
|
@ -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: '../'
|
|
Loading…
Add table
Add a link
Reference in a new issue