Remove guard via identity
* Simplify spec helper via mutation verifier class * There was no need to guard via mapped identity anymore.
This commit is contained in:
parent
c1f3295013
commit
a3b558bec0
7 changed files with 128 additions and 114 deletions
2
Gemfile
2
Gemfile
|
@ -4,6 +4,8 @@ source 'https://rubygems.org'
|
||||||
|
|
||||||
gem 'mutant', path: '.'
|
gem 'mutant', path: '.'
|
||||||
|
|
||||||
|
gem 'unparser', git: 'https://github.com/mbj/unparser.git'
|
||||||
|
|
||||||
gemspec name: 'mutant'
|
gemspec name: 'mutant'
|
||||||
|
|
||||||
gem 'devtools', git: 'https://github.com/rom-rb/devtools.git'
|
gem 'devtools', git: 'https://github.com/rom-rb/devtools.git'
|
||||||
|
|
|
@ -33,18 +33,6 @@ module Mutant
|
||||||
end
|
end
|
||||||
private_class_method :handle
|
private_class_method :handle
|
||||||
|
|
||||||
# Return identity of object (for deduplication)
|
|
||||||
#
|
|
||||||
# @param [Object] object
|
|
||||||
#
|
|
||||||
# @return [Object]
|
|
||||||
#
|
|
||||||
# @api private
|
|
||||||
#
|
|
||||||
def self.identity(object)
|
|
||||||
object
|
|
||||||
end
|
|
||||||
|
|
||||||
# Return input
|
# Return input
|
||||||
#
|
#
|
||||||
# @return [Object]
|
# @return [Object]
|
||||||
|
@ -92,7 +80,7 @@ module Mutant
|
||||||
# @api private
|
# @api private
|
||||||
#
|
#
|
||||||
def new?(object)
|
def new?(object)
|
||||||
!@seen.include?(identity(object))
|
!@seen.include?(object)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Add object to guarded values
|
# Add object to guarded values
|
||||||
|
@ -104,19 +92,7 @@ module Mutant
|
||||||
# @api private
|
# @api private
|
||||||
#
|
#
|
||||||
def guard(object)
|
def guard(object)
|
||||||
@seen << identity(object)
|
@seen << object
|
||||||
end
|
|
||||||
|
|
||||||
# Return identity for input
|
|
||||||
#
|
|
||||||
# @param [Object] input
|
|
||||||
#
|
|
||||||
# @return [Object]
|
|
||||||
#
|
|
||||||
# @api private
|
|
||||||
#
|
|
||||||
def identity(input)
|
|
||||||
self.class.identity(input)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# Dispatch node generations
|
# Dispatch node generations
|
||||||
|
|
|
@ -8,18 +8,6 @@ module Mutant
|
||||||
|
|
||||||
handle :rescue
|
handle :rescue
|
||||||
|
|
||||||
# Return identity
|
|
||||||
#
|
|
||||||
# @param [Parser::AST::Node] node
|
|
||||||
#
|
|
||||||
# @return [String]
|
|
||||||
#
|
|
||||||
# @api private
|
|
||||||
#
|
|
||||||
def self.identity(node)
|
|
||||||
super(NodeHelpers.s(:kwbegin, node))
|
|
||||||
end
|
|
||||||
|
|
||||||
end # Rescue
|
end # Rescue
|
||||||
end # Node
|
end # Node
|
||||||
end # Mutator
|
end # Mutator
|
||||||
|
|
|
@ -18,13 +18,13 @@ module Mutant
|
||||||
module_function :s
|
module_function :s
|
||||||
|
|
||||||
NAN =
|
NAN =
|
||||||
s(:send, s(:float, 0.0), :/, s(:args, s(:float, 0.0)))
|
s(:send, s(:float, 0.0), :/, s(:float, 0.0))
|
||||||
INFINITY =
|
INFINITY =
|
||||||
s(:send, s(:float, 1.0), :/, s(:args, s(:float, 0.0)))
|
s(:send, s(:float, 1.0), :/, s(:float, 0.0))
|
||||||
NEW_OBJECT =
|
NEW_OBJECT =
|
||||||
s(:send, s(:const, s(:cbase), :Object), :new)
|
s(:send, s(:const, s(:cbase), :Object), :new)
|
||||||
NEGATIVE_INFINITY =
|
NEGATIVE_INFINITY =
|
||||||
s(:send, s(:float, -1.0), :/, s(:args, s(:float, 0.0)))
|
s(:send, s(:float, -1.0), :/, s(:float, 0.0))
|
||||||
|
|
||||||
RAISE = s(:send, nil, :raise)
|
RAISE = s(:send, nil, :raise)
|
||||||
|
|
||||||
|
|
|
@ -1,52 +1,12 @@
|
||||||
# encoding: utf-8
|
# encoding: UTF-8
|
||||||
|
|
||||||
class Subject
|
|
||||||
|
|
||||||
include Equalizer.new(:source)
|
|
||||||
|
|
||||||
Undefined = Object.new.freeze
|
|
||||||
|
|
||||||
attr_reader :source
|
|
||||||
|
|
||||||
def self.coerce(input)
|
|
||||||
case input
|
|
||||||
when Parser::AST::Node
|
|
||||||
new(input)
|
|
||||||
when String
|
|
||||||
new(Parser::CurrentRuby.parse(input))
|
|
||||||
else
|
|
||||||
raise
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def to_s
|
|
||||||
"#{@node.inspect}\n#{@source}"
|
|
||||||
end
|
|
||||||
|
|
||||||
def initialize(node)
|
|
||||||
source = Unparser.unparse(node)
|
|
||||||
@node, @source = node, source
|
|
||||||
end
|
|
||||||
|
|
||||||
def assert_transitive!
|
|
||||||
generated = Unparser.generate(@node)
|
|
||||||
parsed = Parser::CurrentRuby.parse(generated)
|
|
||||||
again = Unparser.generate(parsed)
|
|
||||||
unless generated == again
|
|
||||||
# mostly an unparser bug!
|
|
||||||
fail sprintf("Untransitive:\n%s\n---\n%s", generated, again)
|
|
||||||
end
|
|
||||||
self
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
shared_examples_for 'a mutator' do
|
shared_examples_for 'a mutator' do
|
||||||
subject { object.each(node) { |item| yields << item } }
|
subject { object.each(node, &yields.method(:<<)) }
|
||||||
|
|
||||||
let(:yields) { [] }
|
let(:yields) { [] }
|
||||||
let(:object) { described_class }
|
let(:object) { described_class }
|
||||||
|
|
||||||
unless instance_methods.map(&:to_s).include?('node')
|
unless instance_methods.include?(:node)
|
||||||
let(:node) { parse(source) }
|
let(:node) { parse(source) }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -57,42 +17,32 @@ shared_examples_for 'a mutator' do
|
||||||
|
|
||||||
it { should be_instance_of(to_enum.class) }
|
it { should be_instance_of(to_enum.class) }
|
||||||
|
|
||||||
let(:expected_mutations) do
|
def coerce(input)
|
||||||
mutations.map(&Subject.method(:coerce))
|
case input
|
||||||
|
when String
|
||||||
|
Parser::CurrentRuby.parse(input)
|
||||||
|
when Parser::AST::Node
|
||||||
|
input
|
||||||
|
else
|
||||||
|
raise
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
let(:generated_mutations) do
|
def normalize(node)
|
||||||
|
Unparser::Preprocessor.run(node)
|
||||||
|
end
|
||||||
|
|
||||||
|
let(:expected_mutations) do
|
||||||
|
mutations.map(&method(:coerce)).map(&method(:normalize))
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'generates the expected mutations' do
|
it 'generates the expected mutations' do
|
||||||
|
generated_mutations = subject.map(&method(:normalize))
|
||||||
|
|
||||||
generated = subject.map { |node| Subject.new(node) }
|
verifier = MutationVerifier.new(node, expected_mutations, generated_mutations)
|
||||||
|
|
||||||
missing = expected_mutations - generated
|
unless verifier.success?
|
||||||
unexpected = generated - expected_mutations
|
fail verifier.error_report
|
||||||
|
|
||||||
message = []
|
|
||||||
|
|
||||||
if missing.any?
|
|
||||||
message << sprintf('Missing mutations (%i):', missing.length)
|
|
||||||
message.concat(missing)
|
|
||||||
end
|
|
||||||
|
|
||||||
if unexpected.any?
|
|
||||||
message << sprintf('Unexpected mutations (%i):', unexpected.length)
|
|
||||||
message.concat(unexpected)
|
|
||||||
end
|
|
||||||
|
|
||||||
if message.any?
|
|
||||||
|
|
||||||
message = sprintf(
|
|
||||||
"Original:\n%s\n%s\n-----\n%s",
|
|
||||||
generate(node),
|
|
||||||
node.inspect,
|
|
||||||
message.join("\n-----\n")
|
|
||||||
)
|
|
||||||
|
|
||||||
fail message
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -21,8 +21,10 @@ if ENV['COVERAGE'] == 'true'
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
require 'equalizer'
|
require 'concord'
|
||||||
|
require 'adamantium'
|
||||||
require 'devtools/spec_helper'
|
require 'devtools/spec_helper'
|
||||||
|
require 'unparser/cli'
|
||||||
require 'mutant'
|
require 'mutant'
|
||||||
|
|
||||||
$LOAD_PATH << File.join(TestApp.root, 'lib')
|
$LOAD_PATH << File.join(TestApp.root, 'lib')
|
||||||
|
|
96
spec/support/mutation_verifier.rb
Normal file
96
spec/support/mutation_verifier.rb
Normal file
|
@ -0,0 +1,96 @@
|
||||||
|
# encoding: UTF-8
|
||||||
|
|
||||||
|
class MutationVerifier
|
||||||
|
include Adamantium::Flat, Concord.new(:original_node, :expected, :generated)
|
||||||
|
|
||||||
|
# Test if mutation was verified successfully
|
||||||
|
#
|
||||||
|
# @return [Boolean]
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def success?
|
||||||
|
unparser.success? && missing.empty? && unexpected.empty?
|
||||||
|
end
|
||||||
|
|
||||||
|
# Return error report
|
||||||
|
#
|
||||||
|
# @return [String]
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def error_report
|
||||||
|
unless unparser.success?
|
||||||
|
return unparser.error_report
|
||||||
|
end
|
||||||
|
mutation_report
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
# Return unexpected mutationso
|
||||||
|
#
|
||||||
|
# @return [Array<Parser::AST::Node>]
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def unexpected
|
||||||
|
generated - expected
|
||||||
|
end
|
||||||
|
memoize :unexpected
|
||||||
|
|
||||||
|
# Return mutation report
|
||||||
|
#
|
||||||
|
# @return [String]
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def mutation_report
|
||||||
|
message = ['Original:', original_node.inspect]
|
||||||
|
if missing.any?
|
||||||
|
message << 'Missing mutations:'
|
||||||
|
message << missing.map(&method(:format_mutation)).join("\n-----\n")
|
||||||
|
end
|
||||||
|
if unexpected.any?
|
||||||
|
message << 'Unexpected mutations:'
|
||||||
|
message << unexpected.map(&method(:format_mutation)).join("\n-----\n")
|
||||||
|
end
|
||||||
|
message.join("\n======\n")
|
||||||
|
end
|
||||||
|
|
||||||
|
# Format mutation
|
||||||
|
#
|
||||||
|
# @return [String]
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def format_mutation(node)
|
||||||
|
[
|
||||||
|
node.inspect,
|
||||||
|
Unparser.unparse(node)
|
||||||
|
].join("\n")
|
||||||
|
end
|
||||||
|
|
||||||
|
# Return missing mutationso
|
||||||
|
#
|
||||||
|
# @return [Array<Parser::AST::Node>]
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def missing
|
||||||
|
expected - generated
|
||||||
|
end
|
||||||
|
memoize :missing
|
||||||
|
|
||||||
|
# Return unparser verifier
|
||||||
|
#
|
||||||
|
# @return [Unparser::CLI::Source]
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def unparser
|
||||||
|
Unparser::CLI::Source::Node.new(original_node)
|
||||||
|
end
|
||||||
|
memoize :unparser
|
||||||
|
end # MutationVerifier
|
||||||
|
|
Loading…
Reference in a new issue