Skip to content

Commit

Permalink
Add mutation operators preset light and full
Browse files Browse the repository at this point in the history
* Defaults to `light`, giving up to educate the world about the issues with `#==`,
  at least by default.
  • Loading branch information
mbj committed Jul 16, 2023
1 parent fa1c307 commit 6bf1cd7
Show file tree
Hide file tree
Showing 22 changed files with 258 additions and 74 deletions.
7 changes: 7 additions & 0 deletions Changelog.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
# v0.11.22 2023-07-16

* Introduce mutation operators config 'light' and 'full'. Mutant will default to
light set that for the moment does not include `#== -> #eql?`.

[#1394](https://github.com/mbj/mutant/pull/1394)

# v0.11.21 2023-06-15

* [#1389](https://github.com/mbj/mutant/pull/1389)
Expand Down
2 changes: 1 addition & 1 deletion Gemfile.lock
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
PATH
remote: .
specs:
mutant (0.11.21)
mutant (0.11.22)
diff-lcs (~> 1.3)
parser (~> 3.2.2)
regexp_parser (~> 2.6.1)
Expand Down
7 changes: 6 additions & 1 deletion docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ See mutant's configuration file, [mutant.yml](/mutant.yml), for a complete examp

#### `mutation`

Configuration of the mutations to generate.
Configuration of the mutations generator.

```yml
mutation:
Expand All @@ -181,6 +181,11 @@ mutation:
# This is useful to not emit mutations for log statements etc
ignore_patterns:
- send{selector=log}
# Select full mutation operators by default mutant only applies the light set
# Only difference between full and light right now is that light does not apply
# `#== -> #eql?` mutation
# At this moment there is no CLI equivalent for this setting.
operators: full # or `light`
```
Please consult the [AST-Pattern documentation](/docs/ast-pattern.md) for details of AST pattern
Expand Down
1 change: 1 addition & 0 deletions lib/mutant.rb
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ module Mutant
require 'mutant/parallel/worker'
require 'mutant/require_highjack'
require 'mutant/mutation'
require 'mutant/mutation/operators'
require 'mutant/mutation/config'
require 'mutant/mutator'
require 'mutant/mutator/util'
Expand Down
9 changes: 7 additions & 2 deletions lib/mutant/meta.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,13 @@ class Example
# Add example
#
# @return [undefined]
def self.add(*types, &block)
ALL << DSL.call(caller_locations(1).first, Set.new(types), block)
def self.add(*types, operators: :full, &block)
ALL << DSL.call(
block: block,
location: caller_locations(1).first,
operators: Mutation::Operators.parse(operators.to_s).from_right,
types: Set.new(types)
)
end

Pathname.glob(Pathname.new(__dir__).parent.parent.join('meta', '*.rb'))
Expand Down
3 changes: 2 additions & 1 deletion lib/mutant/meta/example.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ class Example
:location,
:lvars,
:node,
:operators,
:original_source,
:types
)
Expand Down Expand Up @@ -56,7 +57,7 @@ def original_source_generated
# @return [Enumerable<Mutant::Mutation>]
def generated
Mutator::Node.mutate(
config: Mutation::Config::DEFAULT,
config: Mutation::Config::DEFAULT.with(operators: operators),
node: node
).map do |node|
Mutation::Evil.new(subject: self, node: node)
Expand Down
20 changes: 11 additions & 9 deletions lib/mutant/meta/example/dsl.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,25 +11,26 @@ class DSL
#
# @param [Thread::Backtrace::Location] location
# @param [Set<Symbol>] types
# @param [Mutation::Operators] operators
#
# @return [Example]
def self.call(location, types, block)
instance = new(location, types)
#
def self.call(location:, types:, operators:, block:) # rubocop:disable Metrics/ParameterLists
instance = new(location, types, operators)
instance.instance_eval(&block)
instance.example
end

private_class_method :new

# Initialize object
#
# @return [undefined]
def initialize(location, types)
@expected = []
@location = location
@lvars = []
@source = nil
@types = types
def initialize(location, types, operators)
@expected = []
@location = location
@lvars = []
@operators = operators
@types = types
end

# Example captured by DSL
Expand All @@ -46,6 +47,7 @@ def example
location: @location,
lvars: @lvars,
node: @node,
operators: @operators,
original_source: @source,
types: @types
)
Expand Down
2 changes: 1 addition & 1 deletion lib/mutant/meta/example/verification.rb
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ def report_mutation(mutation)

def original
[
'Original:',
"Original: (operators: #{example.operators.class.operators_name})",
example.node,
example.original_source
]
Expand Down
14 changes: 12 additions & 2 deletions lib/mutant/mutation/config.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,16 @@
module Mutant
class Mutation
class Config
include Anima.new(:ignore_patterns, :timeout)
include Anima.new(
:ignore_patterns,
:operators,
:timeout
)

DEFAULT = new(
timeout: nil,
ignore_patterns: []
ignore_patterns: [],
operators: Operators::Light.new
)

ignore_pattern = Transform::Block.capture('ignore pattern', &AST::Pattern.method(:parse))
Expand All @@ -20,6 +25,10 @@ class Config
transform: Transform::Array.new(transform: ignore_pattern),
value: 'ignore_patterns'
),
Transform::Hash::Key.new(
transform: Operators::TRANSFORM,
value: 'operators'
),
Transform::Hash::Key.new(
transform: Transform::FLOAT,
value: 'timeout'
Expand All @@ -35,6 +44,7 @@ class Config
def merge(other)
with(
ignore_patterns: other.ignore_patterns,
operators: other.operators,
timeout: other.timeout || timeout
)
end
Expand Down
83 changes: 83 additions & 0 deletions lib/mutant/mutation/operators.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
# frozen_string_literal: true

module Mutant
# Represent a mutated node with its subject
class Mutation
class Operators
include Equalizer.new

class Full < self
NAME = :full

SELECTOR_REPLACEMENTS = {
:< => %i[== eql? equal?],
:<= => %i[< == eql? equal?],
:== => %i[eql? equal?],
:=== => %i[is_a?],
:=~ => %i[match?],
:> => %i[== eql? equal?],
:>= => %i[> == eql? equal?],
__send__: %i[public_send],
all?: %i[any?],
any?: %i[all?],
at: %i[fetch key?],
fetch: %i[key?],
flat_map: %i[map],
gsub: %i[sub],
is_a?: %i[instance_of?],
kind_of?: %i[instance_of?],
map: %i[each],
match: %i[match?],
method: %i[public_method],
reverse_each: %i[each],
reverse_map: %i[map each],
reverse_merge: %i[merge],
send: %i[public_send __send__],
to_a: %i[to_ary],
to_h: %i[to_hash],
to_i: %i[to_int],
to_s: %i[to_str],
values_at: %i[fetch_values]
}.freeze.tap { |hash| hash.values(&:freeze) }
end

class Light < self
NAME = :light

SELECTOR_REPLACEMENTS = Full::SELECTOR_REPLACEMENTS
.dup
.tap do |replacements|
replacements.delete(:==)
replacements.delete(:eql?)
end
.freeze
end

def self.operators_name
self::NAME
end

def selector_replacements
self.class::SELECTOR_REPLACEMENTS
end

def self.parse(value)
klass = [Light, Full].detect { |candidate| candidate.operators_name.to_s.eql?(value) }

if klass
Either::Right.new(klass.new)
else
Either::Left.new("Unknown operators: #{value}")
end
end

TRANSFORM =
Transform::Sequence.new(
steps: [
Transform::STRING,
Transform::Block.capture('parse operator', &method(:parse))
]
)
end # Operators
end # Mutation
end # Mutant
9 changes: 6 additions & 3 deletions lib/mutant/mutator/node/block_pass.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,12 @@ def dispatch
def emit_symbol_to_proc_mutations
return unless n_sym?(argument)

Send::SELECTOR_REPLACEMENTS.fetch(*argument, EMPTY_ARRAY).each do |method|
emit_argument(s(:sym, method))
end
config
.operators
.selector_replacements
.fetch(*argument, EMPTY_ARRAY).each do |method|
emit_argument(s(:sym, method))
end
end
end # Block
end # Node
Expand Down
36 changes: 4 additions & 32 deletions lib/mutant/mutator/node/send.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,37 +13,6 @@ class Send < self

children :receiver, :selector

SELECTOR_REPLACEMENTS = {
:< => %i[== eql? equal?],
:<= => %i[< == eql? equal?],
:== => %i[eql? equal?],
:=== => %i[is_a?],
:=~ => %i[match?],
:> => %i[== eql? equal?],
:>= => %i[> == eql? equal?],
__send__: %i[public_send],
all?: %i[any?],
any?: %i[all?],
at: %i[fetch key?],
fetch: %i[key?],
flat_map: %i[map],
gsub: %i[sub],
is_a?: %i[instance_of?],
kind_of?: %i[instance_of?],
map: %i[each],
match: %i[match?],
method: %i[public_method],
reverse_each: %i[each],
reverse_map: %i[map each],
reverse_merge: %i[merge],
send: %i[public_send __send__],
to_a: %i[to_ary],
to_h: %i[to_hash],
to_i: %i[to_int],
to_s: %i[to_str],
values_at: %i[fetch_values]
}.freeze.tap { |hash| hash.values(&:freeze) }

RECEIVER_SELECTOR_REPLACEMENTS = {
Date: {
parse: %i[jd civil strptime iso8601 rfc3339 xmlschema rfc2822 rfc822 httpdate jisx0301]
Expand Down Expand Up @@ -227,7 +196,10 @@ def emit_const_get_mutation
end

def emit_selector_replacement
SELECTOR_REPLACEMENTS.fetch(selector, EMPTY_ARRAY).each(&method(:emit_selector))
config
.operators
.selector_replacements
.fetch(selector, EMPTY_ARRAY).each(&public_method(:emit_selector))
end

def emit_naked_receiver
Expand Down
2 changes: 1 addition & 1 deletion lib/mutant/version.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@

module Mutant
# Current mutant version
VERSION = '0.11.21'
VERSION = '0.11.22'
end # Mutant
12 changes: 11 additions & 1 deletion meta/send.rb
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@
mutation 'foo.to_hash'
end

Mutant::Meta::Example.add :send do
Mutant::Meta::Example.add :send, operators: :full do
source 'foo == bar'

singleton_mutations
Expand All @@ -191,6 +191,16 @@
mutation 'foo.equal?(bar)'
end

Mutant::Meta::Example.add :send, operators: :light do
source 'foo == bar'

singleton_mutations
mutation 'foo'
mutation 'bar'
mutation 'nil == bar'
mutation 'foo == nil'
end

Mutant::Meta::Example.add :send do
source 'foo.is_a?(bar)'

Expand Down
2 changes: 2 additions & 0 deletions mutant.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ requires:
- mutant
- mutant/integration/rspec
- mutant/meta
mutation:
operators: full
matcher:
subjects:
- Mutant*
Expand Down
Loading

0 comments on commit 6bf1cd7

Please sign in to comment.