Refactor to add Mutant::Mutator.mutate(...)

Refactors Registry to be more generic
This commit is contained in:
John Backus 2016-05-05 23:36:46 -07:00
parent 09eb33bc44
commit 523bad401e
No known key found for this signature in database
GPG key ID: 9A91898D0B0B2FBE
17 changed files with 117 additions and 146 deletions

View file

@ -1,3 +1,3 @@
---
threshold: 16
total_score: 1171
total_score: 1174

View file

@ -71,8 +71,8 @@ require 'mutant/parallel/source'
require 'mutant/warning_filter'
require 'mutant/require_highjack'
require 'mutant/mutation'
require 'mutant/registry'
require 'mutant/mutator'
require 'mutant/mutator/registry'
require 'mutant/mutator/util'
require 'mutant/mutator/util/array'
require 'mutant/mutator/util/symbol'

View file

@ -22,7 +22,7 @@ module Mutant
#
# @return [Enumerable<Mutant::Mutation>]
def generated
Mutator::REGISTRY.call(node).map do |node|
Mutator.mutate(node).map do |node|
Mutation::Evil.new(self, node)
end
end

View file

@ -1,8 +1,21 @@
module Mutant
# Generator for mutations
class Mutator
REGISTRY = Registry.new
include Adamantium::Flat, Concord.new(:input, :parent), AbstractType, Procto.call(:output)
# Lookup and invoke dedicated AST mutator
#
# @param node [Parser::AST::Node]
# @param parent [nil,Mutant::Mutator::Node]
#
# @return [Set<Parser::AST::Node>]
def self.mutate(node, parent = nil)
self::REGISTRY.lookup(node.type).call(node, parent)
end
# Register node class handler
#
# @return [undefined]

View file

@ -58,7 +58,7 @@ module Mutant
# rubocop:disable RedundantBlockCall - its not redundant here
def mutate_child(index, &block)
block ||= TAUTOLOGY
REGISTRY.call(children.fetch(index), self).each do |mutation|
Mutator.mutate(children.fetch(index), self).each do |mutation|
next unless block.call(mutation)
emit_child_update(index, mutation)
end

View file

@ -32,7 +32,7 @@ module Mutant
# @return [undefined]
def emit_argument_mutations
children.each_with_index do |child, index|
REGISTRY.call(child).each do |mutant|
Mutator.mutate(child).each do |mutant|
next if invalid_argument_replacement?(mutant, index)
emit_child_update(index, mutant)
end

View file

@ -1,58 +0,0 @@
module Mutant
class Mutator
# Registry for mutators
class Registry
include Concord.new(:contents)
# Initialize object
#
# @return [undefined]
def initialize
super({})
end
# Raised when the type is an invalid type
RegistryError = Class.new(TypeError)
# Register mutator class for AST node class
#
# @param [Symbol] type
# @param [Class:Mutator] mutator
#
# @return [self]
def register(type, mutator)
fail RegistryError, "Invalid type registration: #{type.inspect}" unless AST::Types::ALL.include?(type)
fail RegistryError, "Duplicate type registration: #{type.inspect}" if contents.key?(type)
contents[type] = mutator
self
end
# Call registry
#
# @return [Enumerable<Mutation>]
def call(node, parent = nil)
lookup(node.type).call(node, parent)
end
private
# Lookup mutator class for node
#
# @param [Symbol] type
#
# @return [Class]
#
# @raise [ArgumentError]
# raises argument error when mutator class cannot be found
def lookup(type)
contents.fetch(type) do
fail RegistryError, "No mutator to handle: #{type.inspect}"
end
end
end # Registry
REGISTRY = Registry.new
end # Mutator
end # Mutant

View file

@ -33,7 +33,7 @@ module Mutant
# @return [undefined]
def dispatch
input.each_with_index do |element, index|
REGISTRY.call(element).each do |mutation|
Mutator.mutate(element).each do |mutation|
dup = dup_input
dup[index] = mutation
emit(dup)

44
lib/mutant/registry.rb Normal file
View file

@ -0,0 +1,44 @@
module Mutant
# Registry for mapping AST types to classes
class Registry
include Concord.new(:contents)
# Initialize object
#
# @return [undefined]
def initialize
super({})
end
# Raised when the type is an invalid type
RegistryError = Class.new(TypeError)
# Register class for AST node class
#
# @param [Symbol] type
# @param [Class] class
#
# @return [self]
def register(type, klass)
fail RegistryError, "Invalid type registration: #{type.inspect}" unless AST::Types::ALL.include?(type)
fail RegistryError, "Duplicate type registration: #{type.inspect}" if contents.key?(type)
contents[type] = klass
self
end
# Lookup class for node
#
# @param [Symbol] type
#
# @return [Class]
#
# @raise [ArgumentError]
# raises argument error when class cannot be found
def lookup(type)
contents.fetch(type) do
fail RegistryError, "No entry for: #{type.inspect}"
end
end
end # Registry
end # Mutant

View file

@ -10,7 +10,7 @@ module Mutant
# @return [undefined]
def mutations
[neutral_mutation].concat(
Mutator::REGISTRY.call(node).map do |mutant|
Mutator.mutate(node).map do |mutant|
Mutation::Evil.new(self, wrap_node(mutant))
end
)

