Reduce zombifier
* And add a full specification
This commit is contained in:
parent
a54e686255
commit
c56ecdb51a
14 changed files with 399 additions and 251 deletions
18
bin/mutant
18
bin/mutant
|
@ -10,7 +10,23 @@ require 'mutant'
|
|||
namespace =
|
||||
if ARGV.include?('--zombie')
|
||||
$stderr.puts('Running mutant zombified!')
|
||||
Mutant.zombify
|
||||
Mutant::Zombifier.call(
|
||||
namespace: :Zombie,
|
||||
load_path: $LOAD_PATH,
|
||||
kernel: Kernel,
|
||||
pathname: Pathname,
|
||||
require_highjack: Mutant::RequireHighjack.method(:call).to_proc.curry.call(Kernel),
|
||||
root_require: 'mutant',
|
||||
includes: %w[
|
||||
mutant
|
||||
unparser
|
||||
morpher
|
||||
adamantium
|
||||
equalizer
|
||||
anima
|
||||
concord
|
||||
]
|
||||
)
|
||||
Zombie::Mutant
|
||||
else
|
||||
Mutant
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
---
|
||||
threshold: 18
|
||||
total_score: 1240
|
||||
total_score: 1227
|
||||
|
|
|
@ -46,8 +46,7 @@ NestedIterators:
|
|||
- Mutant::Mutator::Util::Array::Element#dispatch
|
||||
- Mutant::Mutator::Node::Resbody#mutate_captures
|
||||
- Mutant::Mutator::Node::Arguments#emit_argument_mutations
|
||||
- Mutant::RequireHighjack#infect
|
||||
- Mutant::RequireHighjack#disinfect
|
||||
- Mutant::RequireHighjack#self.call
|
||||
- Mutant::Selector::Expression#call
|
||||
- Mutant::Parallel::Master#run
|
||||
- Parser::Lexer#self.new
|
||||
|
|
|
@ -24,11 +24,8 @@ Thread.abort_on_exception = true
|
|||
|
||||
# Library namespace
|
||||
module Mutant
|
||||
# The frozen empty string used within mutant
|
||||
EMPTY_STRING = ''.freeze
|
||||
# The frozen empty array used within mutant
|
||||
EMPTY_ARRAY = [].freeze
|
||||
|
||||
EMPTY_STRING = ''.freeze
|
||||
EMPTY_ARRAY = [].freeze
|
||||
SCOPE_OPERATOR = '::'.freeze
|
||||
|
||||
# Test if CI is detected via environment
|
||||
|
@ -41,17 +38,6 @@ module Mutant
|
|||
ENV.key?('CI')
|
||||
end
|
||||
|
||||
# Perform self zombification
|
||||
#
|
||||
# @return [self]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def self.zombify
|
||||
Zombifier.run('mutant', :Zombie)
|
||||
self
|
||||
end
|
||||
|
||||
# Define instance of subclassed superclass as constant
|
||||
#
|
||||
# @param [Class] superclass
|
||||
|
@ -216,7 +202,6 @@ require 'mutant/reporter/cli/printer/test_result'
|
|||
require 'mutant/reporter/cli/tput'
|
||||
require 'mutant/reporter/cli/format'
|
||||
require 'mutant/zombifier'
|
||||
require 'mutant/zombifier/file'
|
||||
|
||||
module Mutant
|
||||
# Reopen class to initialize constant to avoid dep circle
|
||||
|
|
|
@ -1,62 +1,23 @@
|
|||
module Mutant
|
||||
# Require highjack
|
||||
class RequireHighjack
|
||||
include Concord.new(:target, :callback)
|
||||
module RequireHighjack
|
||||
|
||||
# Return original method
|
||||
# Install require callback
|
||||
#
|
||||
# @param [Module] target
|
||||
# @param [#call] callback
|
||||
#
|
||||
# @return [#call]
|
||||
# the original implementation on singleton
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
attr_reader :original
|
||||
|
||||
# Run block with highjacked require
|
||||
#
|
||||
# @return [self]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def run
|
||||
infect
|
||||
yield
|
||||
self
|
||||
ensure
|
||||
disinfect
|
||||
end
|
||||
|
||||
# Infect kernel with highjack
|
||||
#
|
||||
# @return [self]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def infect
|
||||
callback = @callback
|
||||
@original = target.method(:require)
|
||||
target.module_eval do
|
||||
undef :require
|
||||
define_method(:require) do |logical_name|
|
||||
callback.call(logical_name)
|
||||
def self.call(target, callback)
|
||||
target.method(:require).tap do
|
||||
target.module_eval do
|
||||
define_method(:require, &callback)
|
||||
public :require
|
||||
end
|
||||
module_function :require
|
||||
end
|
||||
end
|
||||
|
||||
# Imperfectly disinfect kernel from highjack
|
||||
#
|
||||
# @return [self]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def disinfect
|
||||
original = @original
|
||||
target.module_eval do
|
||||
undef :require
|
||||
define_method(:require) do |logical_name|
|
||||
original.call(logical_name)
|
||||
end
|
||||
module_function :require
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -1,20 +1,19 @@
|
|||
module Mutant
|
||||
# Zombifier namespace
|
||||
class Zombifier
|
||||
include Adamantium::Flat, Concord.new(:namespace)
|
||||
include Anima.new(
|
||||
:includes,
|
||||
:namespace,
|
||||
:load_path,
|
||||
:kernel,
|
||||
:require_highjack,
|
||||
:root_require,
|
||||
:pathname
|
||||
)
|
||||
|
||||
# Excluded into zombification
|
||||
includes = %w[
|
||||
mutant
|
||||
unparser
|
||||
morpher
|
||||
adamantium
|
||||
equalizer
|
||||
anima
|
||||
concord
|
||||
]
|
||||
include AST::Sexp
|
||||
|
||||
INCLUDES = %r{\A#{Regexp.union(includes)}(?:/.*)?\z}.freeze
|
||||
LoadError = Class.new(::LoadError)
|
||||
|
||||
# Initialize object
|
||||
#
|
||||
|
@ -24,37 +23,28 @@ module Mutant
|
|||
#
|
||||
# @api private
|
||||
#
|
||||
def initialize(namespace)
|
||||
def initialize(*)
|
||||
super
|
||||
@includes = %r{\A#{Regexp.union(includes)}(?:/.*)?\z}
|
||||
@zombified = Set.new
|
||||
@highjack = RequireHighjack.new(Kernel, method(:require))
|
||||
super(namespace)
|
||||
end
|
||||
|
||||
# Perform zombification of target library
|
||||
#
|
||||
# @param [String] logical_name
|
||||
# @param [Symbol] namespace
|
||||
#
|
||||
# @return [self]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def self.run(logical_name, namespace)
|
||||
new(namespace).run(logical_name)
|
||||
def self.call(*args)
|
||||
new(*args).__send__(:call)
|
||||
self
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Run zombifier
|
||||
#
|
||||
# @param [String] logical_name
|
||||
#
|
||||
# @return [undefined]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def run(logical_name)
|
||||
@highjack.infect
|
||||
require(logical_name)
|
||||
def call
|
||||
@original = require_highjack.call(method(:require))
|
||||
require(root_require)
|
||||
end
|
||||
|
||||
# Test if logical name is subjected to zombification
|
||||
|
@ -64,25 +54,77 @@ module Mutant
|
|||
# @api private
|
||||
#
|
||||
def include?(logical_name)
|
||||
!@zombified.include?(logical_name) && INCLUDES =~ logical_name
|
||||
!@zombified.include?(logical_name) && @includes =~ logical_name
|
||||
end
|
||||
|
||||
# Require file in zombie namespace
|
||||
#
|
||||
# @param [#to_s] logical_name
|
||||
#
|
||||
# @return [self]
|
||||
# @return [undefined]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def require(logical_name)
|
||||
logical_name = logical_name.to_s
|
||||
@highjack.original.call(logical_name)
|
||||
@original.call(logical_name)
|
||||
return unless include?(logical_name)
|
||||
@zombified << logical_name
|
||||
file = File.find(logical_name)
|
||||
file.zombify(namespace) if file
|
||||
self
|
||||
zombify(find(logical_name))
|
||||
end
|
||||
|
||||
# Find file by logical path
|
||||
#
|
||||
# @param [String] logical_name
|
||||
#
|
||||
# @return [File]
|
||||
#
|
||||
# @raise [LoadError]
|
||||
# otherwise
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def find(logical_name)
|
||||
file_name = "#{logical_name}.rb"
|
||||
|
||||
load_path.each do |path|
|
||||
path = pathname.new(path).join(file_name)
|
||||
return path if path.file?
|
||||
end
|
||||
|
||||
fail LoadError, "Cannot find file #{file_name.inspect} in load path"
|
||||
end
|
||||
|
||||
# Zombify contents of file
|
||||
#
|
||||
# Probably the 2nd valid use of eval ever. (First one is inserting mutants!).
|
||||
#
|
||||
# @param [Pathname] source_path
|
||||
#
|
||||
# @return [undefined]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
# rubocop:disable Lint/Eval
|
||||
#
|
||||
def zombify(source_path)
|
||||
kernel.eval(
|
||||
Unparser.unparse(namespaced_node(source_path)),
|
||||
TOPLEVEL_BINDING,
|
||||
source_path.to_s
|
||||
)
|
||||
end
|
||||
|
||||
# Return namespaced root
|
||||
#
|
||||
# @param [Symbol] namespace
|
||||
#
|
||||
# @return [Parser::AST::Node]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def namespaced_node(source_path)
|
||||
s(:module, s(:const, nil, namespace), Parser::CurrentRuby.parse(source_path.read))
|
||||
end
|
||||
|
||||
end # Zombifier
|
||||
|
|
|
@ -1,100 +0,0 @@
|
|||
module Mutant
|
||||
class Zombifier
|
||||
# File containing source being zombified
|
||||
class File
|
||||
include Adamantium::Flat, Concord::Public.new(:path), AST::Sexp
|
||||
|
||||
# Zombify contents of file
|
||||
#
|
||||
# @return [self]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
# Probably one of the only valid uses of eval.
|
||||
#
|
||||
# rubocop:disable Lint/Eval
|
||||
#
|
||||
def zombify(namespace)
|
||||
$stderr.puts("Zombifying #{path}")
|
||||
eval(
|
||||
Unparser.unparse(namespaced_node(namespace)),
|
||||
TOPLEVEL_BINDING,
|
||||
path.to_s
|
||||
)
|
||||
self
|
||||
end
|
||||
|
||||
# Find file by logical path
|
||||
#
|
||||
# @param [String] logical_name
|
||||
#
|
||||
# @return [File]
|
||||
# if found
|
||||
#
|
||||
# @return [nil]
|
||||
# otherwise
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def self.find(logical_name)
|
||||
file_name = expand_file_name(logical_name)
|
||||
|
||||
$LOAD_PATH.each do |path|
|
||||
path = Pathname.new(path).join(file_name)
|
||||
return new(path) if path.file?
|
||||
end
|
||||
|
||||
$stderr.puts "Cannot find file #{file_name} in $LOAD_PATH"
|
||||
nil
|
||||
end
|
||||
|
||||
# Return expanded file name
|
||||
#
|
||||
# @param [String] logical_name
|
||||
#
|
||||
# @return [nil]
|
||||
# if no expansion is possible
|
||||
#
|
||||
# @return [String]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def self.expand_file_name(logical_name)
|
||||
case ::File.extname(logical_name)
|
||||
when '.so'
|
||||
return
|
||||
when '.rb'
|
||||
logical_name
|
||||
else
|
||||
"#{logical_name}.rb"
|
||||
end
|
||||
end
|
||||
private_class_method :expand_file_name
|
||||
|
||||
private
|
||||
|
||||
# Return node
|
||||
#
|
||||
# @return [Parser::AST::Node]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def node
|
||||
Parser::CurrentRuby.parse(path.read, path.to_s)
|
||||
end
|
||||
|
||||
# Return namespaced root
|
||||
#
|
||||
# @param [Symbol] namespace
|
||||
#
|
||||
# @return [Parser::AST::Node]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def namespaced_node(namespace)
|
||||
s(:module, s(:const, nil, namespace), node)
|
||||
end
|
||||
|
||||
end # File
|
||||
end # Zombifier
|
||||
end # Mutant
|
|
@ -1,6 +0,0 @@
|
|||
RSpec.describe 'as a zombie', mutant: false do
|
||||
specify 'it allows to create zombie from mutant' do
|
||||
expect { Mutant.zombify }.to change { defined?(Zombie) }.from(nil).to('constant')
|
||||
expect(Zombie.constants).to include(:Mutant)
|
||||
end
|
||||
end
|
|
@ -20,6 +20,7 @@ end
|
|||
|
||||
require 'tempfile'
|
||||
require 'concord'
|
||||
require 'anima'
|
||||
require 'adamantium'
|
||||
require 'devtools/spec_helper'
|
||||
require 'unparser/cli'
|
||||
|
|
60
spec/support/file_system.rb
Normal file
60
spec/support/file_system.rb
Normal file
|
@ -0,0 +1,60 @@
|
|||
module MutantSpec
|
||||
class FileState
|
||||
DEFAULTS = IceNine.deep_freeze(
|
||||
file: false,
|
||||
contents: nil,
|
||||
requires: []
|
||||
)
|
||||
|
||||
include Adamantium, Anima.new(*DEFAULTS.keys)
|
||||
|
||||
def self.new(attributes = DEFAULTS)
|
||||
super(DEFAULTS.merge(attributes))
|
||||
end
|
||||
|
||||
DOES_NOT_EXIST = new
|
||||
|
||||
alias_method :file?, :file
|
||||
end # FileState
|
||||
|
||||
class FakePathname
|
||||
include Adamantium, Concord.new(:file_system, :pathname)
|
||||
|
||||
def join(*arguments)
|
||||
self.class.new(
|
||||
file_system,
|
||||
pathname.join(*arguments)
|
||||
)
|
||||
end
|
||||
|
||||
def read
|
||||
state.contents
|
||||
end
|
||||
|
||||
def to_s
|
||||
pathname.to_s
|
||||
end
|
||||
|
||||
def file?
|
||||
state.file?
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def state
|
||||
file_system.state(pathname.to_s)
|
||||
end
|
||||
end # Pathname
|
||||
|
||||
class FileSystem
|
||||
include Adamantium, Concord.new(:file_states)
|
||||
|
||||
def state(filename)
|
||||
file_states.fetch(filename, FileState::DOES_NOT_EXIST)
|
||||
end
|
||||
|
||||
def path(filename)
|
||||
FakePathname.new(self, Pathname.new(filename))
|
||||
end
|
||||
end # FileSystem
|
||||
end # MutantSpec
|
82
spec/support/ruby_vm.rb
Normal file
82
spec/support/ruby_vm.rb
Normal file
|
@ -0,0 +1,82 @@
|
|||
module MutantSpec
|
||||
# Not a real VM, just kidding. It connects the require / eval triggers
|
||||
# require semantics Zombifier relies on in a way we can avoid having to
|
||||
# mock around everyhwere to test every detail.
|
||||
#
|
||||
# rubocop:disable LineLength
|
||||
class RubyVM
|
||||
include Concord.new(:expected_events)
|
||||
|
||||
# An event being observed by the VM handlers
|
||||
class EventObservation
|
||||
include Concord::Public.new(:type, :payload)
|
||||
end
|
||||
|
||||
# An event being expected, can advance the VM
|
||||
class EventExpectation
|
||||
include AbstractType, Anima.new(:expected_payload, :trigger_requires)
|
||||
|
||||
DEFAULTS = IceNine.deep_freeze(trigger_requires: [])
|
||||
|
||||
def initialize(attributes)
|
||||
super(DEFAULTS.merge(attributes))
|
||||
end
|
||||
|
||||
def handle(vm, observation)
|
||||
unless match?(observation)
|
||||
fail "Unexpected event observation: #{observation.inspect}, expected #{inspect}"
|
||||
end
|
||||
|
||||
trigger_requires.each(&vm.method(:require))
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
abstract_method :advance_vm
|
||||
|
||||
def match?(observation)
|
||||
observation.type.eql?(self.class) && observation.payload.eql?(expected_payload)
|
||||
end
|
||||
|
||||
# Expectation and advance on require calls
|
||||
class Require < self
|
||||
end
|
||||
|
||||
# Expectation and advance on eval calls
|
||||
class Eval < self
|
||||
end
|
||||
end
|
||||
|
||||
# A fake implementation of Kernel#require
|
||||
def require(logical_name)
|
||||
handle_event(EventObservation.new(EventExpectation::Require, logical_name: logical_name))
|
||||
self
|
||||
end
|
||||
|
||||
# A fake implementation of Kernel#eval
|
||||
def eval(source, binding, location)
|
||||
handle_event(
|
||||
EventObservation.new(
|
||||
EventExpectation::Eval,
|
||||
binding: binding,
|
||||
source: source,
|
||||
source_location: location
|
||||
)
|
||||
)
|
||||
self
|
||||
end
|
||||
|
||||
# Test if VM events where fully processed
|
||||
def done?
|
||||
expected_events.empty?
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def handle_event(observation)
|
||||
fail "Unexpected event: #{observation.type} / #{observation.payload}" if expected_events.empty?
|
||||
|
||||
expected_events.slice!(0).handle(self, observation)
|
||||
end
|
||||
end
|
||||
end # MutantSpec
|
|
@ -1,50 +1,47 @@
|
|||
RSpec.describe Mutant::RequireHighjack do
|
||||
let(:object) { described_class.new(target, highjacked_calls.method(:push)) }
|
||||
|
||||
let(:highjacked_calls) { [] }
|
||||
let(:require_calls) { [] }
|
||||
|
||||
let(:target) do
|
||||
let(:target_module) do
|
||||
acc = require_calls
|
||||
Module.new do
|
||||
define_method(:require, &acc.method(:<<))
|
||||
|
||||
module_function :require
|
||||
public :require
|
||||
end
|
||||
end
|
||||
|
||||
describe '#run' do
|
||||
let(:block) { -> {} }
|
||||
def target_require(logical_name)
|
||||
Object.new.extend(target_module).require(logical_name)
|
||||
end
|
||||
|
||||
describe '.call' do
|
||||
let(:logical_name) { double('Logical Name') }
|
||||
|
||||
subject do
|
||||
object.run(&block)
|
||||
def apply
|
||||
described_class.call(target_module, highjacked_calls.method(:<<))
|
||||
end
|
||||
|
||||
context 'require calls before run' do
|
||||
it 'does not highjack anything' do
|
||||
target.require(logical_name)
|
||||
expect(require_calls).to eql([logical_name])
|
||||
expect(highjacked_calls).to eql([])
|
||||
end
|
||||
it 'returns the original implementation from singleton' do
|
||||
expect { apply.call(logical_name) }
|
||||
.to change { require_calls }
|
||||
.from([])
|
||||
.to([logical_name])
|
||||
end
|
||||
|
||||
context 'require calls during run' do
|
||||
let(:block) { -> { target.require(logical_name) } }
|
||||
|
||||
it 'does highjack the calls' do
|
||||
expect { subject }.to change { highjacked_calls }.from([]).to([logical_name])
|
||||
expect(require_calls).to eql([])
|
||||
end
|
||||
it 'does highjack target object #requires calls' do
|
||||
apply
|
||||
expect { target_require(logical_name) }
|
||||
.to change { highjacked_calls }
|
||||
.from([])
|
||||
.to([logical_name])
|
||||
end
|
||||
|
||||
context 'require calls after run' do
|
||||
|
||||
it 'does not the calls anything' do
|
||||
subject
|
||||
target.require(logical_name)
|
||||
expect(require_calls).to eql([logical_name])
|
||||
expect(highjacked_calls).to eql([])
|
||||
end
|
||||
it 'does not call original require' do
|
||||
apply
|
||||
expect { target_require(logical_name) }
|
||||
.not_to change { require_calls }.from([])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
120
spec/unit/mutant/zombifier_spec.rb
Normal file
120
spec/unit/mutant/zombifier_spec.rb
Normal file
|
@ -0,0 +1,120 @@
|
|||
RSpec.describe Mutant::Zombifier do
|
||||
let(:root_require) { Pathname.new('project') }
|
||||
|
||||
let(:pathname) do
|
||||
instance_double(::Pathname.singleton_class)
|
||||
end
|
||||
|
||||
let(:require_highjack) do
|
||||
lambda do |block|
|
||||
original = ruby_vm.method(:require)
|
||||
allow(ruby_vm).to receive(:require, &block)
|
||||
original
|
||||
end
|
||||
end
|
||||
|
||||
let(:options) do
|
||||
{
|
||||
load_path: %w[a b],
|
||||
includes: %w[project bar],
|
||||
namespace: :Zombie,
|
||||
require_highjack: require_highjack,
|
||||
root_require: root_require,
|
||||
pathname: pathname,
|
||||
kernel: ruby_vm
|
||||
}
|
||||
end
|
||||
|
||||
let(:ruby_vm) do
|
||||
MutantSpec::RubyVM.new(
|
||||
[
|
||||
MutantSpec::RubyVM::EventExpectation::Require.new(
|
||||
expected_payload: {
|
||||
logical_name: 'project'
|
||||
}
|
||||
),
|
||||
MutantSpec::RubyVM::EventExpectation::Eval.new(
|
||||
expected_payload: {
|
||||
binding: TOPLEVEL_BINDING,
|
||||
source: "module Zombie\n module Project\n end\nend",
|
||||
source_location: 'a/project.rb'
|
||||
},
|
||||
trigger_requires: %w[foo bar]
|
||||
),
|
||||
MutantSpec::RubyVM::EventExpectation::Require.new(
|
||||
expected_payload: {
|
||||
logical_name: 'foo'
|
||||
},
|
||||
trigger_requires: %w[bar]
|
||||
),
|
||||
MutantSpec::RubyVM::EventExpectation::Require.new(
|
||||
expected_payload: {
|
||||
logical_name: 'bar'
|
||||
}
|
||||
),
|
||||
MutantSpec::RubyVM::EventExpectation::Eval.new(
|
||||
expected_payload: {
|
||||
binding: TOPLEVEL_BINDING,
|
||||
source: "module Zombie\n module Bar\n end\nend",
|
||||
source_location: 'b/bar.rb'
|
||||
},
|
||||
trigger_requires: %w[]
|
||||
),
|
||||
MutantSpec::RubyVM::EventExpectation::Require.new(
|
||||
expected_payload: {
|
||||
logical_name: 'bar'
|
||||
}
|
||||
)
|
||||
]
|
||||
)
|
||||
end
|
||||
|
||||
let(:require_effects) do
|
||||
{
|
||||
'project' => { requires: [] }
|
||||
}
|
||||
end
|
||||
|
||||
let(:file_entries) do
|
||||
{
|
||||
'a/project.rb' => { file: true, contents: 'module Project; end' },
|
||||
'b/bar.rb' => { file: true, contents: 'module Bar; end' }
|
||||
}
|
||||
end
|
||||
|
||||
let(:file_system) do
|
||||
MutantSpec::FileSystem.new(
|
||||
Hash[
|
||||
file_entries.map { |key, attributes| [key, MutantSpec::FileState.new(attributes)] }
|
||||
]
|
||||
)
|
||||
end
|
||||
|
||||
describe '.call' do
|
||||
def apply
|
||||
described_class.call(options)
|
||||
end
|
||||
|
||||
before do
|
||||
allow(pathname).to receive(:new, &file_system.method(:path))
|
||||
end
|
||||
|
||||
it 'returns self' do
|
||||
expect(apply).to be(described_class)
|
||||
end
|
||||
|
||||
it 'consumes walks the VM through expected steps' do
|
||||
expect { apply }.to change { ruby_vm.done? }.from(false).to(true)
|
||||
end
|
||||
|
||||
context 'when zombifier require fails' do
|
||||
let(:file_entries) do
|
||||
{}
|
||||
end
|
||||
|
||||
it 'raises zombifier specific load error' do
|
||||
expect { apply }.to raise_error(described_class::LoadError, 'Cannot find file "project.rb" in load path')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -13,15 +13,6 @@ RSpec.describe Mutant do
|
|||
it { should be(result) }
|
||||
end
|
||||
|
||||
describe '.zombify' do
|
||||
subject { object.zombify }
|
||||
|
||||
it 'calls the zombifier' do
|
||||
expect(Mutant::Zombifier).to receive(:run).with('mutant', :Zombie)
|
||||
subject
|
||||
end
|
||||
end
|
||||
|
||||
describe '.singleton_subclass_instance' do
|
||||
subject { object.singleton_subclass_instance(name, superclass, &block) }
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue