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:
Markus Schirp 2012-09-11 01:00:18 +02:00
parent c5801b1b34
commit 3de69d832d
21 changed files with 447 additions and 12 deletions

View file

@ -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'

View file

@ -1,6 +1,6 @@
require 'immutable'
require 'ice_nine'
require 'abstract'
require 'abstract_class'
require 'descendants_tracker'
require 'securerandom'
require 'to_source'

View file

@ -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
#

View file

@ -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

View file

@ -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

View file

@ -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

View 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

View 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

View 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

View 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

View 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

View file

@ -1,7 +1,7 @@
module Mutant
# Generator for mutations
class Mutator
include Immutable, Abstract
include Immutable, AbstractClass
# Enumerate mutations on node
#

View file

@ -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)

View file

@ -2,6 +2,7 @@ module Mutant
class Mutator
# Abstract mutator for literal AST nodes
class Literal < self
include AbstractClass
private

View file

@ -3,7 +3,7 @@ module Mutant
class Literal
# Abstract literal range mutator
class Range < self
include Abstract
include AbstractClass
private

View file

@ -1,7 +1,7 @@
module Mutant
# Abstract reporter
class Reporter
include Immutable, Abstract
include Immutable, AbstractClass
# Report subject
#

15
mutant Executable file
View 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)

View file

@ -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

View file

@ -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

View 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
View file

@ -0,0 +1 @@
mutant