View file

@ -1,7 +1,7 @@
RSpec.describe 'AST type coverage', mutant: false do
specify 'mutant should not crash for any node parser can generate' do
Mutant::AST::Types::ALL.each do |type|
Mutant::Mutator::REGISTRY.__send__(:lookup, type)
Mutant::Mutator::REGISTRY.lookup(type)
end
end
end

View file

@ -146,7 +146,7 @@ module MutantSpec
return DEFAULT_MUTATION_COUNT unless node
Mutant::Mutator::REGISTRY.call(node).length
Mutant::Mutator.mutate(node).length
end
# Install mutant

View file

@ -1,5 +1,5 @@
Mutant::Meta::Example::ALL.each.group_by(&:node_type).each do |type, examples|
RSpec.describe Mutant::Mutator::REGISTRY.__send__(:lookup, type) do
RSpec.describe Mutant::Mutator::REGISTRY.lookup(type) do
toplevel_nodes = examples.map { |example| example.node.type }.uniq
it "generates the correct mutations on #{toplevel_nodes} toplevel examples" do

View file

@ -1,75 +0,0 @@
RSpec.describe Mutant::Mutator::Registry do
let(:object) { described_class.new }
let(:mutator) { class_double(Mutant::Mutator) }
let(:node) { s(:true) }
let(:expected_arguments) { [node, nil] }
before do
allow(mutator).to receive(:call).with(*expected_arguments).and_return([s(:nil)])
end
describe '#call' do
let(:call_arguments) { [node] }
subject { object.call(*call_arguments) }
before do
object.register(:true, mutator)
end
context 'on parent given' do
let(:call_arguments) { [node, s(:and)] }
let(:expected_arguments) { call_arguments }
it { should eql([s(:nil)]) }
end
context 'on registered node' do
let(:node) { s(:true) }
it { should eql([s(:nil)]) }
end
context 'on unknown node' do
let(:node) { s(:unknown) }
it 'raises error' do
expect { subject }.to raise_error(described_class::RegistryError, 'No mutator to handle: :unknown')
end
end
end
describe '#register' do
subject { object.register(type, mutator) }
context 'when registering an invalid node type' do
let(:type) { :invalid }
it 'raises error' do
expect { subject }.to raise_error(described_class::RegistryError, 'Invalid type registration: :invalid')
end
end
context 'when registering a valid node type' do
let(:type) { :true }
it 'allows to call mutator' do
subject
expect(object.call(s(type))).to eql([s(:nil)])
end
it_behaves_like 'a command method'
end
context 'when duplicate the registration of a valid node type' do
let(:type) { :true }
it 'allows to lookup mutator' do
object.register(type, mutator)
expect { subject }.to raise_error(described_class::RegistryError, 'Duplicate type registration: :true')
end
it_behaves_like 'a command method'
end
end
end

View file

@ -4,7 +4,7 @@ RSpec.describe Mutant::Mutator do
subject do
Class.new(described_class) do
const_set(:REGISTRY, Mutant::Mutator::Registry.new)
const_set(:REGISTRY, Mutant::Registry.new)
handle :send
@ -15,7 +15,7 @@ RSpec.describe Mutant::Mutator do
end
it 'should register mutator' do
expect(subject::REGISTRY.call(s(:send), s(:parent))).to eql([s(:parent)].to_set)
expect(subject.mutate(s(:send), s(:parent))).to eql([s(:parent)].to_set)
end
end
end

View file

@ -0,0 +1,47 @@
RSpec.describe Mutant::Registry do
let(:lookup) { object.lookup(type) }
let(:object) { described_class.new }
let(:mutator) { class_double(Mutant::Mutator) }
let(:node) { s(:true) }
let(:expected_arguments) { [node, nil] }
def register_mutator
object.register(type, mutator)
end
context 'on registered type' do
subject { register_mutator }
let(:type) { :true }
before { subject }
it 'returns registered mutator' do
expect(lookup).to be(mutator)
end
it_behaves_like 'a command method'
context 'when registered twice' do
it 'fails upon registration' do
expect { register_mutator }.to raise_error(described_class::RegistryError, 'Duplicate type registration: :true')
end
end
end
context 'on unknown type' do
let(:type) { :unknown }
it 'raises error' do
expect { lookup }.to raise_error(described_class::RegistryError, 'No entry for: :unknown')
end
end
context 'when registering an invalid node type' do
let(:type) { :invalid }
it 'raises error' do
expect { register_mutator }.to raise_error(described_class::RegistryError, 'Invalid type registration: :invalid')
end
end
end

View file

@ -71,7 +71,7 @@ RSpec.describe Mutant::Subject do
subject { object.mutations }
before do
expect(Mutant::Mutator::REGISTRY).to receive(:call).with(node).and_return([mutation_a, mutation_b])
expect(Mutant::Mutator).to receive(:mutate).with(node).and_return([mutation_a, mutation_b])
end
let(:mutation_a) { instance_double(Parser::AST::Node, :mutation_a) }