Use environment object inside matchers

This will allow to use reporters from matchers avoiding stupid direct
writes to $stderr.

Also it will allow to remove matching from CLI altogether in a phase of
the runner. Allowing to decouple Mutant::Config from VM environment
allowing to serialize it ;)

Step by step. Takes a while.
This commit is contained in:
Markus Schirp 2014-06-30 13:06:57 +00:00
parent a18a15f8ec
commit 3390abc675
19 changed files with 96 additions and 60 deletions

View file

@ -1,3 +1,3 @@
---
threshold: 18
total_score: 968
total_score: 976

View file

@ -79,6 +79,7 @@ module Mutant
end # Mutant
require 'mutant/version'
require 'mutant/env'
require 'mutant/ast'
require 'mutant/ast/sexp'
require 'mutant/ast/types'

View file

@ -1,7 +1,7 @@
module Mutant
# An AST cache
class Cache
include Equalizer.new
include Equalizer.new, Adamantium::Mutable
# Initialize object
#

View file

@ -39,7 +39,7 @@ module Mutant
# @api private
#
def initialize(arguments = [])
@builder = Matcher::Builder.new(Cache.new)
@builder = Matcher::Builder.new(Env::Boot.new(Reporter::CLI.new($stderr), Cache.new))
@debug = @fail_fast = @zombie = false
@expected_coverage = 100.0
@integration = Integration::Null.new

36
lib/mutant/env.rb Normal file
View file

@ -0,0 +1,36 @@
module Mutant
# Abstract base class for mutant environments
class Env
include AbstractType, Adamantium
# Return config
#
# @return [Config]
#
# @api private
#
abstract_method :config
# Return cache
#
# @return [Cache]
#
# @api private
#
abstract_method :cache
# Return reporter
#
# @return [Reporter]
#
# @api private
#
abstract_method :reporter
# Boot environment used for matching
class Boot < self
include Concord::Public.new(:reporter, :cache)
end # Boot
end # ENV
end # Mutant

View file

@ -5,7 +5,7 @@ module Mutant
# Default matcher build implementation
#
# @param [Cache] cache
# @param [Env] env
# @param [Object] input
#
# @return [undefined]

View file

@ -2,17 +2,17 @@ module Mutant
class Matcher
# Builder for complex matchers
class Builder
include Concord.new(:cache), AST::Sexp
include Concord.new(:env), AST::Sexp
# Initalize object
#
# @param [Cache] cache
# @param [Cache] env
#
# @return [undefined]
#
# @api private
#
def initialize(cache)
def initialize(env)
super
@matchers = []
@subject_ignores = []
@ -28,7 +28,7 @@ module Mutant
# @api private
#
def add_subject_ignore(expression)
@subject_ignores << expression.matcher(cache)
@subject_ignores << expression.matcher(env)
self
end
@ -54,7 +54,7 @@ module Mutant
# @api private
#
def add_match_expression(expression)
@matchers << expression.matcher(cache)
@matchers << expression.matcher(env)
self
end

View file

@ -2,7 +2,7 @@ module Mutant
class Matcher
# Matcher for subjects that are a specific method
class Method < self
include Adamantium::Flat, Concord::Public.new(:cache, :scope, :method)
include Adamantium::Flat, Concord::Public.new(:env, :scope, :method)
include Equalizer.new(:identification)
# Methods within rbx kernel directory are precompiled and their source
@ -78,7 +78,7 @@ module Mutant
# @api private
#
def ast
cache.parse(source_path)
env.cache.parse(source_path)
end
# Return path to source

View file

@ -7,7 +7,7 @@ module Mutant
# Dispatching builder, detects memoizable case
#
# @param [Cache] cache
# @param [Env] env
# @param [Class, Module] scope
# @param [UnboundMethod] method
#
@ -15,10 +15,10 @@ module Mutant
#
# @api private
#
def self.build(cache, scope, method)
def self.build(env, scope, method)
name = method.name
if scope.ancestors.include?(::Memoizable) && scope.memoized?(name)
return Memoized.new(cache, scope, method)
return Memoized.new(env, scope, method)
end
super
end

View file

@ -2,7 +2,7 @@ module Mutant
class Matcher
# Abstract base class for matcher that returns method subjects from scope
class Methods < self
include AbstractType, Concord::Public.new(:cache, :scope)
include AbstractType, Concord::Public.new(:env, :scope)
# Enumerate subjects
#
@ -56,7 +56,7 @@ module Mutant
#
def subjects
methods.map do |method|
matcher.build(cache, scope, method)
matcher.build(env, scope, method)
end.flat_map(&:to_a)
end
memoize :subjects

View file

