Skip to content

Commit

Permalink
Use environment object inside matchers
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
mbj committed Jun 30, 2014
1 parent a18a15f commit 3390abc
Show file tree
Hide file tree
Showing 19 changed files with 96 additions and 60 deletions.
2 changes: 1 addition & 1 deletion config/flay.yml
@@ -1,3 +1,3 @@
---
threshold: 18
total_score: 968
total_score: 976
1 change: 1 addition & 0 deletions lib/mutant.rb
Expand Up @@ -79,6 +79,7 @@ def inspect
end # Mutant

require 'mutant/version'
require 'mutant/env'
require 'mutant/ast'
require 'mutant/ast/sexp'
require 'mutant/ast/types'
Expand Down
2 changes: 1 addition & 1 deletion lib/mutant/cache.rb
@@ -1,7 +1,7 @@
module Mutant
# An AST cache
class Cache
include Equalizer.new
include Equalizer.new, Adamantium::Mutable

# Initialize object
#
Expand Down
2 changes: 1 addition & 1 deletion lib/mutant/cli.rb
Expand Up @@ -39,7 +39,7 @@ def self.run(arguments)
# @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
Expand Down
36 changes: 36 additions & 0 deletions lib/mutant/env.rb
@@ -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
2 changes: 1 addition & 1 deletion lib/mutant/matcher.rb
Expand Up @@ -5,7 +5,7 @@ class Matcher

# Default matcher build implementation
#
# @param [Cache] cache
# @param [Env] env
# @param [Object] input
#
# @return [undefined]
Expand Down
10 changes: 5 additions & 5 deletions lib/mutant/matcher/builder.rb
Expand Up @@ -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 = []
Expand All @@ -28,7 +28,7 @@ def initialize(cache)
# @api private
#
def add_subject_ignore(expression)
@subject_ignores << expression.matcher(cache)
@subject_ignores << expression.matcher(env)
self
end

Expand All @@ -54,7 +54,7 @@ def add_subject_selector(attribute, value)
# @api private
#
def add_match_expression(expression)
@matchers << expression.matcher(cache)
@matchers << expression.matcher(env)
self
end

Expand Down
4 changes: 2 additions & 2 deletions lib/mutant/matcher/method.rb
Expand Up @@ -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
Expand Down Expand Up @@ -78,7 +78,7 @@ def context
# @api private
#
def ast
cache.parse(source_path)
env.cache.parse(source_path)
end

# Return path to source
Expand Down
6 changes: 3 additions & 3 deletions lib/mutant/matcher/method/instance.rb
Expand Up @@ -7,18 +7,18 @@ class Instance < self

# Dispatching builder, detects memoizable case
#
# @param [Cache] cache
# @param [Env] env
# @param [Class, Module] scope
# @param [UnboundMethod] method
#
# @return [Matcher::Method::Instance]
#
# @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
Expand Down
4 changes: 2 additions & 2 deletions lib/mutant/matcher/methods.rb
Expand Up @@ -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
#
Expand Down Expand Up @@ -56,7 +56,7 @@ def methods
#
def subjects
methods.map do |method|
matcher.build(cache, scope, method)
matcher.build(env, scope, method)
end.flat_map(&:to_a)
end
memoize :subjects
Expand Down
4 changes: 2 additions & 2 deletions lib/mutant/matcher/namespace.rb
Expand Up @@ -5,7 +5,7 @@ class Matcher
#
# rubocop:disable LineLength
class Namespace < self
include Concord::Public.new(:cache, :expression)
include Concord::Public.new(:env, :expression)

# Enumerate subjects
#
Expand Down Expand Up @@ -36,7 +36,7 @@ def each(&block)
#
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
Expand Down
4 changes: 2 additions & 2 deletions lib/mutant/matcher/scope.rb
Expand Up @@ -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,
Expand Down Expand Up @@ -34,7 +34,7 @@ def each(&block)
return to_enum unless block_given?

MATCHERS.each do |matcher|
matcher.new(cache, scope).each(&block)
matcher.new(env, scope).each(&block)
end

self
Expand Down
4 changes: 2 additions & 2 deletions spec/spec_helper.rb
Expand Up @@ -30,8 +30,8 @@
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)
Expand Down
14 changes: 7 additions & 7 deletions spec/unit/mutant/cli_new_spec.rb
Expand Up @@ -29,8 +29,8 @@
let(:expected_integration) { Mutant::Integration::Null.new }
let(:expected_reporter) { Mutant::Reporter::CLI.new($stdout) }

let(:ns) { Mutant::Matcher }
let(:cache) { Mutant::Cache.new }
let(:ns) { Mutant::Matcher }
let(:env) { Fixtures::BOOT_ENV }

let(:cli) { object.new(arguments) }

Expand Down Expand Up @@ -71,7 +71,7 @@
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'
Expand All @@ -80,7 +80,7 @@
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'

Expand All @@ -92,7 +92,7 @@
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'

Expand All @@ -104,7 +104,7 @@
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
Expand All @@ -124,7 +124,7 @@

let(:expected_matcher) do
matcher = ns::Method::Instance.new(
cache,
env,
TestApp::Literal, TestApp::Literal.instance_method(:float)
)
predicate = Morpher.compile(
Expand Down
10 changes: 5 additions & 5 deletions spec/unit/mutant/matcher/method/instance_spec.rb
Expand Up @@ -3,12 +3,12 @@
# 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 }
Expand Down Expand Up @@ -118,7 +118,7 @@ def baz
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
Expand All @@ -141,13 +141,13 @@ def bar
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
Expand Down
16 changes: 8 additions & 8 deletions spec/unit/mutant/matcher/method/singleton_spec.rb
Expand Up @@ -4,14 +4,14 @@
describe Mutant::Matcher::Method::Singleton, '#each' do
subject { object.each { |subject| yields << subject } }

let(:object) { described_class.new(cache, scope, method) }
let(:method) { scope.method(method_name) }
let(:cache) { Fixtures::AST_CACHE }
let(:yields) { [] }
let(:namespace) { self.class }
let(:scope) { self.class::Foo }
let(:type) { :defs }
let(:method_arity) { 0 }
let(:object) { described_class.new(env, scope, method) }
let(:method) { scope.method(method_name) }
let(:env) { Fixtures::BOOT_ENV }
let(:yields) { [] }
let(:namespace) { self.class }
let(:scope) { self.class::Foo }
let(:type) { :defs }
let(:method_arity) { 0 }

def name
node.children[1]
Expand Down
10 changes: 5 additions & 5 deletions spec/unit/mutant/matcher/methods/instance_spec.rb
@@ -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 } }

Expand Down Expand Up @@ -46,9 +46,9 @@ def method_c

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
Expand Down
10 changes: 5 additions & 5 deletions spec/unit/mutant/matcher/methods/singleton_spec.rb
@@ -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 } }

Expand Down Expand Up @@ -41,11 +41,11 @@ def self.method_c
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
Expand Down

0 comments on commit 3390abc

Please sign in to comment.