mirror of
https://github.com/ruby/ruby.git
synced 2026-01-26 20:19:19 +00:00
- Fix https://github.com/ruby/rubygems/issues/9238 - ### Problem This is an issue that bites gem maintainers from time to time, with the most recent one in https://github.com/minitest/minitest/issues/1040#issuecomment-3679370619 The issue is summarized as follow: 1) A gem "X" has a feature in "lib/feature.rb" 2) Maintainer wants to extract this feature into its own gem "Y" 3) Maintainer cut a release of X without that new feature. 4) Users install the new version of X and also install the new gem "Y" since the feature is now extracted. 5) When a call to "require 'feature'" is encountered, RG will fail to load the right gem, resulting in a `LoadError`. ### Details Now that we have two gems (old version of X and new gem Y) with the same path, RubyGems will detect that `feature.rb` can be loaded from the old version of X, but if the new version of X had already been loaded, then RubyGems will raise due to versions conflicting. ```ruby require 'x' # Loads the new version of X without the feature which was extracted. require 'feature' # Rubygems see that the old version of X include that file and tries to activate the spec. ``` ### Solution I propose that RubyGems fallback to a spec that's not yet loaded. We try to find a spec by its path and filter it out in case a spec with the same name has already been loaded. Its worth to note that RubyGems already has a `find_inactive_by_path` but we can't use it. This method only checks if the spec object is active and doesn't look if other spec with the same name have been loaded. The new method we are introducing verifies this. https://github.com/ruby/rubygems/commit/f298e2c68e
853 lines
27 KiB
Ruby
853 lines
27 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
require_relative "helper"
|
|
require "rubygems"
|
|
|
|
class TestGemRequire < Gem::TestCase
|
|
class Latch
|
|
def initialize(count = 1)
|
|
@count = count
|
|
@lock = Monitor.new
|
|
@cv = @lock.new_cond
|
|
end
|
|
|
|
def release
|
|
@lock.synchronize do
|
|
@count -= 1 if @count > 0
|
|
@cv.broadcast if @count.zero?
|
|
end
|
|
end
|
|
|
|
def await
|
|
@lock.synchronize do
|
|
@cv.wait_while { @count > 0 }
|
|
end
|
|
end
|
|
end
|
|
|
|
def assert_require(path)
|
|
assert require(path), "'#{path}' was already required"
|
|
end
|
|
|
|
def refute_require(path)
|
|
refute require(path), "'#{path}' was not yet required"
|
|
end
|
|
|
|
def test_respect_loaded_features_caching_like_standard_require
|
|
dir = Dir.mktmpdir("test_require", @tempdir)
|
|
|
|
lp1 = File.join dir, "foo1"
|
|
foo1 = File.join lp1, "foo.rb"
|
|
|
|
FileUtils.mkdir_p lp1
|
|
File.open(foo1, "w") {|f| f.write "class Object; HELLO = 'foo1' end" }
|
|
|
|
lp = $LOAD_PATH.dup
|
|
|
|
$LOAD_PATH.unshift lp1
|
|
assert_require "foo"
|
|
assert_equal "foo1", ::Object::HELLO
|
|
|
|
lp2 = File.join dir, "foo2"
|
|
foo2 = File.join lp2, "foo.rb"
|
|
|
|
FileUtils.mkdir_p lp2
|
|
File.open(foo2, "w") {|f| f.write "class Object; HELLO = 'foo2' end" }
|
|
|
|
$LOAD_PATH.unshift lp2
|
|
refute_require "foo"
|
|
assert_equal "foo1", ::Object::HELLO
|
|
ensure
|
|
$LOAD_PATH.replace lp
|
|
Object.send :remove_const, :HELLO if Object.const_defined? :HELLO
|
|
end
|
|
|
|
# Providing -I on the commandline should always beat gems
|
|
def test_dash_i_beats_gems
|
|
a1 = util_spec "a", "1", { "b" => "= 1" }, "lib/test_gem_require_a.rb"
|
|
b1 = util_spec "b", "1", { "c" => "> 0" }, "lib/b/c.rb"
|
|
c1 = util_spec "c", "1", nil, "lib/c/c.rb"
|
|
c2 = util_spec "c", "2", nil, "lib/c/c.rb"
|
|
|
|
install_specs c1, c2, b1, a1
|
|
|
|
dir = Dir.mktmpdir("test_require", @tempdir)
|
|
dash_i_arg = File.join dir, "lib"
|
|
|
|
c_rb = File.join dash_i_arg, "b", "c.rb"
|
|
|
|
FileUtils.mkdir_p File.dirname c_rb
|
|
File.open(c_rb, "w") {|f| f.write "class Object; HELLO = 'world' end" }
|
|
|
|
# Pretend to provide a commandline argument that overrides a file in gem b
|
|
$LOAD_PATH.unshift dash_i_arg
|
|
|
|
assert_require "test_gem_require_a"
|
|
assert_require "b/c" # this should be required from -I
|
|
assert_equal "world", ::Object::HELLO
|
|
assert_equal %w[a-1 b-1], loaded_spec_names
|
|
ensure
|
|
Object.send :remove_const, :HELLO if Object.const_defined? :HELLO
|
|
end
|
|
|
|
def create_sync_thread
|
|
Thread.new do
|
|
yield
|
|
ensure
|
|
FILE_ENTERED_LATCH.release
|
|
FILE_EXIT_LATCH.await
|
|
end
|
|
end
|
|
|
|
# Providing -I on the commandline should always beat gems
|
|
def test_dash_i_beats_default_gems
|
|
a1 = new_default_spec "a", "1", { "b" => "= 1" }, "test_gem_require_a.rb"
|
|
b1 = new_default_spec "b", "1", { "c" => "> 0" }, "b/c.rb"
|
|
c1 = new_default_spec "c", "1", nil, "c/c.rb"
|
|
c2 = new_default_spec "c", "2", nil, "c/c.rb"
|
|
|
|
install_default_gems c1, c2, b1, a1
|
|
|
|
dir = Dir.mktmpdir("test_require", @tempdir)
|
|
dash_i_arg = File.join dir, "lib"
|
|
|
|
c_rb = File.join dash_i_arg, "c", "c.rb"
|
|
|
|
FileUtils.mkdir_p File.dirname c_rb
|
|
File.open(c_rb, "w") {|f| f.write "class Object; HELLO = 'world' end" }
|
|
|
|
assert_require "test_gem_require_a"
|
|
|
|
# Pretend to provide a commandline argument that overrides a file in gem b
|
|
$LOAD_PATH.unshift dash_i_arg
|
|
|
|
assert_require "b/c"
|
|
assert_require "c/c" # this should be required from -I
|
|
assert_equal "world", ::Object::HELLO
|
|
assert_equal %w[a-1 b-1], loaded_spec_names
|
|
ensure
|
|
Object.send :remove_const, :HELLO if Object.const_defined? :HELLO
|
|
end
|
|
|
|
def test_dash_i_respects_default_library_extension_priority
|
|
pend "needs investigation" if Gem.java_platform?
|
|
pend "not installed yet" unless RbConfig::TOPDIR
|
|
|
|
dash_i_ext_arg = util_install_extension_file("a")
|
|
dash_i_lib_arg = util_install_ruby_file("a")
|
|
|
|
$LOAD_PATH.unshift dash_i_lib_arg
|
|
$LOAD_PATH.unshift dash_i_ext_arg
|
|
assert_require "a"
|
|
assert_match(/a\.rb$/, $LOADED_FEATURES.last)
|
|
end
|
|
|
|
def test_concurrent_require
|
|
Object.const_set :FILE_ENTERED_LATCH, Latch.new(2)
|
|
Object.const_set :FILE_EXIT_LATCH, Latch.new(1)
|
|
|
|
a1 = util_spec "a#{$$}", "1", nil, "lib/a#{$$}.rb"
|
|
b1 = util_spec "b#{$$}", "1", nil, "lib/b#{$$}.rb"
|
|
|
|
install_specs a1, b1
|
|
|
|
t1 = create_sync_thread { assert_require "a#{$$}" }
|
|
t2 = create_sync_thread { assert_require "b#{$$}" }
|
|
|
|
# wait until both files are waiting on the exit latch
|
|
FILE_ENTERED_LATCH.await
|
|
|
|
# now let them finish
|
|
FILE_EXIT_LATCH.release
|
|
|
|
assert t1.join, "thread 1 should exit"
|
|
assert t2.join, "thread 2 should exit"
|
|
ensure
|
|
Object.send :remove_const, :FILE_ENTERED_LATCH if Object.const_defined? :FILE_ENTERED_LATCH
|
|
Object.send :remove_const, :FILE_EXIT_LATCH if Object.const_defined? :FILE_EXIT_LATCH
|
|
end
|
|
|
|
def test_require_is_not_lazy_with_exact_req
|
|
a1 = util_spec "a", "1", { "b" => "= 1" }, "lib/test_gem_require_a.rb"
|
|
b1 = util_spec "b", "1", nil, "lib/b/c.rb"
|
|
b2 = util_spec "b", "2", nil, "lib/b/c.rb"
|
|
|
|
install_specs b1, b2, a1
|
|
|
|
assert_require "test_gem_require_a"
|
|
assert_equal %w[a-1 b-1], loaded_spec_names
|
|
assert_equal unresolved_names, []
|
|
|
|
assert_require "b/c"
|
|
assert_equal %w[a-1 b-1], loaded_spec_names
|
|
end
|
|
|
|
def test_require_is_not_lazy_with_shadowed_default_gem
|
|
b1_default = new_default_spec("b", "1", nil, "foo.rb")
|
|
install_default_gems b1_default
|
|
|
|
a1 = util_spec "a", "1", { "b" => ">= 1" }, "lib/test_gem_require_a.rb"
|
|
b1 = util_spec("b", "1", nil, "lib/foo.rb")
|
|
install_specs b1, a1
|
|
|
|
# Load default ruby gems fresh as if we've just started a ruby script.
|
|
Gem::Specification.reset
|
|
|
|
assert_require "test_gem_require_a"
|
|
assert_equal %w[a-1 b-1], loaded_spec_names
|
|
assert_equal unresolved_names, []
|
|
end
|
|
|
|
def test_require_is_lazy_with_inexact_req
|
|
a1 = util_spec "a", "1", { "b" => ">= 1" }, "lib/test_gem_require_a.rb"
|
|
b1 = util_spec "b", "1", nil, "lib/b/c.rb"
|
|
b2 = util_spec "b", "2", nil, "lib/b/c.rb"
|
|
|
|
install_specs b1, b2, a1
|
|
|
|
assert_require "test_gem_require_a"
|
|
assert_equal %w[a-1], loaded_spec_names
|
|
assert_equal unresolved_names, ["b (>= 1)"]
|
|
|
|
assert_require "b/c"
|
|
assert_equal %w[a-1 b-2], loaded_spec_names
|
|
end
|
|
|
|
def test_require_is_not_lazy_with_one_possible
|
|
a1 = util_spec "a", "1", { "b" => ">= 1" }, "lib/test_gem_require_a.rb"
|
|
b1 = util_spec "b", "1", nil, "lib/b/c.rb"
|
|
|
|
install_specs b1, a1
|
|
|
|
assert_require "test_gem_require_a"
|
|
assert_equal %w[a-1 b-1], loaded_spec_names
|
|
assert_equal unresolved_names, []
|
|
|
|
assert_require "b/c"
|
|
assert_equal %w[a-1 b-1], loaded_spec_names
|
|
end
|
|
|
|
def test_require_can_use_a_pathname_object
|
|
a1 = util_spec "a", "1", nil, "lib/test_gem_require_a.rb"
|
|
|
|
install_specs a1
|
|
|
|
assert_require Pathname.new "test_gem_require_a"
|
|
assert_equal %w[a-1], loaded_spec_names
|
|
assert_equal unresolved_names, []
|
|
end
|
|
|
|
def test_activate_via_require_respects_loaded_files
|
|
pend "Not sure what's going on. If another spec creates a 'a' gem before
|
|
this test, somehow require will load the erb in b, and ignore that the
|
|
stdlib one is already in $LOADED_FEATURES?. Reproducible by running the
|
|
spaceship_specific_file test before this one" if Gem.java_platform?
|
|
|
|
pend "not installed yet" unless RbConfig::TOPDIR
|
|
|
|
lib_dir = File.expand_path("../lib", __dir__)
|
|
rubylibdir = File.realdirpath(RbConfig::CONFIG["rubylibdir"])
|
|
if rubylibdir == lib_dir
|
|
# testing in the ruby repository where RubyGems' lib/ == stdlib lib/
|
|
# In that case we want to move the stdlib lib/ to still be after b-2 in $LOAD_PATH
|
|
lp = $LOAD_PATH.dup
|
|
$LOAD_PATH.delete lib_dir
|
|
$LOAD_PATH.push lib_dir
|
|
load_path_changed = true
|
|
end
|
|
|
|
require "erb" # the stdlib
|
|
|
|
a1 = util_spec "a", "1", { "b" => ">= 1" }, "lib/test_gem_require_a.rb"
|
|
b1 = util_spec "b", "1", nil, "lib/erb.rb"
|
|
b2 = util_spec "b", "2", nil, "lib/erb.rb"
|
|
|
|
install_specs b1, b2, a1
|
|
|
|
# Activates a-1, but not b-1 and b-2
|
|
assert_require "test_gem_require_a"
|
|
assert_equal %w[a-1], loaded_spec_names
|
|
assert $LOAD_PATH.include? a1.full_require_paths[0]
|
|
refute $LOAD_PATH.include? b1.full_require_paths[0]
|
|
refute $LOAD_PATH.include? b2.full_require_paths[0]
|
|
|
|
assert_equal unresolved_names, ["b (>= 1)"]
|
|
|
|
# The require('erb') below will activate b-2. However, its
|
|
# lib/erb.rb won't ever be loaded. The reason is MRI sees that even
|
|
# though b-2 is earlier in $LOAD_PATH it already loaded a erb.rb file
|
|
# and that still exists in $LOAD_PATH (further down),
|
|
# and as a result #gem_original_require returns false.
|
|
refute require("erb"), "the erb stdlib should be recognized as already loaded"
|
|
|
|
assert_includes $LOAD_PATH, b2.full_require_paths[0]
|
|
assert_includes $LOAD_PATH, rubylibdir
|
|
message = proc {
|
|
"this test relies on the b-2 gem lib/ to be before stdlib to make sense\n" +
|
|
$LOAD_PATH.pretty_inspect
|
|
}
|
|
assert_operator $LOAD_PATH.index(b2.full_require_paths[0]), :<, $LOAD_PATH.index(rubylibdir), message
|
|
|
|
# We detected that we should activate b-2, so we did so, but
|
|
# then #gem_original_require decided "I've already got some erb.rb" loaded.
|
|
# This case is fine because our lazy loading provided exactly
|
|
# the same behavior as eager loading would have.
|
|
|
|
assert_equal %w[a-1 b-2], loaded_spec_names
|
|
ensure
|
|
$LOAD_PATH.replace lp if load_path_changed
|
|
end
|
|
|
|
def test_activate_via_require_respects_loaded_default_from_default_gems
|
|
a1 = new_default_spec "a", "1", nil, "a.rb"
|
|
|
|
# simulate requiring a default gem before rubygems is loaded
|
|
Kernel.send(:gem_original_require, "a")
|
|
|
|
# simulate registering default specs on loading rubygems
|
|
install_default_gems a1
|
|
|
|
a2 = util_spec "a", "2", nil, "lib/a.rb"
|
|
|
|
install_specs a2
|
|
|
|
refute_require "a"
|
|
|
|
assert_equal %w[a-1], loaded_spec_names
|
|
end
|
|
|
|
def test_already_activated_direct_conflict
|
|
a1 = util_spec "a", "1", { "b" => "> 0" }
|
|
b1 = util_spec "b", "1", { "c" => ">= 1" }, "lib/ib.rb"
|
|
b2 = util_spec "b", "2", { "c" => ">= 2" }, "lib/ib.rb"
|
|
c1 = util_spec "c", "1", nil, "lib/d.rb"
|
|
c2 = util_spec("c", "2", nil, "lib/d.rb")
|
|
|
|
install_specs c1, c2, b1, b2, a1
|
|
|
|
a1.activate
|
|
c1.activate
|
|
assert_equal %w[a-1 c-1], loaded_spec_names
|
|
assert_equal ["b (> 0)"], unresolved_names
|
|
|
|
assert require("ib")
|
|
|
|
assert_equal %w[a-1 b-1 c-1], loaded_spec_names
|
|
assert_equal [], unresolved_names
|
|
end
|
|
|
|
def test_multiple_gems_with_the_same_path
|
|
a1 = util_spec "a", "1", { "b" => "> 0", "x" => "> 0" }
|
|
b1 = util_spec "b", "1", { "c" => ">= 1" }, "lib/ib.rb"
|
|
b2 = util_spec "b", "2", { "c" => ">= 2" }, "lib/ib.rb"
|
|
x1 = util_spec "x", "1", nil, "lib/ib.rb"
|
|
x2 = util_spec "x", "2", nil, "lib/ib.rb"
|
|
c1 = util_spec "c", "1", nil, "lib/d.rb"
|
|
c2 = util_spec("c", "2", nil, "lib/d.rb")
|
|
|
|
install_specs c1, c2, x1, x2, b1, b2, a1
|
|
|
|
a1.activate
|
|
c1.activate
|
|
assert_equal %w[a-1 c-1], loaded_spec_names
|
|
assert_equal ["b (> 0)", "x (> 0)"], unresolved_names
|
|
|
|
e = assert_raise(Gem::LoadError) do
|
|
require("ib")
|
|
end
|
|
|
|
assert_equal "ib found in multiple gems: b, x", e.message
|
|
end
|
|
|
|
def test_unable_to_find_good_unresolved_version
|
|
a1 = util_spec "a", "1", { "b" => "> 0" }
|
|
b1 = util_spec "b", "1", { "c" => ">= 2" }, "lib/ib.rb"
|
|
b2 = util_spec "b", "2", { "c" => ">= 3" }, "lib/ib.rb"
|
|
|
|
c1 = util_spec "c", "1", nil, "lib/d.rb"
|
|
c2 = util_spec "c", "2", nil, "lib/d.rb"
|
|
c3 = util_spec "c", "3", nil, "lib/d.rb"
|
|
|
|
install_specs c1, c2, c3, b1, b2, a1
|
|
|
|
a1.activate
|
|
c1.activate
|
|
assert_equal %w[a-1 c-1], loaded_spec_names
|
|
assert_equal ["b (> 0)"], unresolved_names
|
|
|
|
e = assert_raise(Gem::LoadError) do
|
|
require("ib")
|
|
end
|
|
|
|
assert_equal "unable to find a version of 'b' to activate", e.message
|
|
end
|
|
|
|
def test_require_works_after_cleanup
|
|
a1 = new_default_spec "a", "1.0", nil, "a/b.rb"
|
|
b1 = new_default_spec "b", "1.0", nil, "b/c.rb"
|
|
b2 = new_default_spec "b", "2.0", nil, "b/d.rb"
|
|
|
|
install_default_gems a1
|
|
install_default_gems b1
|
|
install_default_gems b2
|
|
|
|
# Load default ruby gems fresh as if we've just started a ruby script.
|
|
Gem::Specification.reset
|
|
require "rubygems"
|
|
Gem::Specification.stubs
|
|
|
|
# Remove an old default gem version directly from disk as if someone ran
|
|
# gem cleanup.
|
|
FileUtils.rm_rf(File.join(@gemhome, b1.full_name.to_s))
|
|
FileUtils.rm_rf(File.join(@gemhome, "specifications", "default", "#{b1.full_name}.gemspec"))
|
|
|
|
# Require gems that have not been removed.
|
|
assert_require "a/b"
|
|
assert_equal %w[a-1.0], loaded_spec_names
|
|
assert_require "b/d"
|
|
assert_equal %w[a-1.0 b-2.0], loaded_spec_names
|
|
end
|
|
|
|
def test_require_doesnt_traverse_development_dependencies
|
|
a = util_spec("a#{$$}", "1", nil, "lib/a#{$$}.rb")
|
|
z = util_spec("z", "1", "w" => "> 0")
|
|
w1 = util_spec("w", "1") {|s| s.add_development_dependency "non-existent" }
|
|
w2 = util_spec("w", "2") {|s| s.add_development_dependency "non-existent" }
|
|
|
|
install_specs a, w1, w2, z
|
|
|
|
assert gem("z")
|
|
assert_equal %w[z-1], loaded_spec_names
|
|
assert_equal ["w (> 0)"], unresolved_names
|
|
|
|
assert require("a#{$$}")
|
|
end
|
|
|
|
def test_default_gem_only
|
|
default_gem_spec = new_default_spec("default", "2.0.0.0",
|
|
nil, "default/gem.rb")
|
|
install_default_gems(default_gem_spec)
|
|
assert_require "default/gem"
|
|
assert_equal %w[default-2.0.0.0], loaded_spec_names
|
|
end
|
|
|
|
def test_multiple_gems_with_the_same_path_the_non_activated_spec_is_chosen
|
|
a1 = util_spec "a", "1", nil, "lib/ib.rb"
|
|
a2 = util_spec "a", "2", nil, "lib/foo.rb"
|
|
b1 = util_spec "b", "1", nil, "lib/ib.rb"
|
|
|
|
install_specs a1, a2, b1
|
|
|
|
a2.activate
|
|
|
|
assert_equal %w[a-2], loaded_spec_names
|
|
assert_empty unresolved_names
|
|
|
|
assert_require "ib"
|
|
assert_equal %w[a-2 b-1], loaded_spec_names
|
|
end
|
|
|
|
def test_default_gem_require_activates_just_once
|
|
default_gem_spec = new_default_spec("default", "2.0.0.0",
|
|
nil, "default/gem.rb")
|
|
install_default_gems(default_gem_spec)
|
|
|
|
assert_require "default/gem"
|
|
|
|
times_called = 0
|
|
|
|
Kernel.stub(:gem, ->(_name, _requirement) { times_called += 1 }) do
|
|
refute_require "default/gem"
|
|
end
|
|
|
|
assert_equal 0, times_called
|
|
end
|
|
|
|
def test_second_gem_require_does_not_resolve_path_manually_before_going_through_standard_require
|
|
a1 = util_spec "a", "1", nil, "lib/test_gem_require_a.rb"
|
|
install_gem a1
|
|
|
|
assert_require "test_gem_require_a"
|
|
|
|
stub(:gem_original_require, ->(path) { assert_equal "test_gem_require_a", path }) do
|
|
require "test_gem_require_a"
|
|
end
|
|
end
|
|
|
|
def test_realworld_default_gem
|
|
omit "this test can't work under ruby-core setup" if ruby_repo?
|
|
|
|
cmd = <<-RUBY
|
|
$stderr = $stdout
|
|
require "json"
|
|
puts Gem.loaded_specs["json"]
|
|
RUBY
|
|
output = Gem::Util.popen(*ruby_with_rubygems_in_load_path, "-e", cmd).strip
|
|
assert $?.success?
|
|
refute_empty output
|
|
end
|
|
|
|
def test_realworld_upgraded_default_gem
|
|
omit "this test can't work under ruby-core setup" if ruby_repo?
|
|
|
|
newer_json = util_spec("json", "999.99.9", nil, ["lib/json.rb"])
|
|
install_gem newer_json
|
|
|
|
path = "#{@tempdir}/test_realworld_upgraded_default_gem.rb"
|
|
code = <<-RUBY
|
|
$stderr = $stdout
|
|
require "json"
|
|
puts Gem.loaded_specs["json"].version
|
|
puts $LOADED_FEATURES
|
|
RUBY
|
|
File.write(path, code)
|
|
|
|
output = Gem::Util.popen({ "GEM_HOME" => @gemhome }, *ruby_with_rubygems_in_load_path, path).strip
|
|
refute_empty output
|
|
assert_equal "999.99.9", output.lines[0].chomp
|
|
# Make sure only files from the newer json gem are loaded, and no files from the default json gem
|
|
assert_equal ["#{@gemhome}/gems/json-999.99.9/lib/json.rb"], output.lines.grep(%r{/gems/json-}).map(&:chomp)
|
|
assert $?.success?
|
|
end
|
|
|
|
def test_default_gem_and_normal_gem
|
|
default_gem_spec = new_default_spec("default", "2.0.0.0",
|
|
nil, "default/gem.rb")
|
|
install_default_gems(default_gem_spec)
|
|
normal_gem_spec = util_spec("default", "3.0", nil,
|
|
"lib/default/gem.rb")
|
|
install_specs(normal_gem_spec)
|
|
assert_require "default/gem"
|
|
assert_equal %w[default-3.0], loaded_spec_names
|
|
end
|
|
|
|
def test_default_gem_and_normal_gem_same_version
|
|
default_gem_spec = new_default_spec("default", "3.0",
|
|
nil, "default/gem.rb")
|
|
install_default_gems(default_gem_spec)
|
|
normal_gem_spec = util_spec("default", "3.0", nil,
|
|
"lib/default/gem.rb")
|
|
install_specs(normal_gem_spec)
|
|
|
|
# Load default ruby gems fresh as if we've just started a ruby script.
|
|
Gem::Specification.reset
|
|
|
|
assert_require "default/gem"
|
|
assert_equal %w[default-3.0], loaded_spec_names
|
|
refute Gem.loaded_specs["default"].default_gem?
|
|
end
|
|
|
|
def test_normal_gem_does_not_shadow_default_gem
|
|
default_gem_spec = new_default_spec("foo", "2.0", nil, "foo.rb")
|
|
install_default_gems(default_gem_spec)
|
|
|
|
normal_gem_spec = util_spec("fake-foo", "3.0", nil, "lib/foo.rb")
|
|
install_specs(normal_gem_spec)
|
|
|
|
assert_require "foo"
|
|
assert_equal %w[foo-2.0], loaded_spec_names
|
|
end
|
|
|
|
def test_normal_gems_with_overridden_load_error_message
|
|
normal_gem_spec = util_spec("normal", "3.0", nil, "lib/normal/gem.rb")
|
|
|
|
install_specs(normal_gem_spec)
|
|
|
|
File.write("require_with_overridden_load_error_message.rb", <<-RUBY)
|
|
LoadError.class_eval do
|
|
def message
|
|
"Overridden message"
|
|
end
|
|
end
|
|
|
|
require 'normal/gem'
|
|
RUBY
|
|
|
|
require "open3"
|
|
|
|
output, exit_status = Open3.capture2e(
|
|
{ "GEM_HOME" => Gem.paths.home },
|
|
*ruby_with_rubygems_in_load_path,
|
|
"-r",
|
|
"./require_with_overridden_load_error_message.rb"
|
|
)
|
|
|
|
assert exit_status.success?, "Require failed due to #{output}"
|
|
end
|
|
|
|
def test_default_gem_prerelease
|
|
default_gem_spec = new_default_spec("default", "2.0.0",
|
|
nil, "default/gem.rb")
|
|
install_default_gems(default_gem_spec)
|
|
|
|
normal_gem_higher_prerelease_spec = util_spec("default", "3.0.0.rc2", nil,
|
|
"lib/default/gem.rb")
|
|
install_default_gems(normal_gem_higher_prerelease_spec)
|
|
|
|
assert_require "default/gem"
|
|
assert_equal %w[default-3.0.0.rc2], loaded_spec_names
|
|
end
|
|
|
|
def test_default_gem_with_unresolved_gems_depending_on_it
|
|
my_http_old = util_spec "my-http", "0.1.1", nil, "lib/my/http.rb"
|
|
install_gem my_http_old
|
|
|
|
my_http_default = new_default_spec "my-http", "0.3.0", nil, "my/http.rb"
|
|
install_default_gems my_http_default
|
|
|
|
faraday_1 = util_spec "faraday", "1", { "my-http" => ">= 0" }
|
|
install_gem faraday_1
|
|
|
|
faraday_2 = util_spec "faraday", "2", { "my-http" => ">= 0" }
|
|
install_gem faraday_2
|
|
|
|
chef = util_spec "chef", "1", { "faraday" => [">= 1", "< 3"] }, "lib/chef.rb"
|
|
install_gem chef
|
|
|
|
assert_require "chef"
|
|
assert_require "my/http"
|
|
end
|
|
|
|
def test_default_gem_required_circulary_with_unresolved_gems_depending_on_it
|
|
my_http_old = util_spec "my-http", "0.1.1", nil, "lib/my/http.rb"
|
|
install_gem my_http_old
|
|
|
|
my_http_default = new_default_spec "my-http", "0.3.0", nil, "my/http.rb"
|
|
my_http_default_path = File.join(@tempdir, "default_gems", "lib", "my/http.rb")
|
|
install_default_gems my_http_default
|
|
File.write(my_http_default_path, 'require "my/http"')
|
|
|
|
faraday_1 = util_spec "faraday", "1", { "my-http" => ">= 0" }
|
|
install_gem faraday_1
|
|
|
|
faraday_2 = util_spec "faraday", "2", { "my-http" => ">= 0" }
|
|
install_gem faraday_2
|
|
|
|
chef = util_spec "chef", "1", { "faraday" => [">= 1", "< 3"] }, "lib/chef.rb"
|
|
install_gem chef
|
|
|
|
assert_require "chef"
|
|
|
|
out, err = capture_output do
|
|
assert_require "my/http"
|
|
end
|
|
|
|
assert_empty out
|
|
|
|
circular_require_warning = false
|
|
|
|
err_lines = err.split("\n").reject do |line|
|
|
if line.include?("circular require")
|
|
circular_require_warning = true
|
|
elsif circular_require_warning # ignore backtrace lines for circular require warning
|
|
circular_require_warning = line.start_with?(/[\s]/)
|
|
end
|
|
end
|
|
|
|
assert_empty err_lines
|
|
end
|
|
|
|
def loaded_spec_names
|
|
Gem.loaded_specs.values.map(&:full_name).sort
|
|
end
|
|
|
|
def unresolved_names
|
|
Gem::Specification.unresolved_deps.values.map(&:to_s).sort
|
|
end
|
|
|
|
def test_try_activate_error_unlocks_require_monitor
|
|
silence_warnings do
|
|
class << ::Gem
|
|
alias_method :old_try_activate, :try_activate
|
|
def try_activate(*)
|
|
raise "raised from try_activate"
|
|
end
|
|
end
|
|
end
|
|
|
|
require "does_not_exist_for_try_activate_test"
|
|
rescue RuntimeError => e
|
|
assert_match(/raised from try_activate/, e.message)
|
|
assert Kernel::RUBYGEMS_ACTIVATION_MONITOR.try_enter, "require monitor was not unlocked when try_activate raised"
|
|
ensure
|
|
silence_warnings do
|
|
class << ::Gem
|
|
alias_method :try_activate, :old_try_activate
|
|
end
|
|
end
|
|
Kernel::RUBYGEMS_ACTIVATION_MONITOR.exit
|
|
end
|
|
|
|
def test_require_when_gem_defined
|
|
default_gem_spec = new_default_spec("default", "2.0.0.0",
|
|
nil, "default/gem.rb")
|
|
install_default_gems(default_gem_spec)
|
|
c = Class.new do
|
|
def self.gem(*args)
|
|
raise "received #gem with #{args.inspect}"
|
|
end
|
|
end
|
|
assert c.send(:require, "default/gem")
|
|
assert_equal %w[default-2.0.0.0], loaded_spec_names
|
|
end
|
|
|
|
def test_require_default_when_gem_defined
|
|
a = util_spec("a#{$$}", "1", nil, "lib/a#{$$}.rb")
|
|
install_specs a
|
|
c = Class.new do
|
|
def self.gem(*args)
|
|
raise "received #gem with #{args.inspect}"
|
|
end
|
|
end
|
|
assert c.send(:require, "a#{$$}")
|
|
assert_equal %W[a#{$$}-1], loaded_spec_names
|
|
end
|
|
|
|
def test_require_bundler
|
|
b1 = util_spec("bundler", "1", nil, "lib/bundler/setup.rb")
|
|
b2a = util_spec("bundler", "2.a", nil, "lib/bundler/setup.rb")
|
|
install_specs b1, b2a
|
|
|
|
require "rubygems/bundler_version_finder"
|
|
$:.clear
|
|
assert_require "bundler/setup"
|
|
assert_equal %w[bundler-2.a], loaded_spec_names
|
|
assert_empty unresolved_names
|
|
end
|
|
|
|
["", "Kernel."].each do |prefix|
|
|
define_method "test_no_kernel_require_in_#{prefix.tr(".", "_")}warn_with_uplevel" do
|
|
Dir.mktmpdir("warn_test") do |dir|
|
|
File.write(dir + "/sub.rb", "#{prefix}warn 'uplevel', 'test', uplevel: 1\n")
|
|
File.write(dir + "/main.rb", "require 'sub'\n")
|
|
_, err = capture_subprocess_io do
|
|
system(*ruby_with_rubygems_in_load_path, "-w", "--disable=gems", "-C", dir, "-I", dir, "main.rb")
|
|
end
|
|
assert_match(/main\.rb:1: warning: uplevel\ntest\n$/, err)
|
|
_, err = capture_subprocess_io do
|
|
system(*ruby_with_rubygems_in_load_path, "-w", "--enable=gems", "-C", dir, "-I", dir, "main.rb")
|
|
end
|
|
assert_match(/main\.rb:1: warning: uplevel\ntest\n$/, err)
|
|
end
|
|
end
|
|
|
|
define_method "test_no_other_behavioral_changes_with_#{prefix.tr(".", "_")}warn" do
|
|
Dir.mktmpdir("warn_test") do |dir|
|
|
File.write(dir + "/main.rb", "#{prefix}warn({x:1}, {y:2}, [])\n")
|
|
_, err = capture_subprocess_io do
|
|
system(*ruby_with_rubygems_in_load_path, "-w", "--disable=gems", "-C", dir, "main.rb")
|
|
end
|
|
assert_match(/#{{ x: 1 }.inspect}\n#{{ y: 2 }.inspect}\n$/, err)
|
|
_, err = capture_subprocess_io do
|
|
system(*ruby_with_rubygems_in_load_path, "-w", "--enable=gems", "-C", dir, "main.rb")
|
|
end
|
|
assert_match(/#{{ x: 1 }.inspect}\n#{{ y: 2 }.inspect}\n$/, err)
|
|
end
|
|
end
|
|
end
|
|
|
|
def test_no_crash_when_overriding_warn_with_warning_module
|
|
Dir.mktmpdir("warn_test") do |dir|
|
|
File.write(dir + "/main.rb", "module Warning; def warn(str); super; end; end; warn 'Foo Bar'")
|
|
_, err = capture_subprocess_io do
|
|
system(*ruby_with_rubygems_in_load_path, "-w", "--disable=gems", "-C", dir, "main.rb")
|
|
end
|
|
assert_match(/Foo Bar\n$/, err)
|
|
_, err = capture_subprocess_io do
|
|
system(*ruby_with_rubygems_in_load_path, "-w", "--enable=gems", "-C", dir, "main.rb")
|
|
end
|
|
assert_match(/Foo Bar\n$/, err)
|
|
end
|
|
end
|
|
|
|
def test_expected_backtrace_location_when_inheriting_from_basic_object_and_including_kernel
|
|
Dir.mktmpdir("warn_test") do |dir|
|
|
File.write(dir + "/main.rb", "\nrequire 'sub'\n")
|
|
File.write(dir + "/sub.rb", <<-'RUBY')
|
|
require 'rubygems'
|
|
class C < BasicObject
|
|
include ::Kernel
|
|
def deprecated
|
|
warn "This is a deprecated method", uplevel: 2
|
|
end
|
|
end
|
|
C.new.deprecated
|
|
RUBY
|
|
|
|
_, err = capture_subprocess_io do
|
|
system(*ruby_with_rubygems_in_load_path, "-w", "--disable=gems", "-C", dir, "-I", dir, "main.rb")
|
|
end
|
|
assert_match(/main\.rb:2: warning: This is a deprecated method$/, err)
|
|
_, err = capture_subprocess_io do
|
|
system(*ruby_with_rubygems_in_load_path, "-w", "--enable=gems", "-C", dir, "-I", dir, "main.rb")
|
|
end
|
|
assert_match(/main\.rb:2: warning: This is a deprecated method$/, err)
|
|
end
|
|
end
|
|
|
|
def test_require_does_not_crash_when_utilizing_bundler_version_finder
|
|
a1 = util_spec "a", "1.1", { "bundler" => ">= 0" }
|
|
a2 = util_spec "a", "1.2", { "bundler" => ">= 0" }
|
|
b1 = util_spec "bundler", "2.3.7"
|
|
b2 = util_spec "bundler", "2.3.24"
|
|
c = util_spec "c", "1", { "a" => [">= 1.1", "< 99.0"] }, "lib/test_gem_require_c.rb"
|
|
|
|
install_specs a1, a2, b1, b2, c
|
|
|
|
cmd = <<-RUBY
|
|
require "test_gem_require_c"
|
|
require "json"
|
|
RUBY
|
|
out = Gem::Util.popen({ "GEM_HOME" => @gemhome }, *ruby_with_rubygems_in_load_path, "-e", cmd)
|
|
assert_predicate $?, :success?, "Require failed due to #{out}"
|
|
end
|
|
|
|
private
|
|
|
|
def util_install_extension_file(name)
|
|
spec = quick_gem name
|
|
util_build_gem spec
|
|
|
|
spec.extensions << "extconf.rb"
|
|
write_file File.join(@tempdir, "extconf.rb") do |io|
|
|
io.write <<-RUBY
|
|
require "mkmf"
|
|
CONFIG['LDSHARED'] = '$(TOUCH) $@ ||'
|
|
create_makefile("#{name}")
|
|
RUBY
|
|
end
|
|
|
|
write_file File.join(@tempdir, "#{name}.c") do |io|
|
|
io.write <<-C
|
|
void Init_#{name}() { }
|
|
C
|
|
end
|
|
|
|
write_file File.join(@tempdir, "depend")
|
|
|
|
spec.files += ["extconf.rb", "depend", "#{name}.c"]
|
|
|
|
extension_file = File.join(spec.extension_dir, "#{name}.#{RbConfig::CONFIG["DLEXT"]}")
|
|
assert_path_not_exist extension_file
|
|
|
|
path = Gem::Package.build spec
|
|
installer = Gem::Installer.at path
|
|
installer.install
|
|
assert_path_exist extension_file
|
|
|
|
spec.gem_dir
|
|
end
|
|
|
|
def util_install_ruby_file(name)
|
|
dir_lib = Dir.mktmpdir("test_require_lib", @tempdir)
|
|
dash_i_lib_arg = File.join dir_lib
|
|
|
|
a_rb = File.join dash_i_lib_arg, "#{name}.rb"
|
|
|
|
FileUtils.mkdir_p File.dirname a_rb
|
|
File.open(a_rb, "w") {|f| f.write "# #{name}.rb" }
|
|
|
|
dash_i_lib_arg
|
|
end
|
|
end
|