@ -5,7 +5,7 @@ module Mutant
#
# rubocop:disable LineLength
class Namespace < self
include Concord::Public.new(:cache, :expression)
include Concord::Public.new(:env, :expression)
# Enumerate subjects
#
@ -36,7 +36,7 @@ module Mutant
#
def scopes
::ObjectSpace.each_object(Module).each_with_object([]) do |scope, aggregate|
aggregate << Scope.new(cache, scope) if match?(scope)
aggregate << Scope.new(env, scope) if match?(scope)
end.sort_by(&:identification)
end
memoize :scopes

View file

@ -2,7 +2,7 @@ module Mutant
class Matcher
# Matcher for specific namespace
class Scope < self
include Concord::Public.new(:cache, :scope)
include Concord::Public.new(:env, :scope)
MATCHERS = [
Matcher::Methods::Singleton,
@ -34,7 +34,7 @@ module Mutant
return to_enum unless block_given?
MATCHERS.each do |matcher|
matcher.new(cache, scope).each(&block)
matcher.new(env, scope).each(&block)
end
self

View file

@ -30,8 +30,8 @@ $LOAD_PATH << File.join(TestApp.root, 'lib')
require 'test_app'
module Fixtures
AST_CACHE = Mutant::Cache.new
end
BOOT_ENV = Mutant::Env::Boot.new(Mutant::Reporter::CLI.new(STDERR), Mutant::Cache.new)
end # Fixtures
module ParserHelper
def generate(node)

View file

@ -30,7 +30,7 @@ describe Mutant::CLI, '.new' do
let(:expected_reporter) { Mutant::Reporter::CLI.new($stdout) }
let(:ns) { Mutant::Matcher }
let(:cache) { Mutant::Cache.new }
let(:env) { Fixtures::BOOT_ENV }
let(:cli) { object.new(arguments) }
@ -71,7 +71,7 @@ describe Mutant::CLI, '.new' do
let(:arguments) { %w[TestApp::Literal#float] }
let(:expected_matcher) do
ns::Method::Instance.new(cache, TestApp::Literal, TestApp::Literal.instance_method(:float))
ns::Method::Instance.new(env, TestApp::Literal, TestApp::Literal.instance_method(:float))
end
it_should_behave_like 'a cli parser'
@ -80,7 +80,7 @@ describe Mutant::CLI, '.new' do
context 'with debug flag' do
let(:pattern) { 'TestApp*' }
let(:arguments) { %W[--debug #{pattern}] }
let(:expected_matcher) { ns::Namespace.new(cache, Mutant::Expression.parse(pattern)) }
let(:expected_matcher) { ns::Namespace.new(env, Mutant::Expression.parse(pattern)) }
it_should_behave_like 'a cli parser'
@ -92,7 +92,7 @@ describe Mutant::CLI, '.new' do
context 'with zombie flag' do
let(:pattern) { 'TestApp*' }
let(:arguments) { %W[--zombie #{pattern}] }
let(:expected_matcher) { ns::Namespace.new(cache, Mutant::Expression.parse(pattern)) }
let(:expected_matcher) { ns::Namespace.new(env, Mutant::Expression.parse(pattern)) }
it_should_behave_like 'a cli parser'
@ -104,7 +104,7 @@ describe Mutant::CLI, '.new' do
context 'with namespace pattern' do
let(:pattern) { 'TestApp*' }
let(:arguments) { [pattern] }
let(:expected_matcher) { ns::Namespace.new(cache, Mutant::Expression.parse(pattern)) }
let(:expected_matcher) { ns::Namespace.new(env, Mutant::Expression.parse(pattern)) }
it_should_behave_like 'a cli parser'
end
@ -124,7 +124,7 @@ describe Mutant::CLI, '.new' do
let(:expected_matcher) do
matcher = ns::Method::Instance.new(
cache,
env,
TestApp::Literal, TestApp::Literal.instance_method(:float)
)
predicate = Morpher.compile(

View file

@ -3,12 +3,12 @@ require 'spec_helper'
# rubocop:disable ClassAndModuleChildren
describe Mutant::Matcher::Method::Instance do
let(:cache) { Fixtures::AST_CACHE }
let(:env) { Fixtures::BOOT_ENV }
describe '#each' do
subject { object.each { |subject| yields << subject } }
let(:object) { described_class.new(cache, scope, method) }
let(:object) { described_class.new(env, scope, method) }
let(:method) { scope.instance_method(method_name) }
let(:yields) { [] }
let(:namespace) { self.class }
@ -118,7 +118,7 @@ describe Mutant::Matcher::Method::Instance do
describe '.build' do
let(:object) { described_class }
subject { object.build(cache, scope, method) }
subject { object.build(env, scope, method) }
let(:scope) do
Class.new do
@ -141,13 +141,13 @@ describe Mutant::Matcher::Method::Instance do
context 'with unmemoized method' do
let(:method_name) { :bar }
it { should eql(described_class.new(cache, scope, method)) }
it { should eql(described_class.new(env, scope, method)) }
end
context 'with memoized method' do
let(:method_name) { :foo }
it { should eql(described_class::Memoized.new(cache, scope, method)) }
it { should eql(described_class::Memoized.new(env, scope, method)) }
end
end
end

View file

@ -4,9 +4,9 @@ require 'spec_helper'
describe Mutant::Matcher::Method::Singleton, '#each' do
subject { object.each { |subject| yields << subject } }
let(:object) { described_class.new(cache, scope, method) }
let(:object) { described_class.new(env, scope, method) }
let(:method) { scope.method(method_name) }
let(:cache) { Fixtures::AST_CACHE }
let(:env) { Fixtures::BOOT_ENV }
let(:yields) { [] }
let(:namespace) { self.class }
let(:scope) { self.class::Foo }

View file

@ -1,8 +1,8 @@
require 'spec_helper'
describe Mutant::Matcher::Methods::Instance, '#each' do
let(:object) { described_class.new(cache, Foo) }
let(:cache) { Mutant::Cache.new }
let(:object) { described_class.new(env, Foo) }
let(:env) { Fixtures::BOOT_ENV }
subject { object.each { |matcher| yields << matcher } }
@ -46,9 +46,9 @@ describe Mutant::Matcher::Methods::Instance, '#each' do
before do
matcher = Mutant::Matcher::Method::Instance
allow(matcher).to receive(:new).with(cache, Foo, Foo.instance_method(:method_a)).and_return([subject_a])
allow(matcher).to receive(:new).with(cache, Foo, Foo.instance_method(:method_b)).and_return([subject_b])
allow(matcher).to receive(:new).with(cache, Foo, Foo.instance_method(:method_c)).and_return([subject_c])
allow(matcher).to receive(:new).with(env, Foo, Foo.instance_method(:method_a)).and_return([subject_a])
allow(matcher).to receive(:new).with(env, Foo, Foo.instance_method(:method_b)).and_return([subject_b])
allow(matcher).to receive(:new).with(env, Foo, Foo.instance_method(:method_c)).and_return([subject_c])
end
it 'should yield expected subjects' do

View file

@ -1,8 +1,8 @@
require 'spec_helper'
describe Mutant::Matcher::Methods::Singleton, '#each' do
let(:object) { described_class.new(cache, Foo) }
let(:cache) { Mutant::Cache.new }
let(:object) { described_class.new(env, Foo) }
let(:env) { Fixtures::BOOT_ENV }
subject { object.each { |matcher| yields << matcher } }
@ -41,11 +41,11 @@ describe Mutant::Matcher::Methods::Singleton, '#each' do
before do
matcher = Mutant::Matcher::Method::Singleton
matcher.stub(:new)
.with(cache, Foo, Foo.method(:method_a)).and_return([subject_a])
.with(env, Foo, Foo.method(:method_a)).and_return([subject_a])
matcher.stub(:new)
.with(cache, Foo, Foo.method(:method_b)).and_return([subject_b])
.with(env, Foo, Foo.method(:method_b)).and_return([subject_b])
matcher.stub(:new)
.with(cache, Foo, Foo.method(:method_c)).and_return([subject_c])
.with(env, Foo, Foo.method(:method_c)).and_return([subject_c])
end
it 'should yield expected subjects' do

View file

@ -1,10 +1,9 @@
require 'spec_helper'
describe Mutant::Matcher::Namespace do
let(:object) { described_class.new(cache, Mutant::Expression.parse('TestApp*')) }
let(:object) { described_class.new(env, Mutant::Expression.parse('TestApp*')) }
let(:yields) { [] }
let(:cache) { Mutant::Cache.new }
let(:env) { Fixtures::BOOT_ENV }
subject { object.each { |item| yields << item } }
@ -17,11 +16,11 @@ describe Mutant::Matcher::Namespace do
let(:subject_b) { double('SubjectB') }
before do
allow(Mutant::Matcher::Methods::Singleton).to receive(:new).with(cache, singleton_a).and_return([subject_a])
allow(Mutant::Matcher::Methods::Instance).to receive(:new).with(cache, singleton_a).and_return([])
allow(Mutant::Matcher::Methods::Singleton).to receive(:new).with(env, singleton_a).and_return([subject_a])
allow(Mutant::Matcher::Methods::Instance).to receive(:new).with(env, singleton_a).and_return([])
allow(Mutant::Matcher::Methods::Singleton).to receive(:new).with(cache, singleton_b).and_return([subject_b])
allow(Mutant::Matcher::Methods::Instance).to receive(:new).with(cache, singleton_b).and_return([])
allow(Mutant::Matcher::Methods::Singleton).to receive(:new).with(env, singleton_b).and_return([subject_b])
allow(Mutant::Matcher::Methods::Instance).to receive(:new).with(env, singleton_b).and_return([])
allow(ObjectSpace).to receive(:each_object).with(Module).and_return([singleton_a, singleton_b, singleton_c])
end