Fix zombifier to use the require highjack
This commit is contained in:
parent
7eed61c117
commit
1b2bb54852
6 changed files with 156 additions and 247 deletions
|
@ -10,7 +10,7 @@ require 'mutant'
|
||||||
namespace =
|
namespace =
|
||||||
if ARGV.include?('--zombie')
|
if ARGV.include?('--zombie')
|
||||||
$stderr.puts('Running mutant zombified!')
|
$stderr.puts('Running mutant zombified!')
|
||||||
Mutant::Zombifier.zombify
|
Mutant.zombify
|
||||||
Zombie::Mutant
|
Zombie::Mutant
|
||||||
else
|
else
|
||||||
Mutant
|
Mutant
|
||||||
|
|
|
@ -25,6 +25,18 @@ module Mutant
|
||||||
EMPTY_STRING = ''.freeze
|
EMPTY_STRING = ''.freeze
|
||||||
# The frozen empty array used within mutant
|
# The frozen empty array used within mutant
|
||||||
EMPTY_ARRAY = [].freeze
|
EMPTY_ARRAY = [].freeze
|
||||||
|
|
||||||
|
# Perform self zombification
|
||||||
|
#
|
||||||
|
# @return [self]
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def self.zombify
|
||||||
|
Zombifier.run('mutant', :Zombie)
|
||||||
|
self
|
||||||
|
end
|
||||||
|
|
||||||
end # Mutant
|
end # Mutant
|
||||||
|
|
||||||
require 'mutant/version'
|
require 'mutant/version'
|
||||||
|
@ -133,3 +145,4 @@ require 'mutant/reporter/cli/printer/subject'
|
||||||
require 'mutant/reporter/cli/printer/killer'
|
require 'mutant/reporter/cli/printer/killer'
|
||||||
require 'mutant/reporter/cli/printer/mutation'
|
require 'mutant/reporter/cli/printer/mutation'
|
||||||
require 'mutant/zombifier'
|
require 'mutant/zombifier'
|
||||||
|
require 'mutant/zombifier/file'
|
||||||
|
|
|
@ -25,8 +25,6 @@ module Mutant
|
||||||
desinfect
|
desinfect
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
# Infect kernel with highjack
|
# Infect kernel with highjack
|
||||||
#
|
#
|
||||||
# @return [self]
|
# @return [self]
|
||||||
|
|
|
@ -2,264 +2,113 @@
|
||||||
|
|
||||||
module Mutant
|
module Mutant
|
||||||
# Zombifier namespace
|
# Zombifier namespace
|
||||||
module Zombifier
|
class Zombifier
|
||||||
|
include Adamantium::Flat, Concord.new(:namespace)
|
||||||
|
|
||||||
# Excluded from zombification, reasons
|
# Excluded from zombification
|
||||||
#
|
IGNORE = [
|
||||||
# * Relies dynamic require, zombifier does not know how to recurse (racc)
|
# Unparser is not performant enough (does some backtracking!) for generated lexer.rb
|
||||||
# * Unparser bug (optparse)
|
'parser',
|
||||||
# * Toplevel reference/cbase nodes in code (rspec)
|
'parser/all',
|
||||||
# * Creates useless toplevel modules that get vendored under ::Zombie (set)
|
'parser/current',
|
||||||
#
|
# Wierd constant definitions / guards.
|
||||||
IGNORE = %w(
|
'diff/lcs',
|
||||||
set
|
'diff/lcs/hunk',
|
||||||
rspec
|
# Mix beteen constants defined in .so and .rb files
|
||||||
diff/lcs
|
# Cannot be deterministically namespaced from ruby
|
||||||
diff/lcs/hunk
|
# without dynamically recompiling openssl ;)
|
||||||
unparser
|
'openssl',
|
||||||
parser
|
# Constant propagation errors
|
||||||
parser/all
|
'thread_safe'
|
||||||
parser/current
|
].to_set.freeze
|
||||||
racc/parser
|
|
||||||
optparse
|
|
||||||
).to_set
|
|
||||||
|
|
||||||
# Perform self zombification
|
# Initialize object
|
||||||
|
#
|
||||||
|
# @param [Symbol] namespace
|
||||||
|
#
|
||||||
|
# @return [undefined]
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def initialize(namespace)
|
||||||
|
@namespace = namespace
|
||||||
|
@zombified = Set.new(IGNORE)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Perform zombification of target library
|
||||||
|
#
|
||||||
|
# @param [String] logical_name
|
||||||
|
# @param [Symbol] namespace
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def self.run(logical_name, namespace)
|
||||||
|
new(namespace).run(logical_name)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Run zombifier
|
||||||
|
#
|
||||||
|
# @param [String] logical_name
|
||||||
|
#
|
||||||
|
# @return [undefined]
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def run(logical_name)
|
||||||
|
highjack = RequireHighjack.new(Kernel, method(:require))
|
||||||
|
highjack.infect
|
||||||
|
require(logical_name)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Require file in zombie namespace
|
||||||
|
#
|
||||||
|
# @param [String] logical_name
|
||||||
#
|
#
|
||||||
# @return [self]
|
# @return [self]
|
||||||
#
|
#
|
||||||
# @api private
|
# @api private
|
||||||
#
|
#
|
||||||
def self.zombify
|
def require(logical_name)
|
||||||
run('mutant')
|
return if @zombified.include?(logical_name)
|
||||||
|
@zombified << logical_name
|
||||||
|
file = find(logical_name)
|
||||||
|
file.zombify(namespace) if file
|
||||||
self
|
self
|
||||||
end
|
end
|
||||||
|
|
||||||
# Zombify gem
|
private
|
||||||
|
|
||||||
|
# Find file without cache
|
||||||
#
|
#
|
||||||
# @param [String] name
|
# @param [String] logical_name
|
||||||
#
|
#
|
||||||
# @return [self]
|
# @return [File]
|
||||||
|
# if found
|
||||||
|
#
|
||||||
|
# @return [nil]
|
||||||
|
# otherwise
|
||||||
#
|
#
|
||||||
# @api private
|
# @api private
|
||||||
#
|
#
|
||||||
def self.run(name)
|
def find(logical_name)
|
||||||
Gem.new(name).zombify
|
file_name =
|
||||||
self
|
case File.extname(logical_name)
|
||||||
|
when '.so'
|
||||||
|
return
|
||||||
|
when '.rb'
|
||||||
|
logical_name
|
||||||
|
else
|
||||||
|
"#{logical_name}.rb"
|
||||||
|
end
|
||||||
|
|
||||||
|
$LOAD_PATH.each do |path|
|
||||||
|
path = Pathname.new(path).join(file_name)
|
||||||
|
return File.new(path) if path.file?
|
||||||
|
end
|
||||||
|
|
||||||
|
$stderr.puts "Cannot find file #{file_name} in $LOAD_PATH"
|
||||||
|
nil
|
||||||
end
|
end
|
||||||
|
|
||||||
# Zombifier subject, compatible with mutants loader
|
|
||||||
class Subject < Mutant::Subject
|
|
||||||
include NodeHelpers
|
|
||||||
|
|
||||||
# Return new object
|
|
||||||
#
|
|
||||||
# @param [File]
|
|
||||||
#
|
|
||||||
# @return [Subject]
|
|
||||||
#
|
|
||||||
# @api private
|
|
||||||
#
|
|
||||||
def self.new(file)
|
|
||||||
super(file, file.node)
|
|
||||||
end
|
|
||||||
|
|
||||||
# Perform zombification on subject
|
|
||||||
#
|
|
||||||
# @return [self]
|
|
||||||
#
|
|
||||||
# @api private
|
|
||||||
#
|
|
||||||
def zombify
|
|
||||||
$stderr.puts("Zombifying #{context.source_path}")
|
|
||||||
Loader::Eval.call(zombified_root, self)
|
|
||||||
self
|
|
||||||
end
|
|
||||||
memoize :zombify
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
# Return zombified root
|
|
||||||
#
|
|
||||||
# @return [Parser::AST::Node]
|
|
||||||
#
|
|
||||||
# @api private
|
|
||||||
#
|
|
||||||
def zombified_root
|
|
||||||
s(:module, s(:const, nil, :Zombie), node)
|
|
||||||
end
|
|
||||||
|
|
||||||
end # Subject
|
|
||||||
|
|
||||||
# File containing source beeing zombified
|
|
||||||
class File
|
|
||||||
include Adamantium::Flat, Concord::Public.new(:source_path)
|
|
||||||
|
|
||||||
CACHE = {}
|
|
||||||
|
|
||||||
# Zombify contents of file
|
|
||||||
#
|
|
||||||
# @return [self]
|
|
||||||
#
|
|
||||||
# @api private
|
|
||||||
#
|
|
||||||
def zombify
|
|
||||||
subject.zombify
|
|
||||||
required_paths.each do |path|
|
|
||||||
file = File.find(path)
|
|
||||||
next unless file
|
|
||||||
file.zombify
|
|
||||||
end
|
|
||||||
self
|
|
||||||
end
|
|
||||||
memoize :zombify
|
|
||||||
|
|
||||||
# Find file
|
|
||||||
#
|
|
||||||
# @param [String] logical_name
|
|
||||||
#
|
|
||||||
# @return [File]
|
|
||||||
# if found
|
|
||||||
#
|
|
||||||
# @raise [RuntimeError]
|
|
||||||
# if file cannot be found
|
|
||||||
#
|
|
||||||
# @api private
|
|
||||||
#
|
|
||||||
def self.find(logical_name)
|
|
||||||
return if IGNORE.include?(logical_name)
|
|
||||||
CACHE.fetch(logical_name) do
|
|
||||||
CACHE[logical_name] = find_uncached(logical_name)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Find file without cache
|
|
||||||
#
|
|
||||||
# @param [String] logical_name
|
|
||||||
#
|
|
||||||
# @return [File]
|
|
||||||
# if found
|
|
||||||
#
|
|
||||||
# @return [nil]
|
|
||||||
# otherwise
|
|
||||||
#
|
|
||||||
# @api private
|
|
||||||
#
|
|
||||||
def self.find_uncached(logical_name)
|
|
||||||
file_name =
|
|
||||||
if logical_name.end_with?('.rb')
|
|
||||||
logical_name
|
|
||||||
else
|
|
||||||
"#{logical_name}.rb"
|
|
||||||
end
|
|
||||||
|
|
||||||
$LOAD_PATH.each do |path|
|
|
||||||
path = Pathname.new(path).join(file_name)
|
|
||||||
if path.file?
|
|
||||||
return new(path)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
$stderr.puts "Cannot find file #{file_name} in $LOAD_PATH"
|
|
||||||
nil
|
|
||||||
end
|
|
||||||
|
|
||||||
# Return subject
|
|
||||||
#
|
|
||||||
# @return [Subject]
|
|
||||||
#
|
|
||||||
# @api private
|
|
||||||
#
|
|
||||||
def subject
|
|
||||||
Subject.new(self)
|
|
||||||
end
|
|
||||||
memoize :subject
|
|
||||||
|
|
||||||
# Return node
|
|
||||||
#
|
|
||||||
# @return [Parser::AST::Node]
|
|
||||||
#
|
|
||||||
# @api private
|
|
||||||
#
|
|
||||||
def node
|
|
||||||
Parser::CurrentRuby.parse(::File.read(source_path))
|
|
||||||
end
|
|
||||||
memoize :node
|
|
||||||
|
|
||||||
RECEIVER_INDEX = 0
|
|
||||||
SELECTOR_INDEX = 1
|
|
||||||
ARGUMENT_INDEX = 2..-1.freeze
|
|
||||||
|
|
||||||
# Return required paths
|
|
||||||
#
|
|
||||||
# @return [Enumerable<String>]
|
|
||||||
#
|
|
||||||
# @api private
|
|
||||||
#
|
|
||||||
def required_paths
|
|
||||||
require_nodes.map do |node|
|
|
||||||
arguments = node.children[ARGUMENT_INDEX]
|
|
||||||
unless arguments.length == 1
|
|
||||||
raise "Require node with not exactly one argument: #{node}"
|
|
||||||
end
|
|
||||||
argument = arguments.first
|
|
||||||
unless argument.type == :str
|
|
||||||
raise "Require argument is not a literal string: #{argument}"
|
|
||||||
end
|
|
||||||
argument.children.first
|
|
||||||
end
|
|
||||||
end
|
|
||||||
memoize :required_paths
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
# Return require nodes
|
|
||||||
#
|
|
||||||
# @return [Enumerable<Parser::AST::Node>]
|
|
||||||
#
|
|
||||||
# @api private
|
|
||||||
#
|
|
||||||
def require_nodes
|
|
||||||
children = node.type == :begin ? node.children : [node]
|
|
||||||
children.select do |node|
|
|
||||||
children = node.children
|
|
||||||
node.type == :send &&
|
|
||||||
children.at(RECEIVER_INDEX).nil? &&
|
|
||||||
children.at(SELECTOR_INDEX) == :require
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
end # File
|
|
||||||
|
|
||||||
# Gem beeing zombified
|
|
||||||
class Gem
|
|
||||||
include Adamantium::Flat, Concord.new(:name)
|
|
||||||
|
|
||||||
# Return subjects
|
|
||||||
#
|
|
||||||
# @return [Enumerable<Subject>]
|
|
||||||
#
|
|
||||||
# @api private
|
|
||||||
#
|
|
||||||
def zombify
|
|
||||||
root_file.zombify
|
|
||||||
end
|
|
||||||
memoize :zombify
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
# Return root souce file
|
|
||||||
#
|
|
||||||
# @return [File]
|
|
||||||
#
|
|
||||||
# @api private
|
|
||||||
#
|
|
||||||
def root_file
|
|
||||||
File.find(name) or raise 'No root file!'
|
|
||||||
end
|
|
||||||
memoize :root_file
|
|
||||||
|
|
||||||
end # Gem
|
|
||||||
|
|
||||||
end # Zombifier
|
end # Zombifier
|
||||||
end # Mutant
|
end # Mutant
|
||||||
|
|
49
lib/mutant/zombifier/file.rb
Normal file
49
lib/mutant/zombifier/file.rb
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
module Mutant
|
||||||
|
class Zombifier
|
||||||
|
# File containing source beeing zombified
|
||||||
|
class File
|
||||||
|
include NodeHelpers, Adamantium::Flat, Concord::Public.new(:path)
|
||||||
|
|
||||||
|
# Zombify contents of file
|
||||||
|
#
|
||||||
|
# @return [self]
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def zombify(namespace)
|
||||||
|
$stderr.puts("Zombifying #{path.to_s}")
|
||||||
|
eval(
|
||||||
|
Unparser.unparse(namespaced_node(namespace)),
|
||||||
|
TOPLEVEL_BINDING,
|
||||||
|
path.to_s
|
||||||
|
)
|
||||||
|
self
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
# Return node
|
||||||
|
#
|
||||||
|
# @return [Parser::AST::Node]
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
#
|
||||||
|
def node
|
||||||
|
Parser::CurrentRuby.parse(path.read)
|
||||||
|
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
|
|
@ -3,8 +3,8 @@
|
||||||
require 'spec_helper'
|
require 'spec_helper'
|
||||||
|
|
||||||
describe Mutant, 'as a zombie' do
|
describe Mutant, 'as a zombie' do
|
||||||
pending 'it allows to create zombie from mutant' do
|
specify 'it allows to create zombie from mutant' do
|
||||||
Mutant::Zombifier.run('mutant')
|
expect { Mutant.zombify }.to change { !!defined?(Zombie) }.from(false).to(true)
|
||||||
expect(Zombie.constants).to include(:Mutant)
|
expect(Zombie.constants).to include(:Mutant)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
Loading…
Add table
Reference in a new issue