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:
Markus Schirp 2016-02-29 19:03:08 +00:00
parent 9838f10da8
commit 7408e86fd4
10 changed files with 357 additions and 135 deletions

View file

@ -1,3 +1,3 @@
--- ---
threshold: 18 threshold: 18
total_score: 1182 total_score: 1172

View file

@ -69,7 +69,6 @@ TooManyMethods:
enabled: true enabled: true
exclude: exclude:
- Mutant::CLI - Mutant::CLI
- Mutant::Meta::Example::Verification
- Mutant::Mutator::Node - Mutant::Mutator::Node
- Mutant::Parallel::Master - Mutant::Parallel::Master
max_methods: 10 max_methods: 10
@ -128,7 +127,7 @@ UtilityFunction:
- Mutant::Integration::Null#call - Mutant::Integration::Null#call
- Mutant::Integration::Rspec#parse_example - Mutant::Integration::Rspec#parse_example
- Mutant::Integration::Rspec#parse_expression # intentional, private - 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::Format::Progressive#new_buffer
- Mutant::Reporter::CLI::Printer::StatusProgressive#object # False positive calls super - Mutant::Reporter::CLI::Printer::StatusProgressive#object # False positive calls super
- Mutant::Repository::Diff#tracks? # intentional, private - Mutant::Repository::Diff#tracks? # intentional, private

View file

@ -3,6 +3,7 @@ module Mutant
module Meta module Meta
require 'mutant/meta/example' require 'mutant/meta/example'
require 'mutant/meta/example/dsl' require 'mutant/meta/example/dsl'
require 'mutant/meta/example/verification'
# Mutation example # Mutation example
class Example class Example
@ -15,15 +16,20 @@ module Mutant
# @return [undefined] # @return [undefined]
def self.add(&block) def self.add(&block)
file = caller.first.split(':in', 2).first file = caller.first.split(':in', 2).first
ALL << DSL.run(file, block) ALL << DSL.call(file, block)
end end
Pathname.glob(Pathname.new(__FILE__).parent.parent.parent.join('meta', '**/*.rb')) Pathname.glob(Pathname.new(__dir__).parent.parent.join('meta', '*.rb'))
.sort .sort
.each(&method(:require)) .each(&method(:require))
ALL.freeze 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 # Meta
end # Mutant end # Mutant

View file

@ -27,121 +27,6 @@ module Mutant
end end
end end
memoize :generated 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 # Example
end # Meta end # Meta
end # Mutant end # Mutant

View file

@ -1,7 +1,6 @@
module Mutant module Mutant
module Meta module Meta
class Example class Example
# Example DSL # Example DSL
class DSL class DSL
include AST::Sexp include AST::Sexp
@ -9,18 +8,20 @@ module Mutant
# Run DSL on block # Run DSL on block
# #
# @return [Example] # @return [Example]
def self.run(file, block) def self.call(file, block)
instance = new(file) instance = new(file)
instance.instance_eval(&block) instance.instance_eval(&block)
instance.example instance.example
end end
# Initialize DSL context private_class_method :new
# Initialize object
# #
# @return [undefined] # @return [undefined]
def initialize(file) def initialize(file)
@file = file @file = file
@source = nil @source = nil
@expected = [] @expected = []
end end
@ -41,27 +42,23 @@ module Mutant
# #
# @param [String,Parser::AST::Node] input # @param [String,Parser::AST::Node] input
# #
# @return [self] # @return [undefined]
def source(input) def source(input)
fail 'source already defined' if @source fail 'source already defined' if @source
@source = node(input) @source = node(input)
self
end end
# Add expected mutation # Add expected mutation
# #
# @param [String,Parser::AST::Node] input # @param [String,Parser::AST::Node] input
# #
# @return [self] # @return [undefined]
def mutation(input) def mutation(input)
node = node(input) node = node(input)
if @expected.include?(node) if @expected.include?(node)
fail "Node for input: #{input.inspect} is already expected" fail "Mutation for input: #{input.inspect} is already expected"
end end
@expected << node @expected << node
self
end end
# Add singleton mutations # Add singleton mutations
@ -87,7 +84,7 @@ module Mutant
when ::Parser::AST::Node when ::Parser::AST::Node
input input
else else
fail "Cannot coerce to node: #{source.inspect}" fail "Cannot coerce to node: #{input.inspect}"
end end
end end

View 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

View file

@ -9,7 +9,6 @@ if ENV['COVERAGE'] == 'true'
add_filter 'vendor' add_filter 'vendor'
add_filter 'test_app' add_filter 'test_app'
add_filter 'lib/mutant.rb' # simplecov bug not seeing default block is executed 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'
add_filter 'lib/mutant/zombifier/*' add_filter 'lib/mutant/zombifier/*'
# Trace points shadow each other under 2.0 (fixed in 2.1) # Trace points shadow each other under 2.0 (fixed in 2.1)

View 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

View 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

View 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