Merge branch 'master' into warning-filter
Conflicts: lib/mutant.rb
This commit is contained in:
commit
877e4540d4
32 changed files with 370 additions and 233 deletions
|
@ -4,4 +4,5 @@ AllCops:
|
|||
Excludes:
|
||||
- 'Gemfile.devtools'
|
||||
- 'vendor/**'
|
||||
- 'tmp/**'
|
||||
- 'benchmarks/**'
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
# v0.5.11 2014-04-07
|
||||
# v0.5.11 2014-04-xx
|
||||
|
||||
Changes:
|
||||
|
||||
* Fix crash on while and until without body
|
||||
* Better require highjack based zombifier
|
||||
* Do not mutate nthref $1 to gvar $0
|
||||
* Use faster duplicate guarding hashing AST::Node intances
|
||||
|
||||
# v0.5.10 2014-04-06
|
||||
|
||||
|
|
2
Gemfile
2
Gemfile
|
@ -6,7 +6,5 @@ gem 'mutant', path: '.'
|
|||
|
||||
gemspec name: 'mutant'
|
||||
|
||||
gem 'morpher', git: 'https://github.com/mbj/morpher.git'
|
||||
|
||||
gem 'devtools', git: 'https://github.com/rom-rb/devtools.git'
|
||||
eval_gemfile 'Gemfile.devtools'
|
||||
|
|
|
@ -1,3 +1,15 @@
|
|||
name: mutant
|
||||
namespace: Mutant
|
||||
zombify: true
|
||||
expect_coverage: 64.99
|
||||
ignore_subjects:
|
||||
# Mutation causes infinite runtime
|
||||
- Mutant::Runner.lookup
|
||||
# Suboptimal test selection stragegy (will be fixed soon) causes timeouts on CI
|
||||
- Mutant::Zombifier*
|
||||
- Mutant::Reporter*
|
||||
- Mutant::CLI*
|
||||
- Mutant.singleton_subclass_instance
|
||||
- Mutant.symbolset
|
||||
# Executing this has undefined behavior with the zombifier
|
||||
- Mutant.zombify
|
||||
|
|
|
@ -54,6 +54,7 @@ NestedIterators:
|
|||
- Mutant::Reporter::CLI::Printer::Config::Runner#generic_stats
|
||||
- Mutant::RequireHighjack#infect
|
||||
- Mutant::RequireHighjack#desinfect
|
||||
- Parser::Lexer#self.new
|
||||
max_allowed_nesting: 1
|
||||
ignore_iterators: []
|
||||
NilCheck:
|
||||
|
@ -126,7 +127,7 @@ UncommunicativeVariableName:
|
|||
- !ruby/regexp /^.$/
|
||||
- !ruby/regexp /[0-9]$/
|
||||
- !ruby/regexp /[A-Z]/
|
||||
accept: []
|
||||
accept: ['force_utf32']
|
||||
UnusedParameters:
|
||||
enabled: true
|
||||
exclude: []
|
||||
|
|
|
@ -11,6 +11,7 @@ require 'digest/sha1'
|
|||
require 'inflecto'
|
||||
require 'parser'
|
||||
require 'parser/current'
|
||||
require 'parser_extensions'
|
||||
require 'unparser'
|
||||
require 'ice_nine'
|
||||
require 'diff/lcs'
|
||||
|
@ -37,12 +38,48 @@ module Mutant
|
|||
self
|
||||
end
|
||||
|
||||
# Return a frozen set of symbols from string enumerable
|
||||
#
|
||||
# @param [Enumerable<String>]
|
||||
#
|
||||
# @return [Set<Symbol>]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def self.symbolset(strings)
|
||||
strings.map(&:to_sym).to_set.freeze
|
||||
end
|
||||
private_class_method :symbolset
|
||||
|
||||
# Define instance of subclassed superclass as constant
|
||||
#
|
||||
# @param [Class] superclass
|
||||
# @param [Symbol] name
|
||||
#
|
||||
# @return [self]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def self.singleton_subclass_instance(name, superclass, &block)
|
||||
klass = Class.new(superclass) do
|
||||
def inspect
|
||||
self.class.name
|
||||
end
|
||||
|
||||
define_singleton_method(:name) do
|
||||
"#{superclass.name}::#{name}".freeze
|
||||
end
|
||||
end
|
||||
klass.class_eval(&block)
|
||||
superclass.const_set(name, klass.new)
|
||||
self
|
||||
end
|
||||
|
||||
end # Mutant
|
||||
|
||||
require 'mutant/version'
|
||||
require 'mutant/cache'
|
||||
require 'mutant/node_helpers'
|
||||
require 'mutant/singleton_methods'
|
||||
require 'mutant/warning_filter'
|
||||
require 'mutant/constants'
|
||||
require 'mutant/random'
|
||||
|
@ -91,6 +128,8 @@ require 'mutant/mutator/node/zsuper'
|
|||
require 'mutant/mutator/node/restarg'
|
||||
require 'mutant/mutator/node/send'
|
||||
require 'mutant/mutator/node/send/binary'
|
||||
require 'mutant/mutator/node/send/attribute_assignment'
|
||||
require 'mutant/mutator/node/send/index'
|
||||
require 'mutant/mutator/node/when'
|
||||
require 'mutant/mutator/node/define'
|
||||
require 'mutant/mutator/node/mlhs'
|
||||
|
@ -103,6 +142,7 @@ require 'mutant/mutator/node/case'
|
|||
require 'mutant/mutator/node/splat'
|
||||
require 'mutant/mutator/node/resbody'
|
||||
require 'mutant/mutator/node/rescue'
|
||||
require 'mutant/mutator/node/match_current_line'
|
||||
require 'mutant/config'
|
||||
require 'mutant/loader'
|
||||
require 'mutant/context'
|
||||
|
|
|
@ -2,19 +2,6 @@
|
|||
|
||||
module Mutant
|
||||
|
||||
# Return a frozen set of symbols from string enumerable
|
||||
#
|
||||
# @param [Enumerable<String>]
|
||||
#
|
||||
# @return [Set<Symbol>]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def self.symbolset(strings)
|
||||
strings.map(&:to_sym).to_set.freeze
|
||||
end
|
||||
private_class_method :symbolset
|
||||
|
||||
# Set of nodes that cannot be on the LHS of an assignment
|
||||
NOT_ASSIGNABLE = symbolset %w(
|
||||
int float str dstr class module self
|
||||
|
|
|
@ -33,18 +33,6 @@ module Mutant
|
|||
end
|
||||
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 [Object]
|
||||
|
@ -92,7 +80,7 @@ module Mutant
|
|||
# @api private
|
||||
#
|
||||
def new?(object)
|
||||
!@seen.include?(identity(object))
|
||||
!@seen.include?(object)
|
||||
end
|
||||
|
||||
# Add object to guarded values
|
||||
|
@ -104,19 +92,7 @@ module Mutant
|
|||
# @api private
|
||||
#
|
||||
def guard(object)
|
||||
@seen << identity(object)
|
||||
end
|
||||
|
||||
# Return identity for input
|
||||
#
|
||||
# @param [Object] input
|
||||
#
|
||||
# @return [Object]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def identity(input)
|
||||
self.class.identity(input)
|
||||
@seen << object
|
||||
end
|
||||
|
||||
# Dispatch node generations
|
||||
|
|
|
@ -9,18 +9,6 @@ module Mutant
|
|||
class Node < self
|
||||
include AbstractType, NodeHelpers, Unparser::Constants
|
||||
|
||||
# Return identity of node
|
||||
#
|
||||
# @param [Parser::AST::Node] node
|
||||
#
|
||||
# @return [String]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def self.identity(node)
|
||||
Unparser.unparse(node)
|
||||
end
|
||||
|
||||
# Define named child
|
||||
#
|
||||
# @param [Symbol] name
|
||||
|
@ -58,6 +46,10 @@ module Mutant
|
|||
children.each_with_index.drop(names.length)
|
||||
end
|
||||
|
||||
define_method(:remaining_children_indices) do
|
||||
children.each_index.drop(names.length)
|
||||
end
|
||||
|
||||
define_method(:remaining_children) do
|
||||
children.drop(names.length)
|
||||
end
|
||||
|
|
|
@ -13,7 +13,7 @@ module Mutant
|
|||
:ensure, :redo, :defined?, :regopt, :retry, :arg_expr,
|
||||
:kwrestarg, :kwoptarg, :kwarg, :undef, :module, :empty,
|
||||
:alias, :for, :xstr, :back_ref, :class,
|
||||
:sclass, :match_with_lvasgn, :match_current_line, :while_post,
|
||||
:sclass, :match_with_lvasgn, :while_post,
|
||||
:until_post, :preexe, :postexe, :iflipflop, :eflipflop, :kwsplat,
|
||||
:shadowarg
|
||||
)
|
||||
|
|
|
@ -33,7 +33,7 @@ module Mutant
|
|||
#
|
||||
def mutate_condition
|
||||
emit_condition_mutations
|
||||
emit_self(n_not(condition), if_branch, else_branch)
|
||||
emit_self(n_not(condition), if_branch, else_branch) unless condition.type == :match_current_line
|
||||
emit_self(N_TRUE, if_branch, else_branch)
|
||||
emit_self(N_FALSE, if_branch, else_branch)
|
||||
end
|
||||
|
|
|
@ -31,11 +31,11 @@ module Mutant
|
|||
# @api private
|
||||
#
|
||||
def dispatch
|
||||
emit_nil
|
||||
emit_nil unless parent_type == :match_current_line
|
||||
children.each_with_index do |child, index|
|
||||
mutate_child(index) unless child.type == :str
|
||||
end
|
||||
emit_self(s(:str, EMPTY_STRING), options)
|
||||
emit_self(options)
|
||||
emit_self(s(:str, NULL_REGEXP_SOURCE), options)
|
||||
end
|
||||
|
||||
|
|
27
lib/mutant/mutator/node/match_current_line.rb
Normal file
27
lib/mutant/mutator/node/match_current_line.rb
Normal file
|
@ -0,0 +1,27 @@
|
|||
module Mutant
|
||||
class Mutator
|
||||
class Node
|
||||
# Emitter for perl style match current line node
|
||||
class MatchCurrentLine < self
|
||||
|
||||
handle :match_current_line
|
||||
|
||||
children :regexp
|
||||
|
||||
private
|
||||
|
||||
# Emit mutants
|
||||
#
|
||||
# @return [undefined]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def dispatch
|
||||
emit_nil
|
||||
emit_regexp_mutations
|
||||
end
|
||||
|
||||
end # MatchCurrentLine
|
||||
end # Node
|
||||
end # Mutator
|
||||
end # Mutant
|
|
@ -42,7 +42,7 @@ module Mutant
|
|||
def mutate_name
|
||||
prefix = MAP.fetch(node.type)
|
||||
Mutator::Util::Symbol.each(name, self) do |name|
|
||||
emit_name(prefix + name.to_s)
|
||||
emit_name(:"#{prefix}#{name}")
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -19,7 +19,9 @@ module Mutant
|
|||
# @api private
|
||||
#
|
||||
def dispatch
|
||||
emit_number(number - 1)
|
||||
unless number.equal?(1)
|
||||
emit_number(number - 1)
|
||||
end
|
||||
emit_number(number + 1)
|
||||
end
|
||||
|
||||
|
|
|
@ -8,18 +8,6 @@ module Mutant
|
|||
|
||||
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 # Node
|
||||
end # Mutator
|
||||
|
|
|
@ -22,40 +22,6 @@ module Mutant
|
|||
INDEX_ASSIGN = :[]=
|
||||
ASSIGN_SUFFIX = '='.freeze
|
||||
|
||||
# Base mutator for index operations
|
||||
class Index < self
|
||||
|
||||
# Mutator for index references
|
||||
class Reference < self
|
||||
|
||||
# Perform dispatch
|
||||
#
|
||||
# @return [undefined]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def dispatch
|
||||
emit(receiver)
|
||||
end
|
||||
|
||||
end # Reference
|
||||
|
||||
# Mutator for index assignments
|
||||
class Assign < self
|
||||
|
||||
# Perform dispatch
|
||||
#
|
||||
# @return [undefined]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def dispatch
|
||||
emit(receiver)
|
||||
end
|
||||
|
||||
end # Assign
|
||||
end # Index
|
||||
|
||||
private
|
||||
|
||||
# Perform dispatch
|
||||
|
@ -65,6 +31,7 @@ module Mutant
|
|||
# @api private
|
||||
#
|
||||
def dispatch
|
||||
emit_nil
|
||||
case selector
|
||||
when INDEX_REFERENCE
|
||||
run(Index::Reference)
|
||||
|
@ -73,7 +40,6 @@ module Mutant
|
|||
else
|
||||
non_index_dispatch
|
||||
end
|
||||
emit_nil
|
||||
end
|
||||
|
||||
# Perform non index dispatch
|
||||
|
@ -86,6 +52,8 @@ module Mutant
|
|||
case
|
||||
when binary_operator?
|
||||
run(Binary)
|
||||
when attribute_assignment?
|
||||
run(AttributeAssignment)
|
||||
else
|
||||
normal_dispatch
|
||||
end
|
||||
|
@ -173,7 +141,6 @@ module Mutant
|
|||
# @api private
|
||||
#
|
||||
def mutate_arguments
|
||||
return if arguments.empty?
|
||||
emit_self(receiver, selector)
|
||||
remaining_children_with_index.each do |node, index|
|
||||
mutate_child(index)
|
||||
|
@ -213,7 +180,10 @@ module Mutant
|
|||
# @api private
|
||||
#
|
||||
def emit_implicit_self
|
||||
if receiver.type == :self && !KEYWORDS.include?(selector) && !attribute_assignment?
|
||||
if receiver.type == :self &&
|
||||
!KEYWORDS.include?(selector) &&
|
||||
!attribute_assignment? &&
|
||||
!OP_ASSIGN.include?(parent_type)
|
||||
emit_receiver(nil)
|
||||
end
|
||||
end
|
||||
|
|
51
lib/mutant/mutator/node/send/attribute_assignment.rb
Normal file
51
lib/mutant/mutator/node/send/attribute_assignment.rb
Normal file
|
@ -0,0 +1,51 @@
|
|||
# encoding: utf-8
|
||||
|
||||
module Mutant
|
||||
class Mutator
|
||||
class Node
|
||||
class Send
|
||||
|
||||
# Mutator for sends that correspond to an attribute assignment
|
||||
class AttributeAssignment < self
|
||||
|
||||
private
|
||||
|
||||
# Emit mutations
|
||||
#
|
||||
# @return [undefined]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def dispatch
|
||||
normal_dispatch
|
||||
emit_attribute_read
|
||||
end
|
||||
|
||||
# Mutate arguments
|
||||
#
|
||||
# @return [undefined]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def mutate_arguments
|
||||
remaining_children_indices.each do |index|
|
||||
mutate_child(index)
|
||||
end
|
||||
end
|
||||
|
||||
# Emit attribute read
|
||||
#
|
||||
# @return [undefined]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def emit_attribute_read
|
||||
emit_self(receiver, selector.to_s[0..-2].to_sym)
|
||||
end
|
||||
|
||||
end # AttributeAssignment
|
||||
|
||||
end # Send
|
||||
end # Node
|
||||
end # Mutator
|
||||
end # Mutant
|
43
lib/mutant/mutator/node/send/index.rb
Normal file
43
lib/mutant/mutator/node/send/index.rb
Normal file
|
@ -0,0 +1,43 @@
|
|||
# encoding: UTF-8
|
||||
|
||||
module Mutant
|
||||
class Mutator
|
||||
class Node
|
||||
class Send
|
||||
# Base mutator for index operations
|
||||
class Index < self
|
||||
|
||||
# Mutator for index references
|
||||
class Reference < self
|
||||
|
||||
# Perform dispatch
|
||||
#
|
||||
# @return [undefined]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def dispatch
|
||||
emit(receiver)
|
||||
end
|
||||
|
||||
end # Reference
|
||||
|
||||
# Mutator for index assignments
|
||||
class Assign < self
|
||||
|
||||
# Perform dispatch
|
||||
#
|
||||
# @return [undefined]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def dispatch
|
||||
emit(receiver)
|
||||
end
|
||||
|
||||
end # Assign
|
||||
end # Index
|
||||
end # Send
|
||||
end # Node
|
||||
end # Mutator
|
||||
end # Mutant
|
|
@ -18,13 +18,13 @@ module Mutant
|
|||
module_function :s
|
||||
|
||||
NAN =
|
||||
s(:send, s(:float, 0.0), :/, s(:args, s(:float, 0.0)))
|
||||
s(:send, s(:float, 0.0), :/, s(:float, 0.0))
|
||||
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 =
|
||||
s(:send, s(:const, s(:cbase), :Object), :new)
|
||||
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)
|
||||
|
||||
|
|
|
@ -1,30 +0,0 @@
|
|||
# encoding: utf-8
|
||||
|
||||
# Singleton methods are defined here so zombie can pick them up
|
||||
module Mutant
|
||||
|
||||
# Define instance of subclassed superclass as constant
|
||||
#
|
||||
# @param [Class] superclass
|
||||
# @param [Symbol] name
|
||||
#
|
||||
# @return [self]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def self.singleton_subclass_instance(name, superclass, &block)
|
||||
klass = Class.new(superclass) do
|
||||
def inspect
|
||||
self.class.name
|
||||
end
|
||||
|
||||
define_singleton_method(:name) do
|
||||
"#{superclass.name}::#{name}".freeze
|
||||
end
|
||||
end
|
||||
klass.class_eval(&block)
|
||||
superclass.const_set(name, klass.new)
|
||||
self
|
||||
end
|
||||
|
||||
end # Mutant
|
|
@ -26,9 +26,9 @@ module Mutant
|
|||
# @api private
|
||||
#
|
||||
def initialize(namespace)
|
||||
@namespace = namespace
|
||||
@zombified = Set.new
|
||||
@highjack = RequireHighjack.new(Kernel, method(:require))
|
||||
super(namespace)
|
||||
end
|
||||
|
||||
# Perform zombification of target library
|
||||
|
|
25
lib/parser_extensions.rb
Normal file
25
lib/parser_extensions.rb
Normal file
|
@ -0,0 +1,25 @@
|
|||
# Monkeypatch to silence warnings in parser
|
||||
#
|
||||
# Will be removed once https://github.com/whitequark/parser/issues/145 is solved.
|
||||
|
||||
# Parser namespace
|
||||
module Parser
|
||||
# Monkeypatched lexer
|
||||
class Lexer
|
||||
|
||||
# Return new lexer
|
||||
#
|
||||
# @return [Lexer]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def self.new(*arguments)
|
||||
super.tap do |instance|
|
||||
instance.instance_eval do
|
||||
@force_utf32 = false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end # Lexer
|
||||
end # Parser
|
|
@ -24,18 +24,19 @@ Gem::Specification.new do |gem|
|
|||
gem.required_ruby_version = '>= 1.9.3'
|
||||
|
||||
gem.add_runtime_dependency('parser', '~> 2.1')
|
||||
gem.add_runtime_dependency('ast', '~> 2.0')
|
||||
gem.add_runtime_dependency('diff-lcs', '~> 1.2')
|
||||
gem.add_runtime_dependency('morpher', '~> 0.2.1')
|
||||
gem.add_runtime_dependency('morpher', '~> 0.2.3')
|
||||
gem.add_runtime_dependency('procto', '~> 0.0.2')
|
||||
gem.add_runtime_dependency('abstract_type', '~> 0.0.7')
|
||||
gem.add_runtime_dependency('unparser', '~> 0.1.10')
|
||||
gem.add_runtime_dependency('unparser', '~> 0.1.12')
|
||||
gem.add_runtime_dependency('ice_nine', '~> 0.11.0')
|
||||
gem.add_runtime_dependency('adamantium', '~> 0.2.0')
|
||||
gem.add_runtime_dependency('memoizable', '~> 0.4.2')
|
||||
gem.add_runtime_dependency('equalizer', '~> 0.0.9')
|
||||
gem.add_runtime_dependency('inflecto', '~> 0.0.2')
|
||||
gem.add_runtime_dependency('anima', '~> 0.2.0')
|
||||
gem.add_runtime_dependency('concord', '~> 0.1.4')
|
||||
gem.add_runtime_dependency('concord', '~> 0.1.5')
|
||||
|
||||
gem.add_development_dependency('bundler', '~> 1.3', '>= 1.3.5')
|
||||
end
|
||||
|
|
|
@ -1,52 +1,12 @@
|
|||
# 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
|
||||
# encoding: UTF-8
|
||||
|
||||
shared_examples_for 'a mutator' do
|
||||
subject { object.each(node) { |item| yields << item } }
|
||||
subject { object.each(node, &yields.method(:<<)) }
|
||||
|
||||
let(:yields) { [] }
|
||||
let(:object) { described_class }
|
||||
|
||||
unless instance_methods.map(&:to_s).include?('node')
|
||||
unless instance_methods.include?(:node)
|
||||
let(:node) { parse(source) }
|
||||
end
|
||||
|
||||
|
@ -57,42 +17,32 @@ shared_examples_for 'a mutator' do
|
|||
|
||||
it { should be_instance_of(to_enum.class) }
|
||||
|
||||
let(:expected_mutations) do
|
||||
mutations.map(&Subject.method(:coerce))
|
||||
def coerce(input)
|
||||
case input
|
||||
when String
|
||||
Parser::CurrentRuby.parse(input)
|
||||
when Parser::AST::Node
|
||||
input
|
||||
else
|
||||
raise
|
||||
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
|
||||
|
||||
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
|
||||
unexpected = generated - expected_mutations
|
||||
|
||||
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
|
||||
unless verifier.success?
|
||||
fail verifier.error_report
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -21,8 +21,10 @@ if ENV['COVERAGE'] == 'true'
|
|||
end
|
||||
end
|
||||
|
||||
require 'equalizer'
|
||||
require 'concord'
|
||||
require 'adamantium'
|
||||
require 'devtools/spec_helper'
|
||||
require 'unparser/cli'
|
||||
require 'mutant'
|
||||
|
||||
$LOAD_PATH << File.join(TestApp.root, 'lib')
|
||||
|
@ -39,7 +41,7 @@ module ParserHelper
|
|||
end
|
||||
|
||||
def parse(string)
|
||||
Parser::CurrentRuby.parse(string)
|
||||
Unparser::Preprocessor.run(Parser::CurrentRuby.parse(string))
|
||||
end
|
||||
end
|
||||
|
||||
|
|
95
spec/support/mutation_verifier.rb
Normal file
95
spec/support/mutation_verifier.rb
Normal file
|
@ -0,0 +1,95 @@
|
|||
# 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.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(Unparser::Preprocessor.run(original_node))
|
||||
end
|
||||
memoize :unparser
|
||||
end # MutationVerifier
|
|
@ -3,16 +3,16 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe Mutant::Mutator::Node::Generic, 'match_current_line' do
|
||||
let(:source) { 'true if //' }
|
||||
let(:source) { 'true if /foo/' }
|
||||
|
||||
let(:mutations) do
|
||||
mutations = []
|
||||
mutations << 'false if //'
|
||||
mutations << 'nil if //'
|
||||
mutations << 'false if /foo/'
|
||||
mutations << 'true if //'
|
||||
mutations << 'nil if /foo/'
|
||||
mutations << 'true if true'
|
||||
mutations << 'true if false'
|
||||
mutations << 'true if nil'
|
||||
mutations << s(:if, s(:send, s(:match_current_line, s(:regexp, s(:regopt))), :!), s(:true), nil)
|
||||
mutations << 'true if /a\A/'
|
||||
mutations << 'nil'
|
||||
end
|
||||
|
|
|
@ -60,10 +60,13 @@ describe Mutant::Mutator::Node::NamedValue::Access, 'mutations' do
|
|||
mutants = []
|
||||
mutants << 'a = nil; nil'
|
||||
mutants << 'a = nil'
|
||||
mutants << 'a'
|
||||
mutants << 'a = ::Object.new; a'
|
||||
mutants << 'srandom = nil; a'
|
||||
mutants << 'nil; a'
|
||||
# TODO: fix invalid AST
|
||||
# These ASTs are not valid and should NOT be emitted
|
||||
# Mutations of lvarasgn need to be special cased to avoid this.
|
||||
mutants << s(:begin, s(:lvasgn, :srandom, s(:nil)), s(:lvar, :a))
|
||||
mutants << s(:begin, s(:nil), s(:lvar, :a))
|
||||
mutants << s(:lvar, :a)
|
||||
end
|
||||
|
||||
it_should_behave_like 'a mutator'
|
||||
|
|
|
@ -4,8 +4,8 @@ require 'spec_helper'
|
|||
|
||||
describe Mutant::Mutator, 'nthref' do
|
||||
context '$1' do
|
||||
let(:source) { '$1' }
|
||||
let(:mutations) { ['$2', '$0'] }
|
||||
let(:source) { '$1' }
|
||||
let(:mutations) { ['$2'] }
|
||||
|
||||
it_should_behave_like 'a mutator'
|
||||
end
|
||||
|
|
|
@ -13,10 +13,12 @@ describe Mutant::Mutator::Node::Generic, 'op_asgn' do
|
|||
mutations << '@a.b += 2'
|
||||
mutations << '@a.b += 0'
|
||||
mutations << '@a.b += nil'
|
||||
mutations << '@a += 1'
|
||||
mutations << '@a.b += 5'
|
||||
mutations << 'nil.b += 1'
|
||||
mutations << 'nil'
|
||||
# TODO: fix invalid AST
|
||||
# This should not get emitted as invalid AST with valid unparsed source
|
||||
mutations << s(:op_asgn, s(:ivar, :@a), :+, s(:int, 1))
|
||||
end
|
||||
|
||||
before do
|
||||
|
|
|
@ -63,7 +63,6 @@ describe Mutant::Mutator, 'send' do
|
|||
|
||||
let(:mutations) do
|
||||
mutations = []
|
||||
mutations << 'foo ||= expression'
|
||||
mutations << 'self.foo ||= nil'
|
||||
mutations << 'nil.foo ||= expression'
|
||||
mutations << 'nil'
|
||||
|
|
Loading…
Reference in a new issue