Move bootstrapping into Env::Bootstrap
* Do not mix concerns of domain with building an object graph concerning the domains execution environment * Removes the amount of clutter in Env (mostly a cleanup for tracing where Env will grow a bit)
This commit is contained in:
parent
73474f312f
commit
b65939d527
11 changed files with 316 additions and 203 deletions
1
Gemfile
1
Gemfile
|
@ -5,3 +5,4 @@ source 'https://rubygems.org'
|
|||
gemspec name: 'mutant'
|
||||
|
||||
gem 'devtools', git: 'https://github.com/rom-rb/devtools.git'
|
||||
eval_gemfile 'Gemfile.devtools'
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
---
|
||||
threshold: 18
|
||||
total_score: 1198
|
||||
total_score: 1217
|
||||
|
|
|
@ -95,6 +95,7 @@ end # Mutant
|
|||
|
||||
require 'mutant/version'
|
||||
require 'mutant/env'
|
||||
require 'mutant/env/bootstrap'
|
||||
require 'mutant/ast'
|
||||
require 'mutant/ast/sexp'
|
||||
require 'mutant/ast/types'
|
||||
|
|
|
@ -22,7 +22,7 @@ module Mutant
|
|||
# @api private
|
||||
#
|
||||
def self.run(arguments)
|
||||
Runner.call(Env.new(call(arguments))).success? ? EXIT_SUCCESS : EXIT_FAILURE
|
||||
Runner.call(Env::Bootstrap.call(call(arguments))).success? ? EXIT_SUCCESS : EXIT_FAILURE
|
||||
rescue Error => exception
|
||||
$stderr.puts(exception.message)
|
||||
EXIT_FAILURE
|
||||
|
|
|
@ -1,39 +1,19 @@
|
|||
module Mutant
|
||||
# Abstract base class for mutant environments
|
||||
class Env
|
||||
include Adamantium::Flat, Concord::Public.new(:config, :cache)
|
||||
include Adamantium::Flat, Anima::Update, Anima.new(
|
||||
:config,
|
||||
:actor_env,
|
||||
:cache,
|
||||
:subjects,
|
||||
:matchable_scopes,
|
||||
:mutations
|
||||
)
|
||||
|
||||
SEMANTICS_MESSAGE =
|
||||
"Fix your lib to follow normal ruby semantics!\n" \
|
||||
'{Module,Class}#name should return resolvable constant name as String or nil'.freeze
|
||||
|
||||
# Return new env
|
||||
#
|
||||
# @param [Config] config
|
||||
#
|
||||
# @return [Env]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def self.new(config, cache = Cache.new)
|
||||
super(config, cache)
|
||||
end
|
||||
|
||||
# Initialize env
|
||||
#
|
||||
# @return [undefined]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def initialize(*)
|
||||
super
|
||||
|
||||
infect
|
||||
initialize_matchable_scopes
|
||||
initialize_subjects
|
||||
initialize_mutations
|
||||
end
|
||||
|
||||
# Print warning message
|
||||
#
|
||||
# @param [String]
|
||||
|
@ -47,30 +27,6 @@ module Mutant
|
|||
self
|
||||
end
|
||||
|
||||
# Return subjects
|
||||
#
|
||||
# @return [Array<Subject>]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
attr_reader :subjects
|
||||
|
||||
# Return mutations
|
||||
#
|
||||
# @return [Array<Mutation>]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
attr_reader :mutations
|
||||
|
||||
# Return all usable match scopes
|
||||
#
|
||||
# @return [Array<Matcher::Scope>]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
attr_reader :matchable_scopes
|
||||
|
||||
# Kill mutation
|
||||
#
|
||||
# @param [Mutation] mutation
|
||||
|
@ -87,95 +43,5 @@ module Mutant
|
|||
)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Return scope name
|
||||
#
|
||||
# @param [Class, Module] scope
|
||||
#
|
||||
# @return [String]
|
||||
# if scope has a name and does not raise exceptions obtaining it
|
||||
#
|
||||
# @return [nil]
|
||||
# otherwise
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
# rubocop:disable LineLength
|
||||
#
|
||||
def scope_name(scope)
|
||||
scope.name
|
||||
rescue => exception
|
||||
warn("#{scope.class}#name from: #{scope.inspect} raised an error: #{exception.inspect}. #{SEMANTICS_MESSAGE}")
|
||||
nil
|
||||
end
|
||||
|
||||
# Try to turn scope into expression
|
||||
#
|
||||
# @param [Class, Module] scope
|
||||
#
|
||||
# @return [Expression]
|
||||
# if scope can be represented in an expression
|
||||
#
|
||||
# @return [nil]
|
||||
# otherwise
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def expression(scope)
|
||||
name = scope_name(scope) or return
|
||||
|
||||
unless name.instance_of?(String)
|
||||
warn("#{scope.class}#name from: #{scope.inspect} returned #{name.inspect}. #{SEMANTICS_MESSAGE}")
|
||||
return
|
||||
end
|
||||
|
||||
Expression.try_parse(name)
|
||||
end
|
||||
|
||||
# Initialize subjects
|
||||
#
|
||||
# @return [undefined]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def initialize_subjects
|
||||
@subjects = Matcher::Compiler.call(self, config.matcher_config).to_a
|
||||
end
|
||||
|
||||
# Initialize mutations
|
||||
#
|
||||
# @return [undefined]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def initialize_mutations
|
||||
@mutations = subjects.flat_map(&:mutations)
|
||||
end
|
||||
|
||||
# Infect environment
|
||||
#
|
||||
# @return [undefined]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def infect
|
||||
config.includes.each(&$LOAD_PATH.method(:<<))
|
||||
config.requires.each(&method(:require))
|
||||
end
|
||||
|
||||
# Initialize matchable scopes
|
||||
#
|
||||
# @return [undefined]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def initialize_matchable_scopes
|
||||
@matchable_scopes = ObjectSpace.each_object(Module).each_with_object([]) do |scope, aggregate|
|
||||
expression = expression(scope)
|
||||
aggregate << Matcher::Scope.new(self, scope, expression) if expression
|
||||
end.sort_by(&:identification)
|
||||
end
|
||||
|
||||
end # Env
|
||||
end # Mutant
|
||||
|
|
154
lib/mutant/env/bootstrap.rb
vendored
Normal file
154
lib/mutant/env/bootstrap.rb
vendored
Normal file
|
@ -0,0 +1,154 @@
|
|||
module Mutant
|
||||
class Env
|
||||
# Boostrap environment
|
||||
class Bootstrap
|
||||
include Adamantium::Flat, Concord::Public.new(:config, :cache), Procto.call(:env)
|
||||
|
||||
SEMANTICS_MESSAGE =
|
||||
"Fix your lib to follow normal ruby semantics!\n" \
|
||||
'{Module,Class}#name should return resolvable constant name as String or nil'.freeze
|
||||
|
||||
# Return scopes that are eligible for mnatching
|
||||
#
|
||||
# @return [Enumerable<Matcher::Scope>]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
attr_reader :matchable_scopes
|
||||
|
||||
# Return new boostrap env
|
||||
#
|
||||
# @return [Env]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def self.new(_config, _cache = Cache.new)
|
||||
super
|
||||
end
|
||||
|
||||
# Initialize object
|
||||
#
|
||||
# @return [Object]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def initialize(*)
|
||||
super
|
||||
infect
|
||||
initialize_matchable_scopes
|
||||
end
|
||||
|
||||
# Print warning message
|
||||
#
|
||||
# @param [String]
|
||||
#
|
||||
# @return [self]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def warn(message)
|
||||
config.reporter.warn(message)
|
||||
self
|
||||
end
|
||||
|
||||
# Return environment after boostraping
|
||||
#
|
||||
# @return [Env]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def env
|
||||
subjects = matched_subjects
|
||||
|
||||
Env.new(
|
||||
actor_env: Actor::Env.new(Thread),
|
||||
config: config,
|
||||
cache: cache,
|
||||
subjects: subjects,
|
||||
matchable_scopes: matchable_scopes,
|
||||
mutations: subjects.flat_map(&:mutations)
|
||||
)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Return scope name
|
||||
#
|
||||
# @param [Class, Module] scope
|
||||
#
|
||||
# @return [String]
|
||||
# if scope has a name and does not raise exceptions obtaining it
|
||||
#
|
||||
# @return [nil]
|
||||
# otherwise
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
# rubocop:disable LineLength
|
||||
#
|
||||
def scope_name(scope)
|
||||
scope.name
|
||||
rescue => exception
|
||||
warn("#{scope.class}#name from: #{scope.inspect} raised an error: #{exception.inspect}. #{SEMANTICS_MESSAGE}")
|
||||
nil
|
||||
end
|
||||
|
||||
# Infect environment
|
||||
#
|
||||
# @return [undefined]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def infect
|
||||
config.includes.each(&$LOAD_PATH.method(:<<))
|
||||
config.requires.each(&method(:require))
|
||||
end
|
||||
|
||||
# Try to turn scope into expression
|
||||
#
|
||||
# @param [Class, Module] scope
|
||||
#
|
||||
# @return [Expression]
|
||||
# if scope can be represented in an expression
|
||||
#
|
||||
# @return [nil]
|
||||
# otherwise
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def expression(scope)
|
||||
name = scope_name(scope) or return
|
||||
|
||||
unless name.instance_of?(String)
|
||||
warn("#{scope.class}#name from: #{scope.inspect} returned #{name.inspect}. #{SEMANTICS_MESSAGE}")
|
||||
return
|
||||
end
|
||||
|
||||
Expression.try_parse(name)
|
||||
end
|
||||
|
||||
# Return matched subjects
|
||||
#
|
||||
# @return [Enumerable<Subject>]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def matched_subjects
|
||||
Matcher::Compiler.call(self, config.matcher_config).to_a
|
||||
end
|
||||
|
||||
# Initialize matchable scopes
|
||||
#
|
||||
# @return [undefined]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def initialize_matchable_scopes
|
||||
@matchable_scopes = ObjectSpace.each_object(Module).each_with_object([]) do |scope, aggregate|
|
||||
expression = expression(scope)
|
||||
aggregate << Matcher::Scope.new(self, scope, expression) if expression
|
||||
end.sort_by(&:identification)
|
||||
end
|
||||
end # Boostrap
|
||||
end # Env
|
||||
end # Mutant
|
|
@ -37,14 +37,14 @@ module Mutant
|
|||
|
||||
# Return matcher
|
||||
#
|
||||
# @param [Env] env
|
||||
# @param [Env::Bootstrap] bootstrap
|
||||
#
|
||||
# @return [Matcher]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def matcher(env)
|
||||
Matcher::Namespace.new(env, self)
|
||||
def matcher(bootstrap)
|
||||
Matcher::Namespace.new(bootstrap, self)
|
||||
end
|
||||
|
||||
# Return length of match
|
||||
|
@ -74,14 +74,14 @@ module Mutant
|
|||
|
||||
# Return matcher
|
||||
#
|
||||
# @param [Cache] env
|
||||
# @param [Env::Bootstrap] boostrap
|
||||
#
|
||||
# @return [Matcher]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def matcher(env)
|
||||
Matcher::Scope.new(env, Mutant.constant_lookup(namespace), self)
|
||||
def matcher(bootstrap)
|
||||
Matcher::Scope.new(bootstrap, Mutant.constant_lookup(namespace), self)
|
||||
end
|
||||
|
||||
end # Exact
|
||||
|
|
|
@ -34,7 +34,7 @@ require 'test_app'
|
|||
module Fixtures
|
||||
TEST_CONFIG = Mutant::Config::DEFAULT.update(reporter: Mutant::Reporter::Trace.new)
|
||||
TEST_CACHE = Mutant::Cache.new
|
||||
TEST_ENV = Mutant::Env.new(TEST_CONFIG, TEST_CACHE)
|
||||
TEST_ENV = Mutant::Env::Bootstrap.call(TEST_CONFIG, TEST_CACHE)
|
||||
end # Fixtures
|
||||
|
||||
module ParserHelper
|
||||
|
|
|
@ -25,7 +25,7 @@ RSpec.describe Mutant::CLI do
|
|||
|
||||
before do
|
||||
expect(Mutant::CLI).to receive(:call).with(arguments).and_return(config)
|
||||
expect(Mutant::Env).to receive(:new).with(config).and_return(env)
|
||||
expect(Mutant::Env::Bootstrap).to receive(:call).with(config).and_return(env)
|
||||
expect(Mutant::Runner).to receive(:call).with(env).and_return(report)
|
||||
end
|
||||
|
||||
|
|
129
spec/unit/mutant/env/boostrap_spec.rb
vendored
Normal file
129
spec/unit/mutant/env/boostrap_spec.rb
vendored
Normal file
|
@ -0,0 +1,129 @@
|
|||
RSpec.describe Mutant::Env::Bootstrap do
|
||||
let(:config) do
|
||||
Mutant::Config::DEFAULT.update(
|
||||
jobs: 1,
|
||||
reporter: Mutant::Reporter::Trace.new,
|
||||
includes: [],
|
||||
requires: [],
|
||||
matcher_config: Mutant::Matcher::Config::DEFAULT
|
||||
)
|
||||
end
|
||||
|
||||
let(:expected_env) do
|
||||
Mutant::Env.new(
|
||||
cache: Mutant::Cache.new,
|
||||
subjects: [],
|
||||
matchable_scopes: [],
|
||||
mutations: [],
|
||||
config: config,
|
||||
actor_env: Mutant::Actor::Env.new(Thread)
|
||||
)
|
||||
end
|
||||
|
||||
shared_examples_for 'bootstrap call' do
|
||||
it { should eql(expected_env) }
|
||||
end
|
||||
|
||||
let(:object_space_modules) { [] }
|
||||
|
||||
before do
|
||||
allow(ObjectSpace).to receive(:each_object).with(Module).and_return(object_space_modules.each)
|
||||
end
|
||||
|
||||
describe '.call' do
|
||||
subject { described_class.call(config) }
|
||||
|
||||
context 'when Module#name calls result in exceptions' do
|
||||
let(:invalid_class) do
|
||||
Class.new do
|
||||
def self.name
|
||||
fail
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
let(:object_space_modules) { [invalid_class] }
|
||||
|
||||
after do
|
||||
# Fix Class#name so other specs do not see this one
|
||||
class << invalid_class
|
||||
undef :name
|
||||
def name
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it 'warns via reporter' do
|
||||
expected_warnings = [
|
||||
"Class#name from: #{invalid_class} raised an error: RuntimeError. #{Mutant::Env::SEMANTICS_MESSAGE}"
|
||||
]
|
||||
|
||||
expect { subject }.to change { config.reporter.warn_calls }.from([]).to(expected_warnings)
|
||||
end
|
||||
|
||||
include_examples 'bootstrap call'
|
||||
end
|
||||
|
||||
context 'when includes are present' do
|
||||
let(:config) { super().update(includes: %w[foo bar]) }
|
||||
|
||||
before do
|
||||
%w[foo bar].each do |component|
|
||||
expect($LOAD_PATH).to receive(:<<).with(component).and_return($LOAD_PATH)
|
||||
end
|
||||
end
|
||||
|
||||
include_examples 'bootstrap call'
|
||||
end
|
||||
|
||||
context 'when Module#name does not return a String or nil' do
|
||||
let(:invalid_class) do
|
||||
Class.new do
|
||||
def self.name
|
||||
Object
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
let(:object_space_modules) { [invalid_class] }
|
||||
|
||||
after do
|
||||
# Fix Class#name so other specs do not see this one
|
||||
class << invalid_class
|
||||
undef :name
|
||||
def name
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it 'warns via reporter' do
|
||||
|
||||
expected_warnings = [
|
||||
"Class#name from: #{invalid_class.inspect} returned Object. #{Mutant::Env::SEMANTICS_MESSAGE}"
|
||||
]
|
||||
|
||||
expect { subject }.to change { config.reporter.warn_calls }.from([]).to(expected_warnings)
|
||||
end
|
||||
|
||||
include_examples 'bootstrap call'
|
||||
end
|
||||
|
||||
context 'when scope matches expression' do
|
||||
let(:mutations) { [double('Mutation')] }
|
||||
let(:subjects) { [double('Subject', mutations: mutations)] }
|
||||
|
||||
before do
|
||||
expect(Mutant::Matcher::Compiler).to receive(:call).and_return(subjects)
|
||||
end
|
||||
|
||||
let(:expected_env) do
|
||||
super().update(
|
||||
subjects: subjects,
|
||||
mutations: mutations
|
||||
)
|
||||
end
|
||||
|
||||
include_examples 'bootstrap call'
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,58 +1,20 @@
|
|||
RSpec.describe Mutant::Env do
|
||||
let(:config) { Mutant::Config::DEFAULT.update(jobs: 1, reporter: Mutant::Reporter::Trace.new) }
|
||||
|
||||
context '.new' do
|
||||
subject { described_class.new(config) }
|
||||
|
||||
context 'when Module#name calls result in exceptions' do
|
||||
it 'warns via reporter' do
|
||||
klass = Class.new do
|
||||
def self.name
|
||||
fail
|
||||
end
|
||||
end
|
||||
|
||||
expected_warnings = [
|
||||
"Class#name from: #{klass} raised an error: RuntimeError. #{Mutant::Env::SEMANTICS_MESSAGE}"
|
||||
]
|
||||
|
||||
expect { subject }.to change { config.reporter.warn_calls }.from([]).to(expected_warnings)
|
||||
|
||||
# Fix Class#name so other specs do not see this one
|
||||
class << klass
|
||||
undef :name
|
||||
def name
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when Module#name does not return a String or nil' do
|
||||
it 'warns via reporter' do
|
||||
klass = Class.new do
|
||||
def self.name
|
||||
Object
|
||||
end
|
||||
end
|
||||
|
||||
expected_warnings = ["Class#name from: #{klass.inspect} returned Object. #{Mutant::Env::SEMANTICS_MESSAGE}"]
|
||||
|
||||
expect { subject }.to change { config.reporter.warn_calls }.from([]).to(expected_warnings)
|
||||
|
||||
# Fix Class#name so other specs do not see this one
|
||||
class << klass
|
||||
undef :name
|
||||
def name
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
let(:object) do
|
||||
described_class.new(
|
||||
config: config,
|
||||
actor_env: Mutant::Actor::Env.new(Thread),
|
||||
cache: Mutant::Cache.new,
|
||||
subjects: [],
|
||||
mutations: [],
|
||||
matchable_scopes: []
|
||||
)
|
||||
end
|
||||
|
||||
let(:config) { Mutant::Config::DEFAULT.update(jobs: 1, reporter: Mutant::Reporter::Trace.new) }
|
||||
|
||||
context '#kill_mutation' do
|
||||
let(:object) { described_class.new(config) }
|
||||
let(:result) { double('Result') }
|
||||
let(:mutation) { double('Mutation') }
|
||||
let(:result) { double('Result') }
|
||||
let(:mutation) { double('Mutation') }
|
||||
|
||||
subject { object.kill_mutation(mutation) }
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue