Fix coverage for Mutant::Meta namespace
* Remove unparser invariant checking, does not yield enough value anymore as unparser is more stable these days
This commit is contained in:
parent
9838f10da8
commit
7408e86fd4
10 changed files with 357 additions and 135 deletions
|
@ -1,3 +1,3 @@
|
|||
---
|
||||
threshold: 18
|
||||
total_score: 1182
|
||||
total_score: 1172
|
||||
|
|
|
@ -69,7 +69,6 @@ TooManyMethods:
|
|||
enabled: true
|
||||
exclude:
|
||||
- Mutant::CLI
|
||||
- Mutant::Meta::Example::Verification
|
||||
- Mutant::Mutator::Node
|
||||
- Mutant::Parallel::Master
|
||||
max_methods: 10
|
||||
|
@ -128,7 +127,7 @@ UtilityFunction:
|
|||
- Mutant::Integration::Null#call
|
||||
- Mutant::Integration::Rspec#parse_example
|
||||
- Mutant::Integration::Rspec#parse_expression # intentional, private
|
||||
- Mutant::Meta::Example::Verification#format_mutation
|
||||
- Mutant::Meta::Example::Verification#format_mutations # intentional, private
|
||||
- Mutant::Reporter::CLI::Format::Progressive#new_buffer
|
||||
- Mutant::Reporter::CLI::Printer::StatusProgressive#object # False positive calls super
|
||||
- Mutant::Repository::Diff#tracks? # intentional, private
|
||||
|
|
|
@ -3,6 +3,7 @@ module Mutant
|
|||
module Meta
|
||||
require 'mutant/meta/example'
|
||||
require 'mutant/meta/example/dsl'
|
||||
require 'mutant/meta/example/verification'
|
||||
|
||||
# Mutation example
|
||||
class Example
|
||||
|
@ -15,15 +16,20 @@ module Mutant
|
|||
# @return [undefined]
|
||||
def self.add(&block)
|
||||
file = caller.first.split(':in', 2).first
|
||||
ALL << DSL.run(file, block)
|
||||
ALL << DSL.call(file, block)
|
||||
end
|
||||
|
||||
Pathname.glob(Pathname.new(__FILE__).parent.parent.parent.join('meta', '**/*.rb'))
|
||||
Pathname.glob(Pathname.new(__dir__).parent.parent.join('meta', '*.rb'))
|
||||
.sort
|
||||
.each(&method(:require))
|
||||
|
||||
ALL.freeze
|
||||
|
||||
end # Example
|
||||
# Remove mutation method only present for DSL executions from meta/**/*.rb
|
||||
class << self
|
||||
undef_method :add
|
||||
end
|
||||
|
||||
end # Example
|
||||
end # Meta
|
||||
end # Mutant
|
||||
|
|
|
@ -27,121 +27,6 @@ module Mutant
|
|||
end
|
||||
end
|
||||
memoize :generated
|
||||
|
||||
# Example verification
|
||||
class Verification
|
||||
include Adamantium::Flat, Concord.new(:example, :mutations)
|
||||
|
||||
# Test if mutation was verified successfully
|
||||
#
|
||||
# @return [Boolean]
|
||||
def success?
|
||||
unparser.success? && missing.empty? && unexpected.empty? && no_diffs.empty?
|
||||
end
|
||||
|
||||
# Error report
|
||||
#
|
||||
# @return [String]
|
||||
def error_report
|
||||
unless unparser.success?
|
||||
return unparser.report
|
||||
end
|
||||
mutation_report
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Unexpected mutations
|
||||
#
|
||||
# @return [Array<Parser::AST::Node>]
|
||||
def unexpected
|
||||
mutations.map(&:node) - example.mutations
|
||||
end
|
||||
memoize :unexpected
|
||||
|
||||
# Mutations with no diff to original
|
||||
#
|
||||
# @return [Enumerable<Mutation>]
|
||||
def no_diffs
|
||||
mutations.select { |mutation| mutation.source.eql?(example.source) }
|
||||
end
|
||||
memoize :no_diffs
|
||||
|
||||
# Mutation report
|
||||
#
|
||||
# @return [String]
|
||||
def mutation_report
|
||||
original_node = example.node
|
||||
[
|
||||
"#{example.file}, Original-AST:",
|
||||
original_node.inspect,
|
||||
'Original-Source:',
|
||||
example.source,
|
||||
*missing_report,
|
||||
*unexpected_report,
|
||||
*no_diff_report
|
||||
].join("\n======\n")
|
||||
end
|
||||
|
||||
# Missing mutation report
|
||||
#
|
||||
# @return [Array, nil]
|
||||
def missing_report
|
||||
[
|
||||
'Missing mutations:',
|
||||
missing.map(&method(:format_mutation)).join("\n-----\n")
|
||||
] if missing.any?
|
||||
end
|
||||
|
||||
# No diff mutation report
|
||||
#
|
||||
# @return [Array, nil]
|
||||
def no_diff_report
|
||||
[
|
||||
'No source diffs to original:',
|
||||
no_diffs.map do |mutation|
|
||||
"#{mutation.node.inspect}\n#{mutation.source}"
|
||||
end
|
||||
] if no_diffs.any?
|
||||
end
|
||||
|
||||
# Unexpected mutation report
|
||||
#
|
||||
# @return [Array, nil]
|
||||
def unexpected_report
|
||||
[
|
||||
'Unexpected mutations:',
|
||||
unexpected.map(&method(:format_mutation)).join("\n-----\n")
|
||||
] if unexpected.any?
|
||||
end
|
||||
|
||||
# Format mutation
|
||||
#
|
||||
# @return [String]
|
||||
def format_mutation(node)
|
||||
[
|
||||
node.inspect,
|
||||
Unparser.unparse(node)
|
||||
].join("\n")
|
||||
end
|
||||
|
||||
# Missing mutations
|
||||
#
|
||||
# @return [Array<Parser::AST::Node>]
|
||||
def missing
|
||||
example.mutations - mutations.map(&:node)
|
||||
end
|
||||
memoize :missing
|
||||
|
||||
# Unparser verifier
|
||||
#
|
||||
# @return [Unparser::CLI::Source]
|
||||
def unparser
|
||||
Unparser::CLI::Source::Node.new(Unparser::Preprocessor.run(example.node))
|
||||
end
|
||||
memoize :unparser
|
||||
|
||||
end # Verification
|
||||
end # Example
|
||||
end # Meta
|
||||
end # Mutant
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
module Mutant
|
||||
module Meta
|
||||
class Example
|
||||
|
||||
# Example DSL
|
||||
class DSL
|
||||
include AST::Sexp
|
||||
|
@ -9,18 +8,20 @@ module Mutant
|
|||
# Run DSL on block
|
||||
#
|
||||
# @return [Example]
|
||||
def self.run(file, block)
|
||||
def self.call(file, block)
|
||||
instance = new(file)
|
||||
instance.instance_eval(&block)
|
||||
instance.example
|
||||
end
|
||||
|
||||
# Initialize DSL context
|
||||
private_class_method :new
|
||||
|
||||
# Initialize object
|
||||
#
|
||||
# @return [undefined]
|
||||
def initialize(file)
|
||||
@file = file
|
||||
@source = nil
|
||||
@file = file
|
||||
@source = nil
|
||||
@expected = []
|
||||
end
|
||||
|
||||
|
@ -41,27 +42,23 @@ module Mutant
|
|||
#
|
||||
# @param [String,Parser::AST::Node] input
|
||||
#
|
||||
# @return [self]
|
||||
# @return [undefined]
|
||||
def source(input)
|
||||
fail 'source already defined' if @source
|
||||
@source = node(input)
|
||||
|
||||
self
|
||||
end
|
||||
|
||||
# Add expected mutation
|
||||
#
|
||||
# @param [String,Parser::AST::Node] input
|
||||
#
|
||||
# @return [self]
|
||||
# @return [undefined]
|
||||
def mutation(input)
|
||||
node = node(input)
|
||||
if @expected.include?(node)
|
||||
fail "Node for input: #{input.inspect} is already expected"
|
||||
fail "Mutation for input: #{input.inspect} is already expected"
|
||||
end
|
||||
@expected << node
|
||||
|
||||
self
|
||||
end
|
||||
|
||||
# Add singleton mutations
|
||||
|
@ -87,7 +84,7 @@ module Mutant
|
|||
when ::Parser::AST::Node
|
||||
input
|
||||
else
|
||||
fail "Cannot coerce to node: #{source.inspect}"
|
||||
fail "Cannot coerce to node: #{input.inspect}"
|
||||
end
|
||||
end
|
||||
|
||||
|
|
86
lib/mutant/meta/example/verification.rb
Normal file
86
lib/mutant/meta/example/verification.rb
Normal file
|
@ -0,0 +1,86 @@
|
|||
module Mutant
|
||||
module Meta
|
||||
class Example
|
||||
# Example verification
|
||||
class Verification
|
||||
include Adamantium::Flat, Concord.new(:example, :mutations)
|
||||
|
||||
# Test if mutation was verified successfully
|
||||
#
|
||||
# @return [Boolean]
|
||||
def success?
|
||||
missing.empty? && unexpected.empty? && no_diffs.empty?
|
||||
end
|
||||
|
||||
# Error report
|
||||
#
|
||||
# @return [String]
|
||||
def error_report
|
||||
fail 'no error report on successful validation' if success?
|
||||
|
||||
YAML.dump(
|
||||
'file' => example.file,
|
||||
'original_ast' => example.node.inspect,
|
||||
'original_source' => example.source,
|
||||
'missing' => format_mutations(missing),
|
||||
'unexpected' => format_mutations(unexpected),
|
||||
'no_diff' => no_diff_report
|
||||
)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Unexpected mutations
|
||||
#
|
||||
# @return [Array<Parser::AST::Node>]
|
||||
def unexpected
|
||||
mutations.map(&:node) - example.mutations
|
||||
end
|
||||
memoize :unexpected
|
||||
|
||||
# Mutations with no diff to original
|
||||
#
|
||||
# @return [Enumerable<Mutation>]
|
||||
def no_diffs
|
||||
mutations.select { |mutation| mutation.source.eql?(example.source) }
|
||||
end
|
||||
memoize :no_diffs
|
||||
|
||||
# Mutation report
|
||||
#
|
||||
# @param [Array<Parser::AST::Node>] nodes
|
||||
#
|
||||
# @return [Array<Hash>]
|
||||
def format_mutations(nodes)
|
||||
nodes.map do |node|
|
||||
{
|
||||
'node' => node.inspect,
|
||||
'source' => Unparser.unparse(node)
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
# No diff mutation report
|
||||
#
|
||||
# @return [Array, nil]
|
||||
def no_diff_report
|
||||
no_diffs.map do |mutation|
|
||||
{
|
||||
'node' => mutation.node.inspect,
|
||||
'source' => mutation.source
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
# Missing mutations
|
||||
#
|
||||
# @return [Array<Parser::AST::Node>]
|
||||
def missing
|
||||
example.mutations - mutations.map(&:node)
|
||||
end
|
||||
memoize :missing
|
||||
|
||||
end # Verification
|
||||
end # Example
|
||||
end # Meta
|
||||
end # Mutant
|
|
@ -9,7 +9,6 @@ if ENV['COVERAGE'] == 'true'
|
|||
add_filter 'vendor'
|
||||
add_filter 'test_app'
|
||||
add_filter 'lib/mutant.rb' # simplecov bug not seeing default block is executed
|
||||
add_filter 'lib/mutant/meta/*'
|
||||
add_filter 'lib/mutant/zombifier'
|
||||
add_filter 'lib/mutant/zombifier/*'
|
||||
# Trace points shadow each other under 2.0 (fixed in 2.1)
|
||||
|
|
100
spec/unit/mutant/meta/example/dsl_spec.rb
Normal file
100
spec/unit/mutant/meta/example/dsl_spec.rb
Normal file
|
@ -0,0 +1,100 @@
|
|||
RSpec.describe Mutant::Meta::Example::DSL do
|
||||
describe '.call' do
|
||||
subject { described_class.call(file, block) }
|
||||
|
||||
let(:file) { 'foo.rb' }
|
||||
let(:node) { s(:false) }
|
||||
let(:expected) { [] }
|
||||
|
||||
let(:expected_example) do
|
||||
Mutant::Meta::Example.new(file, node, expected)
|
||||
end
|
||||
|
||||
def self.expect_example(&block)
|
||||
let(:block) { block }
|
||||
|
||||
specify do
|
||||
# Kill mutations to warnings
|
||||
warnings = Mutant::WarningFilter.use do
|
||||
should eql(expected_example)
|
||||
end
|
||||
expect(warnings).to eql([])
|
||||
end
|
||||
end
|
||||
|
||||
def self.expect_error(message, &block)
|
||||
let(:block) { block }
|
||||
|
||||
specify do
|
||||
expect { subject }.to raise_error(RuntimeError, message)
|
||||
end
|
||||
end
|
||||
|
||||
context 'source as node' do
|
||||
expect_example do
|
||||
source s(:false)
|
||||
end
|
||||
end
|
||||
|
||||
context 'source as string' do
|
||||
expect_example do
|
||||
source 'false'
|
||||
end
|
||||
end
|
||||
|
||||
context 'on node that needs unparser preprocessing to be normalized' do
|
||||
let(:node) { s(:send, s(:float, -1.0), :/, s(:float, 0.0)) }
|
||||
|
||||
expect_example do
|
||||
source '(-1.0) / 0.0'
|
||||
end
|
||||
end
|
||||
|
||||
context 'using #mutation' do
|
||||
let(:expected) { [s(:nil)] }
|
||||
|
||||
expect_example do
|
||||
source 'false'
|
||||
|
||||
mutation 'nil'
|
||||
end
|
||||
end
|
||||
|
||||
context 'using #singleton_mutations' do
|
||||
let(:expected) { [s(:nil), s(:self)] }
|
||||
|
||||
expect_example do
|
||||
source 'false'
|
||||
|
||||
singleton_mutations
|
||||
end
|
||||
end
|
||||
|
||||
context 'no definition of source' do
|
||||
expect_error('source not defined') do
|
||||
end
|
||||
end
|
||||
|
||||
context 'duplicate definition of source' do
|
||||
expect_error('source already defined') do
|
||||
source 'true'
|
||||
source 'false'
|
||||
end
|
||||
end
|
||||
|
||||
context 'uncoercable source' do
|
||||
expect_error('Cannot coerce to node: nil') do
|
||||
source nil
|
||||
end
|
||||
end
|
||||
|
||||
context 'duplicate mutation expectation' do
|
||||
expect_error('Mutation for input: "true" is already expected') do
|
||||
source 'false'
|
||||
|
||||
mutation 'true'
|
||||
mutation 'true'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
119
spec/unit/mutant/meta/example/verification_spec.rb
Normal file
119
spec/unit/mutant/meta/example/verification_spec.rb
Normal file
|
@ -0,0 +1,119 @@
|
|||
RSpec.describe Mutant::Meta::Example::Verification do
|
||||
let(:object) { described_class.new(example, mutations) }
|
||||
|
||||
let(:example) do
|
||||
Mutant::Meta::Example.new(
|
||||
'foo.rb',
|
||||
s(:true),
|
||||
expected_nodes
|
||||
)
|
||||
end
|
||||
|
||||
let(:mutations) do
|
||||
generated_nodes.map do |node|
|
||||
Mutant::Mutation::Evil.new(example, node)
|
||||
end
|
||||
end
|
||||
|
||||
let(:generated_nodes) { [] }
|
||||
let(:expected_nodes) { [] }
|
||||
|
||||
describe '#success?' do
|
||||
subject { object.success? }
|
||||
|
||||
context 'when generated nodes equal expected nodes' do
|
||||
it { should be(true) }
|
||||
end
|
||||
|
||||
context 'when expected node is missing' do
|
||||
let(:expected_nodes) { [s(:false)] }
|
||||
|
||||
it { should be(false) }
|
||||
end
|
||||
|
||||
context 'when there is extra generated node' do
|
||||
let(:generated_nodes) { [s(:false)] }
|
||||
|
||||
it { should be(false) }
|
||||
end
|
||||
|
||||
context 'when there is no diff to original source' do
|
||||
let(:expected_nodes) { [s(:true)] }
|
||||
let(:generated_nodes) { [s(:true)] }
|
||||
|
||||
it { should be(false) }
|
||||
end
|
||||
end
|
||||
|
||||
describe '#error_report' do
|
||||
subject { object.error_report }
|
||||
|
||||
context 'on success' do
|
||||
specify do
|
||||
expect { subject }.to raise_error(
|
||||
RuntimeError,
|
||||
'no error report on successful validation'
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when expected node is missing' do
|
||||
let(:expected_nodes) { [s(:false), s(:nil)] }
|
||||
|
||||
specify do
|
||||
should eql(strip_indent(<<-'REPORT'))
|
||||
---
|
||||
file: foo.rb
|
||||
original_ast: s(:true)
|
||||
original_source: 'true'
|
||||
missing:
|
||||
- node: s(:false)
|
||||
source: 'false'
|
||||
- node: s(:nil)
|
||||
source: nil
|
||||
unexpected: []
|
||||
no_diff: []
|
||||
REPORT
|
||||
end
|
||||
end
|
||||
|
||||
context 'when there is extra generated node' do
|
||||
let(:generated_nodes) { [s(:false), s(:nil)] }
|
||||
|
||||
specify do
|
||||
should eql(strip_indent(<<-'REPORT'))
|
||||
---
|
||||
file: foo.rb
|
||||
original_ast: s(:true)
|
||||
original_source: 'true'
|
||||
missing: []
|
||||
unexpected:
|
||||
- node: s(:false)
|
||||
source: 'false'
|
||||
- node: s(:nil)
|
||||
source: nil
|
||||
no_diff: []
|
||||
REPORT
|
||||
end
|
||||
end
|
||||
|
||||
context 'when there is no diff to original source' do
|
||||
let(:expected_nodes) { [s(:true)] }
|
||||
let(:generated_nodes) { [s(:true)] }
|
||||
|
||||
specify do
|
||||
should eql(strip_indent(<<-'REPORT'))
|
||||
---
|
||||
file: foo.rb
|
||||
original_ast: s(:true)
|
||||
original_source: 'true'
|
||||
missing: []
|
||||
unexpected: []
|
||||
no_diff:
|
||||
- node: s(:true)
|
||||
source: 'true'
|
||||
REPORT
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
31
spec/unit/mutant/meta/example_spec.rb
Normal file
31
spec/unit/mutant/meta/example_spec.rb
Normal file
|
@ -0,0 +1,31 @@
|
|||
RSpec.describe Mutant::Meta::Example do
|
||||
let(:object) do
|
||||
described_class.new(
|
||||
file,
|
||||
node,
|
||||
mutation_nodes
|
||||
)
|
||||
end
|
||||
|
||||
let(:file) { 'foo/bar.rb' }
|
||||
let(:node) { s(:true) }
|
||||
let(:mutation_nodes) { [s(:nil), s(:false)] }
|
||||
|
||||
let(:mutations) do
|
||||
mutation_nodes.map do |node|
|
||||
Mutant::Mutation::Evil.new(object, node)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#source' do
|
||||
subject { object.source }
|
||||
|
||||
it { should eql('true') }
|
||||
end
|
||||
|
||||
describe '#verification' do
|
||||
subject { object.verification }
|
||||
|
||||
it { should eql(Mutant::Meta::Example::Verification.new(object, mutations)) }
|
||||
end
|
||||
end
|
Loading…
Reference in a new issue