Refactor to add Mutant::Mutator.mutate(...)
Refactors Registry to be more generic
This commit is contained in:
parent
09eb33bc44
commit
523bad401e
17 changed files with 117 additions and 146 deletions
|
@ -1,3 +1,3 @@
|
|||
---
|
||||
threshold: 16
|
||||
total_score: 1171
|
||||
total_score: 1174
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
44
lib/mutant/registry.rb
Normal 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
|
|
@ -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
|
||||
)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
|
47
spec/unit/mutant/registry_spec.rb
Normal file
47
spec/unit/mutant/registry_spec.rb
Normal 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
|
|
@ -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) }
|
||||
|
|
Loading…
Add table
Reference in a new issue