-
-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Re-write, and re-namespace, a gem in a temp directory (useful for benchmarking)
- Loading branch information
Showing
6 changed files
with
287 additions
and
50 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
# Std Libs Dependencies | ||
require "tempfile" | ||
|
||
# Re-write a gem to a temp directory, re-namespace the primary namespace of that gem module, and load it. | ||
# If the original gem defines multiple top-level namespaces, they can all be renamed by providing more key value pairs. | ||
# If the original gem monkey patches other libraries, that behavior can't be isolated, so YMMV. | ||
# | ||
# NOTE: Non-top-level namespaces do not need to be renamed, as they are isolated within their parent namespace. | ||
# | ||
# Usage | ||
# | ||
# jersey = GemBench::Jersey.new( | ||
# gem_name: "alt_memery" | ||
# trades: { | ||
# "Memery" => "AltMemery" | ||
# }, | ||
# metadata: { | ||
# something: "a value here", | ||
# something_else: :obviously, | ||
# }, | ||
# ) | ||
# jersey.doff_and_don | ||
# # The re-namespaced constant is now available! | ||
# AltMemery # => AltMemery | ||
# | ||
module GemBench | ||
class Jersey | ||
attr_reader :gem_name | ||
attr_reader :gem_path | ||
attr_reader :trades | ||
attr_reader :metadata | ||
attr_reader :files | ||
|
||
def initialize(gem_name:, trades:, metadata: {}) | ||
@gem_name = gem_name | ||
@gem_path = Gem.loaded_specs[gem_name]&.full_gem_path | ||
@trades = trades | ||
@metadata = metadata | ||
end | ||
|
||
def required? | ||
gem_path && trades.values.all? { |new_namespace| Object.const_defined?(new_namespace) } | ||
end | ||
|
||
# Generates tempfiles and requires them, resulting | ||
# in a loaded gem that will not have namespace | ||
# collisions when alongside the original-namespaced gem. | ||
# If a block is provided the contents of each file will be yielded to the block, | ||
# after all namespace substitutions are complete, but before the contents | ||
# are written to the re-namespaced gem. The return value of the block will be | ||
# written to the file in this scenario. | ||
# | ||
# @return void | ||
def doff_and_don(&block) | ||
return unless gem_path | ||
|
||
Dir.mktmpdir do |directory| | ||
Dir["#{gem_path}/lib/**/*.rb"].map do |file| | ||
Tempfile.open([File.basename(file)[0..-4], ".rb"], directory) do |tempfile| | ||
new_jersey(file, tempfile, &block) | ||
end | ||
end | ||
end | ||
|
||
nil | ||
end | ||
|
||
def primary_namespace | ||
trades.values.first | ||
end | ||
|
||
# Will raise NameError if called before #doff_and_don | ||
def as_klass | ||
Object.const_get(primary_namespace) if gem_path | ||
end | ||
|
||
private | ||
|
||
def new_jersey(file, tempfile) | ||
nj = File.read(file) | ||
trades.each do |old_namespace, new_namespace| | ||
nj.gsub!(old_namespace, new_namespace) | ||
end | ||
nj = yield nj if block_given? | ||
tempfile.write(nj) | ||
tempfile.rewind | ||
require tempfile.path | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
require "gem_bench/jersey" | ||
|
||
RSpec.describe GemBench::Jersey do | ||
let(:args) do | ||
{ | ||
gem_name: gem_name, | ||
trades: trades, | ||
metadata: metadata, | ||
} | ||
end | ||
let(:gem_name) { "gem_bench" } | ||
let(:old_namespace) { "GemBench" } | ||
let(:new_namespace) { "#{Constants::ALPHABET[rand(26)]}#{Constants::ALPHABET[rand(26)].downcase}#{Constants::ALPHABET[rand(26)].downcase}#{Constants::ALPHABET[rand(26)]}#{Constants::ALPHABET[rand(26)].downcase}#{Constants::ALPHABET[rand(26)].downcase}#{Constants::ALPHABET[rand(26)]}#{Constants::ALPHABET[rand(26)].downcase}#{Constants::ALPHABET[rand(26)].downcase}" } | ||
let(:trades) { {old_namespace => new_namespace} } | ||
let(:metadata) { {} } | ||
let(:instance) { described_class.new(**args) } | ||
|
||
describe "#initialize" do | ||
subject(:init) { instance } | ||
|
||
it "does not raise error" do | ||
block_is_expected.not_to raise_error | ||
end | ||
end | ||
|
||
describe "#required?" do | ||
subject(:required) { instance.required? } | ||
|
||
it "does not raise error" do | ||
block_is_expected.not_to raise_error | ||
end | ||
|
||
it "returns false" do | ||
expect(required).to be(false) | ||
end | ||
|
||
context "when doff and donned" do | ||
before { instance.doff_and_don } | ||
|
||
it "returns true" do | ||
expect(required).to be(true) | ||
end | ||
end | ||
end | ||
|
||
describe "#doff_and_don" do | ||
subject(:doff_and_don) { instance.doff_and_don } | ||
|
||
it "does not raise error" do | ||
block_is_expected.not_to raise_error | ||
end | ||
|
||
it "returns nil" do | ||
expect(doff_and_don).to be_nil | ||
end | ||
end | ||
|
||
describe "#primary_namespace" do | ||
subject(:primary_namespace) { instance.primary_namespace } | ||
|
||
it "does not raise error" do | ||
block_is_expected.not_to raise_error | ||
end | ||
|
||
it "returns first new namespace" do | ||
expect(primary_namespace).to eq(new_namespace) | ||
end | ||
|
||
context "when multiple namespaces" do | ||
let(:trades) do | ||
{ | ||
"Blue" => "Green", | ||
old_namespace => new_namespace, | ||
} | ||
end | ||
|
||
it "returns first new namespace" do | ||
expect(primary_namespace).to eq("Green") | ||
end | ||
end | ||
end | ||
|
||
describe "#gem_path" do | ||
it "includes the gem's name" do | ||
expect(instance.gem_path).to include("gem_bench") | ||
end | ||
end | ||
|
||
describe "#as_klass" do | ||
subject(:as_klass) { instance.as_klass } | ||
|
||
context "when not doffed and donned" do | ||
it "raises error" do | ||
block_is_expected.to raise_error(NameError, "uninitialized constant #{new_namespace}") | ||
end | ||
end | ||
|
||
context "when doff and donned" do | ||
before { instance.doff_and_don } | ||
|
||
it "does not raise error" do | ||
block_is_expected.not_to raise_error | ||
end | ||
|
||
it "returns a module/class with name of new namespace" do | ||
expect(as_klass.name).to eq(new_namespace) | ||
end | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
module Constants | ||
ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" | ||
end |