Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

[WIP] Mutator configuration #138

Closed
wants to merge 20 commits into from

1 participant

@mbj
Owner
mbj commented

Opening early for discussing.

Its NOT ready misses to connect options with CLI.

Adds the infrastructure to fix:

And all similar "I wanna be able to remove mutation X" - requests.

added some commits
@mbj Add config object to mutator state
Next commits will provide config object from callsides. The config
object is currently empty.

The config object is passed to all children mutators. Currently no
mutator public interface was modified so no spec changes. With next
commits we have public visible stuff so specs will be adjusted.
fc426be
@mbj Move Mutator context and config to dedicated file ff7205d
@mbj Refactor outer interface of mutator to allow injection of context wit…
…h config
4f87183
@mbj Fix comment 0d329df
@mbj Allow injection of Mutator::Context also for utility mutators 78b7985
@mbj Add Mutator::Config::DEFAULT 098b759
@mbj Style and vim typo fixes 2802835
@mbj Adjust reek config beae659
@mbj Steer return value propagation via config objects
* Change util mutators to hide themselves from the context parent chain
  this allows to use parent_type helpers in all node mutators
  consistently.

* This is the OO API feature fix for #10. CLI flags are still missing.
b5aa1c8
@mbj Remove *.gem from gitignore b68b550
@mbj Adjust flay score f8e0f06
@mbj Add a spec that proves last expression is picked for return value pro…
…pagation config

Very artificial but hey, lets try to be most correct as possible.
4d1d557
@mbj Fix whitespace 7dcd1a8
@mbj Merge branch 'master' into mutator-config
Conflicts:
	config/flay.yml
	lib/mutant.rb
	spec/unit/mutant/mutator/node/return_spec.rb
	spec/unit/mutant/mutator_spec.rb
7405f1d
@mbj Merge branch 'master' into mutator-config 87000d0
@mbj Merge remote-tracking branch 'origin' into mutator-config 6f44935
@mbj Merge branch 'master' into mutator-config 5ab7ffc
@mbj Merge branch 'master' into mutator-config 56a90cd
@mbj Fix requires 4d20a46
@mbj Fix rspec expectations 248131e
@mbj mbj added the enhancement label
@mbj
Owner

I'm closing this PR as the feature will be easier to reimplement on top of master than to painfully rebase it.

@mbj mbj closed this
@mbj mbj deleted the branch
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Dec 7, 2013
  1. Add config object to mutator state

    authored
    Next commits will provide config object from callsides. The config
    object is currently empty.
    
    The config object is passed to all children mutators. Currently no
    mutator public interface was modified so no spec changes. With next
    commits we have public visible stuff so specs will be adjusted.
Commits on Dec 8, 2013
  1. Fix comment

    authored
  2. Add Mutator::Config::DEFAULT

    authored
  3. Style and vim typo fixes

    authored
  4. Adjust reek config

    authored
  5. Steer return value propagation via config objects

    authored
    * Change util mutators to hide themselves from the context parent chain
      this allows to use parent_type helpers in all node mutators
      consistently.
    
    * This is the OO API feature fix for #10. CLI flags are still missing.
  6. Remove *.gem from gitignore

    authored
  7. Adjust flay score

    authored
  8. Add a spec that proves last expression is picked for return value pro…

    authored
    …pagation config
    
    Very artificial but hey, lets try to be most correct as possible.
Commits on Dec 10, 2013
  1. Fix whitespace

    authored
Commits on Mar 4, 2014
  1. Merge branch 'master' into mutator-config

    authored
    Conflicts:
    	config/flay.yml
    	lib/mutant.rb
    	spec/unit/mutant/mutator/node/return_spec.rb
    	spec/unit/mutant/mutator_spec.rb
Commits on Mar 8, 2014
Commits on Mar 29, 2014
Commits on Mar 30, 2014
  1. Fix requires

    authored
  2. Fix rspec expectations

    authored
