Class: GemBench::Jersey

Inherits:
Object
  • Object
show all
Defined in:
lib/gem_bench/jersey.rb

Overview

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

Benchmarking Example

See: https://github.com/panorama-ed/memo_wise/blob/main/benchmarks/benchmarks.rb

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(gem_name:, trades:, metadata: {}, verbose: false) ⇒ Jersey

Returns a new instance of Jersey.



38
39
40
41
42
43
44
45
# File 'lib/gem_bench/jersey.rb', line 38

def initialize(gem_name:, trades:, metadata: {}, verbose: false)
  @gem_name = gem_name
  @gem_path = Gem.loaded_specs[gem_name]&.full_gem_path
  @gem_lib_dir = Gem
  @trades = trades
  @metadata = 
  @verbose = verbose
end

Instance Attribute Details

#filesObject (readonly)

Returns the value of attribute files.



35
36
37
# File 'lib/gem_bench/jersey.rb', line 35

def files
  @files
end

#gem_nameObject (readonly)

Returns the value of attribute gem_name.



31
32
33
# File 'lib/gem_bench/jersey.rb', line 31

def gem_name
  @gem_name
end

#gem_pathObject (readonly)

Returns the value of attribute gem_path.



32
33
34
# File 'lib/gem_bench/jersey.rb', line 32

def gem_path
  @gem_path
end

#metadataObject (readonly)

Returns the value of attribute metadata.



34
35
36
# File 'lib/gem_bench/jersey.rb', line 34

def 
  @metadata
end

#tradesObject (readonly)

Returns the value of attribute trades.



33
34
35
# File 'lib/gem_bench/jersey.rb', line 33

def trades
  @trades
end

#verboseObject (readonly)

Returns the value of attribute verbose.



36
37
38
# File 'lib/gem_bench/jersey.rb', line 36

def verbose
  @verbose
end

Instance Method Details

#as_klassClass?

Will raise NameError if called before #doff_and_don

Returns:

  • (Class, nil)


121
122
123
# File 'lib/gem_bench/jersey.rb', line 121

def as_klass
  Object.const_get(donned_primary_namespace) if gem_path
end

#doff_and_don(&block) ⇒ Object

Generates a temp directory, and creates a copy of a gem within it. Re-namespaces the copy according to the ‘trades` configuration. Then requires each file of the “copied gem”, resulting

in a loaded gem that will not have namespace
collisions when loaded alongside the original-namespaced gem.

Note that “copied gem” in the previous sentence is ambiguous without the supporting context. The “copied gem” can mean either the original, or the “copy”, which is why this gem refers to

a "doffed gem" (the original) and a "donned gem" (the copy).

If a block is provided the contents of each file will be yielded to the block,

after all namespace substitutions from `trades` 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.

Returns:

  • void



66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
# File 'lib/gem_bench/jersey.rb', line 66

def doff_and_don(&block)
  return puts "[#{gem_name}] Skipped (not loaded on #{RUBY_VERSION})" unless gem_path

  puts "[#{gem_name}] Doffing #{gem_path}" if verbose
  Dir.mktmpdir do |tmp_dir|
    files = []
    Dir[File.join(gem_path, "lib", "**", "*.rb")].map do |file|
      if verbose
        puts "[#{gem_name}] --------------------------------"
        puts "[#{gem_name}] Doffing file #{file}"
        puts "[#{gem_name}] --------------------------------"
      end
      basename = File.basename(file)
      dirname = File.dirname(file)
      puts "[#{gem_name}][#{basename}] dirname: #{dirname}" if verbose
      is_at_gem_root = dirname[(-4)..(-1)] == "/lib"
      puts "[#{gem_name}][#{basename}] is_at_gem_root: #{is_at_gem_root}" if verbose
      lib_split = dirname.split("/lib/")[-1]
      puts "[#{gem_name}][#{basename}] lib_split: #{lib_split}" if verbose
      # lib_split could be like:
      #   - "ruby/gems/3.2.0/gems/method_source-1.1.0/lib"
      #   - "method_source"
      # Se we check to make sure it is actually a subdir of the gem's lib directory
      full_path = File.join(gem_path, "lib", lib_split)
      relative_path = !is_at_gem_root && Dir.exist?(full_path) && lib_split
      puts "[#{gem_name}][#{basename}] relative_path: #{relative_path}" if verbose

      if relative_path
        dir_path = File.join(tmp_dir, relative_path)
        Dir.mkdir(dir_path) unless Dir.exist?(dir_path)
        puts "[#{gem_name}][#{basename}] copying file to #{dir_path}" if verbose
        files << create_tempfile_copy(file, dir_path, basename, :dd1, &block)
      else
        puts "[#{gem_name}][#{basename}] directory not relative (#{tmp_dir})" if verbose
        files << create_tempfile_copy(file, tmp_dir, basename, :dd2, &block)
      end
    end
    load_gem_copy(tmp_dir, files)
  end

  nil
end

#doffed_primary_namespaceString

Returns Namespace of the doffed (original) gem.

Returns:

  • (String)

    Namespace of the doffed (original) gem



110
111
112
# File 'lib/gem_bench/jersey.rb', line 110

def doffed_primary_namespace
  trades.keys.first
end

#donned_primary_namespaceString

Returns Namespace of the donned gem.

Returns:

  • (String)

    Namespace of the donned gem



115
116
117
# File 'lib/gem_bench/jersey.rb', line 115

def donned_primary_namespace
  trades.values.first
end

#required?Boolean

return [true, false] proxy for whether the copied, re-namespaced gem has been successfully loaded

Returns:

  • (Boolean)


48
49
50
# File 'lib/gem_bench/jersey.rb', line 48

def required?
  gem_path && trades.values.all? { |new_namespace| Object.const_defined?(new_namespace) }
end