Add cli stuff
* Currently broken (sorry) but do have to push to fix some nasty transitive dependency stuff (do not ask). * Fix fix it tomorrow (hopefully I find the time)
This commit is contained in:
parent
c5801b1b34
commit
3de69d832d
21 changed files with 447 additions and 12 deletions
4
Gemfile
4
Gemfile
|
@ -3,9 +3,9 @@ source 'https://rubygems.org'
|
|||
gemspec
|
||||
|
||||
gem 'immutable', :git => 'https://github.com/dkubb/immutable.git', :branch => :experimental
|
||||
gem 'abstract', :git => 'https://github.com/mbj/abstract.git'
|
||||
gem 'descendants_tracker', :git => 'https://github.com/dkubb/descendants_tracker.git'
|
||||
gem 'abstract_class', :git => 'https://github.com/dkubb/abstract_class.git'
|
||||
gem 'to_source', :git => 'https://github.com/mbj/to_source.git'
|
||||
gem 'descendants_tracker', :git => 'https://github.com/mbj/descendants_tracker.git'
|
||||
|
||||
group :development do
|
||||
gem 'rake', '~> 0.9.2'
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
require 'immutable'
|
||||
require 'ice_nine'
|
||||
require 'abstract'
|
||||
require 'abstract_class'
|
||||
require 'descendants_tracker'
|
||||
require 'securerandom'
|
||||
require 'to_source'
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
module Mutant
|
||||
# An abstract context where mutations can be appied to.
|
||||
class Context
|
||||
include Immutable, Abstract
|
||||
include Immutable, AbstractClass
|
||||
|
||||
# Return root ast node
|
||||
#
|
||||
|
|
|
@ -2,7 +2,7 @@ module Mutant
|
|||
class Context
|
||||
# Scope context for mutation (Class or Module)
|
||||
class Scope < self
|
||||
include Immutable, Abstract
|
||||
include Immutable, AbstractClass
|
||||
|
||||
# Class context for mutation
|
||||
class Class < self
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
module Mutant
|
||||
# Abstract runner for mutant killers
|
||||
class Killer
|
||||
include Immutable, Abstract
|
||||
include Immutable, AbstractClass
|
||||
extend MethodObject
|
||||
|
||||
# Test for kill failure
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
module Mutant
|
||||
# Abstract matcher to find ASTs to mutate
|
||||
class Matcher
|
||||
include Enumerable, Abstract, Immutable
|
||||
include Enumerable, AbstractClass, Immutable
|
||||
extend DescendantsTracker
|
||||
|
||||
# Enumerate subjects
|
||||
|
|
133
lib/mutant/matcher/object_space.rb
Normal file
133
lib/mutant/matcher/object_space.rb
Normal file
|
@ -0,0 +1,133 @@
|
|||
module Mutant
|
||||
class Matcher
|
||||
# Matcher against object space
|
||||
class ObjectSpace < self
|
||||
include Equalizer.new(:scope_name_pattern)
|
||||
|
||||
PATTERN = %r(\A::(.+)\z)
|
||||
|
||||
# Parse matcher
|
||||
#
|
||||
# @param [String] input
|
||||
#
|
||||
# @return [Matcher::ObjectSpace]
|
||||
# returns object space matcher if successful
|
||||
#
|
||||
# @return [nil]
|
||||
# returns nil otherwise
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def self.parse(input)
|
||||
match = PATTERN.match(input)
|
||||
return unless match
|
||||
|
||||
new(%r(\A#{Regexp.escape(match[1])}(\z|::)))
|
||||
end
|
||||
|
||||
# Enumerate subjects
|
||||
#
|
||||
# @return [Enumerator<Subject>]
|
||||
# returns subject enumerator when no block given
|
||||
#
|
||||
# @return [self]
|
||||
# returns self otherwise
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def each(&block)
|
||||
return to_enum unless block_given?
|
||||
|
||||
matchers.each do |matcher|
|
||||
matcher.each(&block)
|
||||
end
|
||||
|
||||
self
|
||||
end
|
||||
|
||||
# Return scope name pattern
|
||||
#
|
||||
# @return [Regexp]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def scope_name_pattern; @scope_name_pattern; end
|
||||
|
||||
private
|
||||
|
||||
# Initialize object space matcher
|
||||
#
|
||||
# @param [Regexp] scope_name_pattern
|
||||
# @param [Enumerable<#each(scope)>] matchers
|
||||
#
|
||||
# @return [undefined]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def initialize(scope_name_pattern)
|
||||
@scope_name_pattern, @matchers = scope_name_pattern, [Method::Singleton, Method::Instance]
|
||||
end
|
||||
|
||||
# Return matcher enumerator
|
||||
#
|
||||
# @return [Enumerable<Matcher>]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def matchers(&block)
|
||||
return to_enum(__method__) unless block_given?
|
||||
|
||||
scopes.each do |scope|
|
||||
emit_scope_matches(scope, &block)
|
||||
end
|
||||
|
||||
self
|
||||
end
|
||||
|
||||
# Yield matchers for scope
|
||||
#
|
||||
# @param [::Class,::Module] scope
|
||||
#
|
||||
# @return [undefined]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def emit_scope_matches(scope, &block)
|
||||
@matchers.each do |matcher|
|
||||
p scope
|
||||
p matcher
|
||||
p matcher.each(scope).to_a
|
||||
matcher.each(scope, &block)
|
||||
end
|
||||
end
|
||||
|
||||
# Return scope enumerator
|
||||
#
|
||||
# @return [Enumerable<Object>]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def scopes(&block)
|
||||
return to_enum(__method__) unless block_given?
|
||||
|
||||
::ObjectSpace.each_object(Module) do |scope|
|
||||
emit_scope(scope, &block)
|
||||
end
|
||||
end
|
||||
|
||||
# Yield scope if name matches pattern
|
||||
#
|
||||
# @param [::Module,::Class]
|
||||
#
|
||||
# @return [undefined]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def emit_scope(scope)
|
||||
if [::Module, ::Class].include?(scope.class) and @scope_name_pattern =~ scope.name
|
||||
yield scope
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
75
lib/mutant/mutation/filter.rb
Normal file
75
lib/mutant/mutation/filter.rb
Normal file
|
@ -0,0 +1,75 @@
|
|||
module Mutant
|
||||
class Mutation
|
||||
# Abstract filter for mutations
|
||||
class Filter
|
||||
include Immutable, AbstractClass
|
||||
extend DescendantsTracker
|
||||
|
||||
# Check for match
|
||||
#
|
||||
# @param [Mutation] mutation
|
||||
#
|
||||
# @return [true]
|
||||
# returns true if mutation is matched by filter
|
||||
#
|
||||
# @return [false]
|
||||
# returns false otherwise
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
abstract_method :match?
|
||||
|
||||
# Build filter from string
|
||||
#
|
||||
# @param [String] notation
|
||||
#
|
||||
# @return [Filter]
|
||||
# returns filter when can be buld from string
|
||||
#
|
||||
# @return [nil]
|
||||
# returns nil otherwise
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def self.build(notation)
|
||||
descendants.each do |descendant|
|
||||
filter = descendant.handle(notation)
|
||||
return filter if filter
|
||||
end
|
||||
|
||||
nil
|
||||
end
|
||||
|
||||
# Return filter for handle
|
||||
#
|
||||
# @param [String] notation
|
||||
#
|
||||
# @return [nil]
|
||||
# returns nil
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def self.handle(notation)
|
||||
nil
|
||||
end
|
||||
|
||||
# Mutation filter matching all mutations
|
||||
ALL = Class.new(self) do
|
||||
|
||||
# Test for match
|
||||
#
|
||||
# @pram [Mutation] mutation
|
||||
#
|
||||
# @return [true]
|
||||
# returns true
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def match?(mutation)
|
||||
true
|
||||
end
|
||||
|
||||
end.new.freeze
|
||||
end
|
||||
end
|
||||
end
|
68
lib/mutant/mutation/filter/code.rb
Normal file
68
lib/mutant/mutation/filter/code.rb
Normal file
|
@ -0,0 +1,68 @@
|
|||
module Mutant
|
||||
class Mutation
|
||||
class Filter
|
||||
# Mutation filter that filters on mutation codes
|
||||
class Code < self
|
||||
include Equalizer.new(:code)
|
||||
|
||||
# Test for match
|
||||
#
|
||||
# @param [Mutation] mutation
|
||||
#
|
||||
# @return [true]
|
||||
# returns true if mutation code matches filter code
|
||||
#
|
||||
# @return [false]
|
||||
# returns false otherwise
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def match?(mutation)
|
||||
mutation.code.eql?(code)
|
||||
end
|
||||
|
||||
PATTERN = %r(\Acode:([a-f0-9]{1,6})\z).freeze
|
||||
|
||||
# Test if class handles string
|
||||
#
|
||||
# @param [String] notation
|
||||
#
|
||||
# @return [Filter]
|
||||
# return code filter instance if notation matches pattern
|
||||
#
|
||||
# @return [nil]
|
||||
# returns nil otherwise
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def self.handle(notation)
|
||||
match = PATTERN.match(notation)
|
||||
return unless match
|
||||
new(match[1])
|
||||
end
|
||||
|
||||
# Return code
|
||||
#
|
||||
# @return [String]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def code; @code; end
|
||||
|
||||
private
|
||||
|
||||
# Initialize code filter
|
||||
#
|
||||
# @param [String] code
|
||||
#
|
||||
# @return [undefined]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def initialize(code)
|
||||
@code = code
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
39
lib/mutant/mutation/filter/regexp.rb
Normal file
39
lib/mutant/mutation/filter/regexp.rb
Normal file
|
@ -0,0 +1,39 @@
|
|||
module Mutant
|
||||
class Mutation
|
||||
class Filter
|
||||
# Mutaiton filter filtering in regexp match on mutation identification
|
||||
class Regexp < self
|
||||
|
||||
# Test for match
|
||||
#
|
||||
# @param [Mutation] mutation
|
||||
#
|
||||
# @return [true]
|
||||
# returns true if mutation identification is matched by regexp
|
||||
#
|
||||
# @return [false]
|
||||
# returns false otherwise
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def match?(mutation)
|
||||
!!@regexp.match =~ mutation.identification
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Initialize regexp filter
|
||||
#
|
||||
# @param [Regexp] regexp
|
||||
#
|
||||
# @return [undefined]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def initialize(regexp)
|
||||
@regexp = regexp
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
47
lib/mutant/mutation/filter/whitelist.rb
Normal file
47
lib/mutant/mutation/filter/whitelist.rb
Normal file
|
@ -0,0 +1,47 @@
|
|||
module Mutant
|
||||
class Mutation
|
||||
class Filter
|
||||
class Whitelist < self
|
||||
include Immutable, Equalizer.new(:whitelist)
|
||||
|
||||
# Test for match
|
||||
#
|
||||
# @param [Mutation]
|
||||
#
|
||||
# @return [true]
|
||||
# returns true if mutation matches whitelist
|
||||
#
|
||||
# @return [false]
|
||||
# returns false otherwise
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def match?(mutation)
|
||||
@whitelist.any? { |filter| filter.match?(mutation) }
|
||||
end
|
||||
|
||||
# Return whitelist
|
||||
#
|
||||
# @return [Enumerable<Filter>]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def whitelist; @whitelist; end
|
||||
|
||||
private
|
||||
|
||||
# Initalize white list
|
||||
#
|
||||
# @param [Enumerable<Filter>] whitelist
|
||||
#
|
||||
# @return [undefined]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def initialize(whitelist)
|
||||
@whitelist = whitelist
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,7 +1,7 @@
|
|||
module Mutant
|
||||
# Generator for mutations
|
||||
class Mutator
|
||||
include Immutable, Abstract
|
||||
include Immutable, AbstractClass
|
||||
|
||||
# Enumerate mutations on node
|
||||
#
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
module Mutant
|
||||
class Mutator
|
||||
# Abstract class for mutatiosn where messages are send
|
||||
# Class for mutations where messages are send to objects
|
||||
class Call < self
|
||||
|
||||
handle(Rubinius::AST::Send)
|
||||
|
|
|
@ -2,6 +2,7 @@ module Mutant
|
|||
class Mutator
|
||||
# Abstract mutator for literal AST nodes
|
||||
class Literal < self
|
||||
include AbstractClass
|
||||
|
||||
private
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@ module Mutant
|
|||
class Literal
|
||||
# Abstract literal range mutator
|
||||
class Range < self
|
||||
include Abstract
|
||||
include AbstractClass
|
||||
|
||||
private
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
module Mutant
|
||||
# Abstract reporter
|
||||
class Reporter
|
||||
include Immutable, Abstract
|
||||
include Immutable, AbstractClass
|
||||
|
||||
# Report subject
|
||||
#
|
||||
|
|
15
mutant
Executable file
15
mutant
Executable file
|
@ -0,0 +1,15 @@
|
|||
#!/usr/bin/env ruby
|
||||
|
||||
require 'mutant'
|
||||
require 'mutant/cli'
|
||||
|
||||
namespace =
|
||||
if File.basename($0) == 'zombie'
|
||||
$stderr.puts('Detected zombie environment...')
|
||||
require File.expand_path('../spec/support/zombie.rb', __FILE__)
|
||||
Zombie.setup
|
||||
else
|
||||
Mutant
|
||||
end
|
||||
|
||||
namespace::CLI.run(ARGV)
|
|
@ -21,6 +21,6 @@ Gem::Specification.new do |gem|
|
|||
gem.add_runtime_dependency('descendants_tracker', '~> 0.0.1')
|
||||
gem.add_runtime_dependency('backports', '~> 2.6')
|
||||
gem.add_runtime_dependency('immutable', '~> 0.0.1')
|
||||
gem.add_runtime_dependency('abstract', '~> 0.0.1')
|
||||
gem.add_runtime_dependency('abstract_class', '~> 0.0.1')
|
||||
gem.add_runtime_dependency('diff-lcs', '~> 1.1.3')
|
||||
end
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe Mutant::Matcher::ObjectSpace, '.parse' do
|
||||
subject { object.parse(input) }
|
||||
|
||||
let(:object) { described_class }
|
||||
|
||||
let(:matcher) { mock('Matcher') }
|
||||
|
||||
context 'with valid notation' do
|
||||
let(:input) { '::TestApp::Literal' }
|
||||
|
||||
it 'should return matcher' do
|
||||
described_class.should_receive(:new).with(%r(\ATestApp::Literal(\z|::))).and_return(matcher)
|
||||
should be(matcher)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with invalid notation' do
|
||||
let(:input) { 'TestApp' }
|
||||
|
||||
it { should be(nil) }
|
||||
end
|
||||
end
|
32
spec/unit/mutant/matcher/object_space/each_spec.rb
Normal file
32
spec/unit/mutant/matcher/object_space/each_spec.rb
Normal file
|
@ -0,0 +1,32 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe Mutant::Matcher::ObjectSpace, '#each' do
|
||||
subject { object.each { |item| yields << item } }
|
||||
|
||||
let(:yields) { [] }
|
||||
let(:object) { described_class.new(/\ATestApp::Literal(\z|::)/) }
|
||||
|
||||
before do
|
||||
Mutant::Matcher::Method::Singleton.stub(:each => [matcher_a])
|
||||
Mutant::Matcher::Method::Instance.stub(:each => [matcher_b])
|
||||
end
|
||||
|
||||
|
||||
let(:matcher_a) { mock('Matcher A') }
|
||||
let(:matcher_b) { mock('Matcher B') }
|
||||
|
||||
let(:subject_a) { mock('Subject A') }
|
||||
let(:subject_b) { mock('Subject B') }
|
||||
|
||||
before do
|
||||
matcher_a.stub(:each).and_yield(subject_a).and_return(matcher_a)
|
||||
matcher_b.stub(:each).and_yield(subject_b).and_return(matcher_b)
|
||||
end
|
||||
|
||||
it_should_behave_like 'an #each method'
|
||||
|
||||
|
||||
it 'should yield subjects' do
|
||||
expect { subject }.to change { yields }.from([]).to([subject_a, subject_b])
|
||||
end
|
||||
end
|
1
zombie
Symbolic link
1
zombie
Symbolic link
|
@ -0,0 +1 @@
|
|||
mutant
|
Loading…
Add table
Reference in a new issue