This page is out of date. Refresh to see the latest.
View
1  .gitignore
@@ -18,7 +18,6 @@ tmtags
.rbx
## PROJECT::GENERAL
-*.gem
coverage
profiling
turbulence
View
3  config/reek.yml
@@ -74,7 +74,8 @@ TooManyMethods:
enabled: true
exclude:
- Mutant::CLI
- - Mutant::Mutator::Node
+ - Mutant::Mutator # 90% private and mostly helpers
+ - Mutant::Mutator::Node # 90% private and mostly helpers
- Mutant::Reporter::CLI
max_methods: 10
TooManyStatements:
View
4 lib/mutant.rb
@@ -32,10 +32,12 @@ module Mutant
require 'mutant/constants'
require 'mutant/random'
require 'mutant/walker'
-require 'mutant/mutator'
require 'mutant/mutation'
require 'mutant/mutation/evil'
require 'mutant/mutation/neutral'
+require 'mutant/mutator'
+require 'mutant/mutator/config'
+require 'mutant/mutator/context'
require 'mutant/mutator/registry'
require 'mutant/mutator/util'
require 'mutant/mutator/util/array'
View
62 lib/mutant/mutator.rb
@@ -13,9 +13,10 @@ class Mutator
#
# @api private
#
- def self.each(input, parent = nil, &block)
- return to_enum(__method__, input, parent) unless block_given?
- Registry.lookup(input).new(input, parent, block)
+ def self.each(context, &block)
+ return to_enum(__method__, context) unless block_given?
+
+ Registry.lookup(context.input).new(context, block)
self
end
@@ -45,13 +46,29 @@ def self.identity(object)
object
end
+ # Return parent mutator
+ #
+ # @return [Mutator]
+ # if parent mutator is present
+ #
+ # @return [nil]
+ # otherwise
+ #
+ # @api private
+ #
+ def parent
+ context.parent
+ end
+
# Return input
#
- # @return [Object]
+ # @return [Config]
#
# @api private
#
- attr_reader :input
+ def config
+ context.config
+ end
# Return input
#
@@ -59,27 +76,36 @@ def self.identity(object)
#
# @api private
#
- attr_reader :parent
+ def input
+ context.input
+ end
private
# Initialize object
#
- # @param [Object] input
- # @param [Object] parent
+ # @param [Context] context
# @param [#call(node)] block
#
# @return [undefined]
#
# @api private
#
- def initialize(input, parent, block)
- @input, @parent, @block = input, parent, block
+ def initialize(context, block)
+ @context, @block = context, block
@seen = Set.new
guard(input)
dispatch
end
+ # Return context
+ #
+ # @return [Context]
+ #
+ # @api private
+ #
+ attr_reader :context
+
# Test if generated object is not guarded from emmitting
#
# @param [Object] object
@@ -190,8 +216,20 @@ def emit!(node)
#
# @api private
#
- def run(mutator)
- mutator.new(input, self, method(:emit))
+ def run(mutator, parent = self)
+ mutator.new(inherit_context(input, parent), method(:emit))
+ end
+
+ # Return inherited context for input
+ #
+ # @param [Object] input
+ #
+ # @return [Context]
+ #
+ # @api private
+ #
+ def inherit_context(input, parent = self)
+ Context.new(config, parent, input)
end
# Shortcut to create a new unfrozen duplicate of input
View
18 lib/mutant/mutator/config.rb
@@ -0,0 +1,18 @@
+module Mutant
+ class Mutator
+
+ # Mutator configuration
+ class Config
+ include Adamantium, Anima::Update, Anima.new(
+ # Yeah I know this is long. If someone could please come up with a more shorter but as narrow name!
+ :return_as_last_block_statement_value_propagation
+ )
+
+ DEFAULT = new(
+ return_as_last_block_statement_value_propagation: true
+ )
+
+ end # Config
+
+ end # Mutator
+end # Mutant
View
24 lib/mutant/mutator/context.rb
@@ -0,0 +1,24 @@
+module Mutant
+ class Mutator
+
+ # Context to be mutated
+ class Context
+ include Concord::Public.new(:config, :parent, :input)
+
+ # Return root context for input
+ #
+ # @param [Config] config
+ # @param [Object] input
+ #
+ # @return [Context]
+ #
+ # @api private
+ #
+ def self.root(config, input)
+ new(config, nil, input)
+ end
+
+ end # Context
+
+ end # Mutation
+end # Mutant
View
4 lib/mutant/mutator/node.rb
@@ -103,7 +103,7 @@ def self.children(*names)
# @api private
#
def emit_children_mutations
- Mutator::Util::Array.each(children, self) do |children|
+ Mutator::Util::Array.each(inherit_context(children)) do |children|
emit_self(*children)
end
end
@@ -128,7 +128,7 @@ def children
#
def mutate_child(index, mutator = Mutator)
child = children.at(index)
- mutator.each(child, self) do |mutation|
+ mutator.each(inherit_context(child)) do |mutation|
emit_child_update(index, mutation)
end
end
View
2  lib/mutant/mutator/node/argument.rb
@@ -32,7 +32,7 @@ def dispatch
#
def emit_name_mutation
return if skip?
- Mutator::Util::Symbol.each(name, self) do |name|
+ Mutator::Util::Symbol.each(inherit_context(name)) do |name|
emit_name(name)
end
end
View
2  lib/mutant/mutator/node/begin.rb
@@ -18,7 +18,7 @@ class Begin < self
# @api private
#
def dispatch
- Util::Array.each(children, self) do |children|
+ Util::Array.each(inherit_context(children)) do |children|
emit_child_subset(children)
end
children.each_with_index do |child, index|
View
2  lib/mutant/mutator/node/named_value/constant_assignment.rb
@@ -32,7 +32,7 @@ def dispatch
# @api private
#
def mutate_name
- Mutator::Util::Symbol.each(name, self) do |name|
+ Mutator::Util::Symbol.each(inherit_context(name)) do |name|
emit_name(name.upcase)
end
end
View
2  lib/mutant/mutator/node/named_value/variable_assignment.rb
@@ -41,7 +41,7 @@ def dispatch
#
def mutate_name
prefix = MAP.fetch(node.type)
- Mutator::Util::Symbol.each(name, self) do |name|
+ Mutator::Util::Symbol.each(inherit_context(name)) do |name|
emit_name(prefix + name.to_s)
end
end
View
2  lib/mutant/mutator/node/resbody.rb
@@ -33,7 +33,7 @@ def dispatch
def mutate_captures
return unless captures
emit_captures(nil)
- Util::Array.each(captures.children, self) do |matchers|
+ Util::Array.each(inherit_context(captures.children)) do |matchers|
next if matchers.empty?
emit_captures(s(:array, *matchers))
end
View
27 lib/mutant/mutator/node/return.rb
@@ -20,12 +20,37 @@ class Return < self
#
def dispatch
if value
- emit(value)
+ emit_value_propagation
emit_value_mutations
end
emit_nil
end
+ # Emit value propagation
+ #
+ # @return [undefined]
+ #
+ # @api private
+ #
+ def emit_value_propagation
+ return unless config.return_as_last_block_statement_value_propagation || !last_expression_in_block?
+ emit(value)
+ end
+
+ # Test if node is last expression in a block
+ #
+ # @return [true]
+ # if currently mutated node is last expression of a block
+ #
+ # @return [false]
+ # otherwise
+ #
+ # @api private
+ #
+ def last_expression_in_block?
+ parent_type == :begin && parent.node.children.last.eql?(node)
+ end
+
end # Return
end # Node
end # Mutator
View
6 lib/mutant/mutator/util.rb
@@ -18,10 +18,10 @@ class Util < self
#
# @api private
#
- def self.each(object, parent, &block)
- return to_enum(__method__, object, parent) unless block_given?
+ def self.each(context, &block)
+ return to_enum(__method__, context) unless block_given?
- new(object, parent, block)
+ new(context, block)
self
end
View
6 lib/mutant/mutator/util/array.rb
@@ -43,7 +43,7 @@ class Element < Util
#
def dispatch
input.each_with_index do |element, index|
- Mutator.each(element).each do |mutation|
+ Mutator.each(inherit_context(element, parent)).each do |mutation|
dup = dup_input
dup[index] = mutation
emit(dup)
@@ -62,8 +62,8 @@ def dispatch
# @api private
#
def dispatch
- run(Element)
- run(Presence)
+ run(Element, parent)
+ run(Presence, parent)
emit([])
end
View
2  lib/mutant/subject/method.rb
@@ -49,7 +49,7 @@ def match_expression
#
def generate_mutations(emitter)
emitter << noop_mutation
- Mutator.each(node) do |mutant|
+ Mutator.each(Mutator::Context.root(Mutator::Config::DEFAULT, node)) do |mutant|
emitter << Mutation::Evil.new(self, mutant)
end
end
View
16 spec/shared/mutator_behavior.rb
@@ -41,19 +41,26 @@ def assert_transitive!
end
shared_examples_for 'a mutator' do
- subject { object.each(node) { |item| yields << item } }
+
+ unless instance_methods.include?(:config)
+ let(:config) { Mutant::Mutator::Config::DEFAULT }
+ end
+
+ let(:context) { Mutant::Mutator::Context.root(config, node) }
+
+ subject { object.each(context) { |item| yields << item } }
let(:yields) { [] }
let(:object) { described_class }
- unless instance_methods.map(&:to_s).include?('node')
+ unless instance_methods.include?(:node)
let(:node) { parse(source) }
end
it_should_behave_like 'a command method'
context 'with no block' do
- subject { object.each(node) }
+ subject { object.each(context) }
it { should be_instance_of(to_enum.class) }
@@ -61,9 +68,6 @@ def assert_transitive!
mutations.map(&Subject.method(:coerce))
end
- let(:generated_mutations) do
- end
-
it 'generates the expected mutations' do
generated = subject.map { |node| Subject.new(node) }
View
94 spec/unit/mutant/mutator_spec.rb
@@ -1,29 +1,93 @@
-# encoding: utf-8
-
-# This file is the sandbox for new mutations.
-# Once finished mutation test will be moved to class specfic
-# file.
+# encoding: UTF-8
require 'spec_helper'
describe Mutant::Mutator do
- describe '.each' do
- pending 'interpolated string literal (DynamicString)' do
- let(:source) { '"foo#{1}bar"' }
+ let(:object) { class_under_test.new(context, block) }
+
+ let(:context) { described_class::Context.new(config, parent, input) }
+ let(:block) { Block.new }
+ let(:input) { :input }
+ let(:parent) { :parent }
+ let(:config) { double('Config') }
+
+ class Block
+ attr_reader :arguments
+
+ def called?
+ !!defined?(@arguments)
+ end
- let(:random_string) { 'this-is-random' }
+ def call(*arguments)
+ @arguments = arguments
+ end
+ end
- let(:mutations) do
- mutations = []
- mutations << 'nil'
+ let(:class_under_test) do
+ Class.new(described_class) do
+ def dispatch
+ # noop
end
+ end
+ end
+
+ describe '#emit' do
+
+ subject { object.send(:emit, generated) }
+
+ context 'with generated that is not equal to input' do
+ let(:generated) { :generated }
- before do
- Mutant::Random.stub(hex_string: random_string)
+ it 'should call block' do
+ subject
+ expect(block.called?).to be(true)
end
- it_should_behave_like 'a mutator'
+ it 'should call block with generated' do
+ subject
+ expect(block.arguments).to eql([generated])
+ end
+ end
+
+ context 'with generated object that is equal to input' do
+ let(:generated) { input }
+
+ it 'should not call block' do
+ subject
+ expect(block.called?).to be(false)
+ end
+ end
+ end
+
+ describe '#emit_new' do
+ subject { object.send(:emit_new) { generated } }
+
+ context 'when new object generated' do
+ let(:generated) { :generated }
+
+ it 'should call block' do
+ subject
+ expect(block.called?).to be(true)
+ end
+
+ it 'should call block with generated object' do
+ subject
+ expect(block.arguments).to eql([generated])
+ end
+ end
+
+ context 'when new AST could not be generated' do
+ let(:generated) { input }
+
+ it 'should raise error' do
+ expect do
+ subject
+ end.to raise_error(
+ RuntimeError,
+ 'New AST could not be generated after 3 attempts'
+ )
+ end
end
end
end
Something went wrong with that request. Please try again.