2016-02-01 07:43:26 -05:00
|
|
|
# frozen_string_literal: true
|
2011-01-28 18:46:47 -05:00
|
|
|
require 'rubygems/test_case'
|
2010-02-21 21:52:35 -05:00
|
|
|
require 'rubygems/commands/push_command'
|
|
|
|
|
2011-01-28 18:46:47 -05:00
|
|
|
class TestGemCommandsPushCommand < Gem::TestCase
|
2010-02-21 21:52:35 -05:00
|
|
|
|
|
|
|
def setup
|
|
|
|
super
|
2020-04-28 17:50:02 -04:00
|
|
|
|
|
|
|
credential_setup
|
|
|
|
|
2012-11-29 01:52:18 -05:00
|
|
|
ENV["RUBYGEMS_HOST"] = nil
|
|
|
|
Gem.host = Gem::DEFAULT_HOST
|
|
|
|
Gem.configuration.disable_default_gem_server = false
|
2010-02-21 21:52:35 -05:00
|
|
|
|
2011-03-01 04:41:32 -05:00
|
|
|
@gems_dir = File.join @tempdir, 'gems'
|
2011-05-31 23:45:05 -04:00
|
|
|
@cache_dir = File.join @gemhome, "cache"
|
2011-03-01 04:41:32 -05:00
|
|
|
|
2010-02-21 21:52:35 -05:00
|
|
|
FileUtils.mkdir @gems_dir
|
2011-03-01 04:41:32 -05:00
|
|
|
|
|
|
|
Gem.configuration.rubygems_api_key =
|
|
|
|
"ed244fbf2b1a52e012da8616c512fa47f9aa5250"
|
|
|
|
|
|
|
|
@spec, @path = util_gem "freewill", "1.0.0"
|
2013-02-25 02:30:41 -05:00
|
|
|
@host = 'https://rubygems.example'
|
2012-11-29 01:52:18 -05:00
|
|
|
@api_key = Gem.configuration.rubygems_api_key
|
2010-02-21 21:52:35 -05:00
|
|
|
|
|
|
|
@fetcher = Gem::FakeFetcher.new
|
|
|
|
Gem::RemoteFetcher.fetcher = @fetcher
|
|
|
|
|
|
|
|
@cmd = Gem::Commands::PushCommand.new
|
2012-12-22 19:35:09 -05:00
|
|
|
|
2019-08-22 11:32:47 -04:00
|
|
|
singleton_gem_class.class_eval do
|
2012-12-22 19:35:09 -05:00
|
|
|
alias_method :orig_latest_rubygems_version, :latest_rubygems_version
|
|
|
|
|
|
|
|
def latest_rubygems_version
|
|
|
|
Gem.rubygems_version
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def teardown
|
|
|
|
super
|
|
|
|
|
2019-08-22 11:32:47 -04:00
|
|
|
singleton_gem_class.class_eval do
|
2012-12-22 19:35:09 -05:00
|
|
|
remove_method :latest_rubygems_version
|
|
|
|
alias_method :latest_rubygems_version, :orig_latest_rubygems_version
|
|
|
|
end
|
2010-02-21 21:52:35 -05:00
|
|
|
end
|
|
|
|
|
2011-01-18 19:08:49 -05:00
|
|
|
def send_battery
|
2010-02-21 21:52:35 -05:00
|
|
|
use_ui @ui do
|
2013-02-04 21:37:35 -05:00
|
|
|
@cmd.instance_variable_set :@host, @host
|
2010-02-21 21:52:35 -05:00
|
|
|
@cmd.send_gem(@path)
|
|
|
|
end
|
|
|
|
|
2012-11-29 01:52:18 -05:00
|
|
|
assert_match %r{Pushing gem to #{@host}...}, @ui.output
|
2010-02-21 21:52:35 -05:00
|
|
|
|
|
|
|
assert_equal Net::HTTP::Post, @fetcher.last_request.class
|
|
|
|
assert_equal Gem.read_binary(@path), @fetcher.last_request.body
|
|
|
|
assert_equal File.size(@path), @fetcher.last_request["Content-Length"].to_i
|
|
|
|
assert_equal "application/octet-stream", @fetcher.last_request["Content-Type"]
|
2012-11-29 01:52:18 -05:00
|
|
|
assert_equal @api_key, @fetcher.last_request["Authorization"]
|
2010-02-21 21:52:35 -05:00
|
|
|
|
2011-01-18 19:08:49 -05:00
|
|
|
assert_match @response, @ui.output
|
|
|
|
end
|
|
|
|
|
2013-07-08 18:41:03 -04:00
|
|
|
def test_execute
|
|
|
|
@response = "Successfully registered gem: freewill (1.0.0)"
|
|
|
|
@fetcher.data["#{Gem.host}/api/v1/gems"] = [@response, 200, 'OK']
|
|
|
|
|
2013-09-18 17:29:41 -04:00
|
|
|
@cmd.options[:args] = [@path]
|
2013-07-08 18:41:03 -04:00
|
|
|
|
|
|
|
@cmd.execute
|
|
|
|
|
|
|
|
assert_equal Net::HTTP::Post, @fetcher.last_request.class
|
2013-09-18 17:29:41 -04:00
|
|
|
assert_equal Gem.read_binary(@path), @fetcher.last_request.body
|
2013-07-08 18:41:03 -04:00
|
|
|
assert_equal "application/octet-stream",
|
|
|
|
@fetcher.last_request["Content-Type"]
|
|
|
|
end
|
|
|
|
|
2013-02-25 02:30:41 -05:00
|
|
|
def test_execute_host
|
|
|
|
host = 'https://other.example'
|
|
|
|
|
|
|
|
@response = "Successfully registered gem: freewill (1.0.0)"
|
|
|
|
@fetcher.data["#{host}/api/v1/gems"] = [@response, 200, 'OK']
|
|
|
|
@fetcher.data["#{Gem.host}/api/v1/gems"] =
|
|
|
|
['fail', 500, 'Internal Server Error']
|
|
|
|
|
|
|
|
@cmd.options[:host] = host
|
2013-09-18 17:29:41 -04:00
|
|
|
@cmd.options[:args] = [@path]
|
2013-02-25 02:30:41 -05:00
|
|
|
|
|
|
|
@cmd.execute
|
|
|
|
|
|
|
|
assert_equal Net::HTTP::Post, @fetcher.last_request.class
|
2013-09-18 17:29:41 -04:00
|
|
|
assert_equal Gem.read_binary(@path), @fetcher.last_request.body
|
2013-02-25 02:30:41 -05:00
|
|
|
assert_equal "application/octet-stream",
|
|
|
|
@fetcher.last_request["Content-Type"]
|
|
|
|
end
|
|
|
|
|
2018-08-27 06:05:04 -04:00
|
|
|
def test_execute_allowed_push_host
|
|
|
|
@spec, @path = util_gem "freebird", "1.0.1" do |spec|
|
|
|
|
spec.metadata['allowed_push_host'] = "https://privategemserver.example"
|
|
|
|
end
|
|
|
|
|
|
|
|
@response = "Successfully registered gem: freewill (1.0.0)"
|
|
|
|
@fetcher.data["#{@spec.metadata['allowed_push_host']}/api/v1/gems"] = [@response, 200, 'OK']
|
|
|
|
@fetcher.data["#{Gem.host}/api/v1/gems"] =
|
|
|
|
['fail', 500, 'Internal Server Error']
|
|
|
|
|
|
|
|
@cmd.options[:args] = [@path]
|
|
|
|
|
|
|
|
@cmd.execute
|
|
|
|
|
|
|
|
assert_equal Net::HTTP::Post, @fetcher.last_request.class
|
|
|
|
assert_equal Gem.read_binary(@path), @fetcher.last_request.body
|
|
|
|
assert_equal "application/octet-stream",
|
|
|
|
@fetcher.last_request["Content-Type"]
|
|
|
|
end
|
|
|
|
|
2012-11-29 01:52:18 -05:00
|
|
|
def test_sending_when_default_host_disabled
|
|
|
|
Gem.configuration.disable_default_gem_server = true
|
|
|
|
response = "You must specify a gem server"
|
|
|
|
|
|
|
|
assert_raises Gem::MockGemUi::TermError do
|
|
|
|
use_ui @ui do
|
|
|
|
@cmd.send_gem(@path)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
assert_match response, @ui.error
|
|
|
|
end
|
|
|
|
|
|
|
|
def test_sending_when_default_host_disabled_with_override
|
|
|
|
ENV["RUBYGEMS_HOST"] = @host
|
|
|
|
Gem.configuration.disable_default_gem_server = true
|
|
|
|
@response = "Successfully registered gem: freewill (1.0.0)"
|
2019-02-14 07:59:03 -05:00
|
|
|
@fetcher.data["#{@host}/api/v1/gems"] = [@response, 200, 'OK']
|
2012-11-29 01:52:18 -05:00
|
|
|
|
|
|
|
send_battery
|
|
|
|
end
|
|
|
|
|
|
|
|
def test_sending_gem_to_metadata_host
|
2016-03-03 19:29:40 -05:00
|
|
|
@host = "http://privategemserver.example"
|
2012-11-29 01:52:18 -05:00
|
|
|
|
|
|
|
@spec, @path = util_gem "freebird", "1.0.1" do |spec|
|
|
|
|
spec.metadata['default_gem_server'] = @host
|
|
|
|
end
|
|
|
|
|
|
|
|
@api_key = "EYKEY"
|
|
|
|
|
|
|
|
keys = {
|
|
|
|
:rubygems_api_key => 'KEY',
|
|
|
|
@host => @api_key
|
|
|
|
}
|
|
|
|
|
|
|
|
FileUtils.mkdir_p File.dirname Gem.configuration.credentials_path
|
2018-02-05 21:58:35 -05:00
|
|
|
File.open Gem.configuration.credentials_path, 'w' do |f|
|
2012-11-29 01:52:18 -05:00
|
|
|
f.write keys.to_yaml
|
|
|
|
end
|
|
|
|
Gem.configuration.load_api_keys
|
|
|
|
|
|
|
|
FileUtils.rm Gem.configuration.credentials_path
|
|
|
|
|
|
|
|
@response = "Successfully registered gem: freebird (1.0.1)"
|
2019-02-14 07:59:03 -05:00
|
|
|
@fetcher.data["#{@host}/api/v1/gems"] = [@response, 200, 'OK']
|
2018-12-01 06:01:00 -05:00
|
|
|
|
2012-11-29 01:52:18 -05:00
|
|
|
send_battery
|
|
|
|
end
|
|
|
|
|
2013-02-25 02:30:41 -05:00
|
|
|
def test_sending_gem
|
2011-01-18 19:08:49 -05:00
|
|
|
@response = "Successfully registered gem: freewill (1.0.0)"
|
2019-02-14 07:59:03 -05:00
|
|
|
@fetcher.data["#{@host}/api/v1/gems"] = [@response, 200, 'OK']
|
2011-01-18 19:08:49 -05:00
|
|
|
|
|
|
|
send_battery
|
|
|
|
end
|
|
|
|
|
2013-09-18 17:29:41 -04:00
|
|
|
def test_sending_gem_to_allowed_push_host
|
2016-03-03 19:29:40 -05:00
|
|
|
@host = "http://privategemserver.example"
|
2013-09-18 17:29:41 -04:00
|
|
|
|
|
|
|
@spec, @path = util_gem "freebird", "1.0.1" do |spec|
|
|
|
|
spec.metadata['allowed_push_host'] = @host
|
|
|
|
end
|
|
|
|
|
|
|
|
@api_key = "PRIVKEY"
|
|
|
|
|
|
|
|
keys = {
|
|
|
|
:rubygems_api_key => 'KEY',
|
|
|
|
@host => @api_key
|
|
|
|
}
|
|
|
|
|
|
|
|
FileUtils.mkdir_p File.dirname Gem.configuration.credentials_path
|
2018-02-05 21:58:35 -05:00
|
|
|
File.open Gem.configuration.credentials_path, 'w' do |f|
|
2013-09-18 17:29:41 -04:00
|
|
|
f.write keys.to_yaml
|
|
|
|
end
|
|
|
|
Gem.configuration.load_api_keys
|
|
|
|
|
|
|
|
FileUtils.rm Gem.configuration.credentials_path
|
|
|
|
|
|
|
|
@response = "Successfully registered gem: freebird (1.0.1)"
|
2019-02-14 07:59:03 -05:00
|
|
|
@fetcher.data["#{@host}/api/v1/gems"] = [@response, 200, 'OK']
|
2013-09-18 17:29:41 -04:00
|
|
|
send_battery
|
|
|
|
end
|
|
|
|
|
2019-01-22 01:28:04 -05:00
|
|
|
def test_sending_gem_with_env_var_api_key
|
|
|
|
@host = "http://privategemserver.example"
|
|
|
|
|
|
|
|
@spec, @path = util_gem "freebird", "1.0.1" do |spec|
|
|
|
|
spec.metadata['allowed_push_host'] = @host
|
|
|
|
end
|
|
|
|
|
|
|
|
@api_key = "PRIVKEY"
|
|
|
|
ENV["GEM_HOST_API_KEY"] = "PRIVKEY"
|
|
|
|
|
|
|
|
@response = "Successfully registered gem: freebird (1.0.1)"
|
2019-02-14 07:59:03 -05:00
|
|
|
@fetcher.data["#{@host}/api/v1/gems"] = [@response, 200, 'OK']
|
2019-01-22 01:28:04 -05:00
|
|
|
send_battery
|
|
|
|
end
|
|
|
|
|
2016-02-01 07:43:26 -05:00
|
|
|
def test_sending_gem_to_allowed_push_host_with_basic_credentials
|
2016-03-03 19:29:40 -05:00
|
|
|
@sanitized_host = "http://privategemserver.example"
|
|
|
|
@host = "http://user:password@privategemserver.example"
|
2016-02-01 07:43:26 -05:00
|
|
|
|
|
|
|
@spec, @path = util_gem "freebird", "1.0.1" do |spec|
|
|
|
|
spec.metadata['allowed_push_host'] = @sanitized_host
|
|
|
|
end
|
|
|
|
|
|
|
|
@api_key = "DOESNTMATTER"
|
|
|
|
|
|
|
|
keys = {
|
|
|
|
:rubygems_api_key => @api_key,
|
|
|
|
}
|
|
|
|
|
|
|
|
FileUtils.mkdir_p File.dirname Gem.configuration.credentials_path
|
2018-02-05 21:58:35 -05:00
|
|
|
File.open Gem.configuration.credentials_path, 'w' do |f|
|
2016-02-01 07:43:26 -05:00
|
|
|
f.write keys.to_yaml
|
|
|
|
end
|
|
|
|
Gem.configuration.load_api_keys
|
|
|
|
|
|
|
|
FileUtils.rm Gem.configuration.credentials_path
|
|
|
|
|
|
|
|
@response = "Successfully registered gem: freebird (1.0.1)"
|
2019-02-14 07:59:03 -05:00
|
|
|
@fetcher.data["#{@host}/api/v1/gems"] = [@response, 200, 'OK']
|
2016-02-01 07:43:26 -05:00
|
|
|
send_battery
|
|
|
|
end
|
|
|
|
|
2013-09-18 17:29:41 -04:00
|
|
|
def test_sending_gem_to_disallowed_default_host
|
|
|
|
@spec, @path = util_gem "freebird", "1.0.1" do |spec|
|
2016-03-03 19:29:40 -05:00
|
|
|
spec.metadata['allowed_push_host'] = "https://privategemserver.example"
|
2013-09-18 17:29:41 -04:00
|
|
|
end
|
|
|
|
|
2020-03-24 14:51:43 -04:00
|
|
|
response = %(ERROR: "#{@host}" is not allowed by the gemspec, which only allows "https://privategemserver.example")
|
2013-09-18 17:29:41 -04:00
|
|
|
|
|
|
|
assert_raises Gem::MockGemUi::TermError do
|
|
|
|
send_battery
|
|
|
|
end
|
|
|
|
|
|
|
|
assert_match response, @ui.error
|
|
|
|
end
|
|
|
|
|
|
|
|
def test_sending_gem_to_disallowed_push_host
|
2016-03-03 19:29:40 -05:00
|
|
|
@host = "https://anotherprivategemserver.example"
|
|
|
|
push_host = "https://privategemserver.example"
|
2013-09-18 17:29:41 -04:00
|
|
|
|
|
|
|
@spec, @path = util_gem "freebird", "1.0.1" do |spec|
|
2016-03-03 19:29:40 -05:00
|
|
|
spec.metadata['allowed_push_host'] = push_host
|
2013-09-18 17:29:41 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
@api_key = "PRIVKEY"
|
|
|
|
|
|
|
|
keys = {
|
|
|
|
:rubygems_api_key => 'KEY',
|
|
|
|
@host => @api_key
|
|
|
|
}
|
|
|
|
|
|
|
|
FileUtils.mkdir_p File.dirname Gem.configuration.credentials_path
|
2018-02-05 21:58:35 -05:00
|
|
|
File.open Gem.configuration.credentials_path, 'w' do |f|
|
2013-09-18 17:29:41 -04:00
|
|
|
f.write keys.to_yaml
|
|
|
|
end
|
|
|
|
Gem.configuration.load_api_keys
|
|
|
|
|
|
|
|
FileUtils.rm Gem.configuration.credentials_path
|
|
|
|
|
2016-03-03 19:29:40 -05:00
|
|
|
response = "ERROR: \"#{@host}\" is not allowed by the gemspec, which only allows \"#{push_host}\""
|
2013-09-18 17:29:41 -04:00
|
|
|
|
|
|
|
assert_raises Gem::MockGemUi::TermError do
|
|
|
|
send_battery
|
|
|
|
end
|
|
|
|
|
|
|
|
assert_match response, @ui.error
|
|
|
|
end
|
|
|
|
|
2016-03-03 19:29:40 -05:00
|
|
|
def test_sending_gem_defaulting_to_allowed_push_host
|
|
|
|
host = "http://privategemserver.example"
|
|
|
|
|
|
|
|
@spec, @path = util_gem "freebird", "1.0.1" do |spec|
|
|
|
|
spec.metadata.delete('default_gem_server')
|
|
|
|
spec.metadata['allowed_push_host'] = host
|
|
|
|
end
|
|
|
|
|
|
|
|
api_key = "PRIVKEY"
|
|
|
|
|
|
|
|
keys = {
|
|
|
|
host => api_key
|
|
|
|
}
|
|
|
|
|
|
|
|
FileUtils.mkdir_p File.dirname Gem.configuration.credentials_path
|
2018-02-05 21:58:35 -05:00
|
|
|
File.open Gem.configuration.credentials_path, 'w' do |f|
|
2016-03-03 19:29:40 -05:00
|
|
|
f.write keys.to_yaml
|
|
|
|
end
|
|
|
|
Gem.configuration.load_api_keys
|
|
|
|
|
|
|
|
FileUtils.rm Gem.configuration.credentials_path
|
|
|
|
|
|
|
|
@response = "Successfully registered gem: freebird (1.0.1)"
|
2019-02-14 07:59:03 -05:00
|
|
|
@fetcher.data["#{host}/api/v1/gems"] = [@response, 200, 'OK']
|
2016-03-03 19:29:40 -05:00
|
|
|
|
|
|
|
# do not set @host
|
|
|
|
use_ui(@ui) { @cmd.send_gem(@path) }
|
|
|
|
|
|
|
|
assert_match %r{Pushing gem to #{host}...}, @ui.output
|
|
|
|
|
|
|
|
assert_equal Net::HTTP::Post, @fetcher.last_request.class
|
|
|
|
assert_equal Gem.read_binary(@path), @fetcher.last_request.body
|
|
|
|
assert_equal File.size(@path), @fetcher.last_request["Content-Length"].to_i
|
|
|
|
assert_equal "application/octet-stream", @fetcher.last_request["Content-Type"]
|
|
|
|
assert_equal api_key, @fetcher.last_request["Authorization"]
|
|
|
|
|
|
|
|
assert_match @response, @ui.output
|
|
|
|
end
|
|
|
|
|
2010-02-21 21:52:35 -05:00
|
|
|
def test_raises_error_with_no_arguments
|
2013-02-04 21:37:35 -05:00
|
|
|
def @cmd.sign_in(*); end
|
2010-02-21 21:52:35 -05:00
|
|
|
assert_raises Gem::CommandLineError do
|
|
|
|
@cmd.execute
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def test_sending_gem_denied
|
|
|
|
response = "You don't have permission to push to this gem"
|
2012-11-29 01:52:18 -05:00
|
|
|
@fetcher.data["#{@host}/api/v1/gems"] = [response, 403, 'Forbidden']
|
2013-02-25 02:30:41 -05:00
|
|
|
@cmd.instance_variable_set :@host, @host
|
2010-02-21 21:52:35 -05:00
|
|
|
|
2011-01-28 18:46:47 -05:00
|
|
|
assert_raises Gem::MockGemUi::TermError do
|
2010-02-21 21:52:35 -05:00
|
|
|
use_ui @ui do
|
|
|
|
@cmd.send_gem(@path)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
assert_match response, @ui.output
|
|
|
|
end
|
|
|
|
|
2011-03-01 04:41:32 -05:00
|
|
|
def test_sending_gem_key
|
|
|
|
@response = "Successfully registered gem: freewill (1.0.0)"
|
2012-11-29 01:52:18 -05:00
|
|
|
@fetcher.data["#{@host}/api/v1/gems"] = [@response, 200, "OK"]
|
2011-03-01 04:41:32 -05:00
|
|
|
File.open Gem.configuration.credentials_path, 'a' do |f|
|
|
|
|
f.write ':other: 701229f217cdf23b1344c7b4b54ca97'
|
|
|
|
end
|
|
|
|
Gem.configuration.load_api_keys
|
|
|
|
|
2020-03-24 14:51:43 -04:00
|
|
|
@cmd.handle_options %w[-k other]
|
2013-02-25 02:30:41 -05:00
|
|
|
@cmd.instance_variable_set :@host, @host
|
2011-03-01 04:41:32 -05:00
|
|
|
@cmd.send_gem(@path)
|
2010-02-21 21:52:35 -05:00
|
|
|
|
2011-03-01 04:41:32 -05:00
|
|
|
assert_equal Gem.configuration.api_keys[:other],
|
|
|
|
@fetcher.last_request["Authorization"]
|
|
|
|
end
|
|
|
|
|
2018-12-01 06:01:00 -05:00
|
|
|
def test_otp_verified_success
|
|
|
|
response_fail = "You have enabled multifactor authentication but your request doesn't have the correct OTP code. Please check it and retry."
|
|
|
|
response_success = 'Successfully registered gem: freewill (1.0.0)'
|
|
|
|
|
2019-03-04 22:32:58 -05:00
|
|
|
@fetcher.data["#{Gem.host}/api/v1/gems"] = [
|
|
|
|
[response_fail, 401, 'Unauthorized'],
|
|
|
|
[response_success, 200, 'OK']
|
|
|
|
]
|
2018-12-01 06:01:00 -05:00
|
|
|
|
|
|
|
@otp_ui = Gem::MockGemUi.new "111111\n"
|
|
|
|
use_ui @otp_ui do
|
|
|
|
@cmd.send_gem(@path)
|
|
|
|
end
|
|
|
|
|
|
|
|
assert_match 'You have enabled multi-factor authentication. Please enter OTP code.', @otp_ui.output
|
|
|
|
assert_match 'Code: ', @otp_ui.output
|
|
|
|
assert_match response_success, @otp_ui.output
|
|
|
|
assert_equal '111111', @fetcher.last_request['OTP']
|
|
|
|
end
|
|
|
|
|
|
|
|
def test_otp_verified_failure
|
|
|
|
response = "You have enabled multifactor authentication but your request doesn't have the correct OTP code. Please check it and retry."
|
|
|
|
@fetcher.data["#{Gem.host}/api/v1/gems"] = [response, 401, 'Unauthorized']
|
|
|
|
|
|
|
|
@otp_ui = Gem::MockGemUi.new "111111\n"
|
|
|
|
assert_raises Gem::MockGemUi::TermError do
|
|
|
|
use_ui @otp_ui do
|
|
|
|
@cmd.send_gem(@path)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
assert_match response, @otp_ui.output
|
|
|
|
assert_match 'You have enabled multi-factor authentication. Please enter OTP code.', @otp_ui.output
|
|
|
|
assert_match 'Code: ', @otp_ui.output
|
|
|
|
assert_equal '111111', @fetcher.last_request['OTP']
|
|
|
|
end
|
|
|
|
|
2019-08-22 11:32:47 -04:00
|
|
|
private
|
|
|
|
|
|
|
|
def singleton_gem_class
|
|
|
|
class << Gem; self; end
|
|
|
|
end
|
|
|
|
|
2011-03-01 04:41:32 -05:00
|
|
|
end
|