summaryrefslogtreecommitdiff
path: root/test/rubygems
diff options
context:
space:
mode:
Diffstat (limited to 'test/rubygems')
-rw-r--r--test/rubygems/coverage_setup.rb9
-rw-r--r--test/rubygems/helper.rb145
-rw-r--r--test/rubygems/installer_test_case.rb30
-rw-r--r--test/rubygems/mock_gem_ui.rb2
-rw-r--r--test/rubygems/package/tar_test_case.rb38
-rw-r--r--test/rubygems/test_bundled_ca.rb2
-rw-r--r--test/rubygems/test_config.rb7
-rw-r--r--test/rubygems/test_gem.rb102
-rw-r--r--test/rubygems/test_gem_bundler_version_finder.rb159
-rw-r--r--test/rubygems/test_gem_command_manager.rb45
-rw-r--r--test/rubygems/test_gem_commands_build_command.rb10
-rw-r--r--test/rubygems/test_gem_commands_cert_command.rb22
-rw-r--r--test/rubygems/test_gem_commands_environment_command.rb4
-rw-r--r--test/rubygems/test_gem_commands_exec_command.rb36
-rw-r--r--test/rubygems/test_gem_commands_fetch_command.rb2
-rw-r--r--test/rubygems/test_gem_commands_help_command.rb2
-rw-r--r--test/rubygems/test_gem_commands_info_command.rb2
-rw-r--r--test/rubygems/test_gem_commands_install_command.rb156
-rw-r--r--test/rubygems/test_gem_commands_open_command.rb2
-rw-r--r--test/rubygems/test_gem_commands_owner_command.rb30
-rw-r--r--test/rubygems/test_gem_commands_pristine_command.rb62
-rw-r--r--test/rubygems/test_gem_commands_push_command.rb172
-rw-r--r--test/rubygems/test_gem_commands_query_command.rb830
-rw-r--r--test/rubygems/test_gem_commands_setup_command.rb31
-rw-r--r--test/rubygems/test_gem_commands_signin_command.rb2
-rw-r--r--test/rubygems/test_gem_commands_sources_command.rb685
-rw-r--r--test/rubygems/test_gem_commands_uninstall_command.rb2
-rw-r--r--test/rubygems/test_gem_commands_update_command.rb32
-rw-r--r--test/rubygems/test_gem_commands_which_command.rb2
-rw-r--r--test/rubygems/test_gem_commands_yank_command.rb15
-rw-r--r--test/rubygems/test_gem_config_file.rb56
-rw-r--r--test/rubygems/test_gem_dependency_installer.rb200
-rw-r--r--test/rubygems/test_gem_dependency_resolution_error.rb23
-rw-r--r--test/rubygems/test_gem_ext_builder.rb69
-rw-r--r--test/rubygems/test_gem_ext_cargo_builder.rb52
-rw-r--r--test/rubygems/test_gem_ext_cargo_builder/custom_name/ext/custom_name_lib/Cargo.lock51
-rw-r--r--test/rubygems/test_gem_ext_cargo_builder/custom_name/ext/custom_name_lib/Cargo.toml2
-rw-r--r--test/rubygems/test_gem_ext_cargo_builder/rust_ruby_example/Cargo.lock51
-rw-r--r--test/rubygems/test_gem_ext_cargo_builder/rust_ruby_example/Cargo.toml2
-rw-r--r--test/rubygems/test_gem_ext_cargo_builder_link_flag_converter.rb2
-rw-r--r--test/rubygems/test_gem_ext_cmake_builder.rb97
-rw-r--r--test/rubygems/test_gem_ext_ext_conf_builder.rb33
-rw-r--r--test/rubygems/test_gem_ext_rake_builder.rb2
-rw-r--r--test/rubygems/test_gem_gem_runner.rb11
-rw-r--r--test/rubygems/test_gem_gemcutter_utilities.rb16
-rw-r--r--test/rubygems/test_gem_impossible_dependencies_error.rb60
-rw-r--r--test/rubygems/test_gem_install_update_options.rb12
-rw-r--r--test/rubygems/test_gem_installer.rb522
-rw-r--r--test/rubygems/test_gem_name_tuple.rb37
-rw-r--r--test/rubygems/test_gem_package.rb187
-rw-r--r--test/rubygems/test_gem_package_old.rb2
-rw-r--r--test/rubygems/test_gem_package_tar_header_ractor.rb61
-rw-r--r--test/rubygems/test_gem_package_tar_writer.rb33
-rw-r--r--test/rubygems/test_gem_path_support.rb8
-rw-r--r--test/rubygems/test_gem_platform.rb305
-rw-r--r--test/rubygems/test_gem_remote_fetcher.rb239
-rw-r--r--test/rubygems/test_gem_remote_fetcher_s3.rb296
-rw-r--r--test/rubygems/test_gem_request.rb12
-rw-r--r--test/rubygems/test_gem_request_connection_pools.rb12
-rw-r--r--test/rubygems/test_gem_request_set.rb132
-rw-r--r--test/rubygems/test_gem_request_set_lockfile_parser.rb544
-rw-r--r--test/rubygems/test_gem_request_set_lockfile_tokenizer.rb307
-rw-r--r--test/rubygems/test_gem_requirement.rb22
-rw-r--r--test/rubygems/test_gem_resolver.rb496
-rw-r--r--test/rubygems/test_gem_resolver_best_set.rb14
-rw-r--r--test/rubygems/test_gem_resolver_conflict.rb80
-rw-r--r--test/rubygems/test_gem_resolver_git_specification.rb38
-rw-r--r--test/rubygems/test_gem_resolver_strategy.rb163
-rw-r--r--test/rubygems/test_gem_safe_marshal.rb4
-rw-r--r--test/rubygems/test_gem_safe_yaml.rb1302
-rw-r--r--test/rubygems/test_gem_security_trust_dir.rb6
-rw-r--r--test/rubygems/test_gem_source_git.rb2
-rw-r--r--test/rubygems/test_gem_source_list.rb127
-rw-r--r--test/rubygems/test_gem_source_local.rb24
-rw-r--r--test/rubygems/test_gem_specification.rb235
-rw-r--r--test/rubygems/test_gem_stub_specification.rb26
-rw-r--r--test/rubygems/test_gem_text.rb17
-rw-r--r--test/rubygems/test_gem_uri.rb2
-rw-r--r--test/rubygems/test_gem_util.rb11
-rw-r--r--test/rubygems/test_gem_util_atomic_file_writer.rb12
-rw-r--r--test/rubygems/test_gem_version.rb92
-rw-r--r--test/rubygems/test_project_sanity.rb3
-rw-r--r--test/rubygems/test_require.rb19
-rw-r--r--test/rubygems/test_rubygems.rb1
-rw-r--r--test/rubygems/test_webauthn_listener.rb2
85 files changed, 5614 insertions, 3158 deletions
diff --git a/test/rubygems/coverage_setup.rb b/test/rubygems/coverage_setup.rb
new file mode 100644
index 0000000000..7e978e59e0
--- /dev/null
+++ b/test/rubygems/coverage_setup.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+# This file is loaded via -r flag BEFORE rubygems to enable coverage tracking
+# of rubygems boot files. It must be used with --disable-gems and -Ilib
+# so that Coverage.start runs before rubygems is loaded.
+
+require "coverage"
+Coverage.start(lines: true)
+require "rubygems"
diff --git a/test/rubygems/helper.rb b/test/rubygems/helper.rb
index eaf3e7037e..2411dbc649 100644
--- a/test/rubygems/helper.rb
+++ b/test/rubygems/helper.rb
@@ -3,6 +3,32 @@
require "rubygems"
begin
+ raise LoadError if ENV["GEM_COMMAND"]
+
+ gem "simplecov_json_formatter"
+ require "simplecov"
+
+ unless ENV["SIMPLECOV_SUBPROCESS"]
+ SimpleCov.start do
+ command_name "rubygems"
+ root File.expand_path("../..", __dir__)
+ coverage_dir File.expand_path("../../coverage", __dir__)
+
+ add_filter "/test/"
+ add_filter "/bundler/"
+ add_filter "/tool/"
+ add_filter "/lib/rubygems/vendor/"
+ add_filter ".gemspec"
+ end
+
+ # Prevent SimpleCov from running in subprocesses spawned by assert_separately
+ ENV["SIMPLECOV_SUBPROCESS"] = "1"
+ end
+rescue LoadError
+ # SimpleCov is not installed
+end
+
+begin
gem "test-unit", "~> 3.0"
rescue Gem::LoadError
end
@@ -12,6 +38,7 @@ require "test/unit"
require "fileutils"
require "pathname"
require "pp"
+require "rubygems/installer"
require "rubygems/package"
require "shellwords"
require "tmpdir"
@@ -19,6 +46,24 @@ require "rubygems/vendor/uri/lib/uri"
require "zlib"
require_relative "mock_gem_ui"
+# JRuby on Windows raises TypeError inside File.symlink (the wincode helper
+# trips on a nil path), so any test that exercises Gem::Installer's symlink
+# branch fails to even install the gem. Real users hit the wrapper branch via
+# `gem install` (DependencyInstaller passes wrappers: true), so mirror that
+# default for direct Gem::Installer.at callers in the test suite.
+if Gem.win_platform? && Gem.java_platform?
+ module Gem::InstallerDefaultWrappersOnJRubyWindows
+ def at(path, options = {})
+ super(path, { wrappers: true }.merge(options))
+ end
+
+ def for_spec(spec, options = {})
+ super(spec, { wrappers: true }.merge(options))
+ end
+ end
+ Gem::Installer.singleton_class.prepend(Gem::InstallerDefaultWrappersOnJRubyWindows)
+end
+
module Gem
##
# Allows setting the gem path searcher.
@@ -60,6 +105,44 @@ class Gem::Command
end
end
+class Gem::Installer
+ # Copy from Gem::Installer#install with install_as_default option from old version
+ def install_default_gem
+ pre_install_checks
+
+ run_pre_install_hooks
+
+ spec.loaded_from = default_spec_file
+
+ FileUtils.rm_rf gem_dir
+ FileUtils.rm_rf spec.extension_dir
+
+ dir_mode = options[:dir_mode]
+ FileUtils.mkdir_p gem_dir, mode: dir_mode && 0o755
+
+ extract_bin
+ write_default_spec
+
+ generate_bin
+ generate_plugins
+
+ File.chmod(dir_mode, gem_dir) if dir_mode
+
+ say spec.post_install_message if options[:post_install_message] && !spec.post_install_message.nil?
+
+ Gem::Specification.add_spec(spec)
+
+ load_plugin
+
+ run_post_install_hooks
+
+ spec
+ rescue Errno::EACCES => e
+ # Permission denied - /path/to/foo
+ raise Gem::FilePermissionError, e.message.split(" - ").last
+ end
+end
+
##
# RubyGemTestCase provides a variety of methods for testing rubygems and
# gem-related behavior in a sandbox. Through RubyGemTestCase you can install
@@ -295,8 +378,12 @@ class Gem::TestCase < Test::Unit::TestCase
ENV["XDG_CONFIG_HOME"] = nil
ENV["XDG_DATA_HOME"] = nil
ENV["XDG_STATE_HOME"] = nil
+ ENV["MAKEFLAGS"] = nil
ENV["SOURCE_DATE_EPOCH"] = nil
ENV["BUNDLER_VERSION"] = nil
+ ENV["BUNDLE_CONFIG"] = nil
+ ENV["BUNDLE_USER_CONFIG"] = nil
+ ENV["BUNDLE_USER_HOME"] = nil
ENV["RUBYGEMS_PREVENT_UPDATE_SUGGESTION"] = "true"
@current_dir = Dir.pwd
@@ -400,8 +487,9 @@ class Gem::TestCase < Test::Unit::TestCase
Gem::RemoteFetcher.fetcher = Gem::FakeFetcher.new
@gem_repo = "http://gems.example.com/"
+ Gem.instance_variable_set :@default_sources, [@gem_repo]
+ Gem.instance_variable_set :@sources, nil
@uri = Gem::URI.parse @gem_repo
- Gem.sources.replace [@gem_repo]
Gem.searcher = nil
Gem::SpecFetcher.fetcher = nil
@@ -418,6 +506,9 @@ class Gem::TestCase < Test::Unit::TestCase
@orig_hooks[name] = Gem.send(name).dup
end
+ Gem::Platform.const_get(:GENERIC_CACHE).clear
+ Gem::Platform.const_get(:GENERICS).each {|g| Gem::Platform.const_get(:GENERIC_CACHE)[g] = g }
+
@marshal_version = "#{Marshal::MAJOR_VERSION}.#{Marshal::MINOR_VERSION}"
@orig_loaded_features = $LOADED_FEATURES.dup
end
@@ -680,15 +771,19 @@ class Gem::TestCase < Test::Unit::TestCase
path
end
+ def write_dummy_extconf(gem_name)
+ write_file File.join(@tempdir, "extconf.rb") do |io|
+ io.puts "require 'mkmf'"
+ yield io if block_given?
+ io.puts "create_makefile '#{gem_name}'"
+ end
+ end
+
##
- # Load a YAML string, the psych 3 way
+ # Load a YAML string using the safe loader with gem-spec permitted classes.
def load_yaml(yaml)
- if Psych.respond_to?(:unsafe_load)
- Psych.unsafe_load(yaml)
- else
- Psych.load(yaml)
- end
+ Gem::SafeYAML.safe_load(yaml)
end
##
@@ -713,7 +808,7 @@ class Gem::TestCase < Test::Unit::TestCase
#
# Use this with #write_file to build an installed gem.
- def quick_gem(name, version="2")
+ def quick_gem(name, version = "2")
require "rubygems/specification"
spec = Gem::Specification.new do |s|
@@ -799,8 +894,8 @@ class Gem::TestCase < Test::Unit::TestCase
def install_default_gems(*specs)
specs.each do |spec|
- installer = Gem::Installer.for_spec(spec, install_as_default: true)
- installer.install
+ installer = Gem::Installer.for_spec(spec)
+ installer.install_default_gem
Gem.register_default_spec(spec)
end
end
@@ -1022,7 +1117,7 @@ Also, a list:
# Add +spec+ to +@fetcher+ serving the data in the file +path+.
# +repo+ indicates which repo to make +spec+ appear to be in.
- def add_to_fetcher(spec, path=nil, repo=@gem_repo)
+ def add_to_fetcher(spec, path = nil, repo = @gem_repo)
path ||= spec.cache_file
@fetcher.data["#{@gem_repo}gems/#{spec.file_name}"] = read_binary(path)
end
@@ -1184,6 +1279,26 @@ Also, a list:
system("nmake /? 1>NUL 2>&1")
end
+ @@symlink_supported = nil
+
+ # This is needed for Windows environment without symlink support enabled (the default
+ # for non admin) to be able to skip test for features using symlinks.
+ def symlink_supported?
+ if @@symlink_supported.nil?
+ begin
+ File.symlink(File.join(@tempdir, "a"), File.join(@tempdir, "b"))
+ File.readlink(File.join(@tempdir, "b"))
+ rescue NotImplementedError, SystemCallError
+ @@symlink_supported = false
+ else
+ @@symlink_supported = true
+ ensure
+ File.unlink(File.join(@tempdir, "b")) if File.symlink?(File.join(@tempdir, "b"))
+ end
+ end
+ @@symlink_supported
+ end
+
# In case we're building docs in a background process, this method waits for
# that process to exit (or if it's already been reaped, or never happened,
# swallows the Errno::ECHILD error).
@@ -1195,7 +1310,7 @@ Also, a list:
##
# Allows the proper version of +rake+ to be used for the test.
- def build_rake_in(good=true)
+ def build_rake_in(good = true)
gem_ruby = Gem.ruby
Gem.ruby = self.class.rubybin
env_rake = ENV["rake"]
@@ -1567,3 +1682,9 @@ class Object
end
require_relative "utilities"
+
+# mise installed rubygems_plugin.rb to system wide `site_ruby` directory.
+# This empty module avoid to call `mise` command.
+module ReshimInstaller
+ def self.reshim; end
+end
diff --git a/test/rubygems/installer_test_case.rb b/test/rubygems/installer_test_case.rb
index 8a34d28db8..9e0cbf9c69 100644
--- a/test/rubygems/installer_test_case.rb
+++ b/test/rubygems/installer_test_case.rb
@@ -215,26 +215,26 @@ class Gem::InstallerTestCase < Gem::TestCase
##
# Creates an installer for +spec+ that will install into +gem_home+.
- def util_installer(spec, gem_home, force=true)
+ def util_installer(spec, gem_home, force = true)
Gem::Installer.at(spec.cache_file,
install_dir: gem_home,
force: force)
end
- @@symlink_supported = nil
-
- # This is needed for Windows environment without symlink support enabled (the default
- # for non admin) to be able to skip test for features using symlinks.
- def symlink_supported?
- if @@symlink_supported.nil?
- begin
- File.symlink("", "")
- rescue Errno::ENOENT, Errno::EEXIST
- @@symlink_supported = true
- rescue NotImplementedError, SystemCallError
- @@symlink_supported = false
- end
+ def test_ensure_writable_dir_creates_missing_parent_directories
+ installer = setup_base_installer(false)
+
+ non_existent_parent = File.join(@tempdir, "non_existent_parent")
+ target_dir = File.join(non_existent_parent, "target_dir")
+
+ refute_directory_exists non_existent_parent, "Parent directory should not exist yet"
+ refute_directory_exists target_dir, "Target directory should not exist yet"
+
+ assert_nothing_raised do
+ installer.send(:ensure_writable_dir, target_dir)
end
- @@symlink_supported
+
+ assert_directory_exists non_existent_parent, "Parent directory should exist now"
+ assert_directory_exists target_dir, "Target directory should exist now"
end
end
diff --git a/test/rubygems/mock_gem_ui.rb b/test/rubygems/mock_gem_ui.rb
index 218d4b6965..fb804c5555 100644
--- a/test/rubygems/mock_gem_ui.rb
+++ b/test/rubygems/mock_gem_ui.rb
@@ -77,7 +77,7 @@ class Gem::MockGemUi < Gem::StreamUI
@terminated
end
- def terminate_interaction(status=0)
+ def terminate_interaction(status = 0)
@terminated = true
raise TermError, status if status != 0
diff --git a/test/rubygems/package/tar_test_case.rb b/test/rubygems/package/tar_test_case.rb
index e3d812bf3f..26135cf296 100644
--- a/test/rubygems/package/tar_test_case.rb
+++ b/test/rubygems/package/tar_test_case.rb
@@ -6,23 +6,7 @@ require "rubygems/package"
##
# A test case for Gem::Package::Tar* classes
-class Gem::Package::TarTestCase < Gem::TestCase
- def ASCIIZ(str, length)
- str + "\0" * (length - str.length)
- end
-
- def SP(s)
- s + " "
- end
-
- def SP_Z(s)
- s + " \0"
- end
-
- def Z(s)
- s + "\0"
- end
-
+module Gem::Package::TarTestMethods
def assert_headers_equal(expected, actual)
expected = expected.to_s unless String === expected
actual = actual.to_s unless String === actual
@@ -66,6 +50,26 @@ class Gem::Package::TarTestCase < Gem::TestCase
assert_equal expected[chksum_off, 8], actual[chksum_off, 8]
end
+end
+
+class Gem::Package::TarTestCase < Gem::TestCase
+ include Gem::Package::TarTestMethods
+
+ def ASCIIZ(str, length)
+ str + "\0" * (length - str.length)
+ end
+
+ def SP(s)
+ s + " "
+ end
+
+ def SP_Z(s)
+ s + " \0"
+ end
+
+ def Z(s)
+ s + "\0"
+ end
def calc_checksum(header)
sum = header.sum(0)
diff --git a/test/rubygems/test_bundled_ca.rb b/test/rubygems/test_bundled_ca.rb
index a737185681..cc8fa884ca 100644
--- a/test/rubygems/test_bundled_ca.rb
+++ b/test/rubygems/test_bundled_ca.rb
@@ -12,7 +12,7 @@ require "rubygems/request"
# = Testing Bundled CA
#
-# The tested hosts are explained in detail here: https://github.com/rubygems/rubygems/commit/5e16a5428f973667cabfa07e94ff939e7a83ebd9
+# The tested hosts are explained in detail here: https://github.com/ruby/rubygems/commit/5e16a5428f973667cabfa07e94ff939e7a83ebd9
#
class TestGemBundledCA < Gem::TestCase
diff --git a/test/rubygems/test_config.rb b/test/rubygems/test_config.rb
index 657624d526..822b57b0dc 100644
--- a/test/rubygems/test_config.rb
+++ b/test/rubygems/test_config.rb
@@ -5,13 +5,6 @@ require "rubygems"
require "shellwords"
class TestGemConfig < Gem::TestCase
- def test_datadir
- util_make_gems
- spec = Gem::Specification.find_by_name("a")
- spec.activate
- assert_equal "#{spec.full_gem_path}/data/a", spec.datadir
- end
-
def test_good_rake_path_is_escaped
path = Gem::TestCase.class_variable_get(:@@good_rake)
ruby, rake = path.shellsplit
diff --git a/test/rubygems/test_gem.rb b/test/rubygems/test_gem.rb
index cdc3479e37..c81b0b0547 100644
--- a/test/rubygems/test_gem.rb
+++ b/test/rubygems/test_gem.rb
@@ -150,6 +150,8 @@ class TestGem < Gem::TestCase
end
def assert_self_install_permissions(format_executable: false, data_mode: 0o640)
+ omit "FileUtils.install signature differs on JRuby/Windows" if Gem.win_platform? && Gem.java_platform?
+
mask = Gem.win_platform? ? 0o700 : 0o777
options = {
dir_mode: 0o500,
@@ -199,7 +201,8 @@ class TestGem < Gem::TestCase
end
assert_equal(expected, result)
ensure
- File.chmod(0o755, *Dir.glob(@gemhome + "/gems/**/"))
+ files = Dir.glob(@gemhome + "/gems/**/")
+ File.chmod(0o755, *files) unless files.empty?
end
def test_require_missing
@@ -310,7 +313,7 @@ class TestGem < Gem::TestCase
assert_equal %w[a-1 b-2 c-2], loaded_spec_names
end
- def test_activate_bin_path_raises_a_meaningful_error_if_a_gem_thats_finally_activated_has_orphaned_dependencies
+ def test_activate_bin_path_backtracks_when_highest_version_has_orphaned_dependencies
a1 = util_spec "a", "1" do |s|
s.executables = ["exec"]
s.add_dependency "b"
@@ -328,13 +331,11 @@ class TestGem < Gem::TestCase
install_specs c1, b1, b2, a1
- # c2 is missing, and b2 which has it as a dependency will be activated, so we should get an error about the orphaned dependency
-
- e = assert_raise Gem::UnsatisfiableDependencyError do
- load Gem.activate_bin_path("a", "exec", ">= 0")
- end
+ # c2 is missing, but the resolver backtracks from b2 to b1 which
+ # works with c1, finding a valid solution despite partial installation
+ load Gem.activate_bin_path("a", "exec", ">= 0")
- assert_equal "Unable to resolve dependency: 'b (>= 0)' requires 'c (= 2)'", e.message
+ assert_equal %w[a-1 b-1 c-1], loaded_spec_names
end
def test_activate_bin_path_in_debug_mode
@@ -527,35 +528,6 @@ class TestGem < Gem::TestCase
assert_equal expected, Gem.configuration
end
- def test_self_datadir
- foo = nil
-
- Dir.chdir @tempdir do
- FileUtils.mkdir_p "data"
- File.open File.join("data", "foo.txt"), "w" do |fp|
- fp.puts "blah"
- end
-
- foo = util_spec "foo" do |s|
- s.files = %w[data/foo.txt]
- end
-
- install_gem foo
- end
-
- gem "foo"
-
- expected = File.join @gemhome, "gems", foo.full_name, "data", "foo"
-
- assert_equal expected, Gem::Specification.find_by_name("foo").datadir
- end
-
- def test_self_datadir_nonexistent_package
- assert_raise(Gem::MissingSpecError) do
- Gem::Specification.find_by_name("xyzzy").datadir
- end
- end
-
def test_self_default_exec_format
ruby_install_name "ruby" do
assert_equal "%s", Gem.default_exec_format
@@ -615,6 +587,7 @@ class TestGem < Gem::TestCase
end
def test_self_default_sources
+ Gem.remove_instance_variable :@default_sources
assert_equal %w[https://rubygems.org/], Gem.default_sources
end
@@ -1227,6 +1200,8 @@ class TestGem < Gem::TestCase
Gem.sources = nil
Gem.configuration.sources = %w[http://test.example.com/]
assert_equal %w[http://test.example.com/], Gem.sources
+ ensure
+ Gem.configuration.sources = nil
end
def test_try_activate_returns_true_for_activated_specs
@@ -1239,6 +1214,28 @@ class TestGem < Gem::TestCase
assert Gem.try_activate("b"), "try_activate should still return true"
end
+ def test_try_activate_does_not_raise_no_method_error_on_activation_conflict
+ a1 = util_spec "a", "1.0" do |s|
+ s.files << "lib/a/old.rb"
+ end
+
+ a2 = util_spec "a", "2.0" do |s|
+ s.files << "lib/a/old.rb"
+ s.files << "lib/a/new_file.rb"
+ end
+
+ install_specs a1, a2
+
+ # Activate the older version
+ gem "a", "= 1.0"
+
+ # try_activate a file only in the newer version should not raise
+ # NoMethodError on nil (https://bugs.ruby-lang.org/issues/21954)
+ assert_nothing_raised do
+ Gem.try_activate("a/new_file")
+ end
+ end
+
def test_spec_order_is_consistent
b1 = util_spec "b", "1.0"
b2 = util_spec "b", "2.0"
@@ -1308,10 +1305,14 @@ class TestGem < Gem::TestCase
refute Gem.try_activate "nonexistent"
end
- expected = "Ignoring ext-1 because its extensions are not built. " \
- "Try: gem pristine ext --version 1\n"
+ if RUBY_ENGINE == "jruby"
+ assert_equal "", err
+ else
+ expected = "Ignoring ext-1 because its extensions are not built. " \
+ "Try: gem pristine ext --version 1\n"
- assert_equal expected, err
+ assert_equal expected, err
+ end
end
def test_self_use_paths_with_nils
@@ -1659,6 +1660,27 @@ class TestGem < Gem::TestCase
assert_nil Gem.find_unresolved_default_spec("README")
end
+ def test_register_default_spec_new_style_with_native_extension
+ Gem.clear_default_specs
+
+ dlext = RbConfig::CONFIG["DLEXT"]
+
+ new_style = Gem::Specification.new do |spec|
+ spec.name = "my_ext"
+ spec.version = "1.0"
+ spec.files = ["lib/my_ext.rb", "my_ext_core.#{dlext}", "ext/my_ext/my_ext_core.c", "README.md"]
+ spec.require_paths = ["lib"]
+ end
+
+ Gem.register_default_spec new_style
+
+ assert_equal new_style, Gem.find_unresolved_default_spec("my_ext.rb")
+ assert_equal new_style, Gem.find_unresolved_default_spec("my_ext_core")
+ assert_equal new_style, Gem.find_unresolved_default_spec("my_ext_core.#{dlext}")
+ assert_nil Gem.find_unresolved_default_spec("ext/my_ext/my_ext_core.c")
+ assert_nil Gem.find_unresolved_default_spec("README.md")
+ end
+
def test_register_default_spec_old_style_with_folder_starting_with_lib
Gem.clear_default_specs
diff --git a/test/rubygems/test_gem_bundler_version_finder.rb b/test/rubygems/test_gem_bundler_version_finder.rb
index b72670b802..b5ef6293ab 100644
--- a/test/rubygems/test_gem_bundler_version_finder.rb
+++ b/test/rubygems/test_gem_bundler_version_finder.rb
@@ -2,6 +2,7 @@
require_relative "helper"
require "rubygems/bundler_version_finder"
+require "tempfile"
class TestGemBundlerVersionFinder < Gem::TestCase
def setup
@@ -32,6 +33,11 @@ class TestGemBundlerVersionFinder < Gem::TestCase
assert_equal v("1.1.1.1"), bvf.bundler_version
end
+ def test_bundler_version_with_empty_env_var
+ ENV["BUNDLER_VERSION"] = ""
+ assert_nil bvf.bundler_version
+ end
+
def test_bundler_version_with_bundle_update_bundler
ARGV.replace %w[update --bundler]
assert_nil bvf.bundler_version
@@ -51,6 +57,157 @@ class TestGemBundlerVersionFinder < Gem::TestCase
assert_nil bvf.bundler_version
end
+ def test_bundler_version_with_bundle_config
+ config_content = <<~CONFIG
+ BUNDLE_VERSION: "system"
+ CONFIG
+
+ Tempfile.create("bundle_config") do |f|
+ f.write(config_content)
+ f.flush
+
+ bvf.stub(:bundler_global_config_file, f.path) do
+ assert_nil bvf.bundler_version
+ end
+ end
+ end
+
+ def test_bundler_version_with_bundle_config_single_quoted
+ config_with_single_quoted_version = <<~CONFIG
+ BUNDLE_VERSION: 'system'
+ CONFIG
+
+ Tempfile.create("bundle_config") do |f|
+ f.write(config_with_single_quoted_version)
+ f.flush
+
+ bvf.stub(:bundler_global_config_file, f.path) do
+ assert_nil bvf.bundler_version
+ end
+ end
+ end
+
+ def test_bundler_version_with_bundle_config_version
+ ENV["BUNDLER_VERSION"] = "1.1.1.1"
+
+ config_content = <<~CONFIG
+ BUNDLE_VERSION: "1.2.3"
+ CONFIG
+
+ Tempfile.create("bundle_config") do |f|
+ f.write(config_content)
+ f.flush
+
+ bvf.stub(:bundler_global_config_file, f.path) do
+ assert_equal v("1.1.1.1"), bvf.bundler_version
+ end
+ end
+ end
+
+ def test_bundler_version_with_bundle_version_env_system
+ ENV["BUNDLE_VERSION"] = "system"
+
+ bvf.stub(:lockfile_contents, "\n\nBUNDLED WITH\n 1.1.1.1\n") do
+ assert_nil bvf.bundler_version
+ end
+ end
+
+ def test_bundler_version_with_bundle_version_env_overrides_config
+ ENV["BUNDLE_VERSION"] = "2.3.4"
+
+ config_content = <<~CONFIG
+ BUNDLE_VERSION: "1.2.3"
+ CONFIG
+
+ Tempfile.create("bundle_config") do |f|
+ f.write(config_content)
+ f.flush
+
+ bvf.stub(:bundler_global_config_file, f.path) do
+ assert_equal v("2.3.4"), bvf.bundler_version
+ end
+ end
+ end
+
+ def test_bundler_version_with_empty_bundle_version_env
+ ENV["BUNDLE_VERSION"] = ""
+
+ config_content = <<~CONFIG
+ BUNDLE_VERSION: "1.2.3"
+ CONFIG
+
+ Tempfile.create("bundle_config") do |f|
+ f.write(config_content)
+ f.flush
+
+ bvf.stub(:bundler_global_config_file, f.path) do
+ assert_equal v("1.2.3"), bvf.bundler_version
+ end
+ end
+ end
+
+ def test_bundler_version_with_bundle_version_env_lockfile
+ ENV["BUNDLE_VERSION"] = "lockfile"
+
+ bvf.stub(:lockfile_contents, "\n\nBUNDLED WITH\n 1.1.1.1\n") do
+ assert_equal v("1.1.1.1"), bvf.bundler_version
+ end
+ end
+
+ def test_bundler_version_with_bundle_config_version_lockfile
+ config_content = <<~CONFIG
+ BUNDLE_VERSION: "lockfile"
+ CONFIG
+
+ Tempfile.create("bundle_config") do |f|
+ f.write(config_content)
+ f.flush
+
+ bvf.stub(:bundler_global_config_file, f.path) do
+ bvf.stub(:lockfile_contents, "\n\nBUNDLED WITH\n 1.1.1.1\n") do
+ assert_equal v("1.1.1.1"), bvf.bundler_version
+ end
+ end
+ end
+ end
+
+ def test_bundler_version_with_bundle_config_non_existent_file
+ bvf.stub(:bundler_global_config_file, "/non/existent/path") do
+ assert_nil bvf.bundler_version
+ end
+ end
+
+ def test_bundler_version_set_on_local_config
+ config_content = <<~CONFIG
+ BUNDLE_VERSION: "1.2.3"
+ CONFIG
+
+ Tempfile.create("bundle_config") do |f|
+ f.write(config_content)
+ f.flush
+
+ bvf.stub(:bundler_local_config_file, f.path) do
+ assert_equal v("1.2.3"), bvf.bundler_version
+ end
+ end
+ end
+
+ def test_bundler_version_with_bundle_config_without_version
+ config_without_version = <<~CONFIG
+ BUNDLE_JOBS: "8"
+ BUNDLE_GEM__TEST: "minitest"
+ CONFIG
+
+ Tempfile.create("bundle_config") do |f|
+ f.write(config_without_version)
+ f.flush
+
+ bvf.stub(:bundler_global_config_file, f.path) do
+ assert_nil bvf.bundler_version
+ end
+ end
+ end
+
def test_bundler_version_with_lockfile
bvf.stub(:lockfile_contents, "") do
assert_nil bvf.bundler_version
@@ -82,7 +239,7 @@ class TestGemBundlerVersionFinder < Gem::TestCase
def test_deleted_directory
pend "Cannot perform this test on windows" if Gem.win_platform?
- pend "Cannot perform this test on Solaris" if RUBY_PLATFORM.include?("solaris")
+
require "tmpdir"
orig_dir = Dir.pwd
diff --git a/test/rubygems/test_gem_command_manager.rb b/test/rubygems/test_gem_command_manager.rb
index f3848e498d..889d5ce9e6 100644
--- a/test/rubygems/test_gem_command_manager.rb
+++ b/test/rubygems/test_gem_command_manager.rb
@@ -43,7 +43,7 @@ class TestGemCommandManager < Gem::TestCase
assert_kind_of Gem::Commands::SigninCommand, command
end
- def test_find_logout_alias_comamnd
+ def test_find_logout_alias_command
command = @command_manager.find_command "logout"
assert_kind_of Gem::Commands::SignoutCommand, command
@@ -78,7 +78,7 @@ class TestGemCommandManager < Gem::TestCase
message = "Unknown command pish".dup
- if defined?(DidYouMean::SPELL_CHECKERS) && defined?(DidYouMean::Correctable)
+ if e.respond_to?(:corrections)
message << "\nDid you mean? \"push\""
end
@@ -287,47 +287,6 @@ class TestGemCommandManager < Gem::TestCase
assert_equal "foobar.rb", check_options[:args].first
end
- # HACK: move to query command test
- def test_process_args_query
- # capture all query options
- check_options = nil
- @command_manager["query"].when_invoked do |options|
- check_options = options
- true
- end
-
- # check defaults
- Gem::Deprecate.skip_during do
- @command_manager.process_args %w[query]
- end
- assert_nil(check_options[:name])
- assert_equal :local, check_options[:domain]
- assert_equal false, check_options[:details]
-
- # check settings
- check_options = nil
- Gem::Deprecate.skip_during do
- @command_manager.process_args %w[query --name foobar --local --details]
- end
- assert_equal(/foobar/i, check_options[:name])
- assert_equal :local, check_options[:domain]
- assert_equal true, check_options[:details]
-
- # remote domain
- check_options = nil
- Gem::Deprecate.skip_during do
- @command_manager.process_args %w[query --remote]
- end
- assert_equal :remote, check_options[:domain]
-
- # both (local/remote) domains
- check_options = nil
- Gem::Deprecate.skip_during do
- @command_manager.process_args %w[query --both]
- end
- assert_equal :both, check_options[:domain]
- end
-
# HACK: move to update command test
def test_process_args_update
# capture all update options
diff --git a/test/rubygems/test_gem_commands_build_command.rb b/test/rubygems/test_gem_commands_build_command.rb
index d44126d204..9339f41f7c 100644
--- a/test/rubygems/test_gem_commands_build_command.rb
+++ b/test/rubygems/test_gem_commands_build_command.rb
@@ -43,16 +43,6 @@ class TestGemCommandsBuildCommand < Gem::TestCase
assert_includes Gem.platforms, Gem::Platform.local
end
- def test_handle_deprecated_options
- use_ui @ui do
- @cmd.handle_options %w[-C ./test/dir]
- end
-
- assert_equal "WARNING: The \"-C\" option has been deprecated and will be removed in Rubygems 4.0. " \
- "-C is a global flag now. Use `gem -C PATH build GEMSPEC_FILE [options]` instead\n",
- @ui.error
- end
-
def test_options_filename
gemspec_file = File.join(@tempdir, @gem.spec_name)
diff --git a/test/rubygems/test_gem_commands_cert_command.rb b/test/rubygems/test_gem_commands_cert_command.rb
index c173467935..ed1a1c8627 100644
--- a/test/rubygems/test_gem_commands_cert_command.rb
+++ b/test/rubygems/test_gem_commands_cert_command.rb
@@ -31,14 +31,6 @@ class TestGemCommandsCertCommand < Gem::TestCase
@cmd = Gem::Commands::CertCommand.new
@trust_dir = Gem::Security.trust_dir
-
- @cleanup = []
- end
-
- def teardown
- FileUtils.rm_f(@cleanup)
-
- super
end
def test_certificates_matching
@@ -498,7 +490,7 @@ Removed '/CN=alternate/DC=example'
assert_equal "/CN=nobody/DC=example", cert.issuer.to_s
- mask = 0o100600 & (~File.umask)
+ mask = 0o100600 & ~File.umask
assert_equal mask, File.stat(path).mode unless Gem.win_platform?
end
@@ -527,7 +519,7 @@ Removed '/CN=alternate/DC=example'
assert_equal "/CN=nobody/DC=example", cert.issuer.to_s
- mask = 0o100600 & (~File.umask)
+ mask = 0o100600 & ~File.umask
assert_equal mask, File.stat(path).mode unless Gem.win_platform?
end
@@ -559,7 +551,7 @@ Removed '/CN=alternate/DC=example'
assert_equal "/CN=nobody/DC=example", cert.issuer.to_s
- mask = 0o100600 & (~File.umask)
+ mask = 0o100600 & ~File.umask
assert_equal mask, File.stat(path).mode unless Gem.win_platform?
end
@@ -591,7 +583,7 @@ Removed '/CN=alternate/DC=example'
assert_equal "/CN=nobody/DC=example", cert.issuer.to_s
- mask = 0o100600 & (~File.umask)
+ mask = 0o100600 & ~File.umask
assert_equal mask, File.stat(path).mode unless Gem.win_platform?
end
@@ -661,8 +653,7 @@ ERROR: --private-key not specified and ~/.gem/gem-private_key.pem does not exis
assert_equal "/CN=nobody/DC=example", EXPIRED_PUBLIC_CERT.issuer.to_s
- tmp_expired_cert_file = File.join(Dir.tmpdir, File.basename(EXPIRED_PUBLIC_CERT_FILE))
- @cleanup << tmp_expired_cert_file
+ tmp_expired_cert_file = File.join(@tempdir, File.basename(EXPIRED_PUBLIC_CERT_FILE))
File.write(tmp_expired_cert_file, File.read(EXPIRED_PUBLIC_CERT_FILE))
@cmd.handle_options %W[
@@ -694,8 +685,7 @@ ERROR: --private-key not specified and ~/.gem/gem-private_key.pem does not exis
assert_equal "/CN=nobody/DC=example", EXPIRED_PUBLIC_CERT.issuer.to_s
- tmp_expired_cert_file = File.join(Dir.tmpdir, File.basename(EXPIRED_PUBLIC_CERT_FILE))
- @cleanup << tmp_expired_cert_file
+ tmp_expired_cert_file = File.join(@tempdir, File.basename(EXPIRED_PUBLIC_CERT_FILE))
File.write(tmp_expired_cert_file, File.read(EXPIRED_PUBLIC_CERT_FILE))
@cmd.handle_options %W[
diff --git a/test/rubygems/test_gem_commands_environment_command.rb b/test/rubygems/test_gem_commands_environment_command.rb
index 48252d84d4..e27de544c6 100644
--- a/test/rubygems/test_gem_commands_environment_command.rb
+++ b/test/rubygems/test_gem_commands_environment_command.rb
@@ -164,4 +164,8 @@ class TestGemCommandsEnvironmentCommand < Gem::TestCase
assert_equal "#{Gem.platforms.join File::PATH_SEPARATOR}\n", @ui.output
assert_equal "", @ui.error
end
+
+ def test_description_mentions_concurrent_downloads
+ assert_match(/:concurrent_downloads:/, @cmd.description)
+ end
end
diff --git a/test/rubygems/test_gem_commands_exec_command.rb b/test/rubygems/test_gem_commands_exec_command.rb
index b9d5888068..b949cd34a6 100644
--- a/test/rubygems/test_gem_commands_exec_command.rb
+++ b/test/rubygems/test_gem_commands_exec_command.rb
@@ -370,8 +370,11 @@ class TestGemCommandsExecCommand < Gem::TestCase
util_clear_gems
use_ui @ui do
- @cmd.invoke "a:2"
- assert_equal "a-2 foo\n", @ui.output
+ e = assert_raise Gem::MockGemUi::TermError do
+ @cmd.invoke "a:2"
+ end
+ assert_equal 1, e.exit_code
+ assert_equal "ERROR: Ambiguous which executable from gem `a` should be run: the options are [\"bar\", \"foo\"], specify one via COMMAND, and use `-g` and `-v` to specify gem and version\n", @ui.error
end
end
@@ -853,4 +856,33 @@ class TestGemCommandsExecCommand < Gem::TestCase
assert_equal %w[a-1.1.a], @installed_specs.map(&:full_name)
end
end
+
+ def test_install_dependency_resolution_error
+ spec_fetcher do |fetcher|
+ fetcher.gem "a", 2 do |s|
+ s.executables = %w[a]
+ s.add_dependency "b", "~> 1.0"
+ s.add_dependency "c", "~> 1.0"
+ end
+ fetcher.gem "b", 1 do |s|
+ s.add_dependency "d", "= 1.0"
+ end
+ fetcher.gem "c", 1 do |s|
+ s.add_dependency "d", "= 2.0"
+ end
+ fetcher.gem "d", 1
+ fetcher.gem "d", 2
+ end
+
+ util_clear_gems
+
+ use_ui @ui do
+ e = assert_raise Gem::MockGemUi::TermError do
+ @cmd.invoke "a:2"
+ end
+ assert_equal 2, e.exit_code
+ end
+
+ assert_match(/ERROR:.*Error installing a:/, @ui.error)
+ end
end
diff --git a/test/rubygems/test_gem_commands_fetch_command.rb b/test/rubygems/test_gem_commands_fetch_command.rb
index 84fad08fd6..e673e391fe 100644
--- a/test/rubygems/test_gem_commands_fetch_command.rb
+++ b/test/rubygems/test_gem_commands_fetch_command.rb
@@ -157,7 +157,7 @@ class TestGemCommandsFetchCommand < Gem::TestCase
execute_with_term_error
msg = "ERROR: Can't use --version with multiple gems. You can specify multiple gems with" \
- " version requirements using `gem fetch 'my_gem:1.0.0' 'my_other_gem:~>2.0.0'`"
+ " version requirements using `gem fetch 'my_gem:1.0.0' 'my_other_gem:>=2'`"
assert_empty @ui.output
assert_equal msg, @ui.error.chomp
diff --git a/test/rubygems/test_gem_commands_help_command.rb b/test/rubygems/test_gem_commands_help_command.rb
index 01ab4aab2f..4ce7285d1f 100644
--- a/test/rubygems/test_gem_commands_help_command.rb
+++ b/test/rubygems/test_gem_commands_help_command.rb
@@ -36,7 +36,7 @@ class TestGemCommandsHelpCommand < Gem::TestCase
def test_gem_help_build
util_gem "build" do |out, err|
- assert_match(/-C PATH *Run as if gem build was started in <PATH>/, out)
+ assert_match(/--platform PLATFORM\s+Specify the platform of gem to build/, out)
assert_equal "", err
end
end
diff --git a/test/rubygems/test_gem_commands_info_command.rb b/test/rubygems/test_gem_commands_info_command.rb
index f020d380d2..dab7cfb836 100644
--- a/test/rubygems/test_gem_commands_info_command.rb
+++ b/test/rubygems/test_gem_commands_info_command.rb
@@ -13,7 +13,7 @@ class TestGemCommandsInfoCommand < Gem::TestCase
def gem(name, version = "1.0")
spec = quick_gem name do |gem|
gem.summary = "test gem"
- gem.homepage = "https://github.com/rubygems/rubygems"
+ gem.homepage = "https://github.com/ruby/rubygems"
gem.files = %W[lib/#{name}.rb Rakefile]
gem.authors = ["Colby", "Jack"]
gem.license = "MIT"
diff --git a/test/rubygems/test_gem_commands_install_command.rb b/test/rubygems/test_gem_commands_install_command.rb
index 4e49f52b4c..d75ba349f9 100644
--- a/test/rubygems/test_gem_commands_install_command.rb
+++ b/test/rubygems/test_gem_commands_install_command.rb
@@ -119,11 +119,7 @@ class TestGemCommandsInstallCommand < Gem::TestCase
end
end
- expected = <<-EXPECTED
-ERROR: Could not find a valid gem 'bar' (= 0.5) (required by 'foo' (>= 0)) in any repository
- EXPECTED
-
- assert_equal expected, @ui.error
+ assert_match(/ERROR:.*foo.*bar/m, @ui.error)
end
def test_execute_local_dependency_nonexistent_ignore_dependencies
@@ -303,11 +299,7 @@ ERROR: Could not find a valid gem 'bar' (= 0.5) (required by 'foo' (>= 0)) in a
assert_equal 2, e.exit_code
end
- expected = <<-EXPECTED
-ERROR: Could not find a valid gem 'bar' (= 0.5) (required by 'foo' (>= 0)) in any repository
- EXPECTED
-
- assert_equal expected, @ui.error
+ assert_match(/ERROR:.*foo.*bar/m, @ui.error)
end
def test_execute_http_proxy
@@ -647,17 +639,10 @@ ERROR: Possible alternatives: non_existent_with_hint
@cmd.options[:args] = %w[a]
use_ui @ui do
- # Don't use Dir.chdir with a block, it warnings a lot because
- # of a downstream Dir.chdir with a block
- old = Dir.getwd
-
- begin
- Dir.chdir @tempdir
+ Dir.chdir @tempdir do
assert_raise Gem::MockGemUi::SystemExitException, @ui.error do
@cmd.execute
end
- ensure
- Dir.chdir old
end
end
@@ -684,17 +669,10 @@ ERROR: Possible alternatives: non_existent_with_hint
@cmd.options[:args] = %w[a]
use_ui @ui do
- # Don't use Dir.chdir with a block, it warnings a lot because
- # of a downstream Dir.chdir with a block
- old = Dir.getwd
-
- begin
- Dir.chdir @tempdir
+ Dir.chdir @tempdir do
assert_raise Gem::MockGemUi::SystemExitException, @ui.error do
@cmd.execute
end
- ensure
- Dir.chdir old
end
end
@@ -720,17 +698,10 @@ ERROR: Possible alternatives: non_existent_with_hint
@cmd.options[:args] = %w[a]
use_ui @ui do
- # Don't use Dir.chdir with a block, it warnings a lot because
- # of a downstream Dir.chdir with a block
- old = Dir.getwd
-
- begin
- Dir.chdir @tempdir
+ Dir.chdir @tempdir do
assert_raise Gem::MockGemUi::SystemExitException, @ui.error do
@cmd.execute
end
- ensure
- Dir.chdir old
end
end
@@ -901,7 +872,7 @@ ERROR: Possible alternatives: non_existent_with_hint
assert_empty @cmd.installed_specs
msg = "ERROR: Can't use --version with multiple gems. You can specify multiple gems with" \
- " version requirements using `gem install 'my_gem:1.0.0' 'my_other_gem:~>2.0.0'`"
+ " version requirements using `gem install 'my_gem:1.0.0' 'my_other_gem:>=2'`"
assert_empty @ui.output
assert_equal msg, @ui.error.chomp
@@ -1005,6 +976,38 @@ ERROR: Possible alternatives: non_existent_with_hint
assert_equal %W[a-3-#{local}], @cmd.installed_specs.map(&:full_name)
end
+ def test_install_gem_platform_specificity_match
+ util_set_arch "arm64-darwin-20"
+
+ spec_fetcher do |fetcher|
+ %w[ruby universal-darwin universal-darwin-20 x64-darwin-20 arm64-darwin-20].each do |platform|
+ fetcher.download "a", 3 do |s|
+ s.platform = platform
+ end
+ end
+ end
+
+ @cmd.install_gem "a", ">= 0"
+
+ assert_equal %w[a-3-arm64-darwin-20], @cmd.installed_specs.map(&:full_name)
+ end
+
+ def test_install_gem_platform_specificity_match_reverse_order
+ util_set_arch "arm64-darwin-20"
+
+ spec_fetcher do |fetcher|
+ %w[ruby universal-darwin universal-darwin-20 x64-darwin-20 arm64-darwin-20].reverse_each do |platform|
+ fetcher.download "a", 3 do |s|
+ s.platform = platform
+ end
+ end
+ end
+
+ @cmd.install_gem "a", ">= 0"
+
+ assert_equal %w[a-3-arm64-darwin-20], @cmd.installed_specs.map(&:full_name)
+ end
+
def test_install_gem_ignore_dependencies_specific_file
spec = util_spec "a", 2
@@ -1214,6 +1217,30 @@ ERROR: Possible alternatives: non_existent_with_hint
assert_match "Installing a (2)", @ui.output
end
+ def test_execute_installs_from_a_gemdeps_with_prerelease
+ spec_fetcher do |fetcher|
+ fetcher.download "a", 1
+ fetcher.download "a", "2.a"
+ end
+
+ File.open @gemdeps, "w" do |f|
+ f << "gem 'a'"
+ end
+
+ @cmd.handle_options %w[--prerelease]
+ @cmd.options[:gemdeps] = @gemdeps
+
+ use_ui @ui do
+ assert_raise Gem::MockGemUi::SystemExitException, @ui.error do
+ @cmd.execute
+ end
+ end
+
+ assert_equal %w[a-2.a], @cmd.installed_specs.map(&:full_name)
+
+ assert_match "Installing a (2.a)", @ui.output
+ end
+
def test_execute_installs_deps_a_gemdeps
spec_fetcher do |fetcher|
fetcher.download "q", "1.0"
@@ -1548,4 +1575,63 @@ ERROR: Possible alternatives: non_existent_with_hint
assert_includes @ui.output, "A new release of RubyGems is available: 1.2.3 → 2.0.0!"
end
end
+
+ def test_pass_down_the_job_option_to_make
+ gemspec = nil
+
+ spec_fetcher do |fetcher|
+ fetcher.gem "a", 2 do |spec|
+ gemspec = spec
+
+ extconf_path = "#{spec.gem_dir}/extconf.rb"
+
+ write_file(extconf_path) do |io|
+ io.puts "require 'mkmf'"
+ io.puts "create_makefile '#{spec.name}'"
+ end
+
+ spec.extensions = "extconf.rb"
+ end
+ end
+
+ use_ui @ui do
+ assert_raise Gem::MockGemUi::SystemExitException, @ui.error do
+ @cmd.invoke "a", "-j4"
+ end
+ end
+
+ gem_make_out = File.read(File.join(gemspec.extension_dir, "gem_make.out"))
+ if vc_windows? && nmake_found?
+ refute_includes(gem_make_out, " -j4")
+ else
+ assert_includes(gem_make_out, "make -j4")
+ end
+ end
+
+ def test_execute_bindir_with_nonexistent_parent_dirs
+ spec_fetcher do |fetcher|
+ fetcher.gem "a", 2 do |s|
+ s.executables = %w[a_bin]
+ s.files = %w[bin/a_bin]
+ end
+ end
+
+ @cmd.options[:args] = %w[a]
+
+ nested_bin_dir = File.join(@tempdir, "not", "exists")
+ refute_directory_exists nested_bin_dir, "Nested bin directory should not exist yet"
+
+ @cmd.options[:bin_dir] = nested_bin_dir
+
+ use_ui @ui do
+ assert_raise Gem::MockGemUi::SystemExitException, @ui.error do
+ @cmd.execute
+ end
+ end
+
+ assert_directory_exists nested_bin_dir, "Nested bin directory should exist now"
+ assert_path_exist File.join(nested_bin_dir, "a_bin")
+
+ assert_equal %w[a-2], @cmd.installed_specs.map(&:full_name)
+ end
end
diff --git a/test/rubygems/test_gem_commands_open_command.rb b/test/rubygems/test_gem_commands_open_command.rb
index d9e518048c..addc7427e2 100644
--- a/test/rubygems/test_gem_commands_open_command.rb
+++ b/test/rubygems/test_gem_commands_open_command.rb
@@ -21,6 +21,8 @@ class TestGemCommandsOpenCommand < Gem::TestCase
end
def test_execute
+ omit "JRuby on Windows spawns the editor with a different cwd" if Gem.win_platform? && Gem.java_platform?
+
@cmd.options[:args] = %w[foo]
@cmd.options[:editor] = (ruby_with_rubygems_in_load_path + ["-e", "puts(ARGV,Dir.pwd)", "--"]).join(" ")
diff --git a/test/rubygems/test_gem_commands_owner_command.rb b/test/rubygems/test_gem_commands_owner_command.rb
index bc4f13ff2a..f6d4d03f84 100644
--- a/test/rubygems/test_gem_commands_owner_command.rb
+++ b/test/rubygems/test_gem_commands_owner_command.rb
@@ -32,9 +32,12 @@ class TestGemCommandsOwnerCommand < Gem::TestCase
- email: user1@example.com
id: 1
handle: user1
+ role: owner
- email: user2@example.com
+ role: maintainer
- id: 3
handle: user3
+ role: owner
- id: 4
EOF
@@ -48,14 +51,14 @@ EOF
assert_equal Gem.configuration.rubygems_api_key, @stub_fetcher.last_request["Authorization"]
assert_match(/Owners for gem: freewill/, @stub_ui.output)
- assert_match(/- user1@example.com/, @stub_ui.output)
- assert_match(/- user2@example.com/, @stub_ui.output)
- assert_match(/- user3/, @stub_ui.output)
+ assert_match(/- user1@example.com \(owner\)/, @stub_ui.output)
+ assert_match(/- user2@example.com \(maintainer\)/, @stub_ui.output)
+ assert_match(/- user3 \(owner\)/, @stub_ui.output)
assert_match(/- 4/, @stub_ui.output)
end
def test_show_owners_dont_load_objects
- pend "testing a psych-only API" unless defined?(::Psych::DisallowedClass)
+ Gem.load_yaml
response = <<EOF
---
@@ -386,9 +389,10 @@ EOF
end
end
- assert_match "You have enabled multi-factor authentication. Please visit #{@stub_fetcher.webauthn_url_with_port(server.port)} " \
+ assert_match "You have enabled multi-factor authentication. Please visit the following URL " \
"to authenticate via security device. If you can't verify using WebAuthn but have OTP enabled, " \
"you can re-run the gem signin command with the `--otp [your_code]` option.", @stub_ui.output
+ assert_match @stub_fetcher.webauthn_url_with_port(server.port), @stub_ui.output
assert_match "You are verified with a security device. You may close the browser window.", @stub_ui.output
assert_equal "Uvh6T57tkWuUnWYo", @stub_fetcher.last_request["OTP"]
assert_match response_success, @stub_ui.output
@@ -412,10 +416,12 @@ EOF
end
end
- assert_match @stub_fetcher.last_request["Authorization"], Gem.configuration.rubygems_api_key
- assert_match "You have enabled multi-factor authentication. Please visit #{@stub_fetcher.webauthn_url_with_port(server.port)} " \
+ webauthn_verification_request = @stub_fetcher.requests.find {|req| req.path == "/api/v1/webauthn_verification" }
+ assert_match webauthn_verification_request["Authorization"], Gem.configuration.rubygems_api_key
+ assert_match "You have enabled multi-factor authentication. Please visit the following URL " \
"to authenticate via security device. If you can't verify using WebAuthn but have OTP enabled, " \
"you can re-run the gem signin command with the `--otp [your_code]` option.", @stub_ui.output
+ assert_match @stub_fetcher.webauthn_url_with_port(server.port), @stub_ui.output
assert_match "ERROR: Security device verification failed: Something went wrong", @stub_ui.error
refute_match "You are verified with a security device. You may close the browser window.", @stub_ui.output
refute_match response_success, @stub_ui.output
@@ -435,9 +441,10 @@ EOF
end
end
- assert_match "You have enabled multi-factor authentication. Please visit #{@stub_fetcher.webauthn_url_with_port(server.port)} " \
+ assert_match "You have enabled multi-factor authentication. Please visit the following URL " \
"to authenticate via security device. If you can't verify using WebAuthn but have OTP enabled, you can re-run the gem signin " \
"command with the `--otp [your_code]` option.", @stub_ui.output
+ assert_match @stub_fetcher.webauthn_url_with_port(server.port), @stub_ui.output
assert_match "You are verified with a security device. You may close the browser window.", @stub_ui.output
assert_equal "Uvh6T57tkWuUnWYo", @stub_fetcher.last_request["OTP"]
assert_match response_success, @stub_ui.output
@@ -463,16 +470,17 @@ EOF
end
assert_match @stub_fetcher.last_request["Authorization"], Gem.configuration.rubygems_api_key
- assert_match "You have enabled multi-factor authentication. Please visit #{@stub_fetcher.webauthn_url_with_port(server.port)} " \
+ assert_match "You have enabled multi-factor authentication. Please visit the following URL " \
"to authenticate via security device. If you can't verify using WebAuthn but have OTP enabled, you can re-run the gem signin " \
"command with the `--otp [your_code]` option.", @stub_ui.output
+ assert_match @stub_fetcher.webauthn_url_with_port(server.port), @stub_ui.output
assert_match "ERROR: Security device verification failed: The token in the link you used has either expired " \
"or been used already.", @stub_ui.error
refute_match "You are verified with a security device. You may close the browser window.", @stub_ui.output
refute_match response_success, @stub_ui.output
end
- def test_remove_owners_unathorized_api_key
+ def test_remove_owners_unauthorized_api_key
response_forbidden = "The API key doesn't have access"
response_success = "Owner removed successfully."
@@ -537,7 +545,7 @@ EOF
assert_empty reused_otp_codes
end
- def test_add_owners_unathorized_api_key
+ def test_add_owners_unauthorized_api_key
response_forbidden = "The API key doesn't have access"
response_success = "Owner added successfully."
diff --git a/test/rubygems/test_gem_commands_pristine_command.rb b/test/rubygems/test_gem_commands_pristine_command.rb
index 46c06db014..0ea140897c 100644
--- a/test/rubygems/test_gem_commands_pristine_command.rb
+++ b/test/rubygems/test_gem_commands_pristine_command.rb
@@ -125,8 +125,8 @@ class TestGemCommandsPristineCommand < Gem::TestCase
@cmd.execute
end
- assert File.exist?(gem_bin)
- assert File.exist?(gem_stub)
+ assert_path_exist gem_bin
+ assert_path_exist gem_stub
out = @ui.output.split "\n"
@@ -248,7 +248,13 @@ class TestGemCommandsPristineCommand < Gem::TestCase
end
refute_includes @ui.output, "Restored #{a.full_name}"
- assert_includes @ui.output, "Restored #{b.full_name}"
+
+ if Gem.java_platform?
+ refute_includes @ui.output, "Restored #{b.full_name}"
+ assert_includes @ui.output, "No gems with missing extensions to restore"
+ else
+ assert_includes @ui.output, "Restored #{b.full_name}"
+ end
end
def test_execute_no_extension
@@ -537,8 +543,8 @@ class TestGemCommandsPristineCommand < Gem::TestCase
@cmd.execute
end
- assert File.exist? gem_exec
- refute File.exist? gem_lib
+ assert_path_exist gem_exec
+ assert_path_not_exist gem_lib
end
def test_execute_only_plugins
@@ -572,9 +578,9 @@ class TestGemCommandsPristineCommand < Gem::TestCase
@cmd.execute
end
- refute File.exist? gem_exec
- assert File.exist? gem_plugin
- refute File.exist? gem_lib
+ assert_path_not_exist gem_exec
+ assert_path_exist gem_plugin
+ assert_path_not_exist gem_lib
end
def test_execute_bindir
@@ -606,8 +612,8 @@ class TestGemCommandsPristineCommand < Gem::TestCase
@cmd.execute
end
- refute File.exist? gem_exec
- assert File.exist? gem_bindir
+ assert_path_not_exist gem_exec
+ assert_path_exist gem_bindir
end
def test_execute_unknown_gem_at_remote_source
@@ -659,6 +665,42 @@ class TestGemCommandsPristineCommand < Gem::TestCase
refute_includes "ruby_executable_hooks", File.read(exe)
end
+ def test_execute_default_gem_and_regular_gem
+ a_default = new_default_spec("a", "1.2.0")
+
+ a = util_spec "a" do |s|
+ s.extensions << "ext/a/extconf.rb"
+ end
+
+ ext_path = File.join @tempdir, "ext", "a", "extconf.rb"
+ write_file ext_path do |io|
+ io.write <<-'RUBY'
+ File.open "Makefile", "w" do |f|
+ f.puts "clean:\n\techo cleaned\n"
+ f.puts "all:\n\techo built\n"
+ f.puts "install:\n\techo installed\n"
+ end
+ RUBY
+ end
+
+ install_default_gems a_default
+ install_gem a
+
+ # Remove the extension files for a
+ FileUtils.rm_rf a.gem_build_complete_path
+
+ @cmd.options[:args] = %w[a]
+
+ use_ui @ui do
+ @cmd.execute
+ end
+
+ assert_includes @ui.output, "Restored #{a.full_name}"
+
+ # Check extension files for a were restored
+ assert_path_exist a.gem_build_complete_path
+ end
+
def test_execute_multi_platform
a = util_spec "a" do |s|
s.extensions << "ext/a/extconf.rb"
diff --git a/test/rubygems/test_gem_commands_push_command.rb b/test/rubygems/test_gem_commands_push_command.rb
index 2d0190b49f..ada95e89b4 100644
--- a/test/rubygems/test_gem_commands_push_command.rb
+++ b/test/rubygems/test_gem_commands_push_command.rb
@@ -115,40 +115,116 @@ class TestGemCommandsPushCommand < Gem::TestCase
assert_equal Gem::Net::HTTP::Post, @fetcher.last_request.class
content_length = @fetcher.last_request["Content-Length"].to_i
assert_equal content_length, @fetcher.last_request.body.length
- assert_equal "multipart", @fetcher.last_request.main_type, @fetcher.last_request.content_type
- assert_equal "form-data", @fetcher.last_request.sub_type
- assert_include @fetcher.last_request.type_params, "boundary"
- boundary = @fetcher.last_request.type_params["boundary"]
+ assert_attestation_multipart Gem.read_binary("#{@path}.sigstore.json")
+ end
- parts = @fetcher.last_request.body.split(/(?:\r\n|\A)--#{Regexp.quote(boundary)}(?:\r\n|--)/m)
- refute_empty parts
- assert_empty parts[0]
- parts.shift # remove the first empty part
+ def test_execute_attestation_auto
+ omit if RUBY_ENGINE == "jruby"
- p1 = parts.shift
- p2 = parts.shift
- assert_equal "\r\n", parts.shift
- assert_empty parts
+ ENV["GITHUB_ACTIONS"] = "true"
+ begin
+ @response = "Successfully registered gem: freewill (1.0.0)"
+ @fetcher.data["#{Gem.host}/api/v1/gems"] = HTTPResponseFactory.create(body: @response, code: 200, msg: "OK")
- assert_equal [
- "Content-Disposition: form-data; name=\"gem\"; filename=\"#{@path}\"",
- "Content-Type: application/octet-stream",
- nil,
- Gem.read_binary(@path),
- ].join("\r\n").b, p1
- assert_equal [
- "Content-Disposition: form-data; name=\"attestations\"",
- nil,
- "[#{Gem.read_binary("#{@path}.sigstore.json")}]",
- ].join("\r\n").b, p2
+ attestation_path = "#{@path}.sigstore.json"
+ attestation_content = "auto-attestation"
+ File.write(attestation_path, attestation_content)
+ @cmd.options[:args] = [@path]
+
+ @cmd.stub(:attest!, attestation_path) do
+ @cmd.execute
+ end
+
+ assert_equal Gem::Net::HTTP::Post, @fetcher.last_request.class
+ content_length = @fetcher.last_request["Content-Length"].to_i
+ assert_equal content_length, @fetcher.last_request.body.length
+ assert_attestation_multipart attestation_content
+ ensure
+ ENV.delete("GITHUB_ACTIONS")
+ end
end
- def test_execute_allowed_push_host
+ def test_execute_attestation_fallback
+ omit if RUBY_ENGINE == "jruby"
+
+ ENV["GITHUB_ACTIONS"] = "true"
+ begin
+ @response = "Successfully registered gem: freewill (1.0.0)"
+ @fetcher.data["#{Gem.host}/api/v1/gems"] = HTTPResponseFactory.create(body: @response, code: 200, msg: "OK")
+
+ @cmd.options[:args] = [@path]
+
+ @cmd.stub(:attest!, proc { raise Gem::Exception, "boom" }) do
+ use_ui @ui do
+ @cmd.execute
+ end
+ end
+
+ assert_match "Failed to push with attestation, retrying without attestation.", @ui.error
+ assert_equal Gem::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"]
+ ensure
+ ENV.delete("GITHUB_ACTIONS")
+ end
+ end
+
+ def test_execute_attestation_skipped_on_non_rubygems_host
@spec, @path = util_gem "freebird", "1.0.1" do |spec|
spec.metadata["allowed_push_host"] = "https://privategemserver.example"
end
+ @response = "Successfully registered gem: freebird (1.0.1)"
+ @fetcher.data["#{@spec.metadata["allowed_push_host"]}/api/v1/gems"] = HTTPResponseFactory.create(body: @response, code: 200, msg: "OK")
+
+ @cmd.options[:args] = [@path]
+
+ attest_called = false
+ @cmd.stub(:attest!, proc { attest_called = true }) do
+ @cmd.execute
+ end
+
+ refute attest_called, "attest! should not be called for non-rubygems.org hosts"
+ assert_equal Gem::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
+
+ def test_execute_attestation_skipped_on_jruby
@response = "Successfully registered gem: freewill (1.0.0)"
+ @fetcher.data["#{Gem.host}/api/v1/gems"] = HTTPResponseFactory.create(body: @response, code: 200, msg: "OK")
+
+ @cmd.options[:args] = [@path]
+
+ attest_called = false
+ engine = RUBY_ENGINE
+ Object.send :remove_const, :RUBY_ENGINE
+ Object.const_set :RUBY_ENGINE, "jruby"
+
+ begin
+ @cmd.stub(:attest!, proc { attest_called = true }) do
+ @cmd.execute
+ end
+
+ refute attest_called, "attest! should not be called on JRuby"
+ assert_equal Gem::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"]
+ ensure
+ Object.send :remove_const, :RUBY_ENGINE
+ Object.const_set :RUBY_ENGINE, engine
+ end
+ end
+
+ 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: freebird (1.0.1)"
@fetcher.data["#{@spec.metadata["allowed_push_host"]}/api/v1/gems"] = HTTPResponseFactory.create(body: @response, code: 200, msg: "OK")
@fetcher.data["#{Gem.host}/api/v1/gems"] =
["fail", 500, "Internal Server Error"]
@@ -477,15 +553,17 @@ class TestGemCommandsPushCommand < Gem::TestCase
end
end
- assert_match "You have enabled multi-factor authentication. Please visit #{@fetcher.webauthn_url_with_port(server.port)} " \
+ assert_match "You have enabled multi-factor authentication. Please visit the following URL " \
"to authenticate via security device. If you can't verify using WebAuthn but have OTP enabled, " \
"you can re-run the gem signin command with the `--otp [your_code]` option.", @ui.output
+ assert_match @fetcher.webauthn_url_with_port(server.port), @ui.output
assert_match "You are verified with a security device. You may close the browser window.", @ui.output
assert_equal "Uvh6T57tkWuUnWYo", @fetcher.last_request["OTP"]
assert_match response_success, @ui.output
end
def test_with_webauthn_enabled_failure
+ pend "Flaky on TruffleRuby" if RUBY_ENGINE == "truffleruby"
response_success = "Successfully registered gem: freewill (1.0.0)"
server = Gem::MockTCPServer.new
error = Gem::WebauthnVerificationError.new("Something went wrong")
@@ -505,9 +583,10 @@ class TestGemCommandsPushCommand < Gem::TestCase
assert_equal 1, error.exit_code
assert_match @fetcher.last_request["Authorization"], Gem.configuration.rubygems_api_key
- assert_match "You have enabled multi-factor authentication. Please visit #{@fetcher.webauthn_url_with_port(server.port)} " \
+ assert_match "You have enabled multi-factor authentication. Please visit the following URL " \
"to authenticate via security device. If you can't verify using WebAuthn but have OTP enabled, " \
"you can re-run the gem signin command with the `--otp [your_code]` option.", @ui.output
+ assert_match @fetcher.webauthn_url_with_port(server.port), @ui.output
assert_match "ERROR: Security device verification failed: Something went wrong", @ui.error
refute_match "You are verified with a security device. You may close the browser window.", @ui.output
refute_match response_success, @ui.output
@@ -527,9 +606,10 @@ class TestGemCommandsPushCommand < Gem::TestCase
end
end
- assert_match "You have enabled multi-factor authentication. Please visit #{@fetcher.webauthn_url_with_port(server.port)} " \
+ assert_match "You have enabled multi-factor authentication. Please visit the following URL " \
"to authenticate via security device. If you can't verify using WebAuthn but have OTP enabled, " \
"you can re-run the gem signin command with the `--otp [your_code]` option.", @ui.output
+ assert_match @fetcher.webauthn_url_with_port(server.port), @ui.output
assert_match "You are verified with a security device. You may close the browser window.", @ui.output
assert_equal "Uvh6T57tkWuUnWYo", @fetcher.last_request["OTP"]
assert_match response_success, @ui.output
@@ -553,16 +633,17 @@ class TestGemCommandsPushCommand < Gem::TestCase
assert_equal 1, error.exit_code
assert_match @fetcher.last_request["Authorization"], Gem.configuration.rubygems_api_key
- assert_match "You have enabled multi-factor authentication. Please visit #{@fetcher.webauthn_url_with_port(server.port)} " \
- "to authenticate via security device. If you can't verify using WebAuthn but have OTP enabled, you can re-run the gem signin " \
- "command with the `--otp [your_code]` option.", @ui.output
+ assert_match "You have enabled multi-factor authentication. Please visit the following URL " \
+ "to authenticate via security device. If you can't verify using WebAuthn but have OTP enabled, " \
+ "you can re-run the gem signin command with the `--otp [your_code]` option.", @ui.output
+ assert_match @fetcher.webauthn_url_with_port(server.port), @ui.output
assert_match "ERROR: Security device verification failed: The token in the link you used has either expired " \
"or been used already.", @ui.error
refute_match "You are verified with a security device. You may close the browser window.", @ui.output
refute_match response_success, @ui.output
end
- def test_sending_gem_unathorized_api_key_with_mfa_enabled
+ def test_sending_gem_unauthorized_api_key_with_mfa_enabled
response_mfa_enabled = "You have enabled multifactor authentication but your request doesn't have the correct OTP code. Please check it and retry."
response_forbidden = "The API key doesn't have access"
response_success = "Successfully registered gem: freewill (1.0.0)"
@@ -638,6 +719,35 @@ class TestGemCommandsPushCommand < Gem::TestCase
private
+ def assert_attestation_multipart(attestation_payload)
+ assert_equal "multipart", @fetcher.last_request.main_type, @fetcher.last_request.content_type
+ assert_equal "form-data", @fetcher.last_request.sub_type
+ assert_include @fetcher.last_request.type_params, "boundary"
+ boundary = @fetcher.last_request.type_params["boundary"]
+
+ parts = @fetcher.last_request.body.split(/(?:\r\n|\A)--#{Regexp.quote(boundary)}(?:\r\n|--)/m)
+ refute_empty parts
+ assert_empty parts[0]
+ parts.shift # remove the first empty part
+
+ p1 = parts.shift
+ p2 = parts.shift
+ assert_equal "\r\n", parts.shift
+ assert_empty parts
+
+ assert_equal [
+ "Content-Disposition: form-data; name=\"gem\"; filename=\"#{@path}\"",
+ "Content-Type: application/octet-stream",
+ nil,
+ Gem.read_binary(@path),
+ ].join("\r\n").b, p1
+ assert_equal [
+ "Content-Disposition: form-data; name=\"attestations\"",
+ nil,
+ "[#{attestation_payload}]",
+ ].join("\r\n").b, p2
+ end
+
def singleton_gem_class
class << Gem; self; end
end
diff --git a/test/rubygems/test_gem_commands_query_command.rb b/test/rubygems/test_gem_commands_query_command.rb
deleted file mode 100644
index 8e590df124..0000000000
--- a/test/rubygems/test_gem_commands_query_command.rb
+++ /dev/null
@@ -1,830 +0,0 @@
-# frozen_string_literal: true
-
-require_relative "helper"
-require "rubygems/commands/query_command"
-
-module TestGemCommandsQueryCommandSetup
- def setup
- super
-
- @cmd = Gem::Commands::QueryCommand.new
-
- @specs = add_gems_to_fetcher
- @stub_ui = Gem::MockGemUi.new
- @stub_fetcher = Gem::FakeFetcher.new
-
- @stub_fetcher.data["#{@gem_repo}Marshal.#{Gem.marshal_version}"] = proc do
- raise Gem::RemoteFetcher::FetchError
- end
- end
-end
-
-class TestGemCommandsQueryCommandWithInstalledGems < Gem::TestCase
- include TestGemCommandsQueryCommandSetup
-
- def test_execute
- spec_fetcher(&:legacy_platform)
-
- @cmd.handle_options %w[-r]
-
- use_ui @stub_ui do
- @cmd.execute
- end
-
- expected = <<-EOF
-
-*** REMOTE GEMS ***
-
-a (2)
-pl (1 i386-linux)
- EOF
-
- assert_equal expected, @stub_ui.output
- assert_equal "", @stub_ui.error
- end
-
- def test_execute_all
- spec_fetcher(&:legacy_platform)
-
- @cmd.handle_options %w[-r --all]
-
- use_ui @stub_ui do
- @cmd.execute
- end
-
- expected = <<-EOF
-
-*** REMOTE GEMS ***
-
-a (2, 1)
-pl (1 i386-linux)
- EOF
-
- assert_equal expected, @stub_ui.output
- assert_equal "", @stub_ui.error
- end
-
- def test_execute_all_prerelease
- spec_fetcher(&:legacy_platform)
-
- @cmd.handle_options %w[-r --all --prerelease]
-
- use_ui @stub_ui do
- @cmd.execute
- end
-
- expected = <<-EOF
-
-*** REMOTE GEMS ***
-
-a (3.a, 2, 1)
-pl (1 i386-linux)
- EOF
-
- assert_equal expected, @stub_ui.output
- assert_equal "", @stub_ui.error
- end
-
- def test_execute_details
- spec_fetcher do |fetcher|
- fetcher.spec "a", 2 do |s|
- s.summary = "This is a lot of text. " * 4
- s.authors = ["Abraham Lincoln", "Hirohito"]
- s.homepage = "http://a.example.com/"
- end
-
- fetcher.legacy_platform
- end
-
- @cmd.handle_options %w[-r -d]
-
- use_ui @stub_ui do
- @cmd.execute
- end
-
- expected = <<-EOF
-
-*** REMOTE GEMS ***
-
-a (2)
- Authors: Abraham Lincoln, Hirohito
- Homepage: http://a.example.com/
-
- This is a lot of text. This is a lot of text. This is a lot of text.
- This is a lot of text.
-
-pl (1)
- Platform: i386-linux
- Author: A User
- Homepage: http://example.com
-
- this is a summary
- EOF
-
- assert_equal expected, @stub_ui.output
- assert_equal "", @stub_ui.error
- end
-
- def test_execute_details_cleans_text
- spec_fetcher do |fetcher|
- fetcher.spec "a", 2 do |s|
- s.summary = "This is a lot of text. " * 4
- s.authors = ["Abraham Lincoln \x01", "\x02 Hirohito"]
- s.homepage = "http://a.example.com/\x03"
- end
-
- fetcher.legacy_platform
- end
-
- @cmd.handle_options %w[-r -d]
-
- use_ui @stub_ui do
- @cmd.execute
- end
-
- expected = <<-EOF
-
-*** REMOTE GEMS ***
-
-a (2)
- Authors: Abraham Lincoln ., . Hirohito
- Homepage: http://a.example.com/.
-
- This is a lot of text. This is a lot of text. This is a lot of text.
- This is a lot of text.
-
-pl (1)
- Platform: i386-linux
- Author: A User
- Homepage: http://example.com
-
- this is a summary
- EOF
-
- assert_equal expected, @stub_ui.output
- assert_equal "", @stub_ui.error
- end
-
- def test_execute_details_truncates_summary
- spec_fetcher do |fetcher|
- fetcher.spec "a", 2 do |s|
- s.summary = "This is a lot of text. " * 10_000
- s.authors = ["Abraham Lincoln \x01", "\x02 Hirohito"]
- s.homepage = "http://a.example.com/\x03"
- end
-
- fetcher.legacy_platform
- end
-
- @cmd.handle_options %w[-r -d]
-
- use_ui @stub_ui do
- @cmd.execute
- end
-
- expected = <<-EOF
-
-*** REMOTE GEMS ***
-
-a (2)
- Authors: Abraham Lincoln ., . Hirohito
- Homepage: http://a.example.com/.
-
- Truncating the summary for a-2 to 100,000 characters:
-#{" This is a lot of text. This is a lot of text. This is a lot of text.\n" * 1449} This is a lot of te
-
-pl (1)
- Platform: i386-linux
- Author: A User
- Homepage: http://example.com
-
- this is a summary
- EOF
-
- assert_equal expected, @stub_ui.output
- assert_equal "", @stub_ui.error
- end
-
- def test_execute_installed
- @cmd.handle_options %w[-n a --installed]
-
- assert_raise Gem::MockGemUi::SystemExitException do
- use_ui @stub_ui do
- @cmd.execute
- end
- end
-
- assert_equal "true\n", @stub_ui.output
- assert_equal "", @stub_ui.error
- end
-
- def test_execute_installed_inverse
- @cmd.handle_options %w[-n a --no-installed]
-
- e = assert_raise Gem::MockGemUi::TermError do
- use_ui @stub_ui do
- @cmd.execute
- end
- end
-
- assert_equal "false\n", @stub_ui.output
- assert_equal "", @stub_ui.error
-
- assert_equal 1, e.exit_code
- end
-
- def test_execute_installed_inverse_not_installed
- @cmd.handle_options %w[-n not_installed --no-installed]
-
- assert_raise Gem::MockGemUi::SystemExitException do
- use_ui @stub_ui do
- @cmd.execute
- end
- end
-
- assert_equal "true\n", @stub_ui.output
- assert_equal "", @stub_ui.error
- end
-
- def test_execute_installed_no_name
- @cmd.handle_options %w[--installed]
-
- e = assert_raise Gem::MockGemUi::TermError do
- use_ui @stub_ui do
- @cmd.execute
- end
- end
-
- assert_equal "", @stub_ui.output
- assert_equal "ERROR: You must specify a gem name\n", @stub_ui.error
-
- assert_equal 4, e.exit_code
- end
-
- def test_execute_installed_not_installed
- @cmd.handle_options %w[-n not_installed --installed]
-
- e = assert_raise Gem::MockGemUi::TermError do
- use_ui @stub_ui do
- @cmd.execute
- end
- end
-
- assert_equal "false\n", @stub_ui.output
- assert_equal "", @stub_ui.error
-
- assert_equal 1, e.exit_code
- end
-
- def test_execute_installed_version
- @cmd.handle_options %w[-n a --installed --version 2]
-
- assert_raise Gem::MockGemUi::SystemExitException do
- use_ui @stub_ui do
- @cmd.execute
- end
- end
-
- assert_equal "true\n", @stub_ui.output
- assert_equal "", @stub_ui.error
- end
-
- def test_execute_installed_version_not_installed
- @cmd.handle_options %w[-n c --installed --version 2]
-
- e = assert_raise Gem::MockGemUi::TermError do
- use_ui @stub_ui do
- @cmd.execute
- end
- end
-
- assert_equal "false\n", @stub_ui.output
- assert_equal "", @stub_ui.error
-
- assert_equal 1, e.exit_code
- end
-
- def test_execute_local
- spec_fetcher(&:legacy_platform)
-
- @cmd.options[:domain] = :local
-
- use_ui @stub_ui do
- @cmd.execute
- end
-
- expected = <<-EOF
-
-*** LOCAL GEMS ***
-
-a (3.a, 2, 1)
-pl (1 i386-linux)
- EOF
-
- assert_equal expected, @stub_ui.output
- assert_equal "", @stub_ui.error
- end
-
- def test_execute_local_notty
- spec_fetcher(&:legacy_platform)
-
- @cmd.handle_options %w[]
-
- @stub_ui.outs.tty = false
-
- use_ui @stub_ui do
- @cmd.execute
- end
-
- expected = <<-EOF
-a (3.a, 2, 1)
-pl (1 i386-linux)
- EOF
-
- assert_equal expected, @stub_ui.output
- assert_equal "", @stub_ui.error
- end
-
- def test_execute_local_quiet
- spec_fetcher(&:legacy_platform)
-
- @cmd.options[:domain] = :local
- Gem.configuration.verbose = false
-
- use_ui @stub_ui do
- @cmd.execute
- end
-
- expected = <<-EOF
-a (3.a, 2, 1)
-pl (1 i386-linux)
- EOF
-
- assert_equal expected, @stub_ui.output
- assert_equal "", @stub_ui.error
- end
-
- def test_execute_no_versions
- spec_fetcher(&:legacy_platform)
-
- @cmd.handle_options %w[-r --no-versions]
-
- use_ui @stub_ui do
- @cmd.execute
- end
-
- expected = <<-EOF
-
-*** REMOTE GEMS ***
-
-a
-pl
- EOF
-
- assert_equal expected, @stub_ui.output
- assert_equal "", @stub_ui.error
- end
-
- def test_execute_notty
- spec_fetcher(&:legacy_platform)
-
- @cmd.handle_options %w[-r]
-
- @stub_ui.outs.tty = false
-
- use_ui @stub_ui do
- @cmd.execute
- end
-
- expected = <<-EOF
-a (2)
-pl (1 i386-linux)
- EOF
-
- assert_equal expected, @stub_ui.output
- assert_equal "", @stub_ui.error
- end
-
- def test_execute_prerelease
- @cmd.handle_options %w[-r --prerelease]
-
- use_ui @stub_ui do
- @cmd.execute
- end
-
- expected = <<-EOF
-
-*** REMOTE GEMS ***
-
-a (3.a)
- EOF
-
- assert_equal expected, @stub_ui.output
- assert_equal "", @stub_ui.error
- end
-
- def test_execute_prerelease_local
- spec_fetcher(&:legacy_platform)
-
- @cmd.handle_options %w[-l --prerelease]
-
- use_ui @stub_ui do
- @cmd.execute
- end
-
- expected = <<-EOF
-
-*** LOCAL GEMS ***
-
-a (3.a, 2, 1)
-pl (1 i386-linux)
- EOF
-
- assert_equal expected, @stub_ui.output
- end
-
- def test_execute_no_prerelease_local
- spec_fetcher(&:legacy_platform)
-
- @cmd.handle_options %w[-l --no-prerelease]
-
- use_ui @stub_ui do
- @cmd.execute
- end
-
- expected = <<-EOF
-
-*** LOCAL GEMS ***
-
-a (2, 1)
-pl (1 i386-linux)
- EOF
-
- assert_equal expected, @stub_ui.output
- end
-
- def test_execute_remote
- spec_fetcher(&:legacy_platform)
-
- @cmd.options[:domain] = :remote
-
- use_ui @stub_ui do
- @cmd.execute
- end
-
- expected = <<-EOF
-
-*** REMOTE GEMS ***
-
-a (2)
-pl (1 i386-linux)
- EOF
-
- assert_equal expected, @stub_ui.output
- assert_equal "", @stub_ui.error
- end
-
- def test_execute_remote_notty
- spec_fetcher(&:legacy_platform)
-
- @cmd.handle_options %w[]
-
- @stub_ui.outs.tty = false
-
- use_ui @stub_ui do
- @cmd.execute
- end
-
- expected = <<-EOF
-a (3.a, 2, 1)
-pl (1 i386-linux)
- EOF
-
- assert_equal expected, @stub_ui.output
- assert_equal "", @stub_ui.error
- end
-
- def test_execute_remote_quiet
- spec_fetcher(&:legacy_platform)
-
- @cmd.options[:domain] = :remote
- Gem.configuration.verbose = false
-
- use_ui @stub_ui do
- @cmd.execute
- end
-
- expected = <<-EOF
-a (2)
-pl (1 i386-linux)
- EOF
-
- assert_equal expected, @stub_ui.output
- assert_equal "", @stub_ui.error
- end
-
- def test_make_entry
- a_2_name = @specs["a-2"].original_name
-
- @stub_fetcher.data.delete \
- "#{@gem_repo}quick/Marshal.#{Gem.marshal_version}/#{a_2_name}.gemspec.rz"
-
- a2 = @specs["a-2"]
- entry_tuples = [
- [Gem::NameTuple.new(a2.name, a2.version, a2.platform),
- Gem.sources.first],
- ]
-
- platforms = { a2.version => [a2.platform] }
-
- entry = @cmd.send :make_entry, entry_tuples, platforms
-
- assert_equal "a (2)", entry
- end
-
- # Test for multiple args handling!
- def test_execute_multiple_args
- spec_fetcher(&:legacy_platform)
-
- @cmd.handle_options %w[a pl]
-
- use_ui @stub_ui do
- @cmd.execute
- end
-
- assert_match(/^a /, @stub_ui.output)
- assert_match(/^pl /, @stub_ui.output)
- assert_equal "", @stub_ui.error
- end
-
- def test_show_gems
- @cmd.options[:name] = //
- @cmd.options[:domain] = :remote
-
- use_ui @stub_ui do
- @cmd.send :show_gems, /a/i
- end
-
- assert_match(/^a /, @stub_ui.output)
- refute_match(/^pl /, @stub_ui.output)
- assert_empty @stub_ui.error
- end
-
- private
-
- def add_gems_to_fetcher
- spec_fetcher do |fetcher|
- fetcher.spec "a", 1
- fetcher.spec "a", 2
- fetcher.spec "a", "3.a"
- end
- end
-end
-
-class TestGemCommandsQueryCommandWithoutInstalledGems < Gem::TestCase
- include TestGemCommandsQueryCommandSetup
-
- def test_execute_platform
- spec_fetcher do |fetcher|
- fetcher.spec "a", 1
- fetcher.spec "a", 1 do |s|
- s.platform = "x86-linux"
- end
-
- fetcher.spec "a", 2 do |s|
- s.platform = "universal-darwin"
- end
- end
-
- @cmd.handle_options %w[-r -a]
-
- use_ui @stub_ui do
- @cmd.execute
- end
-
- expected = <<-EOF
-
-*** REMOTE GEMS ***
-
-a (2 universal-darwin, 1 ruby x86-linux)
- EOF
-
- assert_equal expected, @stub_ui.output
- assert_equal "", @stub_ui.error
- end
-
- def test_execute_show_default_gems
- spec_fetcher {|fetcher| fetcher.spec "a", 2 }
-
- a1 = new_default_spec "a", 1
- install_default_gems a1
-
- use_ui @stub_ui do
- @cmd.execute
- end
-
- expected = <<-EOF
-
-*** LOCAL GEMS ***
-
-a (2, default: 1)
-EOF
-
- assert_equal expected, @stub_ui.output
- end
-
- def test_execute_show_default_gems_with_platform
- a1 = new_default_spec "a", 1
- a1.platform = "java"
- install_default_gems a1
-
- use_ui @stub_ui do
- @cmd.execute
- end
-
- expected = <<-EOF
-
-*** LOCAL GEMS ***
-
-a (default: 1 java)
-EOF
-
- assert_equal expected, @stub_ui.output
- end
-
- def test_execute_default_details
- spec_fetcher do |fetcher|
- fetcher.spec "a", 2
- end
-
- a1 = new_default_spec "a", 1
- install_default_gems a1
-
- @cmd.handle_options %w[-l -d]
-
- use_ui @stub_ui do
- @cmd.execute
- end
-
- expected = <<-EOF
-
-*** LOCAL GEMS ***
-
-a (2, 1)
- Author: A User
- Homepage: http://example.com
- Installed at (2): #{@gemhome}
- (1, default): #{a1.base_dir}
-
- this is a summary
- EOF
-
- assert_equal expected, @stub_ui.output
- end
-
- def test_execute_local_details
- spec_fetcher do |fetcher|
- fetcher.spec "a", 1 do |s|
- s.platform = "x86-linux"
- end
-
- fetcher.spec "a", 2 do |s|
- s.summary = "This is a lot of text. " * 4
- s.authors = ["Abraham Lincoln", "Hirohito"]
- s.homepage = "http://a.example.com/"
- s.platform = "universal-darwin"
- end
-
- fetcher.legacy_platform
- end
-
- @cmd.handle_options %w[-l -d]
-
- use_ui @stub_ui do
- @cmd.execute
- end
-
- str = @stub_ui.output
-
- str.gsub!(/\(\d\): [^\n]*/, "-")
- str.gsub!(/at: [^\n]*/, "at: -")
-
- expected = <<-EOF
-
-*** LOCAL GEMS ***
-
-a (2, 1)
- Platforms:
- 1: x86-linux
- 2: universal-darwin
- Authors: Abraham Lincoln, Hirohito
- Homepage: http://a.example.com/
- Installed at -
- -
-
- This is a lot of text. This is a lot of text. This is a lot of text.
- This is a lot of text.
-
-pl (1)
- Platform: i386-linux
- Author: A User
- Homepage: http://example.com
- Installed at: -
-
- this is a summary
- EOF
-
- assert_equal expected, @stub_ui.output
- end
-
- def test_execute_exact_remote
- spec_fetcher do |fetcher|
- fetcher.spec "coolgem-omg", 3
- fetcher.spec "coolgem", "4.2.1"
- fetcher.spec "wow_coolgem", 1
- end
-
- @cmd.handle_options %w[--remote --exact coolgem]
-
- use_ui @stub_ui do
- @cmd.execute
- end
-
- expected = <<-EOF
-
-*** REMOTE GEMS ***
-
-coolgem (4.2.1)
- EOF
-
- assert_equal expected, @stub_ui.output
- end
-
- def test_execute_exact_local
- spec_fetcher do |fetcher|
- fetcher.spec "coolgem-omg", 3
- fetcher.spec "coolgem", "4.2.1"
- fetcher.spec "wow_coolgem", 1
- end
-
- @cmd.handle_options %w[--exact coolgem]
-
- use_ui @stub_ui do
- @cmd.execute
- end
-
- expected = <<-EOF
-
-*** LOCAL GEMS ***
-
-coolgem (4.2.1)
- EOF
-
- assert_equal expected, @stub_ui.output
- end
-
- def test_execute_exact_multiple
- spec_fetcher do |fetcher|
- fetcher.spec "coolgem-omg", 3
- fetcher.spec "coolgem", "4.2.1"
- fetcher.spec "wow_coolgem", 1
-
- fetcher.spec "othergem-omg", 3
- fetcher.spec "othergem", "1.2.3"
- fetcher.spec "wow_othergem", 1
- end
-
- @cmd.handle_options %w[--exact coolgem othergem]
-
- use_ui @stub_ui do
- @cmd.execute
- end
-
- expected = <<-EOF
-
-*** LOCAL GEMS ***
-
-coolgem (4.2.1)
-
-*** LOCAL GEMS ***
-
-othergem (1.2.3)
- EOF
-
- assert_equal expected, @stub_ui.output
- end
-
- def test_depprecated
- assert @cmd.deprecated?
- end
-
- private
-
- def add_gems_to_fetcher
- spec_fetcher do |fetcher|
- fetcher.download "a", 1
- fetcher.download "a", 2
- fetcher.download "a", "3.a"
- end
- end
-end
diff --git a/test/rubygems/test_gem_commands_setup_command.rb b/test/rubygems/test_gem_commands_setup_command.rb
index c3622c02cd..b33e05ab28 100644
--- a/test/rubygems/test_gem_commands_setup_command.rb
+++ b/test/rubygems/test_gem_commands_setup_command.rb
@@ -4,13 +4,6 @@ require_relative "helper"
require "rubygems/commands/setup_command"
class TestGemCommandsSetupCommand < Gem::TestCase
- bundler_gemspec = File.expand_path("../../bundler/lib/bundler/version.rb", __dir__)
- if File.exist?(bundler_gemspec)
- BUNDLER_VERS = File.read(bundler_gemspec).match(/VERSION = "(#{Gem::Version::VERSION_PATTERN})"/)[1]
- else
- BUNDLER_VERS = "2.0.1"
- end
-
def setup
super
@@ -35,9 +28,10 @@ class TestGemCommandsSetupCommand < Gem::TestCase
create_dummy_files(filelist)
- gemspec = util_spec "bundler", BUNDLER_VERS do |s|
+ gemspec = util_spec "bundler", "9.9.9" do |s|
s.bindir = "exe"
s.executables = ["bundle", "bundler"]
+ s.files = ["lib/bundler.rb"]
end
File.open "bundler/bundler.gemspec", "w" do |io|
@@ -229,6 +223,9 @@ class TestGemCommandsSetupCommand < Gem::TestCase
assert_path_exist "#{Gem.dir}/gems/bundler-#{bundler_version}"
assert_path_exist "#{Gem.dir}/gems/bundler-audit-1.0.0"
+
+ assert_path_exist "#{Gem.dir}/gems/bundler-#{bundler_version}/exe/bundle"
+ assert_path_not_exist "#{Gem.dir}/gems/bundler-#{bundler_version}/lib/bundler.rb"
end
def test_install_default_bundler_gem_with_default_gems_not_installed_at_default_dir
@@ -380,20 +377,22 @@ class TestGemCommandsSetupCommand < Gem::TestCase
File.open "CHANGELOG.md", "w" do |io|
io.puts <<-HISTORY_TXT
-# #{Gem::VERSION} / 2013-03-26
+# Changelog
+
+## #{Gem::VERSION} / 2013-03-26
-## Bug fixes:
+### Bug fixes:
* Fixed release note display for LANG=C when installing rubygems
* π is tasty
-# 2.0.2 / 2013-03-06
+## 2.0.2 / 2013-03-06
-## Bug fixes:
+### Bug fixes:
* Other bugs fixed
-# 2.0.1 / 2013-03-05
+## 2.0.1 / 2013-03-05
-## Bug fixes:
+### Bug fixes:
* Yet more bugs fixed
HISTORY_TXT
end
@@ -403,9 +402,9 @@ class TestGemCommandsSetupCommand < Gem::TestCase
end
expected = <<-EXPECTED
-# #{Gem::VERSION} / 2013-03-26
+## #{Gem::VERSION} / 2013-03-26
-## Bug fixes:
+### Bug fixes:
* Fixed release note display for LANG=C when installing rubygems
* π is tasty
diff --git a/test/rubygems/test_gem_commands_signin_command.rb b/test/rubygems/test_gem_commands_signin_command.rb
index 29e5edceb7..e612288faf 100644
--- a/test/rubygems/test_gem_commands_signin_command.rb
+++ b/test/rubygems/test_gem_commands_signin_command.rb
@@ -121,7 +121,7 @@ class TestGemCommandsSigninCommand < Gem::TestCase
assert_match "The default access scope is:", key_name_ui.output
assert_match "index_rubygems: y", key_name_ui.output
assert_match "Do you want to customise scopes? [yN]", key_name_ui.output
- assert_equal "name=test-key&index_rubygems=true", fetcher.last_request.body
+ assert_equal "name=test-key&index_rubygems=true&push_rubygem=true", fetcher.last_request.body
credentials = load_yaml_file Gem.configuration.credentials_path
assert_equal api_key, credentials[:rubygems_api_key]
diff --git a/test/rubygems/test_gem_commands_sources_command.rb b/test/rubygems/test_gem_commands_sources_command.rb
index 5e675e5c84..71c6d5ce16 100644
--- a/test/rubygems/test_gem_commands_sources_command.rb
+++ b/test/rubygems/test_gem_commands_sources_command.rb
@@ -32,7 +32,7 @@ class TestGemCommandsSourcesCommand < Gem::TestCase
end
expected = <<-EOF
-*** CURRENT SOURCES ***
+*** NO CONFIGURED SOURCES, DEFAULT SOURCES LISTED BELOW ***
#{@gem_repo}
EOF
@@ -42,23 +42,104 @@ class TestGemCommandsSourcesCommand < Gem::TestCase
end
def test_execute_add
- spec_fetcher do |fetcher|
- fetcher.spec "a", 1
+ setup_fake_source(@new_repo)
+
+ @cmd.handle_options %W[--add #{@new_repo}]
+
+ use_ui @ui do
+ @cmd.execute
end
- specs = Gem::Specification.map do |spec|
- [spec.name, spec.version, spec.original_platform]
+ assert_equal [@gem_repo, @new_repo], Gem.sources
+
+ expected = <<-EOF
+#{@new_repo} added to sources
+ EOF
+
+ assert_equal expected, @ui.output
+ assert_equal "", @ui.error
+ end
+
+ def test_execute_add_without_trailing_slash
+ setup_fake_source("https://rubygems.pkg.github.com/my-org")
+
+ @cmd.handle_options %W[--add https://rubygems.pkg.github.com/my-org]
+
+ use_ui @ui do
+ @cmd.execute
end
- specs_dump_gz = StringIO.new
- Zlib::GzipWriter.wrap specs_dump_gz do |io|
- Marshal.dump specs, io
+ assert_equal [@gem_repo, "https://rubygems.pkg.github.com/my-org/"], Gem.sources
+
+ expected = <<-EOF
+https://rubygems.pkg.github.com/my-org/ added to sources
+ EOF
+
+ assert_equal expected, @ui.output
+ assert_equal "", @ui.error
+ end
+
+ def test_execute_add_multiple_trailing_slash
+ setup_fake_source("https://rubygems.pkg.github.com/my-org/")
+
+ @cmd.handle_options %W[--add https://rubygems.pkg.github.com/my-org///]
+
+ use_ui @ui do
+ @cmd.execute
end
- @fetcher.data["#{@new_repo}/specs.#{@marshal_version}.gz"] =
- specs_dump_gz.string
+ assert_equal [@gem_repo, "https://rubygems.pkg.github.com/my-org/"], Gem.sources
- @cmd.handle_options %W[--add #{@new_repo}]
+ expected = <<-EOF
+https://rubygems.pkg.github.com/my-org/ added to sources
+ EOF
+
+ assert_equal expected, @ui.output
+ assert_equal "", @ui.error
+ end
+
+ def test_execute_append_without_trailing_slash
+ setup_fake_source("https://rubygems.pkg.github.com/my-org")
+
+ @cmd.handle_options %W[--append https://rubygems.pkg.github.com/my-org]
+
+ use_ui @ui do
+ @cmd.execute
+ end
+
+ assert_equal [@gem_repo, "https://rubygems.pkg.github.com/my-org/"], Gem.sources
+
+ expected = <<-EOF
+https://rubygems.pkg.github.com/my-org/ added to sources
+ EOF
+
+ assert_equal expected, @ui.output
+ assert_equal "", @ui.error
+ end
+
+ def test_execute_prepend_without_trailing_slash
+ setup_fake_source("https://rubygems.pkg.github.com/my-org")
+
+ @cmd.handle_options %W[--prepend https://rubygems.pkg.github.com/my-org]
+
+ use_ui @ui do
+ @cmd.execute
+ end
+
+ assert_equal ["https://rubygems.pkg.github.com/my-org/", @gem_repo], Gem.sources
+
+ expected = <<-EOF
+https://rubygems.pkg.github.com/my-org/ added to sources
+ EOF
+
+ assert_equal expected, @ui.output
+ assert_equal "", @ui.error
+ end
+
+ def test_execute_append
+ setup_fake_source(@new_repo)
+
+ @cmd.handle_options %W[--append #{@new_repo}]
use_ui @ui do
@cmd.execute
@@ -77,21 +158,31 @@ class TestGemCommandsSourcesCommand < Gem::TestCase
def test_execute_add_allow_typo_squatting_source
rubygems_org = "https://rubyems.org"
- spec_fetcher do |fetcher|
- fetcher.spec("a", 1)
- end
+ setup_fake_source(rubygems_org)
- specs = Gem::Specification.map do |spec|
- [spec.name, spec.version, spec.original_platform]
- end
+ @cmd.handle_options %W[--add #{rubygems_org}]
+ ui = Gem::MockGemUi.new("y")
- specs_dump_gz = StringIO.new
- Zlib::GzipWriter.wrap(specs_dump_gz) do |io|
- Marshal.dump(specs, io)
+ use_ui ui do
+ @cmd.execute
end
- @fetcher.data["#{rubygems_org}/specs.#{@marshal_version}.gz"] = specs_dump_gz.string
- @cmd.handle_options %W[--add #{rubygems_org}]
+ expected = "https://rubyems.org is too similar to https://rubygems.org\n\nDo you want to add this source? [yn] https://rubyems.org added to sources\n"
+
+ assert_equal expected, ui.output
+
+ source = Gem::Source.new(rubygems_org)
+ assert Gem.sources.include?(source)
+
+ assert_empty ui.error
+ end
+
+ def test_execute_append_allow_typo_squatting_source
+ rubygems_org = "https://rubyems.org"
+
+ setup_fake_source(rubygems_org)
+
+ @cmd.handle_options %W[--append #{rubygems_org}]
ui = Gem::MockGemUi.new("y")
use_ui ui do
@@ -111,21 +202,27 @@ class TestGemCommandsSourcesCommand < Gem::TestCase
def test_execute_add_allow_typo_squatting_source_forced
rubygems_org = "https://rubyems.org"
- spec_fetcher do |fetcher|
- fetcher.spec("a", 1)
- end
+ setup_fake_source(rubygems_org)
- specs = Gem::Specification.map do |spec|
- [spec.name, spec.version, spec.original_platform]
- end
+ @cmd.handle_options %W[--force --add #{rubygems_org}]
- specs_dump_gz = StringIO.new
- Zlib::GzipWriter.wrap(specs_dump_gz) do |io|
- Marshal.dump(specs, io)
- end
+ @cmd.execute
- @fetcher.data["#{rubygems_org}/specs.#{@marshal_version}.gz"] = specs_dump_gz.string
- @cmd.handle_options %W[--force --add #{rubygems_org}]
+ expected = "https://rubyems.org added to sources\n"
+ assert_equal expected, ui.output
+
+ source = Gem::Source.new(rubygems_org)
+ assert Gem.sources.include?(source)
+
+ assert_empty ui.error
+ end
+
+ def test_execute_append_allow_typo_squatting_source_forced
+ rubygems_org = "https://rubyems.org"
+
+ setup_fake_source(rubygems_org)
+
+ @cmd.handle_options %W[--force --append #{rubygems_org}]
@cmd.execute
@@ -141,23 +238,34 @@ class TestGemCommandsSourcesCommand < Gem::TestCase
def test_execute_add_deny_typo_squatting_source
rubygems_org = "https://rubyems.org"
- spec_fetcher do |fetcher|
- fetcher.spec("a", 1)
- end
+ setup_fake_source(rubygems_org)
- specs = Gem::Specification.map do |spec|
- [spec.name, spec.version, spec.original_platform]
- end
+ @cmd.handle_options %W[--add #{rubygems_org}]
- specs_dump_gz = StringIO.new
- Zlib::GzipWriter.wrap(specs_dump_gz) do |io|
- Marshal.dump(specs, io)
+ ui = Gem::MockGemUi.new("n")
+
+ use_ui ui do
+ assert_raise Gem::MockGemUi::TermError do
+ @cmd.execute
+ end
end
- @fetcher.data["#{rubygems_org}/specs.#{@marshal_version}.gz"] =
- specs_dump_gz.string
+ expected = "https://rubyems.org is too similar to https://rubygems.org\n\nDo you want to add this source? [yn] "
- @cmd.handle_options %W[--add #{rubygems_org}]
+ assert_equal expected, ui.output
+
+ source = Gem::Source.new(rubygems_org)
+ refute Gem.sources.include?(source)
+
+ assert_empty ui.error
+ end
+
+ def test_execute_append_deny_typo_squatting_source
+ rubygems_org = "https://rubyems.org"
+
+ setup_fake_source(rubygems_org)
+
+ @cmd.handle_options %W[--append #{rubygems_org}]
ui = Gem::MockGemUi.new("n")
@@ -202,6 +310,31 @@ Error fetching http://beta-gems.example.com:
assert_equal "", @ui.error
end
+ def test_execute_append_nonexistent_source
+ spec_fetcher
+
+ uri = "http://beta-gems.example.com/specs.#{@marshal_version}.gz"
+ @fetcher.data[uri] = proc do
+ raise Gem::RemoteFetcher::FetchError.new("it died", uri)
+ end
+
+ @cmd.handle_options %w[--append http://beta-gems.example.com]
+
+ use_ui @ui do
+ assert_raise Gem::MockGemUi::TermError do
+ @cmd.execute
+ end
+ end
+
+ expected = <<-EOF
+Error fetching http://beta-gems.example.com:
+\tit died (#{uri})
+ EOF
+
+ assert_equal expected, @ui.output
+ assert_equal "", @ui.error
+ end
+
def test_execute_add_existent_source_invalid_uri
spec_fetcher
@@ -227,6 +360,31 @@ Error fetching https://u:REDACTED@example.com:
assert_equal "", @ui.error
end
+ def test_execute_append_existent_source_invalid_uri
+ spec_fetcher
+
+ uri = "https://u:p@example.com/specs.#{@marshal_version}.gz"
+
+ @cmd.handle_options %w[--append https://u:p@example.com]
+ @fetcher.data[uri] = proc do
+ raise Gem::RemoteFetcher::FetchError.new("it died", uri)
+ end
+
+ use_ui @ui do
+ assert_raise Gem::MockGemUi::TermError do
+ @cmd.execute
+ end
+ end
+
+ expected = <<-EOF
+Error fetching https://u:REDACTED@example.com:
+\tit died (https://u:REDACTED@example.com/specs.#{@marshal_version}.gz)
+ EOF
+
+ assert_equal expected, @ui.output
+ assert_equal "", @ui.error
+ end
+
def test_execute_add_existent_source_invalid_uri_with_error_by_chance_including_the_uri_password
spec_fetcher
@@ -252,6 +410,31 @@ Error fetching https://u:REDACTED@example.com:
assert_equal "", @ui.error
end
+ def test_execute_append_existent_source_invalid_uri_with_error_by_chance_including_the_uri_password
+ spec_fetcher
+
+ uri = "https://u:secret@example.com/specs.#{@marshal_version}.gz"
+
+ @cmd.handle_options %w[--append https://u:secret@example.com]
+ @fetcher.data[uri] = proc do
+ raise Gem::RemoteFetcher::FetchError.new("it secretly died", uri)
+ end
+
+ use_ui @ui do
+ assert_raise Gem::MockGemUi::TermError do
+ @cmd.execute
+ end
+ end
+
+ expected = <<-EOF
+Error fetching https://u:REDACTED@example.com:
+\tit secretly died (https://u:REDACTED@example.com/specs.#{@marshal_version}.gz)
+ EOF
+
+ assert_equal expected, @ui.output
+ assert_equal "", @ui.error
+ end
+
def test_execute_add_redundant_source
spec_fetcher
@@ -271,27 +454,34 @@ source #{@gem_repo} already present in the cache
assert_equal "", @ui.error
end
- def test_execute_add_redundant_source_trailing_slash
+ def test_execute_append_redundant_source
spec_fetcher
- # Remove pre-existing gem source (w/ slash)
- repo_with_slash = "http://gems.example.com/"
- @cmd.handle_options %W[--remove #{repo_with_slash}]
+ @cmd.handle_options %W[--append #{@gem_repo}]
+
use_ui @ui do
@cmd.execute
end
- source = Gem::Source.new repo_with_slash
- assert_equal false, Gem.sources.include?(source)
+
+ assert_equal [@gem_repo], Gem.sources
expected = <<-EOF
-#{repo_with_slash} removed from sources
+#{@gem_repo} moved to end of sources
EOF
assert_equal expected, @ui.output
assert_equal "", @ui.error
+ end
+
+ def test_execute_add_redundant_source_trailing_slash
+ repo_with_slash = "http://sample.repo/"
+
+ Gem.configuration.sources = [repo_with_slash]
+
+ setup_fake_source(repo_with_slash)
# Re-add pre-existing gem source (w/o slash)
- repo_without_slash = "http://gems.example.com"
+ repo_without_slash = repo_with_slash.delete_suffix("/")
@cmd.handle_options %W[--add #{repo_without_slash}]
use_ui @ui do
@cmd.execute
@@ -300,8 +490,7 @@ source #{@gem_repo} already present in the cache
assert_equal true, Gem.sources.include?(source)
expected = <<-EOF
-http://gems.example.com/ removed from sources
-http://gems.example.com added to sources
+source #{repo_without_slash} already present in the cache
EOF
assert_equal expected, @ui.output
@@ -316,35 +505,46 @@ http://gems.example.com added to sources
assert_equal true, Gem.sources.include?(source)
expected = <<-EOF
-http://gems.example.com/ removed from sources
-http://gems.example.com added to sources
-source http://gems.example.com/ already present in the cache
+source #{repo_without_slash} already present in the cache
+source #{repo_with_slash} already present in the cache
EOF
assert_equal expected, @ui.output
assert_equal "", @ui.error
+ ensure
+ Gem.configuration.sources = nil
end
def test_execute_add_http_rubygems_org
http_rubygems_org = "http://rubygems.org/"
- spec_fetcher do |fetcher|
- fetcher.spec "a", 1
- end
+ setup_fake_source(http_rubygems_org)
- specs = Gem::Specification.map do |spec|
- [spec.name, spec.version, spec.original_platform]
- end
+ @cmd.handle_options %W[--add #{http_rubygems_org}]
- specs_dump_gz = StringIO.new
- Zlib::GzipWriter.wrap specs_dump_gz do |io|
- Marshal.dump specs, io
+ ui = Gem::MockGemUi.new "n"
+
+ use_ui ui do
+ assert_raise Gem::MockGemUi::TermError do
+ @cmd.execute
+ end
end
- @fetcher.data["#{http_rubygems_org}/specs.#{@marshal_version}.gz"] =
- specs_dump_gz.string
+ assert_equal [@gem_repo], Gem.sources
- @cmd.handle_options %W[--add #{http_rubygems_org}]
+ expected = <<-EXPECTED
+ EXPECTED
+
+ assert_equal expected, @ui.output
+ assert_empty @ui.error
+ end
+
+ def test_execute_append_http_rubygems_org
+ http_rubygems_org = "http://rubygems.org/"
+
+ setup_fake_source(http_rubygems_org)
+
+ @cmd.handle_options %W[--append #{http_rubygems_org}]
ui = Gem::MockGemUi.new "n"
@@ -366,21 +566,27 @@ source http://gems.example.com/ already present in the cache
def test_execute_add_http_rubygems_org_forced
rubygems_org = "http://rubygems.org"
- spec_fetcher do |fetcher|
- fetcher.spec("a", 1)
- end
+ setup_fake_source(rubygems_org)
- specs = Gem::Specification.map do |spec|
- [spec.name, spec.version, spec.original_platform]
- end
+ @cmd.handle_options %W[--force --add #{rubygems_org}]
- specs_dump_gz = StringIO.new
- Zlib::GzipWriter.wrap(specs_dump_gz) do |io|
- Marshal.dump(specs, io)
- end
+ @cmd.execute
- @fetcher.data["#{rubygems_org}/specs.#{@marshal_version}.gz"] = specs_dump_gz.string
- @cmd.handle_options %W[--force --add #{rubygems_org}]
+ expected = "http://rubygems.org added to sources\n"
+ assert_equal expected, ui.output
+
+ source = Gem::Source.new(rubygems_org)
+ assert Gem.sources.include?(source)
+
+ assert_empty ui.error
+ end
+
+ def test_execute_append_http_rubygems_org_forced
+ rubygems_org = "http://rubygems.org"
+
+ setup_fake_source(rubygems_org)
+
+ @cmd.handle_options %W[--force --append #{rubygems_org}]
@cmd.execute
@@ -396,27 +602,68 @@ source http://gems.example.com/ already present in the cache
def test_execute_add_https_rubygems_org
https_rubygems_org = "https://rubygems.org/"
- spec_fetcher do |fetcher|
- fetcher.spec "a", 1
+ setup_fake_source(https_rubygems_org)
+
+ @cmd.handle_options %W[--add #{https_rubygems_org}]
+
+ use_ui @ui do
+ @cmd.execute
end
- specs = Gem::Specification.map do |spec|
- [spec.name, spec.version, spec.original_platform]
+ assert_equal [@gem_repo, https_rubygems_org], Gem.sources
+
+ expected = <<-EXPECTED
+#{https_rubygems_org} added to sources
+ EXPECTED
+
+ assert_equal expected, @ui.output
+ assert_empty @ui.error
+ end
+
+ def test_execute_append_https_rubygems_org
+ https_rubygems_org = "https://rubygems.org/"
+
+ setup_fake_source(https_rubygems_org)
+
+ @cmd.handle_options %W[--append #{https_rubygems_org}]
+
+ use_ui @ui do
+ @cmd.execute
end
- specs_dump_gz = StringIO.new
- Zlib::GzipWriter.wrap specs_dump_gz do |io|
- Marshal.dump specs, io
+ assert_equal [@gem_repo, https_rubygems_org], Gem.sources
+
+ expected = <<-EXPECTED
+#{https_rubygems_org} added to sources
+ EXPECTED
+
+ assert_equal expected, @ui.output
+ assert_empty @ui.error
+ end
+
+ def test_execute_add_bad_uri
+ @cmd.handle_options %w[--add beta-gems.example.com]
+
+ use_ui @ui do
+ assert_raise Gem::MockGemUi::TermError do
+ @cmd.execute
+ end
end
- @fetcher.data["#{https_rubygems_org}/specs.#{@marshal_version}.gz"] =
- specs_dump_gz.string
+ assert_equal [@gem_repo], Gem.sources
- @cmd.handle_options %W[--add #{https_rubygems_org}]
+ expected = <<-EOF
+beta-gems.example.com/ is not a URI
+ EOF
- ui = Gem::MockGemUi.new "n"
+ assert_equal expected, @ui.output
+ assert_equal "", @ui.error
+ end
- use_ui ui do
+ def test_execute_append_bad_uri
+ @cmd.handle_options %w[--append beta-gems.example.com]
+
+ use_ui @ui do
assert_raise Gem::MockGemUi::TermError do
@cmd.execute
end
@@ -424,15 +671,16 @@ source http://gems.example.com/ already present in the cache
assert_equal [@gem_repo], Gem.sources
- expected = <<-EXPECTED
- EXPECTED
+ expected = <<-EOF
+beta-gems.example.com/ is not a URI
+ EOF
assert_equal expected, @ui.output
- assert_empty @ui.error
+ assert_equal "", @ui.error
end
- def test_execute_add_bad_uri
- @cmd.handle_options %w[--add beta-gems.example.com]
+ def test_execute_prepend_bad_uri
+ @cmd.handle_options %w[--prepend beta-gems.example.com]
use_ui @ui do
assert_raise Gem::MockGemUi::TermError do
@@ -443,7 +691,7 @@ source http://gems.example.com/ already present in the cache
assert_equal [@gem_repo], Gem.sources
expected = <<-EOF
-beta-gems.example.com is not a URI
+beta-gems.example.com/ is not a URI
EOF
assert_equal expected, @ui.output
@@ -476,7 +724,7 @@ beta-gems.example.com is not a URI
end
expected = <<-EOF
-*** CURRENT SOURCES ***
+*** NO CONFIGURED SOURCES, DEFAULT SOURCES LISTED BELOW ***
#{@gem_repo}
EOF
@@ -486,24 +734,32 @@ beta-gems.example.com is not a URI
end
def test_execute_remove
- @cmd.handle_options %W[--remove #{@gem_repo}]
+ Gem.configuration.sources = [@new_repo]
+
+ setup_fake_source(@new_repo)
+
+ @cmd.handle_options %W[--remove #{@new_repo}]
use_ui @ui do
@cmd.execute
end
- expected = "#{@gem_repo} removed from sources\n"
+ expected = "#{@new_repo} removed from sources\n"
assert_equal expected, @ui.output
assert_equal "", @ui.error
+ ensure
+ Gem.configuration.sources = nil
end
def test_execute_remove_no_network
+ Gem.configuration.sources = [@new_repo]
+
spec_fetcher
- @cmd.handle_options %W[--remove #{@gem_repo}]
+ @cmd.handle_options %W[--remove #{@new_repo}]
- @fetcher.data["#{@gem_repo}Marshal.#{Gem.marshal_version}"] = proc do
+ @fetcher.data["#{@new_repo}Marshal.#{Gem.marshal_version}"] = proc do
raise Gem::RemoteFetcher::FetchError
end
@@ -511,10 +767,129 @@ beta-gems.example.com is not a URI
@cmd.execute
end
+ expected = "#{@new_repo} removed from sources\n"
+
+ assert_equal expected, @ui.output
+ assert_equal "", @ui.error
+ ensure
+ Gem.configuration.sources = nil
+ end
+
+ def test_execute_remove_not_present
+ Gem.configuration.sources = ["https://other.repo"]
+
+ @cmd.handle_options %W[--remove #{@new_repo}]
+
+ use_ui @ui do
+ @cmd.execute
+ end
+
+ expected = "source #{@new_repo} cannot be removed because it's not present in #{Gem.configuration.config_file_name}\n"
+
+ assert_equal expected, @ui.output
+ assert_equal "", @ui.error
+ ensure
+ Gem.configuration.sources = nil
+ end
+
+ def test_execute_remove_nothing_configured
+ spec_fetcher
+
+ @cmd.handle_options %W[--remove https://does.not.exist]
+
+ use_ui @ui do
+ @cmd.execute
+ end
+
+ expected = "source https://does.not.exist cannot be removed because there are no configured sources in #{Gem.configuration.config_file_name}\n"
+
+ assert_equal expected, @ui.output
+ assert_equal "", @ui.error
+ end
+
+ def test_remove_default_also_present_in_configuration
+ Gem.configuration.sources = [@gem_repo]
+
+ @cmd.handle_options %W[--remove #{@gem_repo}]
+
+ use_ui @ui do
+ @cmd.execute
+ end
+
+ expected = "WARNING: Removing a default source when it is the only source has no effect. Add a different source to #{Gem.configuration.config_file_name} if you want to stop using it as a source.\n"
+
+ assert_equal "", @ui.output
+ assert_equal expected, @ui.error
+ ensure
+ Gem.configuration.sources = nil
+ end
+
+ def test_remove_default_also_present_in_configuration_when_there_are_more_configured_sources
+ Gem.configuration.sources = [@gem_repo, "https://other.repo"]
+
+ @cmd.handle_options %W[--remove #{@gem_repo}]
+
+ use_ui @ui do
+ @cmd.execute
+ end
+
expected = "#{@gem_repo} removed from sources\n"
assert_equal expected, @ui.output
assert_equal "", @ui.error
+ ensure
+ Gem.configuration.sources = nil
+ end
+
+ def test_execute_remove_redundant_source_trailing_slash
+ repo_with_slash = "http://sample.repo/"
+
+ Gem.configuration.sources = [repo_with_slash]
+
+ setup_fake_source(repo_with_slash)
+
+ repo_without_slash = repo_with_slash.delete_suffix("/")
+
+ @cmd.handle_options %W[--remove #{repo_without_slash}]
+ use_ui @ui do
+ @cmd.execute
+ end
+ source = Gem::Source.new repo_without_slash
+ assert_equal false, Gem.sources.include?(source)
+
+ expected = <<-EOF
+#{repo_without_slash} removed from sources
+ EOF
+
+ assert_equal expected, @ui.output
+ assert_equal "", @ui.error
+ ensure
+ Gem.configuration.sources = nil
+ end
+
+ def test_execute_remove_without_trailing_slash
+ source_uri = "https://rubygems.pkg.github.com/my-org/"
+
+ Gem.configuration.sources = [source_uri]
+
+ setup_fake_source(source_uri)
+
+ @cmd.handle_options %W[--remove https://rubygems.pkg.github.com/my-org]
+
+ use_ui @ui do
+ @cmd.execute
+ end
+
+ assert_equal [], Gem.sources
+
+ expected = <<-EOF
+#{source_uri} removed from sources
+ EOF
+
+ assert_equal expected, @ui.output
+ assert_equal "", @ui.error
+ ensure
+ Gem.configuration.sources = nil
end
def test_execute_update
@@ -531,4 +906,102 @@ beta-gems.example.com is not a URI
assert_equal "source cache successfully updated\n", @ui.output
assert_equal "", @ui.error
end
+
+ def test_execute_prepend_new_source
+ setup_fake_source(@new_repo)
+
+ @cmd.handle_options %W[--prepend #{@new_repo}]
+
+ use_ui @ui do
+ @cmd.execute
+ end
+
+ assert_equal [@new_repo, @gem_repo], Gem.sources
+
+ expected = <<-EOF
+#{@new_repo} added to sources
+ EOF
+
+ assert_equal expected, @ui.output
+ assert_equal "", @ui.error
+ end
+
+ def test_execute_prepend_existing_source
+ setup_fake_source(@new_repo)
+
+ # Append the source normally first
+ @cmd.handle_options %W[--append #{@new_repo}]
+ use_ui @ui do
+ @cmd.execute
+ end
+
+ # Initial state: [@gem_repo, @new_repo]
+ assert_equal [@gem_repo, @new_repo], Gem.sources
+
+ # Now prepend the existing source
+ @cmd.handle_options %W[--prepend #{@new_repo}]
+ use_ui @ui do
+ @cmd.execute
+ end
+
+ # Should be moved to front: [@new_repo, @gem_repo]
+ assert_equal [@new_repo, @gem_repo], Gem.sources
+
+ expected = <<-EOF
+#{@new_repo} added to sources
+#{@new_repo} moved to top of sources
+ EOF
+
+ assert_equal expected, @ui.output
+ assert_equal "", @ui.error
+ end
+
+ def test_execute_append_existing_source
+ setup_fake_source(@new_repo)
+
+ # Prepend the source first so it's at the beginning
+ @cmd.handle_options %W[--prepend #{@new_repo}]
+ use_ui @ui do
+ @cmd.execute
+ end
+
+ # Initial state: [@new_repo, @gem_repo] (new_repo is first)
+ assert_equal [@new_repo, @gem_repo], Gem.sources
+
+ # Now append the existing source
+ @cmd.handle_options %W[--append #{@new_repo}]
+ use_ui @ui do
+ @cmd.execute
+ end
+
+ # Should be moved to end: [@gem_repo, @new_repo]
+ assert_equal [@gem_repo, @new_repo], Gem.sources
+
+ expected = <<-EOF
+#{@new_repo} added to sources
+#{@new_repo} moved to end of sources
+ EOF
+
+ assert_equal expected, @ui.output
+ assert_equal "", @ui.error
+ end
+
+ private
+
+ def setup_fake_source(uri)
+ spec_fetcher do |fetcher|
+ fetcher.spec "a", 1
+ end
+
+ specs = Gem::Specification.map do |spec|
+ [spec.name, spec.version, spec.original_platform]
+ end
+
+ specs_dump_gz = StringIO.new
+ Zlib::GzipWriter.wrap specs_dump_gz do |io|
+ Marshal.dump specs, io
+ end
+
+ @fetcher.data["#{uri.chomp("/")}/specs.#{@marshal_version}.gz"] = specs_dump_gz.string
+ end
end
diff --git a/test/rubygems/test_gem_commands_uninstall_command.rb b/test/rubygems/test_gem_commands_uninstall_command.rb
index 32553d1730..71ceb22ce5 100644
--- a/test/rubygems/test_gem_commands_uninstall_command.rb
+++ b/test/rubygems/test_gem_commands_uninstall_command.rb
@@ -513,7 +513,7 @@ WARNING: Use your OS package manager to uninstall vendor gems
end
msg = "ERROR: Can't use --version with multiple gems. You can specify multiple gems with" \
- " version requirements using `gem uninstall 'my_gem:1.0.0' 'my_other_gem:~>2.0.0'`"
+ " version requirements using `gem uninstall 'my_gem:1.0.0' 'my_other_gem:>=2'`"
assert_empty @ui.output
assert_equal msg, @ui.error.lines.last.chomp
diff --git a/test/rubygems/test_gem_commands_update_command.rb b/test/rubygems/test_gem_commands_update_command.rb
index 3b106e4581..5ed12ad481 100644
--- a/test/rubygems/test_gem_commands_update_command.rb
+++ b/test/rubygems/test_gem_commands_update_command.rb
@@ -696,6 +696,38 @@ class TestGemCommandsUpdateCommand < Gem::TestCase
assert_equal expected, @cmd.fetch_remote_gems(specs["a-1"])
end
+ def test_pass_down_the_job_option_to_make
+ gemspec = nil
+
+ spec_fetcher do |fetcher|
+ fetcher.download "a", 3 do |spec|
+ gemspec = spec
+
+ extconf_path = "#{spec.gem_dir}/extconf.rb"
+
+ write_file(extconf_path) do |io|
+ io.puts "require 'mkmf'"
+ io.puts "create_makefile '#{spec.name}'"
+ end
+
+ spec.extensions = "extconf.rb"
+ end
+
+ fetcher.gem "a", 2
+ end
+
+ use_ui @ui do
+ @cmd.invoke("a", "-j2")
+ end
+
+ gem_make_out = File.read(File.join(gemspec.extension_dir, "gem_make.out"))
+ if vc_windows? && nmake_found?
+ refute_includes(gem_make_out, " -j2")
+ else
+ assert_includes(gem_make_out, "make -j2")
+ end
+ end
+
def test_handle_options_system
@cmd.handle_options %w[--system]
diff --git a/test/rubygems/test_gem_commands_which_command.rb b/test/rubygems/test_gem_commands_which_command.rb
index cbd5b5ef14..e114d6e689 100644
--- a/test/rubygems/test_gem_commands_which_command.rb
+++ b/test/rubygems/test_gem_commands_which_command.rb
@@ -38,8 +38,6 @@ class TestGemCommandsWhichCommand < Gem::TestCase
end
def test_execute_one_missing
- # TODO: this test fails in isolation
-
util_foo_bar
@cmd.handle_options %w[foo_bar missinglib]
diff --git a/test/rubygems/test_gem_commands_yank_command.rb b/test/rubygems/test_gem_commands_yank_command.rb
index eb78e3a542..457a0e65c8 100644
--- a/test/rubygems/test_gem_commands_yank_command.rb
+++ b/test/rubygems/test_gem_commands_yank_command.rb
@@ -131,15 +131,17 @@ class TestGemCommandsYankCommand < Gem::TestCase
end
assert_match %r{Yanking gem from http://example}, @ui.output
- assert_match "You have enabled multi-factor authentication. Please visit #{@fetcher.webauthn_url_with_port(server.port)} " \
+ assert_match "You have enabled multi-factor authentication. Please visit the following URL " \
"to authenticate via security device. If you can't verify using WebAuthn but have OTP enabled, " \
"you can re-run the gem signin command with the `--otp [your_code]` option.", @ui.output
+ assert_match @fetcher.webauthn_url_with_port(server.port), @ui.output
assert_match "You are verified with a security device. You may close the browser window.", @ui.output
assert_equal "Uvh6T57tkWuUnWYo", @fetcher.last_request["OTP"]
assert_match "Successfully yanked", @ui.output
end
def test_with_webauthn_enabled_failure
+ pend "Flaky on TruffleRuby" if RUBY_ENGINE == "truffleruby"
server = Gem::MockTCPServer.new
error = Gem::WebauthnVerificationError.new("Something went wrong")
@@ -163,9 +165,10 @@ class TestGemCommandsYankCommand < Gem::TestCase
assert_match @fetcher.last_request["Authorization"], Gem.configuration.rubygems_api_key
assert_match %r{Yanking gem from http://example}, @ui.output
- assert_match "You have enabled multi-factor authentication. Please visit #{@fetcher.webauthn_url_with_port(server.port)} " \
+ assert_match "You have enabled multi-factor authentication. Please visit the following URL " \
"to authenticate via security device. If you can't verify using WebAuthn but have OTP enabled, " \
"you can re-run the gem signin command with the `--otp [your_code]` option.", @ui.output
+ assert_match @fetcher.webauthn_url_with_port(server.port), @ui.output
assert_match "ERROR: Security device verification failed: Something went wrong", @ui.error
refute_match "You are verified with a security device. You may close the browser window.", @ui.output
refute_match "Successfully yanked", @ui.output
@@ -189,9 +192,10 @@ class TestGemCommandsYankCommand < Gem::TestCase
end
assert_match %r{Yanking gem from http://example}, @ui.output
- assert_match "You have enabled multi-factor authentication. Please visit #{@fetcher.webauthn_url_with_port(server.port)} " \
+ assert_match "You have enabled multi-factor authentication. Please visit the following URL " \
"to authenticate via security device. If you can't verify using WebAuthn but have OTP enabled, " \
"you can re-run the gem signin command with the `--otp [your_code]` option.", @ui.output
+ assert_match @fetcher.webauthn_url_with_port(server.port), @ui.output
assert_match "You are verified with a security device. You may close the browser window.", @ui.output
assert_equal "Uvh6T57tkWuUnWYo", @fetcher.last_request["OTP"]
assert_match "Successfully yanked", @ui.output
@@ -219,9 +223,10 @@ class TestGemCommandsYankCommand < Gem::TestCase
assert_match @fetcher.last_request["Authorization"], Gem.configuration.rubygems_api_key
assert_match %r{Yanking gem from http://example}, @ui.output
- assert_match "You have enabled multi-factor authentication. Please visit #{@fetcher.webauthn_url_with_port(server.port)} " \
+ assert_match "You have enabled multi-factor authentication. Please visit the following URL " \
"to authenticate via security device. If you can't verify using WebAuthn but have OTP enabled, " \
"you can re-run the gem signin command with the `--otp [your_code]` option.", @ui.output
+ assert_match @fetcher.webauthn_url_with_port(server.port), @ui.output
assert_match "ERROR: Security device verification failed: The token in the link you used has either expired " \
"or been used already.", @ui.error
refute_match "You are verified with a security device. You may close the browser window.", @ui.output
@@ -267,7 +272,7 @@ class TestGemCommandsYankCommand < Gem::TestCase
assert_equal [yank_uri], @fetcher.paths
end
- def test_yank_gem_unathorized_api_key
+ def test_yank_gem_unauthorized_api_key
response_forbidden = "The API key doesn't have access"
response_success = "Successfully yanked"
host = "http://example"
diff --git a/test/rubygems/test_gem_config_file.rb b/test/rubygems/test_gem_config_file.rb
index 4230eda4d3..3c79cb0762 100644
--- a/test/rubygems/test_gem_config_file.rb
+++ b/test/rubygems/test_gem_config_file.rb
@@ -43,6 +43,7 @@ class TestGemConfigFile < Gem::TestCase
assert_equal [@gem_repo], Gem.sources
assert_equal 365, @cfg.cert_expiration_length_days
assert_equal false, @cfg.ipv4_fallback_enabled
+ assert_equal true, @cfg.install_extension_in_lib
File.open @temp_conf, "w" do |fp|
fp.puts ":backtrace: true"
@@ -52,14 +53,16 @@ class TestGemConfigFile < Gem::TestCase
fp.puts ":sources:"
fp.puts " - http://more-gems.example.com"
fp.puts "install: --wrappers"
+ fp.puts ":gemhome: /tmp/gems"
fp.puts ":gempath:"
fp.puts "- /usr/ruby/1.8/lib/ruby/gems/1.8"
fp.puts "- /var/ruby/1.8/gem_home"
fp.puts ":ssl_verify_mode: 0"
fp.puts ":ssl_ca_cert: /etc/ssl/certs"
fp.puts ":cert_expiration_length_days: 28"
- fp.puts ":install_extension_in_lib: true"
+ fp.puts ":install_extension_in_lib: false"
fp.puts ":ipv4_fallback_enabled: true"
+ fp.puts ":use_psych: true"
end
util_config_file
@@ -69,13 +72,15 @@ class TestGemConfigFile < Gem::TestCase
assert_equal false, @cfg.update_sources
assert_equal %w[http://more-gems.example.com], @cfg.sources
assert_equal "--wrappers", @cfg[:install]
+ assert_equal "/tmp/gems", @cfg.home
assert_equal(["/usr/ruby/1.8/lib/ruby/gems/1.8", "/var/ruby/1.8/gem_home"],
@cfg.path)
assert_equal 0, @cfg.ssl_verify_mode
assert_equal "/etc/ssl/certs", @cfg.ssl_ca_cert
assert_equal 28, @cfg.cert_expiration_length_days
- assert_equal true, @cfg.install_extension_in_lib
+ assert_equal false, @cfg.install_extension_in_lib
assert_equal true, @cfg.ipv4_fallback_enabled
+ assert_equal true, @cfg.use_psych
end
def test_initialize_ipv4_fallback_enabled_env
@@ -83,6 +88,53 @@ class TestGemConfigFile < Gem::TestCase
util_config_file %W[--config-file #{@temp_conf}]
assert_equal true, @cfg.ipv4_fallback_enabled
+ ensure
+ ENV.delete("IPV4_FALLBACK_ENABLED")
+ end
+
+ def test_initialize_global_gem_cache_default
+ util_config_file %W[--config-file #{@temp_conf}]
+
+ assert_equal false, @cfg.global_gem_cache
+ end
+
+ def test_initialize_global_gem_cache_env
+ ENV["RUBYGEMS_GLOBAL_GEM_CACHE"] = "true"
+ util_config_file %W[--config-file #{@temp_conf}]
+
+ assert_equal true, @cfg.global_gem_cache
+ ensure
+ ENV.delete("RUBYGEMS_GLOBAL_GEM_CACHE")
+ end
+
+ def test_initialize_global_gem_cache_gemrc
+ File.open @temp_conf, "w" do |fp|
+ fp.puts ":global_gem_cache: true"
+ end
+
+ util_config_file %W[--config-file #{@temp_conf}]
+
+ assert_equal true, @cfg.global_gem_cache
+ end
+
+ def test_initialize_use_psych_env
+ orig_use_psych = ENV["RUBYGEMS_USE_PSYCH"]
+ ENV["RUBYGEMS_USE_PSYCH"] = "true"
+ util_config_file %W[--config-file #{@temp_conf}]
+
+ assert_equal true, @cfg.use_psych
+ ensure
+ ENV["RUBYGEMS_USE_PSYCH"] = orig_use_psych
+ end
+
+ def test_initialize_concurrent_downloads
+ File.open @temp_conf, "w" do |fp|
+ fp.puts ":concurrent_downloads: 2"
+ end
+
+ util_config_file %W[--config-file #{@temp_conf}]
+
+ assert_equal 2, @cfg.concurrent_downloads
end
def test_initialize_handle_arguments_config_file
diff --git a/test/rubygems/test_gem_dependency_installer.rb b/test/rubygems/test_gem_dependency_installer.rb
index 56b84160c4..c2fb6f264b 100644
--- a/test/rubygems/test_gem_dependency_installer.rb
+++ b/test/rubygems/test_gem_dependency_installer.rb
@@ -382,13 +382,9 @@ class TestGemDependencyInstaller < Gem::TestCase
FileUtils.mv f1_gem, @tempdir
inst = nil
- pwd = Dir.getwd
- Dir.chdir @tempdir
- begin
+ Dir.chdir @tempdir do
inst = Gem::DependencyInstaller.new
inst.install "f"
- ensure
- Dir.chdir pwd
end
assert_equal %w[f-1], inst.installed_gems.map(&:full_name)
@@ -523,6 +519,58 @@ class TestGemDependencyInstaller < Gem::TestCase
assert_equal %w[a-1], inst.installed_gems.map(&:full_name)
end
+ def test_install_local_with_extensions_already_installed
+ pend "needs investigation" if Gem.java_platform?
+ pend "ruby.h is not provided by ruby repo" if ruby_repo?
+
+ @spec = quick_gem "a" do |s|
+ s.extensions << "extconf.rb"
+ s.files += %w[extconf.rb a.c]
+ end
+
+ write_dummy_extconf "a"
+
+ c_source_path = File.join(@tempdir, "a.c")
+
+ write_file c_source_path do |io|
+ io.write <<-C
+ #include <ruby.h>
+ void Init_a() { }
+ C
+ end
+
+ package_path = Gem::Package.build @spec
+ installer = Gem::Installer.at(package_path)
+
+ # Make sure the gem is installed and backup the correct package
+
+ installer.install
+
+ package_bkp_path = "#{package_path}.bkp"
+ FileUtils.cp package_path, package_bkp_path
+
+ # Break the extension, rebuild it, and try to install it
+
+ write_file c_source_path do |io|
+ io.write "typo"
+ end
+
+ Gem::Package.build @spec
+
+ assert_raise Gem::Ext::BuildError do
+ installer.install
+ end
+
+ # Make sure installing the good package again still works
+
+ FileUtils.cp "#{package_path}.bkp", package_path
+
+ Dir.chdir @tempdir do
+ inst = Gem::DependencyInstaller.new domain: :local
+ inst.install package_path
+ end
+ end
+
def test_install_minimal_deps
util_setup_gems
@@ -629,8 +677,7 @@ class TestGemDependencyInstaller < Gem::TestCase
util_setup_gems
FileUtils.mv @b1_gem, @tempdir
- si = util_setup_spec_fetcher @b1
- @fetcher.data["http://gems.example.com/gems/yaml"] = si.to_yaml
+ util_setup_spec_fetcher @b1
inst = nil
Dir.chdir @tempdir do
@@ -641,6 +688,25 @@ class TestGemDependencyInstaller < Gem::TestCase
assert_equal %w[b-1], inst.installed_gems.map(&:full_name)
end
+ def test_install_force_with_unsatisfiable_dep
+ # foo depends on bar >= 2.0, but only bar-1.0 exists.
+ # With --force, the unsatisfiable dep should be skipped.
+ _, foo_gem = util_gem "foo", "1" do |s|
+ s.add_dependency "bar", ">= 2.0"
+ end
+
+ util_setup_spec_fetcher(util_spec("bar", "1.0"))
+ FileUtils.mv foo_gem, @tempdir
+ inst = nil
+
+ Dir.chdir @tempdir do
+ inst = Gem::DependencyInstaller.new force: true
+ inst.install "foo"
+ end
+
+ assert_equal %w[foo-1], inst.installed_gems.map(&:full_name)
+ end
+
def test_install_build_args
util_setup_gems
@@ -746,13 +812,12 @@ class TestGemDependencyInstaller < Gem::TestCase
inst = nil
Dir.chdir @tempdir do
- e = assert_raise Gem::UnsatisfiableDependencyError do
+ e = assert_raise Gem::DependencyResolutionError do
inst = Gem::DependencyInstaller.new domain: :local
inst.install "b"
end
- expected = "Unable to resolve dependency: 'b (>= 0)' requires 'a (>= 0)'"
- assert_equal expected, e.message
+ assert_match(/depends on a >= 0 which could not be found in any repository/, e.message)
end
assert_equal [], inst.installed_gems.map(&:full_name)
@@ -907,9 +972,7 @@ class TestGemDependencyInstaller < Gem::TestCase
s.platform = Gem::Platform.new %w[cpu other_platform 1]
end
- si = util_setup_spec_fetcher @a1, a2_o
-
- @fetcher.data["http://gems.example.com/gems/yaml"] = si.to_yaml
+ util_setup_spec_fetcher @a1, a2_o
a1_data = nil
a2_o_data = nil
@@ -1066,117 +1129,6 @@ class TestGemDependencyInstaller < Gem::TestCase
assert_equal %w[activesupport-1.0.0], Gem::Specification.map(&:full_name)
end
- def test_find_gems_gems_with_sources
- util_setup_gems
-
- inst = Gem::DependencyInstaller.new
- dep = Gem::Dependency.new "b", ">= 0"
-
- Gem::Specification.reset
-
- set = Gem::Deprecate.skip_during do
- inst.find_gems_with_sources(dep)
- end
-
- assert_kind_of Gem::AvailableSet, set
-
- s = set.set.first
-
- assert_equal @b1, s.spec
- assert_equal Gem::Source.new(@gem_repo), s.source
- end
-
- def test_find_gems_with_sources_local
- util_setup_gems
-
- FileUtils.mv @a1_gem, @tempdir
- inst = Gem::DependencyInstaller.new
- dep = Gem::Dependency.new "a", ">= 0"
- set = nil
-
- Dir.chdir @tempdir do
- set = Gem::Deprecate.skip_during do
- inst.find_gems_with_sources dep
- end
- end
-
- gems = set.sorted
-
- assert_equal 2, gems.length
-
- remote, local = gems
-
- assert_equal "a-1", local.spec.full_name, "local spec"
- assert_equal File.join(@tempdir, @a1.file_name),
- local.source.download(local.spec), "local path"
-
- assert_equal "a-1", remote.spec.full_name, "remote spec"
- assert_equal Gem::Source.new(@gem_repo), remote.source, "remote path"
- end
-
- def test_find_gems_with_sources_prerelease
- util_setup_gems
-
- installer = Gem::DependencyInstaller.new
-
- dependency = Gem::Dependency.new("a", Gem::Requirement.default)
-
- set = Gem::Deprecate.skip_during do
- installer.find_gems_with_sources(dependency)
- end
-
- releases = set.all_specs
-
- assert releases.any? {|s| s.name == "a" && s.version.to_s == "1" }
- refute releases.any? {|s| s.name == "a" && s.version.to_s == "1.a" }
-
- dependency.prerelease = true
-
- set = Gem::Deprecate.skip_during do
- installer.find_gems_with_sources(dependency)
- end
-
- prereleases = set.all_specs
-
- assert_equal [@a1_pre, @a1], prereleases
- end
-
- def test_find_gems_with_sources_with_best_only_and_platform
- util_setup_gems
- a1_x86_mingw32, = util_gem "a", "1" do |s|
- s.platform = "x86-mingw32"
- end
- util_setup_spec_fetcher @a1, a1_x86_mingw32
- Gem.platforms << Gem::Platform.new("x86-mingw32")
-
- installer = Gem::DependencyInstaller.new
-
- dependency = Gem::Dependency.new("a", Gem::Requirement.default)
-
- set = Gem::Deprecate.skip_during do
- installer.find_gems_with_sources(dependency, true)
- end
-
- releases = set.all_specs
-
- assert_equal [a1_x86_mingw32], releases
- end
-
- def test_find_gems_with_sources_with_bad_source
- Gem.sources.replace ["http://not-there.nothing"]
-
- installer = Gem::DependencyInstaller.new
-
- dep = Gem::Dependency.new("a")
-
- out = Gem::Deprecate.skip_during do
- installer.find_gems_with_sources(dep)
- end
-
- assert out.empty?
- assert_kind_of Gem::SourceFetchProblem, installer.errors.first
- end
-
def test_resolve_dependencies
util_setup_gems
diff --git a/test/rubygems/test_gem_dependency_resolution_error.rb b/test/rubygems/test_gem_dependency_resolution_error.rb
index 98a6b6b8fd..d8fa96a260 100644
--- a/test/rubygems/test_gem_dependency_resolution_error.rb
+++ b/test/rubygems/test_gem_dependency_resolution_error.rb
@@ -6,20 +6,23 @@ class TestGemDependencyResolutionError < Gem::TestCase
def setup
super
- @spec = util_spec "a", 2
-
- @a1_req = Gem::Resolver::DependencyRequest.new dep("a", "= 1"), nil
- @a2_req = Gem::Resolver::DependencyRequest.new dep("a", "= 2"), nil
+ failure = Struct.new(:explanation).new("a depends on b (= 1.0) but no versions match")
+ @error = Gem::DependencyResolutionError.new failure
+ end
- @activated = Gem::Resolver::ActivationRequest.new @spec, @a2_req
+ def test_message
+ assert_equal "a depends on b (= 1.0) but no versions match", @error.message
+ end
- @conflict = Gem::Resolver::Conflict.new @a1_req, @activated
+ def test_explanation
+ assert_equal "a depends on b (= 1.0) but no versions match", @error.explanation
+ end
- @error = Gem::DependencyResolutionError.new @conflict
+ def test_conflict
+ assert_nil @error.conflict
end
- def test_message
- assert_match(/^conflicting dependencies a \(= 1\) and a \(= 2\)$/,
- @error.message)
+ def test_conflicting_dependencies
+ assert_equal [], @error.conflicting_dependencies
end
end
diff --git a/test/rubygems/test_gem_ext_builder.rb b/test/rubygems/test_gem_ext_builder.rb
index 34f85e6b75..37204f3c47 100644
--- a/test/rubygems/test_gem_ext_builder.rb
+++ b/test/rubygems/test_gem_ext_builder.rb
@@ -18,7 +18,7 @@ class TestGemExtBuilder < Gem::TestCase
@spec = util_spec "a"
- @builder = Gem::Ext::Builder.new @spec, ""
+ @builder = Gem::Ext::Builder.new @spec
end
def teardown
@@ -106,6 +106,22 @@ install:
assert_match(/install: OK/, results)
end
+ def test_class_run_closes_stdin
+ results = []
+ check_stdin_script = <<~'RUBY'
+ if IO.select([STDIN], nil, nil, 1)
+ puts "STDIN: #{STDIN.read.inspect}"
+ else
+ puts "NOT_READY"
+ end
+ RUBY
+
+ Gem::Ext::Builder.run([Gem.ruby, "-e", check_stdin_script], results)
+
+ command_output = results.last
+ assert_equal "STDIN: \"\"\n", command_output
+ end
+
def test_build_extensions
pend "terminates on mswin" if vc_windows? && ruby_repo?
@@ -201,6 +217,57 @@ install:
Gem.configuration.install_extension_in_lib = @orig_install_extension_in_lib
end
+ def test_build_multiple_extensions
+ pend if RUBY_ENGINE == "truffleruby"
+ pend "terminates on ruby/ruby" if ruby_repo?
+
+ extension_in_lib do
+ @spec.extensions << "ext/Rakefile"
+ @spec.extensions << "ext/extconf.rb"
+
+ ext_dir = File.join @spec.gem_dir, "ext"
+
+ FileUtils.mkdir_p ext_dir
+
+ extconf_rb = File.join ext_dir, "extconf.rb"
+ rakefile = File.join ext_dir, "Rakefile"
+
+ File.open extconf_rb, "w" do |f|
+ f.write <<-'RUBY'
+ require 'mkmf'
+
+ create_makefile 'a'
+ RUBY
+ end
+
+ File.open rakefile, "w" do |f|
+ f.write <<-RUBY
+ task :default do
+ FileUtils.touch File.join "#{ext_dir}", 'foo'
+ end
+ RUBY
+ end
+
+ ext_lib_dir = File.join ext_dir, "lib"
+ FileUtils.mkdir ext_lib_dir
+ FileUtils.touch File.join ext_lib_dir, "a.rb"
+ FileUtils.mkdir File.join ext_lib_dir, "a"
+ FileUtils.touch File.join ext_lib_dir, "a", "b.rb"
+
+ use_ui @ui do
+ @builder.build_extensions
+ end
+
+ assert_path_exist @spec.extension_dir
+ assert_path_exist @spec.gem_build_complete_path
+ assert_path_exist File.join @spec.gem_dir, "ext", "foo"
+ assert_path_exist File.join @spec.extension_dir, "gem_make.out"
+ assert_path_exist File.join @spec.extension_dir, "a.rb"
+ assert_path_exist File.join @spec.gem_dir, "lib", "a.rb"
+ assert_path_exist File.join @spec.gem_dir, "lib", "a", "b.rb"
+ end
+ end
+
def test_build_extensions_none
use_ui @ui do
@builder.build_extensions
diff --git a/test/rubygems/test_gem_ext_cargo_builder.rb b/test/rubygems/test_gem_ext_cargo_builder.rb
index 5035937544..b970e442c2 100644
--- a/test/rubygems/test_gem_ext_cargo_builder.rb
+++ b/test/rubygems/test_gem_ext_cargo_builder.rb
@@ -141,6 +141,58 @@ class TestGemExtCargoBuilder < Gem::TestCase
end
end
+ def test_linker_args
+ orig_cc = RbConfig::MAKEFILE_CONFIG["CC"]
+ RbConfig::MAKEFILE_CONFIG["CC"] = "clang"
+
+ builder = Gem::Ext::CargoBuilder.new
+ args = builder.send(:linker_args)
+
+ assert args[1], "linker=clang"
+ assert_nil args[2]
+ ensure
+ RbConfig::MAKEFILE_CONFIG["CC"] = orig_cc
+ end
+
+ def test_linker_args_with_options
+ orig_cc = RbConfig::MAKEFILE_CONFIG["CC"]
+ RbConfig::MAKEFILE_CONFIG["CC"] = "gcc -Wl,--no-undefined"
+
+ builder = Gem::Ext::CargoBuilder.new
+ args = builder.send(:linker_args)
+
+ assert args[1], "linker=clang"
+ assert args[3], "link-args=-Wl,--no-undefined"
+ ensure
+ RbConfig::MAKEFILE_CONFIG["CC"] = orig_cc
+ end
+
+ def test_linker_args_with_cachetools
+ orig_cc = RbConfig::MAKEFILE_CONFIG["CC"]
+ RbConfig::MAKEFILE_CONFIG["CC"] = "sccache clang"
+
+ builder = Gem::Ext::CargoBuilder.new
+ args = builder.send(:linker_args)
+
+ assert args[1], "linker=clang"
+ assert_nil args[2]
+ ensure
+ RbConfig::MAKEFILE_CONFIG["CC"] = orig_cc
+ end
+
+ def test_linker_args_with_cachetools_and_options
+ orig_cc = RbConfig::MAKEFILE_CONFIG["CC"]
+ RbConfig::MAKEFILE_CONFIG["CC"] = "ccache gcc -Wl,--no-undefined"
+
+ builder = Gem::Ext::CargoBuilder.new
+ args = builder.send(:linker_args)
+
+ assert args[1], "linker=clang"
+ assert args[3], "link-args=-Wl,--no-undefined"
+ ensure
+ RbConfig::MAKEFILE_CONFIG["CC"] = orig_cc
+ end
+
private
def skip_unsupported_platforms!
diff --git a/test/rubygems/test_gem_ext_cargo_builder/custom_name/ext/custom_name_lib/Cargo.lock b/test/rubygems/test_gem_ext_cargo_builder/custom_name/ext/custom_name_lib/Cargo.lock
index f16c0eb140..d6c49c3de1 100644
--- a/test/rubygems/test_gem_ext_cargo_builder/custom_name/ext/custom_name_lib/Cargo.lock
+++ b/test/rubygems/test_gem_ext_cargo_builder/custom_name/ext/custom_name_lib/Cargo.lock
@@ -13,16 +13,14 @@ dependencies = [
[[package]]
name = "bindgen"
-version = "0.69.1"
+version = "0.72.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9ffcebc3849946a7170a05992aac39da343a90676ab392c51a4280981d6379c2"
+checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895"
dependencies = [
"bitflags",
"cexpr",
"clang-sys",
- "lazy_static",
- "lazycell",
- "peeking_take_while",
+ "itertools",
"proc-macro2",
"quote",
"regex",
@@ -71,22 +69,31 @@ dependencies = [
]
[[package]]
+name = "either"
+version = "1.15.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
+
+[[package]]
name = "glob"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574"
[[package]]
-name = "lazy_static"
-version = "1.4.0"
+name = "itertools"
+version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
+checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186"
+dependencies = [
+ "either",
+]
[[package]]
-name = "lazycell"
-version = "1.3.0"
+name = "lazy_static"
+version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
+checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "libc"
@@ -127,16 +134,10 @@ dependencies = [
]
[[package]]
-name = "peeking_take_while"
-version = "0.1.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099"
-
-[[package]]
name = "proc-macro2"
-version = "1.0.66"
+version = "1.0.106"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9"
+checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
dependencies = [
"unicode-ident",
]
@@ -152,18 +153,18 @@ dependencies = [
[[package]]
name = "rb-sys"
-version = "0.9.111"
+version = "0.9.128"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "becea799ce051c16fb140be80f5e7cf781070f99ca099332383c2b17861249af"
+checksum = "45ca28513560e56cfb79a62b1fce363c73af170a182024ce880c77ee9429920a"
dependencies = [
"rb-sys-build",
]
[[package]]
name = "rb-sys-build"
-version = "0.9.111"
+version = "0.9.128"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "64691175abc704862f60a9ca8ef06174080cc50615f2bf1d4759f46db18b4d29"
+checksum = "ce04b2c55eff3a21aaa623fcc655d94373238e72cac6b3e1a3641ff31649f99a"
dependencies = [
"bindgen",
"lazy_static",
@@ -193,9 +194,9 @@ checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848"
[[package]]
name = "rustc-hash"
-version = "1.1.0"
+version = "2.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
+checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe"
[[package]]
name = "shell-words"
diff --git a/test/rubygems/test_gem_ext_cargo_builder/custom_name/ext/custom_name_lib/Cargo.toml b/test/rubygems/test_gem_ext_cargo_builder/custom_name/ext/custom_name_lib/Cargo.toml
index a66404aa41..056567c708 100644
--- a/test/rubygems/test_gem_ext_cargo_builder/custom_name/ext/custom_name_lib/Cargo.toml
+++ b/test/rubygems/test_gem_ext_cargo_builder/custom_name/ext/custom_name_lib/Cargo.toml
@@ -7,4 +7,4 @@ edition = "2021"
crate-type = ["cdylib"]
[dependencies]
-rb-sys = "0.9.111"
+rb-sys = "0.9.128"
diff --git a/test/rubygems/test_gem_ext_cargo_builder/rust_ruby_example/Cargo.lock b/test/rubygems/test_gem_ext_cargo_builder/rust_ruby_example/Cargo.lock
index 1230f8ae96..806d51d3a1 100644
--- a/test/rubygems/test_gem_ext_cargo_builder/rust_ruby_example/Cargo.lock
+++ b/test/rubygems/test_gem_ext_cargo_builder/rust_ruby_example/Cargo.lock
@@ -13,16 +13,14 @@ dependencies = [
[[package]]
name = "bindgen"
-version = "0.69.1"
+version = "0.72.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9ffcebc3849946a7170a05992aac39da343a90676ab392c51a4280981d6379c2"
+checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895"
dependencies = [
"bitflags",
"cexpr",
"clang-sys",
- "lazy_static",
- "lazycell",
- "peeking_take_while",
+ "itertools",
"proc-macro2",
"quote",
"regex",
@@ -64,22 +62,31 @@ dependencies = [
]
[[package]]
+name = "either"
+version = "1.15.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
+
+[[package]]
name = "glob"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574"
[[package]]
-name = "lazy_static"
-version = "1.4.0"
+name = "itertools"
+version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
+checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186"
+dependencies = [
+ "either",
+]
[[package]]
-name = "lazycell"
-version = "1.3.0"
+name = "lazy_static"
+version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
+checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "libc"
@@ -120,16 +127,10 @@ dependencies = [
]
[[package]]
-name = "peeking_take_while"
-version = "0.1.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099"
-
-[[package]]
name = "proc-macro2"
-version = "1.0.66"
+version = "1.0.106"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9"
+checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
dependencies = [
"unicode-ident",
]
@@ -145,18 +146,18 @@ dependencies = [
[[package]]
name = "rb-sys"
-version = "0.9.111"
+version = "0.9.128"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "becea799ce051c16fb140be80f5e7cf781070f99ca099332383c2b17861249af"
+checksum = "45ca28513560e56cfb79a62b1fce363c73af170a182024ce880c77ee9429920a"
dependencies = [
"rb-sys-build",
]
[[package]]
name = "rb-sys-build"
-version = "0.9.111"
+version = "0.9.128"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "64691175abc704862f60a9ca8ef06174080cc50615f2bf1d4759f46db18b4d29"
+checksum = "ce04b2c55eff3a21aaa623fcc655d94373238e72cac6b3e1a3641ff31649f99a"
dependencies = [
"bindgen",
"lazy_static",
@@ -193,9 +194,9 @@ dependencies = [
[[package]]
name = "rustc-hash"
-version = "1.1.0"
+version = "2.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
+checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe"
[[package]]
name = "shell-words"
diff --git a/test/rubygems/test_gem_ext_cargo_builder/rust_ruby_example/Cargo.toml b/test/rubygems/test_gem_ext_cargo_builder/rust_ruby_example/Cargo.toml
index 03853fea08..f0ddeeb91c 100644
--- a/test/rubygems/test_gem_ext_cargo_builder/rust_ruby_example/Cargo.toml
+++ b/test/rubygems/test_gem_ext_cargo_builder/rust_ruby_example/Cargo.toml
@@ -7,4 +7,4 @@ edition = "2021"
crate-type = ["cdylib"]
[dependencies]
-rb-sys = "0.9.111"
+rb-sys = "0.9.128"
diff --git a/test/rubygems/test_gem_ext_cargo_builder_link_flag_converter.rb b/test/rubygems/test_gem_ext_cargo_builder_link_flag_converter.rb
index a3fef50d54..3693f63df6 100644
--- a/test/rubygems/test_gem_ext_cargo_builder_link_flag_converter.rb
+++ b/test/rubygems/test_gem_ext_cargo_builder_link_flag_converter.rb
@@ -25,7 +25,7 @@ class TestGemExtCargoBuilderLinkFlagConverter < Gem::TestCase
}.freeze
CASES.each do |test_name, (arg, expected)|
- raise "duplicate test name" if instance_methods.include?(test_name)
+ raise "duplicate test name" if method_defined?(test_name)
define_method(test_name) do
assert_equal(expected, Gem::Ext::CargoBuilder::LinkFlagConverter.convert(arg))
diff --git a/test/rubygems/test_gem_ext_cmake_builder.rb b/test/rubygems/test_gem_ext_cmake_builder.rb
index 5f886af05f..b9b57084d4 100644
--- a/test/rubygems/test_gem_ext_cmake_builder.rb
+++ b/test/rubygems/test_gem_ext_cmake_builder.rb
@@ -7,7 +7,7 @@ class TestGemExtCmakeBuilder < Gem::TestCase
def setup
super
- # Details: https://github.com/rubygems/rubygems/issues/1270#issuecomment-177368340
+ # Details: https://github.com/ruby/rubygems/issues/1270#issuecomment-177368340
pend "CmakeBuilder doesn't work on Windows." if Gem.win_platform?
require "open3"
@@ -29,7 +29,7 @@ class TestGemExtCmakeBuilder < Gem::TestCase
def test_self_build
File.open File.join(@ext, "CMakeLists.txt"), "w" do |cmakelists|
cmakelists.write <<-EO_CMAKE
-cmake_minimum_required(VERSION 2.6)
+cmake_minimum_required(VERSION 3.26)
project(self_build NONE)
install (FILES test.txt DESTINATION bin)
EO_CMAKE
@@ -39,46 +39,107 @@ install (FILES test.txt DESTINATION bin)
output = []
- Gem::Ext::CmakeBuilder.build nil, @dest_path, output, [], nil, @ext
+ builder = Gem::Ext::CmakeBuilder.new
+ builder.build nil, @dest_path, output, [], @dest_path, @ext
output = output.join "\n"
- assert_match(/^cmake \. -DCMAKE_INSTALL_PREFIX\\=#{Regexp.escape @dest_path}/, output)
+ assert_match(/^current directory: #{Regexp.escape @ext}/, output)
+ assert_match(/cmake.*-DCMAKE_RUNTIME_OUTPUT_DIRECTORY\\=#{Regexp.escape @dest_path}/, output)
+ assert_match(/cmake.*-DCMAKE_LIBRARY_OUTPUT_DIRECTORY\\=#{Regexp.escape @dest_path}/, output)
+ assert_match(/#{Regexp.escape @ext}/, output)
+ end
+
+ def test_self_build_presets
+ File.open File.join(@ext, "CMakeLists.txt"), "w" do |cmakelists|
+ cmakelists.write <<-EO_CMAKE
+cmake_minimum_required(VERSION 3.26)
+project(self_build NONE)
+install (FILES test.txt DESTINATION bin)
+ EO_CMAKE
+ end
+
+ File.open File.join(@ext, "CMakePresets.json"), "w" do |presets|
+ presets.write <<-EO_CMAKE
+{
+ "version": 6,
+ "configurePresets": [
+ {
+ "name": "debug",
+ "displayName": "Debug",
+ "generator": "Ninja",
+ "binaryDir": "build/debug",
+ "cacheVariables": {
+ "CMAKE_BUILD_TYPE": "Debug"
+ }
+ },
+ {
+ "name": "release",
+ "displayName": "Release",
+ "generator": "Ninja",
+ "binaryDir": "build/release",
+ "cacheVariables": {
+ "CMAKE_BUILD_TYPE": "Release"
+ }
+ }
+ ]
+}
+ EO_CMAKE
+ end
+
+ FileUtils.touch File.join(@ext, "test.txt")
+
+ output = []
+
+ builder = Gem::Ext::CmakeBuilder.new
+ builder.build nil, @dest_path, output, [], @dest_path, @ext
+
+ output = output.join "\n"
+
+ assert_match(/The gem author provided a list of presets that can be used to build the gem./, output)
+ assert_match(/Available configure presets/, output)
+ assert_match(/\"debug\" - Debug/, output)
+ assert_match(/\"release\" - Release/, output)
+ assert_match(/^current directory: #{Regexp.escape @ext}/, output)
+ assert_match(/cmake.*-DCMAKE_RUNTIME_OUTPUT_DIRECTORY\\=#{Regexp.escape @dest_path}/, output)
+ assert_match(/cmake.*-DCMAKE_LIBRARY_OUTPUT_DIRECTORY\\=#{Regexp.escape @dest_path}/, output)
assert_match(/#{Regexp.escape @ext}/, output)
- assert_contains_make_command "", output
- assert_contains_make_command "install", output
- assert_match(/test\.txt/, output)
end
def test_self_build_fail
output = []
+ builder = Gem::Ext::CmakeBuilder.new
error = assert_raise Gem::InstallError do
- Gem::Ext::CmakeBuilder.build nil, @dest_path, output, [], nil, @ext
+ builder.build nil, @dest_path, output, [], @dest_path, @ext
end
- output = output.join "\n"
+ assert_match "cmake_configure failed", error.message
shell_error_msg = /(CMake Error: .*)/
-
- assert_match "cmake failed", error.message
-
- assert_match(/^cmake . -DCMAKE_INSTALL_PREFIX\\=#{Regexp.escape @dest_path}/, output)
+ output = output.join "\n"
assert_match(/#{shell_error_msg}/, output)
+ assert_match(/CMake Error: The source directory .* does not appear to contain CMakeLists.txt./, output)
end
def test_self_build_has_makefile
- File.open File.join(@ext, "Makefile"), "w" do |makefile|
- makefile.puts "all:\n\t@echo ok\ninstall:\n\t@echo ok"
+ File.open File.join(@ext, "CMakeLists.txt"), "w" do |cmakelists|
+ cmakelists.write <<-EO_CMAKE
+cmake_minimum_required(VERSION 3.26)
+project(self_build NONE)
+install (FILES test.txt DESTINATION bin)
+ EO_CMAKE
end
output = []
- Gem::Ext::CmakeBuilder.build nil, @dest_path, output, [], nil, @ext
+ builder = Gem::Ext::CmakeBuilder.new
+ builder.build nil, @dest_path, output, [], @dest_path, @ext
output = output.join "\n"
- assert_contains_make_command "", output
- assert_contains_make_command "install", output
+ # The default generator will create a Makefile in the build directory
+ makefile = File.join(@ext, "build", "Makefile")
+ assert(File.exist?(makefile))
end
end
diff --git a/test/rubygems/test_gem_ext_ext_conf_builder.rb b/test/rubygems/test_gem_ext_ext_conf_builder.rb
index 218c6f3d5e..bc383e5540 100644
--- a/test/rubygems/test_gem_ext_ext_conf_builder.rb
+++ b/test/rubygems/test_gem_ext_ext_conf_builder.rb
@@ -15,15 +15,12 @@ class TestGemExtExtConfBuilder < Gem::TestCase
end
def test_class_build
- if Gem.java_platform?
- pend("failing on jruby")
- end
-
if vc_windows? && !nmake_found?
pend("test_class_build skipped - nmake not found")
end
File.open File.join(@ext, "extconf.rb"), "w" do |extconf|
+ extconf.puts "return if Gem.java_platform?"
extconf.puts "require 'mkmf'\ncreate_makefile 'foo'"
end
@@ -35,20 +32,22 @@ class TestGemExtExtConfBuilder < Gem::TestCase
assert_match(/^current directory:/, output[0])
assert_match(/^#{Regexp.quote(Gem.ruby)}.* extconf.rb/, output[1])
- assert_equal "creating Makefile\n", output[2]
- assert_match(/^current directory:/, output[3])
- assert_contains_make_command "clean", output[4]
- assert_contains_make_command "", output[7]
- assert_contains_make_command "install", output[10]
+
+ if Gem.java_platform?
+ assert_includes(output, "Skipping make for extconf.rb as no Makefile was found.")
+ else
+ assert_equal "creating Makefile\n", output[2]
+ assert_match(/^current directory:/, output[3])
+ assert_contains_make_command "clean", output[4]
+ assert_contains_make_command "", output[7]
+ assert_contains_make_command "install", output[10]
+ end
+
assert_empty Dir.glob(File.join(@ext, "siteconf*.rb"))
assert_empty Dir.glob(File.join(@ext, ".gem.*"))
end
def test_class_build_rbconfig_make_prog
- if Gem.java_platform?
- pend("failing on jruby")
- end
-
configure_args do
File.open File.join(@ext, "extconf.rb"), "w" do |extconf|
extconf.puts "require 'mkmf'\ncreate_makefile 'foo'"
@@ -72,10 +71,6 @@ class TestGemExtExtConfBuilder < Gem::TestCase
env_large_make = ENV.delete "MAKE"
ENV["MAKE"] = "anothermake"
- if Gem.java_platform?
- pend("failing on jruby")
- end
-
configure_args "" do
File.open File.join(@ext, "extconf.rb"), "w" do |extconf|
extconf.puts "require 'mkmf'\ncreate_makefile 'foo'"
@@ -206,11 +201,11 @@ end
end
def test_class_make_no_Makefile
- error = assert_raise Gem::InstallError do
+ error = assert_raise Gem::Ext::Builder::NoMakefileError do
Gem::Ext::ExtConfBuilder.make @ext, ["output"], @ext
end
- assert_equal "Makefile not found", error.message
+ assert_match(/No Makefile found/, error.message)
end
def configure_args(args = nil)
diff --git a/test/rubygems/test_gem_ext_rake_builder.rb b/test/rubygems/test_gem_ext_rake_builder.rb
index bd72c1aa08..68ad15b044 100644
--- a/test/rubygems/test_gem_ext_rake_builder.rb
+++ b/test/rubygems/test_gem_ext_rake_builder.rb
@@ -29,7 +29,7 @@ class TestGemExtRakeBuilder < Gem::TestCase
end
end
- # https://github.com/rubygems/rubygems/pull/1819
+ # https://github.com/ruby/rubygems/pull/1819
#
# It should not fail with a non-empty args list either
def test_class_build_with_args
diff --git a/test/rubygems/test_gem_gem_runner.rb b/test/rubygems/test_gem_gem_runner.rb
index 4fb205040c..9cc2fac619 100644
--- a/test/rubygems/test_gem_gem_runner.rb
+++ b/test/rubygems/test_gem_gem_runner.rb
@@ -82,17 +82,6 @@ class TestGemGemRunner < Gem::TestCase
assert_equal %w[--foo], args
end
- def test_query_is_deprecated
- args = %w[query]
-
- use_ui @ui do
- @runner.run(args)
- end
-
- assert_match(/WARNING: query command is deprecated. It will be removed in Rubygems [0-9]+/, @ui.error)
- assert_match(/WARNING: It is recommended that you use `gem search` or `gem list` instead/, @ui.error)
- end
-
def test_info_succeeds
args = %w[info]
diff --git a/test/rubygems/test_gem_gemcutter_utilities.rb b/test/rubygems/test_gem_gemcutter_utilities.rb
index a3236e6276..ca34c8d03d 100644
--- a/test/rubygems/test_gem_gemcutter_utilities.rb
+++ b/test/rubygems/test_gem_gemcutter_utilities.rb
@@ -150,7 +150,7 @@ class TestGemGemcutterUtilities < Gem::TestCase
util_sign_in
- assert_equal "", @sign_in_ui.output
+ assert_match(/You are already signed in/, @sign_in_ui.output)
end
def test_sign_in_skips_with_key_override
@@ -158,7 +158,7 @@ class TestGemGemcutterUtilities < Gem::TestCase
@cmd.options[:key] = :KEY
util_sign_in
- assert_equal "", @sign_in_ui.output
+ assert_match(/You are already signed in/, @sign_in_ui.output)
end
def test_sign_in_with_other_credentials_doesnt_overwrite_other_keys
@@ -233,9 +233,10 @@ class TestGemGemcutterUtilities < Gem::TestCase
end
end
- assert_match "You have enabled multi-factor authentication. Please visit #{@fetcher.webauthn_url_with_port(server.port)} " \
+ assert_match "You have enabled multi-factor authentication. Please visit the following URL " \
"to authenticate via security device. If you can't verify using WebAuthn but have OTP enabled, " \
"you can re-run the gem signin command with the `--otp [your_code]` option.", @sign_in_ui.output
+ assert_match @fetcher.webauthn_url_with_port(server.port), @sign_in_ui.output
assert_match "You are verified with a security device. You may close the browser window.", @sign_in_ui.output
assert_equal "Uvh6T57tkWuUnWYo", @fetcher.last_request["OTP"]
end
@@ -255,9 +256,10 @@ class TestGemGemcutterUtilities < Gem::TestCase
end
assert_equal 1, error.exit_code
- assert_match "You have enabled multi-factor authentication. Please visit #{@fetcher.webauthn_url_with_port(server.port)} " \
+ assert_match "You have enabled multi-factor authentication. Please visit the following URL " \
"to authenticate via security device. If you can't verify using WebAuthn but have OTP enabled, " \
"you can re-run the gem signin command with the `--otp [your_code]` option.", @sign_in_ui.output
+ assert_match @fetcher.webauthn_url_with_port(server.port), @sign_in_ui.output
assert_match "ERROR: Security device verification failed: Something went wrong", @sign_in_ui.error
refute_match "You are verified with a security device. You may close the browser window.", @sign_in_ui.output
refute_match "Signed in with API key:", @sign_in_ui.output
@@ -273,9 +275,10 @@ class TestGemGemcutterUtilities < Gem::TestCase
util_sign_in
end
- assert_match "You have enabled multi-factor authentication. Please visit #{@fetcher.webauthn_url_with_port(server.port)} " \
+ assert_match "You have enabled multi-factor authentication. Please visit the following URL " \
"to authenticate via security device. If you can't verify using WebAuthn but have OTP enabled, " \
"you can re-run the gem signin command with the `--otp [your_code]` option.", @sign_in_ui.output
+ assert_match @fetcher.webauthn_url_with_port(server.port), @sign_in_ui.output
assert_match "You are verified with a security device. You may close the browser window.", @sign_in_ui.output
assert_equal "Uvh6T57tkWuUnWYo", @fetcher.last_request["OTP"]
end
@@ -292,9 +295,10 @@ class TestGemGemcutterUtilities < Gem::TestCase
end
end
- assert_match "You have enabled multi-factor authentication. Please visit #{@fetcher.webauthn_url_with_port(server.port)} " \
+ assert_match "You have enabled multi-factor authentication. Please visit the following URL " \
"to authenticate via security device. If you can't verify using WebAuthn but have OTP enabled, " \
"you can re-run the gem signin command with the `--otp [your_code]` option.", @sign_in_ui.output
+ assert_match @fetcher.webauthn_url_with_port(server.port), @sign_in_ui.output
assert_match "ERROR: Security device verification failed: " \
"The token in the link you used has either expired or been used already.", @sign_in_ui.error
end
diff --git a/test/rubygems/test_gem_impossible_dependencies_error.rb b/test/rubygems/test_gem_impossible_dependencies_error.rb
deleted file mode 100644
index 94c0290ea1..0000000000
--- a/test/rubygems/test_gem_impossible_dependencies_error.rb
+++ /dev/null
@@ -1,60 +0,0 @@
-# frozen_string_literal: true
-
-require_relative "helper"
-
-class TestGemImpossibleDependenciesError < Gem::TestCase
- def test_message_conflict
- request = dependency_request dep("net-ssh", ">= 2.0.13"), "rye", "0.9.8"
-
- conflicts = []
-
- # These conflicts are lies as their dependencies does not have the correct
- # requested-by entries, but they are suitable for testing the message.
- # See #485 to construct a correct conflict.
- net_ssh_2_2_2 =
- dependency_request dep("net-ssh", ">= 2.6.5"), "net-ssh", "2.2.2", request
- net_ssh_2_6_5 =
- dependency_request dep("net-ssh", "~> 2.2.2"), "net-ssh", "2.6.5", request
-
- conflict1 = Gem::Resolver::Conflict.new \
- net_ssh_2_6_5, net_ssh_2_6_5.requester
-
- conflict2 = Gem::Resolver::Conflict.new \
- net_ssh_2_2_2, net_ssh_2_2_2.requester
-
- conflicts << [net_ssh_2_6_5.requester.spec, conflict1]
- conflicts << [net_ssh_2_2_2.requester.spec, conflict2]
-
- error = Gem::ImpossibleDependenciesError.new request, conflicts
-
- expected = <<-EXPECTED
-rye-0.9.8 requires net-ssh (>= 2.0.13) but it conflicted:
- Activated net-ssh-2.6.5
- which does not match conflicting dependency (~> 2.2.2)
-
- Conflicting dependency chains:
- rye (= 0.9.8), 0.9.8 activated, depends on
- net-ssh (>= 2.0.13), 2.6.5 activated
-
- versus:
- rye (= 0.9.8), 0.9.8 activated, depends on
- net-ssh (>= 2.0.13), 2.6.5 activated, depends on
- net-ssh (~> 2.2.2)
-
- Activated net-ssh-2.2.2
- which does not match conflicting dependency (>= 2.6.5)
-
- Conflicting dependency chains:
- rye (= 0.9.8), 0.9.8 activated, depends on
- net-ssh (>= 2.0.13), 2.2.2 activated
-
- versus:
- rye (= 0.9.8), 0.9.8 activated, depends on
- net-ssh (>= 2.0.13), 2.2.2 activated, depends on
- net-ssh (>= 2.6.5)
-
- EXPECTED
-
- assert_equal expected, error.message
- end
-end
diff --git a/test/rubygems/test_gem_install_update_options.rb b/test/rubygems/test_gem_install_update_options.rb
index 8fd5d9c543..1e451dcb05 100644
--- a/test/rubygems/test_gem_install_update_options.rb
+++ b/test/rubygems/test_gem_install_update_options.rb
@@ -202,4 +202,16 @@ class TestGemInstallUpdateOptions < Gem::InstallerTestCase
assert_equal true, @cmd.options[:minimal_deps]
end
+
+ def test_build_jobs_short_version
+ @cmd.handle_options %w[-j 4]
+
+ assert_equal 4, @cmd.options[:build_jobs]
+ end
+
+ def test_build_jobs_long_version
+ @cmd.handle_options %w[--build-jobs 4]
+
+ assert_equal 4, @cmd.options[:build_jobs]
+ end
end
diff --git a/test/rubygems/test_gem_installer.rb b/test/rubygems/test_gem_installer.rb
index 83e43c135f..8947694f53 100644
--- a/test/rubygems/test_gem_installer.rb
+++ b/test/rubygems/test_gem_installer.rb
@@ -24,36 +24,35 @@ class TestGemInstaller < Gem::InstallerTestCase
util_make_exec @spec, ""
- expected = <<-EOF
-#!#{Gem.ruby}
-#
-# This file was generated by RubyGems.
-#
-# The application 'a' is installed as part of a gem, and
-# this file is here to facilitate running it.
-#
-
-require 'rubygems'
-
-Gem.use_gemdeps
-
-version = \">= 0.a\"
-
-str = ARGV.first
-if str
- str = str.b[/\\A_(.*)_\\z/, 1]
- if str and Gem::Version.correct?(str)
- version = str
- ARGV.shift
- end
-end
+ expected = <<~EOF
+ #!#{Gem.ruby}
+ #
+ # This file was generated by RubyGems.
+ #
+ # The application 'a' is installed as part of a gem, and
+ # this file is here to facilitate running it.
+ #
+
+ require 'rubygems'
+
+ Gem.use_gemdeps
+
+ version = \">= 0.a\"
+
+ str = ARGV.first
+ if str
+ str = str.b[/\\A_(.*)_\\z/, 1]
+ if str and Gem::Version.correct?(str)
+ version = str
+ ARGV.shift
+ end
+ end
-if Gem.respond_to?(:activate_bin_path)
-load Gem.activate_bin_path('a', 'executable', version)
-else
-gem "a", version
-load Gem.bin_path("a", "executable", version)
-end
+ if Gem.respond_to?(:activate_and_load_bin_path)
+ Gem.activate_and_load_bin_path('a', 'executable', version)
+ else
+ load Gem.activate_bin_path('a', 'executable', version)
+ end
EOF
wrapper = installer.app_script_text "executable"
@@ -61,6 +60,21 @@ end
end
end
+ def test_app_script_text_escapes_executable_name
+ installer = setup_base_installer
+
+ malicious = "evil');system('id');#"
+ @spec.bindir = "bin"
+ write_file @spec.bin_file(malicious) do |io|
+ io.puts "#!/usr/bin/ruby"
+ end
+
+ wrapper = installer.app_script_text malicious
+
+ assert_includes wrapper, %q{Gem.activate_and_load_bin_path('a', 'evil\');system(\'id\');#', version)}
+ assert_includes wrapper, %q{load Gem.activate_bin_path('a', 'evil\');system(\'id\');#', version)}
+ end
+
def test_check_executable_overwrite
installer = setup_base_installer
@@ -121,12 +135,12 @@ end
end
File.open File.join(util_inst_bindir, "executable"), "w" do |io|
- io.write <<-EXEC
-#!/usr/local/bin/ruby
-#
-# This file was generated by RubyGems
+ io.write <<~EXEC
+ #!/usr/local/bin/ruby
+ #
+ # This file was generated by RubyGems
-gem 'other', version
+ gem 'other', version
EXEC
end
@@ -690,8 +704,11 @@ gem 'other', version
def test_generate_bin_symlink_win32
old_win_platform = Gem.win_platform?
- Gem.win_platform = true
old_alt_separator = File::ALT_SEPARATOR
+
+ omit "JRuby on Windows still creates the symlink so the wrapper branch is not exercised" if Gem.win_platform? && Gem.java_platform?
+
+ Gem.win_platform = true
File.__send__(:remove_const, :ALT_SEPARATOR)
File.const_set(:ALT_SEPARATOR, "\\")
@@ -744,6 +761,8 @@ gem 'other', version
end
def test_generate_bin_with_dangling_symlink
+ omit "JRuby on Windows still creates the symlink so the wrapper branch is not exercised" if Gem.win_platform? && Gem.java_platform?
+
gem_with_dangling_symlink = File.expand_path("packages/ascii_binder-0.1.10.1.gem", __dir__)
installer = Gem::Installer.at(
@@ -760,8 +779,12 @@ gem 'other', version
errors = @ui.error.split("\n")
assert_equal "WARNING: ascii_binder-0.1.10.1 ships with a dangling symlink named bin/ascii_binder pointing to missing bin/asciibinder file. Ignoring", errors.shift
- assert_empty errors
-
+ if symlink_supported?
+ assert_empty errors
+ else
+ assert_match(/Unable to use symlinks, installing wrapper/i,
+ errors.to_s)
+ end
assert_empty @ui.output
end
@@ -869,11 +892,11 @@ gem 'other', version
spec_version = spec.version
plugin_path = File.join("lib", "rubygems_plugin.rb")
write_file File.join(@tempdir, plugin_path) do |io|
- io.write <<-PLUGIN
-#{self.class}.plugin_loaded = true
-Gem.post_install do
- #{self.class}.post_install_is_called = true
-end
+ io.write <<~PLUGIN
+ #{self.class}.plugin_loaded = true
+ Gem.post_install do
+ #{self.class}.post_install_is_called = true
+ end
PLUGIN
end
spec.files += [plugin_path]
@@ -1247,7 +1270,7 @@ end
end
assert_raise(Gem::Ext::BuildError) do
- installer.install
+ build_rake_in { installer.install }
end
assert_path_not_exist(File.join(installer.bin_dir, "executable.lock"))
@@ -1473,17 +1496,45 @@ end
refute_match(/I am a shiny gem!/, @ui.output)
end
+ def test_install_sanitizes_post_install_message
+ # Use for_spec so the in-memory message reaches the installer verbatim;
+ # building a gem would escape the control characters during serialization.
+ @spec = setup_base_spec
+ @spec.post_install_message = "shiny \e]2;pwn\a gem"
+
+ installer = Gem::Installer.for_spec @spec, post_install_message: true
+ installer.gem_home = @gemhome
+
+ use_ui @ui do
+ installer.install
+ end
+
+ assert_match(/shiny \.\]2;pwn\. gem/, @ui.output)
+ refute_match(/\e\]2;pwn/, @ui.output)
+ end
+
+ def test_install_handles_non_string_post_install_message
+ # post_install_message may be a non-String (the gemspec schema allows an
+ # array), so sanitizing must not assume it responds to gsub.
+ @spec = setup_base_spec
+ @spec.post_install_message = %w[one two]
+
+ installer = Gem::Installer.for_spec @spec, post_install_message: true
+ installer.gem_home = @gemhome
+
+ use_ui @ui do
+ installer.install
+ end
+
+ assert_match(/one/, @ui.output)
+ end
+
def test_install_extension_dir
gemhome2 = "#{@gemhome}2"
@spec = setup_base_spec
@spec.extensions << "extconf.rb"
- write_file File.join(@tempdir, "extconf.rb") do |io|
- io.write <<-RUBY
- require "mkmf"
- create_makefile("#{@spec.name}")
- RUBY
- end
+ write_dummy_extconf @spec.name
@spec.files += %w[extconf.rb]
@@ -1503,12 +1554,7 @@ end
@spec = setup_base_spec
@spec.extensions << "extconf.rb"
- write_file File.join(@tempdir, "extconf.rb") do |io|
- io.write <<-RUBY
- require "mkmf"
- create_makefile("#{@spec.name}")
- RUBY
- end
+ write_dummy_extconf @spec.name
@spec.files += %w[extconf.rb]
@@ -1539,12 +1585,7 @@ end
def test_install_user_extension_dir
@spec = setup_base_spec
@spec.extensions << "extconf.rb"
- write_file File.join(@tempdir, "extconf.rb") do |io|
- io.write <<-RUBY
- require "mkmf"
- create_makefile("#{@spec.name}")
- RUBY
- end
+ write_dummy_extconf @spec.name
@spec.files += %w[extconf.rb]
@@ -1571,22 +1612,20 @@ end
@spec = setup_base_spec
@spec.extensions << "extconf.rb"
- write_file File.join(@tempdir, "extconf.rb") do |io|
- io.write <<-RUBY
- require "mkmf"
+ write_dummy_extconf @spec.name do |io|
+ io.write <<~RUBY
CONFIG['CC'] = '$(TOUCH) $@ ||'
CONFIG['LDSHARED'] = '$(TOUCH) $@ ||'
$ruby = '#{Gem.ruby}'
- create_makefile("#{@spec.name}")
RUBY
end
write_file File.join(@tempdir, "depend")
write_file File.join(@tempdir, "a.c") do |io|
- io.write <<-C
+ io.write <<~C
#include <ruby.h>
void Init_a() { }
C
@@ -1618,17 +1657,12 @@ end
@spec = setup_base_spec
@spec.extensions << "extconf.rb"
- write_file File.join(@tempdir, "extconf.rb") do |io|
- io.write <<-RUBY
- require "mkmf"
- create_makefile("#{@spec.name}")
- RUBY
- end
+ write_dummy_extconf @spec.name
rb = File.join("lib", "#{@spec.name}.rb")
@spec.files += [rb]
write_file File.join(@tempdir, rb) do |io|
- io.write <<-RUBY
+ io.write <<~RUBY
# #{@spec.name}.rb
RUBY
end
@@ -1637,7 +1671,7 @@ end
rb2 = File.join("lib", @spec.name, "#{@spec.name}.rb")
@spec.files << rb2
write_file File.join(@tempdir, rb2) do |io|
- io.write <<-RUBY
+ io.write <<~RUBY
# #{@spec.name}/#{@spec.name}.rb
RUBY
end
@@ -1663,15 +1697,13 @@ end
@spec.extensions << "extconf.rb"
- write_file File.join(@tempdir, "extconf.rb") do |io|
- io.write <<-RUBY
- require "mkmf"
+ write_dummy_extconf @spec.name do |io|
+ io.write <<~RUBY
CONFIG['CC'] = '$(TOUCH) $@ ||'
CONFIG['LDSHARED'] = '$(TOUCH) $@ ||'
$ruby = '#{Gem.ruby}'
- create_makefile("#{@spec.name}")
RUBY
end
@@ -1698,13 +1730,13 @@ end
@spec.require_paths = ["."]
@spec.extensions << "extconf.rb"
- File.write File.join(@tempdir, "extconf.rb"), <<-RUBY
- require "mkmf"
- CONFIG['CC'] = '$(TOUCH) $@ ||'
- CONFIG['LDSHARED'] = '$(TOUCH) $@ ||'
- $ruby = '#{Gem.ruby}'
- create_makefile("#{@spec.name}")
- RUBY
+ write_dummy_extconf @spec.name do |io|
+ io.write <<~RUBY
+ CONFIG['CC'] = '$(TOUCH) $@ ||'
+ CONFIG['LDSHARED'] = '$(TOUCH) $@ ||'
+ $ruby = '#{Gem.ruby}'
+ RUBY
+ end
# empty depend file for no auto dependencies
@spec.files += %W[depend #{@spec.name}.c].each do |file|
@@ -1937,11 +1969,87 @@ end
end
end
+ def test_pre_install_checks_malicious_executables_before_eval
+ spec = util_spec "malicious", "1"
+ def spec.full_name # so the spec is buildable
+ "malicious-1"
+ end
+
+ def spec.validate(*args); end
+ spec.executables = ["../../../tmp/malicious"]
+
+ util_build_gem spec
+
+ gem = File.join(@gemhome, "cache", spec.file_name)
+
+ use_ui @ui do
+ installer = Gem::Installer.at gem
+ e = assert_raise Gem::InstallError do
+ installer.pre_install_checks
+ end
+ assert_equal "#<Gem::Specification name=malicious version=1> has an invalid executable", e.message
+ end
+ end
+
+ def test_pre_install_checks_malicious_bindir_before_eval
+ spec = util_spec "malicious", "1"
+ def spec.full_name # so the spec is buildable
+ "malicious-1"
+ end
+
+ def spec.validate(*args); end
+ spec.bindir = "../../../tmp/malicious"
+
+ util_build_gem spec
+
+ gem = File.join(@gemhome, "cache", spec.file_name)
+
+ use_ui @ui do
+ installer = Gem::Installer.at gem
+ e = assert_raise Gem::InstallError do
+ installer.pre_install_checks
+ end
+ assert_equal "#<Gem::Specification name=malicious version=1> has an invalid bindir", e.message
+ end
+ end
+
+ def test_pre_install_checks_non_string_executable
+ spec = util_spec "malicious", "1"
+ def spec.validate(*args); end
+ spec.executables = [nil]
+
+ installer = Gem::Installer.for_spec spec
+ installer.gem_home = @gemhome
+
+ use_ui @ui do
+ e = assert_raise Gem::InstallError do
+ installer.pre_install_checks
+ end
+ assert_equal "#<Gem::Specification name=malicious version=1> has an invalid executable", e.message
+ end
+ end
+
+ def test_pre_install_checks_non_string_bindir
+ spec = util_spec "malicious", "1"
+ def spec.validate(*args); end
+ spec.bindir = true
+
+ installer = Gem::Installer.for_spec spec
+ installer.gem_home = @gemhome
+
+ use_ui @ui do
+ e = assert_raise Gem::InstallError do
+ installer.pre_install_checks
+ end
+ assert_equal "#<Gem::Specification name=malicious version=1> has an invalid bindir", e.message
+ end
+ end
+
def test_pre_install_checks_malicious_platform_before_eval
- gem_with_ill_formated_platform = File.expand_path("packages/ill-formatted-platform-1.0.0.10.gem", __dir__)
+ gem_with_ill_formatted_platform = File.expand_path("packages/ill-formatted-platform-1.0.0.10.gem", __dir__)
installer = Gem::Installer.at(
- gem_with_ill_formated_platform,
+ gem_with_ill_formatted_platform,
install_dir: @gemhome,
user_install: false,
force: true
@@ -2304,19 +2412,6 @@ end
assert_equal "#!1 #{bin_env} 2 #{Gem.ruby} -ws 3 executable", shebang
end
- def test_unpack
- installer = util_setup_installer
-
- dest = File.join @gemhome, "gems", @spec.full_name
-
- Gem::Deprecate.skip_during do
- installer.unpack dest
- end
-
- assert_path_exist File.join dest, "lib", "code.rb"
- assert_path_exist File.join dest, "bin", "executable"
- end
-
def test_write_build_info_file
installer = setup_base_installer
@@ -2423,25 +2518,31 @@ end
installer = Gem::Installer.for_spec @spec
installer.gem_home = @gemhome
- File.singleton_class.class_eval do
- alias_method :original_binwrite, :binwrite
-
- def binwrite(path, data)
+ assert_raise(Errno::ENOSPC) do
+ Gem::AtomicFileWriter.open(@spec.spec_file) do
raise Errno::ENOSPC
end
end
- assert_raise Errno::ENOSPC do
- installer.write_spec
- end
-
assert_path_not_exist @spec.spec_file
- ensure
- File.singleton_class.class_eval do
- remove_method :binwrite
- alias_method :binwrite, :original_binwrite
- remove_method :original_binwrite
- end
+ end
+
+ def test_write_default_spec
+ @spec = setup_base_spec
+ @spec.files = %w[a.rb b.rb c.rb]
+
+ installer = Gem::Installer.for_spec @spec
+ installer.gem_home = @gemhome
+
+ installer.write_default_spec
+
+ assert_path_exist installer.default_spec_file
+
+ loaded = Gem::Specification.load installer.default_spec_file
+
+ assert_equal @spec.files, loaded.files
+ assert_equal @spec.name, loaded.name
+ assert_equal @spec.version, loaded.version
end
def test_dir
@@ -2450,137 +2551,154 @@ end
assert_match %r{/gemhome/gems/a-2$}, installer.dir
end
- def test_default_gem_loaded_from
- spec = util_spec "a"
- installer = Gem::Installer.for_spec spec, install_as_default: true
- installer.install
- assert_predicate spec, :default_gem?
+ def test_package_attribute
+ gem = quick_gem "c" do |spec|
+ util_make_exec spec, "#!/usr/bin/ruby", "exe"
+ end
+
+ installer = util_installer(gem, @gemhome)
+ assert_respond_to(installer, :package)
+ assert_kind_of(Gem::Package, installer.package)
end
- def test_default_gem_without_wrappers
- installer = setup_base_installer
+ def test_gem_attribute
+ gem = quick_gem "c" do |spec|
+ util_make_exec spec, "#!/usr/bin/ruby", "exe"
+ end
+
+ installer = util_installer(gem, @gemhome)
+ assert_respond_to(installer, :gem)
+ assert_kind_of(String, installer.gem)
+ end
- FileUtils.rm_rf File.join(Gem.default_dir, "specifications")
+ def test_install_no_build_extension
+ installer = util_setup_installer
- installer.wrappers = false
- installer.options[:install_as_default] = true
- installer.gem_dir = @spec.gem_dir
+ gemdir = File.join @gemhome, "gems", @spec.full_name
+
+ installer.options[:build_extension] = false
use_ui @ui do
installer.install
end
- assert_directory_exists File.join(@spec.gem_dir, "bin")
- installed_exec = File.join @spec.gem_dir, "bin", "executable"
- assert_path_exist installed_exec
-
- assert_directory_exists File.join(Gem.default_dir, "specifications")
- assert_directory_exists File.join(Gem.default_dir, "specifications", "default")
-
- default_spec = eval File.read File.join(Gem.default_dir, "specifications", "default", "a-2.gemspec")
- assert_equal Gem::Version.new("2"), default_spec.version
- assert_equal ["bin/executable"], default_spec.files
+ assert_path_exist gemdir
+ assert_path_not_exist File.join(@spec.extension_dir, "gem.build_complete")
+ assert_match "contains native extensions that were not built", @ui.error
+ assert_match "gem pristine #{@spec.name} --extensions", @ui.error
+ end
- assert_directory_exists util_inst_bindir
+ def test_install_no_build_extension_without_extensions
+ spec = quick_gem "b", 2
- installed_exec = File.join util_inst_bindir, "executable"
- assert_path_exist installed_exec
+ util_build_gem spec
- wrapper = File.read installed_exec
+ installer = util_installer spec, @gemhome
+ installer.options[:build_extension] = false
- if symlink_supported?
- refute_match(/generated by RubyGems/, wrapper)
- else # when symlink not supported, it warns and fallbacks back to installing wrapper
- assert_match(/Unable to use symlinks, installing wrapper/, @ui.error)
- assert_match(/generated by RubyGems/, wrapper)
+ use_ui @ui do
+ installer.install
end
- end
- def test_default_gem_with_wrappers
- installer = setup_base_installer
+ refute_match "contains native extensions", @ui.error
+ end
- installer.wrappers = true
- installer.options[:install_as_default] = true
- installer.gem_dir = @spec.gem_dir
+ def test_install_no_install_plugin
+ installer = util_setup_installer do |spec|
+ write_file File.join(@tempdir, "lib", "rubygems_plugin.rb") do |io|
+ io.write "# do nothing"
+ end
- use_ui @ui do
- installer.install
+ spec.files += %w[lib/rubygems_plugin.rb]
end
- assert_directory_exists util_inst_bindir
+ installer.options[:install_plugin] = false
- installed_exec = File.join util_inst_bindir, "executable"
- assert_path_exist installed_exec
+ build_rake_in do
+ use_ui @ui do
+ installer.install
+ end
+ end
- wrapper = File.read installed_exec
- assert_match(/generated by RubyGems/, wrapper)
+ plugin_path = File.join Gem.plugindir, "a_plugin.rb"
+ refute File.exist?(plugin_path), "plugin must not be written when --no-install-plugin"
+ assert_match "contains plugins that were not installed", @ui.error
+ assert_match "gem pristine #{@spec.name} --only-plugins", @ui.error
end
- def test_default_gem_with_exe_as_bindir
- @spec = quick_gem "c" do |spec|
- util_make_exec spec, "#!/usr/bin/ruby", "exe"
+ def test_install_no_install_plugin_skips_load_plugin
+ installer = util_setup_installer do |spec|
+ write_file File.join(@tempdir, "lib", "rubygems_plugin.rb") do |io|
+ io.write "$no_install_plugin_test_loaded = true"
+ end
+
+ spec.files += %w[lib/rubygems_plugin.rb]
end
- util_build_gem @spec
+ # Simulate a pre-existing plugin wrapper from a previous install
+ FileUtils.mkdir_p Gem.plugindir
+ plugin_path = File.join Gem.plugindir, "a_plugin.rb"
+ File.write(plugin_path, "require_relative '../../gems/#{@spec.full_name}/lib/rubygems_plugin'")
- @spec.cache_file
+ installer.options[:install_plugin] = false
- installer = util_installer @spec, @gemhome
+ build_rake_in do
+ use_ui @ui do
+ installer.install
+ end
+ end
- installer.options[:install_as_default] = true
- installer.gem_dir = @spec.gem_dir
+ refute defined?($no_install_plugin_test_loaded) && $no_install_plugin_test_loaded,
+ "plugin must not be loaded when --no-install-plugin"
+ ensure
+ $no_install_plugin_test_loaded = nil
+ end
- use_ui @ui do
- installer.install
- end
+ def test_install_no_install_plugin_without_plugins
+ installer = util_setup_installer
- assert_directory_exists File.join(@spec.gem_dir, "exe")
- installed_exec = File.join @spec.gem_dir, "exe", "executable"
- assert_path_exist installed_exec
+ installer.options[:install_plugin] = false
- assert_directory_exists File.join(Gem.default_dir, "specifications")
- assert_directory_exists File.join(Gem.default_dir, "specifications", "default")
+ build_rake_in do
+ use_ui @ui do
+ installer.install
+ end
+ end
- default_spec = eval File.read File.join(Gem.default_dir, "specifications", "default", "c-2.gemspec")
- assert_equal Gem::Version.new("2"), default_spec.version
- assert_equal ["exe/executable"], default_spec.files
+ refute_match "contains plugins", @ui.error
end
- def test_default_gem_to_specific_install_dir
- @gem = setup_base_gem
- installer = util_installer @spec, "#{@gemhome}2"
- installer.options[:install_as_default] = true
+ def test_install_no_install_plugin_removes_stale_wrappers
+ # First install a version with a plugin
+ installer = util_setup_installer do |spec|
+ write_file File.join(@tempdir, "lib", "rubygems_plugin.rb") do |io|
+ io.write "# plugin code"
+ end
- use_ui @ui do
- installer.install
+ spec.files += %w[lib/rubygems_plugin.rb]
end
- assert_directory_exists File.join("#{@gemhome}2", "specifications")
- assert_directory_exists File.join("#{@gemhome}2", "specifications", "default")
+ build_rake_in do
+ use_ui @ui do
+ installer.install
+ end
+ end
- default_spec = eval File.read File.join("#{@gemhome}2", "specifications", "default", "a-2.gemspec")
- assert_equal Gem::Version.new("2"), default_spec.version
- assert_equal ["bin/executable"], default_spec.files
- end
+ plugin_path = File.join Gem.plugindir, "a_plugin.rb"
+ assert File.exist?(plugin_path), "plugin wrapper should exist after first install"
- def test_package_attribute
- gem = quick_gem "c" do |spec|
- util_make_exec spec, "#!/usr/bin/ruby", "exe"
- end
+ # Now install a new version without plugins, using --no-install-plugin
+ spec2 = quick_gem "a", 3
+ util_build_gem spec2
- installer = util_installer(gem, @gemhome)
- assert_respond_to(installer, :package)
- assert_kind_of(Gem::Package, installer.package)
- end
+ installer2 = util_installer spec2, @gemhome
+ installer2.options[:install_plugin] = false
- def test_gem_attribute
- gem = quick_gem "c" do |spec|
- util_make_exec spec, "#!/usr/bin/ruby", "exe"
+ use_ui @ui do
+ installer2.install
end
- installer = util_installer(gem, @gemhome)
- assert_respond_to(installer, :gem)
- assert_kind_of(String, installer.gem)
+ refute File.exist?(plugin_path), "stale plugin wrapper must be removed"
end
private
diff --git a/test/rubygems/test_gem_name_tuple.rb b/test/rubygems/test_gem_name_tuple.rb
index bdb8181ce8..4876737c83 100644
--- a/test/rubygems/test_gem_name_tuple.rb
+++ b/test/rubygems/test_gem_name_tuple.rb
@@ -57,4 +57,41 @@ class TestGemNameTuple < Gem::TestCase
assert_equal 1, a_p.<=>(a)
end
+
+ def test_deconstruct
+ name_tuple = Gem::NameTuple.new "rails", Gem::Version.new("7.0.0"), "ruby"
+ assert_equal ["rails", Gem::Version.new("7.0.0"), "ruby"], name_tuple.deconstruct
+ end
+
+ def test_deconstruct_keys
+ name_tuple = Gem::NameTuple.new "rails", Gem::Version.new("7.0.0"), "x86_64-linux"
+ keys = name_tuple.deconstruct_keys(nil)
+ assert_equal "rails", keys[:name]
+ assert_equal Gem::Version.new("7.0.0"), keys[:version]
+ assert_equal "x86_64-linux", keys[:platform]
+ end
+
+ def test_pattern_matching_array
+ name_tuple = Gem::NameTuple.new "rails", Gem::Version.new("7.0.0"), "ruby"
+ result =
+ case name_tuple
+ in [name, version, "ruby"]
+ "#{name}-#{version}"
+ else
+ "no match"
+ end
+ assert_equal "rails-7.0.0", result
+ end
+
+ def test_pattern_matching_hash
+ name_tuple = Gem::NameTuple.new "rails", Gem::Version.new("7.0.0"), "ruby"
+ result =
+ case name_tuple
+ in name: "rails", version:, platform: "ruby"
+ version.to_s
+ else
+ "no match"
+ end
+ assert_equal "7.0.0", result
+ end
end
diff --git a/test/rubygems/test_gem_package.rb b/test/rubygems/test_gem_package.rb
index 8a9cc85580..0014c20737 100644
--- a/test/rubygems/test_gem_package.rb
+++ b/test/rubygems/test_gem_package.rb
@@ -175,6 +175,9 @@ class TestGemPackage < Gem::Package::TarTestCase
end
def test_add_files_symlink
+ unless symlink_supported?
+ omit("symlink - developer mode must be enabled on Windows")
+ end
spec = Gem::Specification.new
spec.files = %w[lib/code.rb lib/code_sym.rb lib/code_sym2.rb]
@@ -185,16 +188,8 @@ class TestGemPackage < Gem::Package::TarTestCase
end
# NOTE: 'code.rb' is correct, because it's relative to lib/code_sym.rb
- begin
- File.symlink("code.rb", "lib/code_sym.rb")
- File.symlink("../lib/code.rb", "lib/code_sym2.rb")
- rescue Errno::EACCES => e
- if Gem.win_platform?
- pend "symlink - must be admin with no UAC on Windows"
- else
- raise e
- end
- end
+ File.symlink("code.rb", "lib/code_sym.rb")
+ File.symlink("../lib/code.rb", "lib/code_sym2.rb")
package = Gem::Package.new "bogus.gem"
package.spec = spec
@@ -506,7 +501,7 @@ class TestGemPackage < Gem::Package::TarTestCase
extracted = File.join @destination, "lib/code.rb"
assert_path_exist extracted
- mask = 0o100666 & (~File.umask)
+ mask = 0o100666 & ~File.umask
assert_equal mask.to_s(8), File.stat(extracted).mode.to_s(8) unless
Gem.win_platform?
@@ -583,25 +578,71 @@ class TestGemPackage < Gem::Package::TarTestCase
tar.add_symlink "lib/foo.rb", "../relative.rb", 0o644
end
- begin
- package.extract_tar_gz tgz_io, @destination
- rescue Errno::EACCES => e
- if Gem.win_platform?
- pend "symlink - must be admin with no UAC on Windows"
- else
- raise e
- end
- end
+ package.extract_tar_gz tgz_io, @destination
extracted = File.join @destination, "lib/foo.rb"
assert_path_exist extracted
- assert_equal "../relative.rb",
- File.readlink(extracted)
+ if symlink_supported?
+ assert_equal "../relative.rb",
+ File.readlink(extracted)
+ end
assert_equal "hi",
+ File.read(extracted),
+ "should read file content either by following symlink or on Windows by reading copy"
+ end
+
+ def test_extract_tar_gz_symlink_directory
+ package = Gem::Package.new @gem
+ package.verify
+
+ tgz_io = util_tar_gz do |tar|
+ tar.add_symlink "link", "lib/orig", 0o644
+ tar.mkdir "lib", 0o755
+ tar.mkdir "lib/orig", 0o755
+ tar.add_file "lib/orig/file.rb", 0o644 do |io|
+ io.write "ok"
+ end
+ end
+
+ package.extract_tar_gz tgz_io, @destination
+ extracted = File.join @destination, "link/file.rb"
+ assert_path_exist extracted
+ if symlink_supported?
+ assert_equal "lib/orig",
+ File.readlink(File.dirname(extracted))
+ end
+ assert_equal "ok",
File.read(extracted)
end
+ def test_extract_tar_gz_rejects_preexisting_symlink_escape
+ omit "Symlinks not supported or not enabled" unless symlink_supported?
+
+ package = Gem::Package.new @gem
+
+ tgz_io = util_tar_gz do |tar|
+ tar.add_file "lib/owned.txt", 0o644 do |io|
+ io.write "poc-content"
+ end
+ end
+
+ escape_dir = File.join(@tempdir, "escape")
+ FileUtils.mkdir_p escape_dir
+
+ FileUtils.rm_rf File.join(@destination, "lib")
+ File.symlink escape_dir, File.join(@destination, "lib")
+
+ escaped = File.join(escape_dir, "owned.txt")
+
+ assert_raise Gem::Package::PathError do
+ package.extract_tar_gz tgz_io, @destination
+ end
+
+ refute File.exist?(escaped), "must not write outside extraction root via symlink"
+ end
+
def test_extract_symlink_into_symlink_dir
+ omit "Symlinks not supported or not enabled" unless symlink_supported?
package = Gem::Package.new @gem
tgz_io = util_tar_gz do |tar|
tar.mkdir "lib", 0o755
@@ -665,14 +706,10 @@ class TestGemPackage < Gem::Package::TarTestCase
destination_subdir = File.join @destination, "subdir"
FileUtils.mkdir_p destination_subdir
- expected_exceptions = Gem.win_platform? ? [Gem::Package::SymlinkError, Errno::EACCES] : [Gem::Package::SymlinkError]
-
- e = assert_raise(*expected_exceptions) do
+ e = assert_raise(Gem::Package::SymlinkError) do
package.extract_tar_gz tgz_io, destination_subdir
end
- pend "symlink - must be admin with no UAC on Windows" if Errno::EACCES === e
-
assert_equal("installing symlink 'lib/link' pointing to parent path #{@destination} of " \
"#{destination_subdir} is not allowed", e.message)
@@ -700,14 +737,10 @@ class TestGemPackage < Gem::Package::TarTestCase
tar.add_symlink "link/dir", ".", 16_877
end
- expected_exceptions = Gem.win_platform? ? [Gem::Package::SymlinkError, Errno::EACCES] : [Gem::Package::SymlinkError]
-
- e = assert_raise(*expected_exceptions) do
+ e = assert_raise(Gem::Package::SymlinkError) do
package.extract_tar_gz tgz_io, destination_subdir
end
- pend "symlink - must be admin with no UAC on Windows" if Errno::EACCES === e
-
assert_equal("installing symlink 'link' pointing to parent path #{destination_user_dir} of " \
"#{destination_subdir} is not allowed", e.message)
@@ -858,7 +891,7 @@ class TestGemPackage < Gem::Package::TarTestCase
"#{@destination} is not allowed", e.message)
end
- def test_load_spec
+ def test_load_spec_from_metadata
entry = StringIO.new Gem::Util.gzip @spec.to_yaml
def entry.full_name
"metadata.gz"
@@ -866,7 +899,7 @@ class TestGemPackage < Gem::Package::TarTestCase
package = Gem::Package.new "nonexistent.gem"
- spec = package.load_spec entry
+ spec = package.load_spec_from_metadata entry
assert_equal @spec, spec
end
@@ -909,7 +942,11 @@ class TestGemPackage < Gem::Package::TarTestCase
}
tar.add_file "checksums.yaml.gz", 0o444 do |io|
Zlib::GzipWriter.wrap io do |gz_io|
- gz_io.write Psych.dump bogus_checksums
+ if Gem.use_psych?
+ gz_io.write Psych.dump(bogus_checksums)
+ else
+ gz_io.write Gem::YAMLSerializer.dump(bogus_checksums)
+ end
end
end
end
@@ -955,7 +992,11 @@ class TestGemPackage < Gem::Package::TarTestCase
tar.add_file "checksums.yaml.gz", 0o444 do |io|
Zlib::GzipWriter.wrap io do |gz_io|
- gz_io.write Psych.dump checksums
+ if Gem.use_psych?
+ gz_io.write Psych.dump(checksums)
+ else
+ gz_io.write Gem::YAMLSerializer.dump(checksums)
+ end
end
end
@@ -1247,71 +1288,25 @@ class TestGemPackage < Gem::Package::TarTestCase
# end #verify tests
- def test_verify_entry
- entry = Object.new
- def entry.full_name
- raise ArgumentError, "whatever"
- end
-
- package = Gem::Package.new @gem
-
- _, err = use_ui @ui do
- e = nil
-
- out_err = capture_output do
- e = assert_raise ArgumentError do
- package.verify_entry entry
+ def test_missing_metadata
+ invalid_metadata = ["metadataxgz", "foobar\nmetadata", "metadata\nfoobar"]
+ invalid_metadata.each do |fname|
+ tar = StringIO.new
+
+ Gem::Package::TarWriter.new(tar) do |gem_tar|
+ gem_tar.add_file fname, 0o444 do |io|
+ gz_io = Zlib::GzipWriter.new io, Zlib::BEST_COMPRESSION
+ gz_io.write "bad metadata"
+ gz_io.close
end
end
- assert_equal "whatever", e.message
- assert_equal "full_name", e.backtrace_locations.first.label
-
- out_err
- end
-
- assert_equal "Exception while verifying #{@gem}\n", err
-
- valid_metadata = ["metadata", "metadata.gz"]
- valid_metadata.each do |vm|
- $spec_loaded = false
- $good_name = vm
-
- entry = Object.new
- def entry.full_name
- $good_name
- end
-
- package = Gem::Package.new(@gem)
- package.instance_variable_set(:@files, [])
- def package.load_spec(entry)
- $spec_loaded = true
- end
-
- package.verify_entry(entry)
+ tar.rewind
- assert $spec_loaded
- end
-
- invalid_metadata = ["metadataxgz", "foobar\nmetadata", "metadata\nfoobar"]
- invalid_metadata.each do |vm|
- $spec_loaded = false
- $bad_name = vm
-
- entry = Object.new
- def entry.full_name
- $bad_name
- end
-
- package = Gem::Package.new(@gem)
- package.instance_variable_set(:@files, [])
- def package.load_spec(entry)
- $spec_loaded = true
+ package = Gem::Package.new(Gem::Package::IOSource.new(tar))
+ assert_raise Gem::Package::FormatError do
+ package.verify
end
-
- package.verify_entry(entry)
-
- refute $spec_loaded
end
end
diff --git a/test/rubygems/test_gem_package_old.rb b/test/rubygems/test_gem_package_old.rb
index 7582dbedd4..e532fa25e1 100644
--- a/test/rubygems/test_gem_package_old.rb
+++ b/test/rubygems/test_gem_package_old.rb
@@ -39,7 +39,7 @@ unless Gem.java_platform? # jruby can't require the simple_gem file
extracted = File.join @destination, "lib/foo.rb"
assert_path_exist extracted
- mask = 0o100644 & (~File.umask)
+ mask = 0o100644 & ~File.umask
assert_equal mask, File.stat(extracted).mode unless Gem.win_platform?
end
diff --git a/test/rubygems/test_gem_package_tar_header_ractor.rb b/test/rubygems/test_gem_package_tar_header_ractor.rb
new file mode 100644
index 0000000000..5714064805
--- /dev/null
+++ b/test/rubygems/test_gem_package_tar_header_ractor.rb
@@ -0,0 +1,61 @@
+# frozen_string_literal: true
+
+require_relative "package/tar_test_case"
+
+unless Gem::Package::TarTestCase.method_defined?(:assert_ractor)
+ require "core_assertions"
+ Gem::Package::TarTestCase.include Test::Unit::CoreAssertions
+end
+
+class TestGemPackageTarHeaderRactor < Gem::Package::TarTestCase
+ SETUP = <<~RUBY
+ header = {
+ name: "x",
+ mode: 0o644,
+ uid: 1000,
+ gid: 10_000,
+ size: 100,
+ mtime: 12_345,
+ typeflag: "0",
+ linkname: "link",
+ uname: "user",
+ gname: "group",
+ devmajor: 1,
+ devminor: 2,
+ prefix: "y",
+ }
+
+ tar_header = Gem::Package::TarHeader.new header
+ # Move this require to arguments of assert_ractor after Ruby 4.0 or updating core_assertions.rb at Ruby 3.4.
+ require "stringio"
+ # Remove this after Ruby 4.0 or updating core_assertions.rb at Ruby 3.4.
+ class Ractor; alias value take unless method_defined?(:value); end
+ RUBY
+
+ def test_decode_in_ractor
+ assert_ractor(SETUP + <<~RUBY, require: "rubygems/package", require_relative: "package/tar_test_case")
+ include Gem::Package::TarTestMethods
+
+ new_header = Ractor.new(tar_header.to_s) do |str|
+ Gem::Package::TarHeader.from StringIO.new str
+ end.value
+
+ assert_headers_equal tar_header, new_header
+ RUBY
+ end
+
+ def test_encode_in_ractor
+ assert_ractor(SETUP + <<~RUBY, require: "rubygems/package", require_relative: "package/tar_test_case")
+ include Gem::Package::TarTestMethods
+
+ header_bytes = tar_header.to_s
+
+ new_header_bytes = Ractor.new(header_bytes) do |str|
+ new_header = Gem::Package::TarHeader.from StringIO.new str
+ new_header.to_s
+ end.value
+
+ assert_headers_equal header_bytes, new_header_bytes
+ RUBY
+ end
+end unless RUBY_PLATFORM.match?(/mingw|mswin/)
diff --git a/test/rubygems/test_gem_package_tar_writer.rb b/test/rubygems/test_gem_package_tar_writer.rb
index 751ceaca81..cb9e0d26fa 100644
--- a/test/rubygems/test_gem_package_tar_writer.rb
+++ b/test/rubygems/test_gem_package_tar_writer.rb
@@ -33,7 +33,7 @@ class TestGemPackageTarWriter < Gem::Package::TarTestCase
f.write "a" * 10
end
- assert_headers_equal(tar_file_header("x", "", 0o644, 10, Time.now),
+ assert_headers_equal(tar_file_header("x", "", 0o644, 10, Time.at(Gem::DEFAULT_SOURCE_DATE_EPOCH).utc),
@io.string[0, 512])
end
assert_equal "aaaaaaaaaa#{"\0" * 502}", @io.string[512, 512]
@@ -50,11 +50,24 @@ class TestGemPackageTarWriter < Gem::Package::TarTestCase
end
end
+ def test_add_file_with_mtime
+ Time.stub :now, Time.at(1_458_518_157) do
+ mtime = Time.now
+
+ @tar_writer.add_file "x", 0o644, mtime do |f|
+ f.write "a" * 10
+ end
+
+ assert_headers_equal(tar_file_header("x", "", 0o644, 10, mtime),
+ @io.string[0, 512])
+ end
+ end
+
def test_add_symlink
Time.stub :now, Time.at(1_458_518_157) do
@tar_writer.add_symlink "x", "y", 0o644
- assert_headers_equal(tar_symlink_header("x", "", 0o644, Time.now, "y"),
+ assert_headers_equal(tar_symlink_header("x", "", 0o644, Time.at(Gem::DEFAULT_SOURCE_DATE_EPOCH).utc, "y"),
@io.string[0, 512])
end
assert_equal 512, @io.pos
@@ -86,7 +99,7 @@ class TestGemPackageTarWriter < Gem::Package::TarTestCase
"e1cf14b0",
digests["SHA512"].hexdigest
- assert_headers_equal(tar_file_header("x", "", 0o644, 10, Time.now),
+ assert_headers_equal(tar_file_header("x", "", 0o644, 10, Time.at(Gem::DEFAULT_SOURCE_DATE_EPOCH).utc),
@io.string[0, 512])
end
assert_equal "aaaaaaaaaa#{"\0" * 502}", @io.string[512, 512]
@@ -109,7 +122,7 @@ class TestGemPackageTarWriter < Gem::Package::TarTestCase
"e1cf14b0",
digests["SHA512"].hexdigest
- assert_headers_equal(tar_file_header("x", "", 0o644, 10, Time.now),
+ assert_headers_equal(tar_file_header("x", "", 0o644, 10, Time.at(Gem::DEFAULT_SOURCE_DATE_EPOCH).utc),
@io.string[0, 512])
end
assert_equal "aaaaaaaaaa#{"\0" * 502}", @io.string[512, 512]
@@ -126,7 +139,7 @@ class TestGemPackageTarWriter < Gem::Package::TarTestCase
io.write "a" * 10
end
- assert_headers_equal(tar_file_header("x", "", 0o644, 10, Time.now),
+ assert_headers_equal(tar_file_header("x", "", 0o644, 10, Time.at(Gem::DEFAULT_SOURCE_DATE_EPOCH).utc),
@io.string[0, 512])
assert_equal "aaaaaaaaaa#{"\0" * 502}", @io.string[512, 512]
@@ -137,7 +150,7 @@ class TestGemPackageTarWriter < Gem::Package::TarTestCase
signature = signer.sign digest.digest
assert_headers_equal(tar_file_header("x.sig", "", 0o444, signature.length,
- Time.now),
+ Time.at(Gem::DEFAULT_SOURCE_DATE_EPOCH).utc),
@io.string[1024, 512])
assert_equal "#{signature}#{"\0" * (512 - signature.length)}",
@io.string[1536, 512]
@@ -154,7 +167,7 @@ class TestGemPackageTarWriter < Gem::Package::TarTestCase
io.write "a" * 10
end
- assert_headers_equal(tar_file_header("x", "", 0o644, 10, Time.now),
+ assert_headers_equal(tar_file_header("x", "", 0o644, 10, Time.at(Gem::DEFAULT_SOURCE_DATE_EPOCH).utc),
@io.string[0, 512])
end
assert_equal "aaaaaaaaaa#{"\0" * 502}", @io.string[512, 512]
@@ -168,7 +181,7 @@ class TestGemPackageTarWriter < Gem::Package::TarTestCase
io.write "a" * 10
end
- assert_headers_equal(tar_file_header("x", "", 0o644, 10, Time.now),
+ assert_headers_equal(tar_file_header("x", "", 0o644, 10, Time.at(Gem::DEFAULT_SOURCE_DATE_EPOCH).utc),
@io.string[0, 512])
assert_equal "aaaaaaaaaa#{"\0" * 502}", @io.string[512, 512]
@@ -192,7 +205,7 @@ class TestGemPackageTarWriter < Gem::Package::TarTestCase
Time.stub :now, Time.at(1_458_518_157) do
@tar_writer.add_file_simple "x", 0, 100
- assert_headers_equal tar_file_header("x", "", 0, 100, Time.now),
+ assert_headers_equal tar_file_header("x", "", 0, 100, Time.at(Gem::DEFAULT_SOURCE_DATE_EPOCH).utc),
@io.string[0, 512]
end
@@ -250,7 +263,7 @@ class TestGemPackageTarWriter < Gem::Package::TarTestCase
Time.stub :now, Time.at(1_458_518_157) do
@tar_writer.mkdir "foo", 0o644
- assert_headers_equal tar_dir_header("foo", "", 0o644, Time.now),
+ assert_headers_equal tar_dir_header("foo", "", 0o644, Time.at(Gem::DEFAULT_SOURCE_DATE_EPOCH).utc),
@io.string[0, 512]
assert_equal 512, @io.pos
diff --git a/test/rubygems/test_gem_path_support.rb b/test/rubygems/test_gem_path_support.rb
index 8720bcf858..c5181496c0 100644
--- a/test/rubygems/test_gem_path_support.rb
+++ b/test/rubygems/test_gem_path_support.rb
@@ -121,14 +121,12 @@ class TestGemPathSupport < Gem::TestCase
end
def test_gem_paths_do_not_contain_symlinks
+ pend "symlinks not supported" unless symlink_supported?
+
dir = "#{@tempdir}/realgemdir"
symlink = "#{@tempdir}/symdir"
Dir.mkdir dir
- begin
- File.symlink(dir, symlink)
- rescue NotImplementedError, SystemCallError
- pend "symlinks not supported"
- end
+ File.symlink(dir, symlink)
not_existing = "#{@tempdir}/does_not_exist"
path = "#{symlink}#{File::PATH_SEPARATOR}#{not_existing}"
diff --git a/test/rubygems/test_gem_platform.rb b/test/rubygems/test_gem_platform.rb
index 070c8007bc..c1ff36772b 100644
--- a/test/rubygems/test_gem_platform.rb
+++ b/test/rubygems/test_gem_platform.rb
@@ -11,15 +11,6 @@ class TestGemPlatform < Gem::TestCase
assert_equal Gem::Platform.new(%w[x86 darwin 8]), Gem::Platform.local
end
- def test_self_match
- Gem::Deprecate.skip_during do
- assert Gem::Platform.match(nil), "nil == ruby"
- assert Gem::Platform.match(Gem::Platform.local), "exact match"
- assert Gem::Platform.match(Gem::Platform.local.to_s), "=~ match"
- assert Gem::Platform.match(Gem::Platform::RUBY), "ruby"
- end
- end
-
def test_self_match_gem?
assert Gem::Platform.match_gem?(nil, "json"), "nil == ruby"
assert Gem::Platform.match_gem?(Gem::Platform.local, "json"), "exact match"
@@ -148,12 +139,29 @@ class TestGemPlatform < Gem::TestCase
"wasm32-wasi" => ["wasm32", "wasi", nil],
"wasm32-wasip1" => ["wasm32", "wasi", nil],
"wasm32-wasip2" => ["wasm32", "wasi", nil],
+
+ "darwin-java-java" => ["darwin", "java", nil],
+ "linux-linux-linux" => ["linux", "linux", "linux"],
+ "linux-linux-linux1.0" => ["linux", "linux", "linux1"],
+ "x86x86-1x86x86x86x861linuxx86x86" => ["x86x86", "linux", "x86x86"],
+ "freebsd0" => [nil, "freebsd", "0"],
+ "darwin0" => [nil, "darwin", "0"],
+ "darwin0---" => [nil, "darwin", "0"],
+ "x86-linux-x8611.0l" => ["x86", "linux", "x8611"],
+ "0-x86linuxx86---" => ["0", "linux", "x86"],
+ "x86_64-macruby-x86" => ["x86_64", "macruby", nil],
+ "x86_64-dotnetx86" => ["x86_64", "dotnet", nil],
+ "x86_64-dalvik0" => ["x86_64", "dalvik", "0"],
+ "x86_64-dotnet1." => ["x86_64", "dotnet", "1"],
+
+ "--" => [nil, "unknown", nil],
}
test_cases.each do |arch, expected|
platform = Gem::Platform.new arch
assert_equal expected, platform.to_a, arch.inspect
- assert_equal expected, Gem::Platform.new(platform.to_s).to_a, arch.inspect
+ platform2 = Gem::Platform.new platform.to_s
+ assert_equal expected, platform2.to_a, "#{arch.inspect} => #{platform2.inspect}"
end
end
@@ -246,19 +254,19 @@ class TestGemPlatform < Gem::TestCase
x86_darwin8 = Gem::Platform.new "i686-darwin8.0"
util_set_arch "powerpc-darwin8"
- assert((ppc_darwin8 === Gem::Platform.local), "powerpc =~ universal")
- assert((uni_darwin8 === Gem::Platform.local), "powerpc =~ universal")
- refute((x86_darwin8 === Gem::Platform.local), "powerpc =~ universal")
+ assert(ppc_darwin8 === Gem::Platform.local, "powerpc =~ universal")
+ assert(uni_darwin8 === Gem::Platform.local, "powerpc =~ universal")
+ refute(x86_darwin8 === Gem::Platform.local, "powerpc =~ universal")
util_set_arch "i686-darwin8"
- refute((ppc_darwin8 === Gem::Platform.local), "powerpc =~ universal")
- assert((uni_darwin8 === Gem::Platform.local), "x86 =~ universal")
- assert((x86_darwin8 === Gem::Platform.local), "powerpc =~ universal")
+ refute(ppc_darwin8 === Gem::Platform.local, "powerpc =~ universal")
+ assert(uni_darwin8 === Gem::Platform.local, "x86 =~ universal")
+ assert(x86_darwin8 === Gem::Platform.local, "powerpc =~ universal")
util_set_arch "universal-darwin8"
- assert((ppc_darwin8 === Gem::Platform.local), "universal =~ ppc")
- assert((uni_darwin8 === Gem::Platform.local), "universal =~ universal")
- assert((x86_darwin8 === Gem::Platform.local), "universal =~ x86")
+ assert(ppc_darwin8 === Gem::Platform.local, "universal =~ ppc")
+ assert(uni_darwin8 === Gem::Platform.local, "universal =~ universal")
+ assert(x86_darwin8 === Gem::Platform.local, "universal =~ x86")
end
def test_nil_cpu_arch_is_treated_as_universal
@@ -266,18 +274,18 @@ class TestGemPlatform < Gem::TestCase
with_uni_arch = Gem::Platform.new ["universal", "mingw32"]
with_x86_arch = Gem::Platform.new ["x86", "mingw32"]
- assert((with_nil_arch === with_uni_arch), "nil =~ universal")
- assert((with_uni_arch === with_nil_arch), "universal =~ nil")
- assert((with_nil_arch === with_x86_arch), "nil =~ x86")
- assert((with_x86_arch === with_nil_arch), "x86 =~ nil")
+ assert(with_nil_arch === with_uni_arch, "nil =~ universal")
+ assert(with_uni_arch === with_nil_arch, "universal =~ nil")
+ assert(with_nil_arch === with_x86_arch, "nil =~ x86")
+ assert(with_x86_arch === with_nil_arch, "x86 =~ nil")
end
def test_nil_version_is_treated_as_any_version
x86_darwin_8 = Gem::Platform.new "i686-darwin8.0"
x86_darwin_nil = Gem::Platform.new "i686-darwin"
- assert((x86_darwin_8 === x86_darwin_nil), "8.0 =~ nil")
- assert((x86_darwin_nil === x86_darwin_8), "nil =~ 8.0")
+ assert(x86_darwin_8 === x86_darwin_nil, "8.0 =~ nil")
+ assert(x86_darwin_nil === x86_darwin_8, "nil =~ 8.0")
end
def test_nil_version_is_stricter_for_linux_os
@@ -371,40 +379,33 @@ class TestGemPlatform < Gem::TestCase
arm64 = Gem::Platform.new "arm64-linux"
util_set_arch "armv5-linux"
- assert((arm === Gem::Platform.local), "arm === armv5")
- assert((armv5 === Gem::Platform.local), "armv5 === armv5")
- refute((armv7 === Gem::Platform.local), "armv7 === armv5")
- refute((arm64 === Gem::Platform.local), "arm64 === armv5")
- refute((Gem::Platform.local === arm), "armv5 === arm")
+ assert(arm === Gem::Platform.local, "arm === armv5")
+ assert(armv5 === Gem::Platform.local, "armv5 === armv5")
+ refute(armv7 === Gem::Platform.local, "armv7 === armv5")
+ refute(arm64 === Gem::Platform.local, "arm64 === armv5")
+ refute(Gem::Platform.local === arm, "armv5 === arm")
util_set_arch "armv7-linux"
- assert((arm === Gem::Platform.local), "arm === armv7")
- refute((armv5 === Gem::Platform.local), "armv5 === armv7")
- assert((armv7 === Gem::Platform.local), "armv7 === armv7")
- refute((arm64 === Gem::Platform.local), "arm64 === armv7")
- refute((Gem::Platform.local === arm), "armv7 === arm")
+ assert(arm === Gem::Platform.local, "arm === armv7")
+ refute(armv5 === Gem::Platform.local, "armv5 === armv7")
+ assert(armv7 === Gem::Platform.local, "armv7 === armv7")
+ refute(arm64 === Gem::Platform.local, "arm64 === armv7")
+ refute(Gem::Platform.local === arm, "armv7 === arm")
util_set_arch "arm64-linux"
- refute((arm === Gem::Platform.local), "arm === arm64")
- refute((armv5 === Gem::Platform.local), "armv5 === arm64")
- refute((armv7 === Gem::Platform.local), "armv7 === arm64")
- assert((arm64 === Gem::Platform.local), "arm64 === arm64")
+ refute(arm === Gem::Platform.local, "arm === arm64")
+ refute(armv5 === Gem::Platform.local, "armv5 === arm64")
+ refute(armv7 === Gem::Platform.local, "armv7 === arm64")
+ assert(arm64 === Gem::Platform.local, "arm64 === arm64")
end
def test_equals3_universal_mingw
uni_mingw = Gem::Platform.new "universal-mingw"
- mingw32 = Gem::Platform.new "x64-mingw32"
mingw_ucrt = Gem::Platform.new "x64-mingw-ucrt"
- util_set_arch "x64-mingw32"
- assert((uni_mingw === Gem::Platform.local), "uni_mingw === mingw32")
- assert((mingw32 === Gem::Platform.local), "mingw32 === mingw32")
- refute((mingw_ucrt === Gem::Platform.local), "mingw32 === mingw_ucrt")
-
util_set_arch "x64-mingw-ucrt"
- assert((uni_mingw === Gem::Platform.local), "uni_mingw === mingw32")
- assert((mingw_ucrt === Gem::Platform.local), "mingw_ucrt === mingw_ucrt")
- refute((mingw32 === Gem::Platform.local), "mingw32 === mingw_ucrt")
+ assert(uni_mingw === Gem::Platform.local, "uni_mingw === mingw_ucrt")
+ assert(mingw_ucrt === Gem::Platform.local, "mingw_ucrt === mingw_ucrt")
end
def test_equals3_version
@@ -415,11 +416,11 @@ class TestGemPlatform < Gem::TestCase
x86_darwin8 = Gem::Platform.new ["x86", "darwin", "8"]
x86_darwin9 = Gem::Platform.new ["x86", "darwin", "9"]
- assert((x86_darwin === Gem::Platform.local), "x86_darwin === x86_darwin8")
- assert((x86_darwin8 === Gem::Platform.local), "x86_darwin8 === x86_darwin8")
+ assert(x86_darwin === Gem::Platform.local, "x86_darwin === x86_darwin8")
+ assert(x86_darwin8 === Gem::Platform.local, "x86_darwin8 === x86_darwin8")
- refute((x86_darwin7 === Gem::Platform.local), "x86_darwin7 === x86_darwin8")
- refute((x86_darwin9 === Gem::Platform.local), "x86_darwin9 === x86_darwin8")
+ refute(x86_darwin7 === Gem::Platform.local, "x86_darwin7 === x86_darwin8")
+ refute(x86_darwin9 === Gem::Platform.local, "x86_darwin9 === x86_darwin8")
end
def test_equals_tilde
@@ -492,15 +493,171 @@ class TestGemPlatform < Gem::TestCase
assert_equal 1, result.scan(/@version=/).size
end
- def test_gem_platform_match_with_string_argument
- util_set_arch "x86_64-linux-musl"
+ def test_constants
+ assert_equal [nil, "java", nil], Gem::Platform::JAVA.to_a
+ assert_equal ["x86", "mswin32", nil], Gem::Platform::MSWIN.to_a
+ assert_equal [nil, "mswin64", nil], Gem::Platform::MSWIN64.to_a
+ assert_equal ["x86", "mingw32", nil], Gem::Platform::MINGW.to_a
+ assert_equal ["x64", "mingw", "ucrt"], Gem::Platform::X64_MINGW.to_a
+ assert_equal ["universal", "mingw", nil], Gem::Platform::UNIVERSAL_MINGW.to_a
+ assert_equal [["x86", "mswin32", nil], [nil, "mswin64", nil], ["universal", "mingw", nil]], Gem::Platform::WINDOWS.map(&:to_a)
+ assert_equal ["x86_64", "linux", nil], Gem::Platform::X64_LINUX.to_a
+ assert_equal ["x86_64", "linux", "musl"], Gem::Platform::X64_LINUX_MUSL.to_a
+ end
+
+ def test_generic
+ # converts non-windows platforms into ruby
+ assert_equal Gem::Platform::RUBY, Gem::Platform.generic(Gem::Platform.new("x86-darwin-10"))
+ assert_equal Gem::Platform::RUBY, Gem::Platform.generic(Gem::Platform::RUBY)
+
+ # converts java platform variants into java
+ assert_equal Gem::Platform::JAVA, Gem::Platform.generic(Gem::Platform.new("java"))
+ assert_equal Gem::Platform::JAVA, Gem::Platform.generic(Gem::Platform.new("universal-java-17"))
+
+ # converts mswin platform variants into x86-mswin32
+ assert_equal Gem::Platform::MSWIN, Gem::Platform.generic(Gem::Platform.new("mswin32"))
+ assert_equal Gem::Platform::MSWIN, Gem::Platform.generic(Gem::Platform.new("i386-mswin32"))
+ assert_equal Gem::Platform::MSWIN, Gem::Platform.generic(Gem::Platform.new("x86-mswin32"))
+
+ # converts 32-bit mingw platform variants into universal-mingw
+ assert_equal Gem::Platform::UNIVERSAL_MINGW, Gem::Platform.generic(Gem::Platform.new("i386-mingw32"))
+ assert_equal Gem::Platform::UNIVERSAL_MINGW, Gem::Platform.generic(Gem::Platform.new("x86-mingw32"))
+
+ # converts 64-bit mingw platform variants into universal-mingw
+ assert_equal Gem::Platform::UNIVERSAL_MINGW, Gem::Platform.generic(Gem::Platform.new("x64-mingw32"))
- Gem::Deprecate.skip_during do
- assert(Gem::Platform.match(Gem::Platform.new("x86_64-linux")), "should match Gem::Platform")
- assert(Gem::Platform.match("x86_64-linux"), "should match String platform")
+ # converts x64 mingw UCRT platform variants into universal-mingw
+ assert_equal Gem::Platform::UNIVERSAL_MINGW, Gem::Platform.generic(Gem::Platform.new("x64-mingw-ucrt"))
+
+ # converts aarch64 mingw UCRT platform variants into universal-mingw
+ assert_equal Gem::Platform::UNIVERSAL_MINGW, Gem::Platform.generic(Gem::Platform.new("aarch64-mingw-ucrt"))
+
+ assert_equal Gem::Platform::RUBY, Gem::Platform.generic(Gem::Platform.new("unknown"))
+ assert_equal Gem::Platform::RUBY, Gem::Platform.generic(nil)
+ assert_equal Gem::Platform::MSWIN64, Gem::Platform.generic(Gem::Platform.new("mswin64"))
+ end
+
+ def test_platform_specificity_match
+ [
+ ["ruby", "ruby", -1, -1],
+ ["x86_64-linux-musl", "x86_64-linux-musl", -1, -1],
+ ["x86_64-linux", "x86_64-linux-musl", 100, 200],
+ ["universal-darwin", "x86-darwin", 10, 20],
+ ["universal-darwin-19", "x86-darwin", 210, 120],
+ ["universal-darwin-19", "universal-darwin-20", 200, 200],
+ ["arm-darwin-19", "arm64-darwin-19", 0, 20],
+ ].each do |spec_platform, user_platform, s1, s2|
+ spec_platform = Gem::Platform.new(spec_platform)
+ user_platform = Gem::Platform.new(user_platform)
+ assert_equal s1, Gem::Platform.platform_specificity_match(spec_platform, user_platform),
+ "Gem::Platform.platform_specificity_match(#{spec_platform.to_s.inspect}, #{user_platform.to_s.inspect})"
+ assert_equal s2, Gem::Platform.platform_specificity_match(user_platform, spec_platform),
+ "Gem::Platform.platform_specificity_match(#{user_platform.to_s.inspect}, #{spec_platform.to_s.inspect})"
end
end
+ def test_sort_and_filter_best_platform_match
+ a_1 = util_spec "a", "1"
+ a_1_java = util_spec "a", "1" do |s|
+ s.platform = Gem::Platform::JAVA
+ end
+ a_1_universal_darwin = util_spec "a", "1" do |s|
+ s.platform = Gem::Platform.new("universal-darwin")
+ end
+ a_1_universal_darwin_19 = util_spec "a", "1" do |s|
+ s.platform = Gem::Platform.new("universal-darwin-19")
+ end
+ a_1_universal_darwin_20 = util_spec "a", "1" do |s|
+ s.platform = Gem::Platform.new("universal-darwin-20")
+ end
+ a_1_arm_darwin_19 = util_spec "a", "1" do |s|
+ s.platform = Gem::Platform.new("arm64-darwin-19")
+ end
+ a_1_x86_darwin = util_spec "a", "1" do |s|
+ s.platform = Gem::Platform.new("x86-darwin")
+ end
+ specs = [a_1, a_1_java, a_1_universal_darwin, a_1_universal_darwin_19, a_1_universal_darwin_20, a_1_arm_darwin_19, a_1_x86_darwin]
+ assert_equal [a_1], Gem::Platform.sort_and_filter_best_platform_match(specs, "ruby")
+ assert_equal [a_1_java], Gem::Platform.sort_and_filter_best_platform_match(specs, Gem::Platform::JAVA)
+ assert_equal [a_1_arm_darwin_19], Gem::Platform.sort_and_filter_best_platform_match(specs, Gem::Platform.new("arm64-darwin-19"))
+ assert_equal [a_1_universal_darwin_20], Gem::Platform.sort_and_filter_best_platform_match(specs, Gem::Platform.new("arm64-darwin-20"))
+ assert_equal [a_1_universal_darwin_19], Gem::Platform.sort_and_filter_best_platform_match(specs, Gem::Platform.new("x86-darwin-19"))
+ assert_equal [a_1_universal_darwin_20], Gem::Platform.sort_and_filter_best_platform_match(specs, Gem::Platform.new("x86-darwin-20"))
+ assert_equal [a_1_x86_darwin], Gem::Platform.sort_and_filter_best_platform_match(specs, Gem::Platform.new("x86-darwin-21"))
+ end
+
+ def test_sort_best_platform_match
+ a_1 = util_spec "a", "1"
+ a_1_java = util_spec "a", "1" do |s|
+ s.platform = Gem::Platform::JAVA
+ end
+ a_1_universal_darwin = util_spec "a", "1" do |s|
+ s.platform = Gem::Platform.new("universal-darwin")
+ end
+ a_1_universal_darwin_19 = util_spec "a", "1" do |s|
+ s.platform = Gem::Platform.new("universal-darwin-19")
+ end
+ a_1_universal_darwin_20 = util_spec "a", "1" do |s|
+ s.platform = Gem::Platform.new("universal-darwin-20")
+ end
+ a_1_arm_darwin_19 = util_spec "a", "1" do |s|
+ s.platform = Gem::Platform.new("arm64-darwin-19")
+ end
+ a_1_x86_darwin = util_spec "a", "1" do |s|
+ s.platform = Gem::Platform.new("x86-darwin")
+ end
+ specs = [a_1, a_1_java, a_1_universal_darwin, a_1_universal_darwin_19, a_1_universal_darwin_20, a_1_arm_darwin_19, a_1_x86_darwin]
+ assert_equal ["ruby",
+ "java",
+ "universal-darwin",
+ "universal-darwin-19",
+ "universal-darwin-20",
+ "arm64-darwin-19",
+ "x86-darwin"], Gem::Platform.sort_best_platform_match(specs, "ruby").map {|s| s.platform.to_s }
+ assert_equal ["java",
+ "universal-darwin",
+ "x86-darwin",
+ "universal-darwin-19",
+ "universal-darwin-20",
+ "arm64-darwin-19",
+ "ruby"], Gem::Platform.sort_best_platform_match(specs, Gem::Platform::JAVA).map {|s| s.platform.to_s }
+ assert_equal ["arm64-darwin-19",
+ "universal-darwin-19",
+ "universal-darwin",
+ "java",
+ "x86-darwin",
+ "universal-darwin-20",
+ "ruby"], Gem::Platform.sort_best_platform_match(specs, Gem::Platform.new("arm64-darwin-19")).map {|s| s.platform.to_s }
+ assert_equal ["universal-darwin-20",
+ "universal-darwin",
+ "java",
+ "x86-darwin",
+ "arm64-darwin-19",
+ "universal-darwin-19",
+ "ruby"], Gem::Platform.sort_best_platform_match(specs, Gem::Platform.new("arm64-darwin-20")).map {|s| s.platform.to_s }
+ assert_equal ["universal-darwin-19",
+ "arm64-darwin-19",
+ "x86-darwin",
+ "universal-darwin",
+ "java",
+ "universal-darwin-20",
+ "ruby"], Gem::Platform.sort_best_platform_match(specs, Gem::Platform.new("x86-darwin-19")).map {|s| s.platform.to_s }
+ assert_equal ["universal-darwin-20",
+ "x86-darwin",
+ "universal-darwin",
+ "java",
+ "universal-darwin-19",
+ "arm64-darwin-19",
+ "ruby"], Gem::Platform.sort_best_platform_match(specs, Gem::Platform.new("x86-darwin-20")).map {|s| s.platform.to_s }
+ assert_equal ["x86-darwin",
+ "universal-darwin",
+ "java",
+ "universal-darwin-19",
+ "universal-darwin-20",
+ "arm64-darwin-19",
+ "ruby"], Gem::Platform.sort_best_platform_match(specs, Gem::Platform.new("x86-darwin-21")).map {|s| s.platform.to_s }
+ end
+
def assert_local_match(name)
assert_match Gem::Platform.local, name
end
@@ -508,4 +665,38 @@ class TestGemPlatform < Gem::TestCase
def refute_local_match(name)
refute_match Gem::Platform.local, name
end
+
+ def test_deconstruct
+ platform = Gem::Platform.new("x86_64-linux")
+ assert_equal ["x86_64", "linux", nil], platform.deconstruct
+ end
+
+ def test_deconstruct_keys
+ platform = Gem::Platform.new("x86_64-darwin-20")
+ assert_equal({ cpu: "x86_64", os: "darwin", version: "20" }, platform.deconstruct_keys(nil))
+ end
+
+ def test_pattern_matching_array
+ platform = Gem::Platform.new("arm64-darwin-21")
+ result =
+ case platform
+ in ["arm64", "darwin", version]
+ version
+ else
+ "no match"
+ end
+ assert_equal "21", result
+ end
+
+ def test_pattern_matching_hash
+ platform = Gem::Platform.new("x86_64-linux")
+ result =
+ case platform
+ in cpu: "x86_64", os: "linux"
+ "matched"
+ else
+ "no match"
+ end
+ assert_equal "matched", result
+ end
end
diff --git a/test/rubygems/test_gem_remote_fetcher.rb b/test/rubygems/test_gem_remote_fetcher.rb
index ca858cfda5..c35da2fc5a 100644
--- a/test/rubygems/test_gem_remote_fetcher.rb
+++ b/test/rubygems/test_gem_remote_fetcher.rb
@@ -60,7 +60,7 @@ class TestGemRemoteFetcher < Gem::TestCase
uri = Gem::URI "http://example/file"
path = File.join @tempdir, "file"
- fetcher = util_fuck_with_fetcher "hello"
+ fetcher = fake_fetcher(uri.to_s, "hello")
data = fetcher.cache_update_path uri, path
@@ -75,7 +75,7 @@ class TestGemRemoteFetcher < Gem::TestCase
path = File.join @tempdir, "file"
data = String.new("\xC8").force_encoding(Encoding::BINARY)
- fetcher = util_fuck_with_fetcher data
+ fetcher = fake_fetcher(uri.to_s, data)
written_data = fetcher.cache_update_path uri, path
@@ -88,7 +88,7 @@ class TestGemRemoteFetcher < Gem::TestCase
uri = Gem::URI "http://example/file"
path = File.join @tempdir, "file"
- fetcher = util_fuck_with_fetcher "hello"
+ fetcher = fake_fetcher(uri.to_s, "hello")
data = fetcher.cache_update_path uri, path, false
@@ -97,103 +97,79 @@ class TestGemRemoteFetcher < Gem::TestCase
assert_path_not_exist path
end
- def util_fuck_with_fetcher(data, blow = false)
- fetcher = Gem::RemoteFetcher.fetcher
- fetcher.instance_variable_set :@test_data, data
-
- if blow
- def fetcher.fetch_path(arg, *rest)
- # OMG I'm such an ass
- class << self; remove_method :fetch_path; end
- def self.fetch_path(arg, *rest)
- @test_arg = arg
- @test_data
- end
+ def test_cache_update_path_overwrites_existing_file
+ uri = Gem::URI "http://example/file"
+ path = File.join @tempdir, "file"
- raise Gem::RemoteFetcher::FetchError.new("haha!", "")
- end
- else
- def fetcher.fetch_path(arg, *rest)
- @test_arg = arg
- @test_data
- end
- end
+ # Create existing file with old content
+ File.write(path, "old content")
+ assert_equal "old content", File.read(path)
+
+ fetcher = fake_fetcher(uri.to_s, "new content")
+
+ data = fetcher.cache_update_path uri, path
- fetcher
+ assert_equal "new content", data
+ assert_equal "new content", File.read(path)
end
def test_download
- a1_data = nil
- File.open @a1_gem, "rb" do |fp|
- a1_data = fp.read
- end
+ a1_data = File.open @a1_gem, "rb", &:read
+ a1_url = "http://gems.example.com/gems/a-1.gem"
- fetcher = util_fuck_with_fetcher a1_data
+ fetcher = fake_fetcher(a1_url, a1_data)
a1_cache_gem = @a1.cache_file
assert_equal a1_cache_gem, fetcher.download(@a1, "http://gems.example.com")
- assert_equal("http://gems.example.com/gems/a-1.gem",
- fetcher.instance_variable_get(:@test_arg).to_s)
+ assert_equal a1_url, fetcher.paths.last
assert File.exist?(a1_cache_gem)
end
def test_download_with_auth
- a1_data = nil
- File.open @a1_gem, "rb" do |fp|
- a1_data = fp.read
- end
+ a1_data = File.open @a1_gem, "rb", &:read
+ a1_url = "http://user:password@gems.example.com/gems/a-1.gem"
- fetcher = util_fuck_with_fetcher a1_data
+ fetcher = fake_fetcher(a1_url, a1_data)
a1_cache_gem = @a1.cache_file
assert_equal a1_cache_gem, fetcher.download(@a1, "http://user:password@gems.example.com")
- assert_equal("http://user:password@gems.example.com/gems/a-1.gem",
- fetcher.instance_variable_get(:@test_arg).to_s)
+ assert_equal a1_url, fetcher.paths.last
assert File.exist?(a1_cache_gem)
end
def test_download_with_token
- a1_data = nil
- File.open @a1_gem, "rb" do |fp|
- a1_data = fp.read
- end
+ a1_data = File.open @a1_gem, "rb", &:read
+ a1_url = "http://token@gems.example.com/gems/a-1.gem"
- fetcher = util_fuck_with_fetcher a1_data
+ fetcher = fake_fetcher(a1_url, a1_data)
a1_cache_gem = @a1.cache_file
assert_equal a1_cache_gem, fetcher.download(@a1, "http://token@gems.example.com")
- assert_equal("http://token@gems.example.com/gems/a-1.gem",
- fetcher.instance_variable_get(:@test_arg).to_s)
+ assert_equal a1_url, fetcher.paths.last
assert File.exist?(a1_cache_gem)
end
def test_download_with_x_oauth_basic
- a1_data = nil
- File.open @a1_gem, "rb" do |fp|
- a1_data = fp.read
- end
+ a1_data = File.open @a1_gem, "rb", &:read
+ a1_url = "http://token:x-oauth-basic@gems.example.com/gems/a-1.gem"
- fetcher = util_fuck_with_fetcher a1_data
+ fetcher = fake_fetcher(a1_url, a1_data)
a1_cache_gem = @a1.cache_file
assert_equal a1_cache_gem, fetcher.download(@a1, "http://token:x-oauth-basic@gems.example.com")
- assert_equal("http://token:x-oauth-basic@gems.example.com/gems/a-1.gem",
- fetcher.instance_variable_get(:@test_arg).to_s)
+ assert_equal a1_url, fetcher.paths.last
assert File.exist?(a1_cache_gem)
end
def test_download_with_encoded_auth
- a1_data = nil
- File.open @a1_gem, "rb" do |fp|
- a1_data = fp.read
- end
+ a1_data = File.open @a1_gem, "rb", &:read
+ a1_url = "http://user:%25pas%25sword@gems.example.com/gems/a-1.gem"
- fetcher = util_fuck_with_fetcher a1_data
+ fetcher = fake_fetcher(a1_url, a1_data)
a1_cache_gem = @a1.cache_file
assert_equal a1_cache_gem, fetcher.download(@a1, "http://user:%25pas%25sword@gems.example.com")
- assert_equal("http://user:%25pas%25sword@gems.example.com/gems/a-1.gem",
- fetcher.instance_variable_get(:@test_arg).to_s)
+ assert_equal a1_url, fetcher.paths.last
assert File.exist?(a1_cache_gem)
end
@@ -235,8 +211,9 @@ class TestGemRemoteFetcher < Gem::TestCase
def test_download_install_dir
a1_data = File.open @a1_gem, "rb", &:read
+ a1_url = "http://gems.example.com/gems/a-1.gem"
- fetcher = util_fuck_with_fetcher a1_data
+ fetcher = fake_fetcher(a1_url, a1_data)
install_dir = File.join @tempdir, "more_gems"
@@ -245,8 +222,7 @@ class TestGemRemoteFetcher < Gem::TestCase
actual = fetcher.download(@a1, "http://gems.example.com", install_dir)
assert_equal a1_cache_gem, actual
- assert_equal("http://gems.example.com/gems/a-1.gem",
- fetcher.instance_variable_get(:@test_arg).to_s)
+ assert_equal a1_url, fetcher.paths.last
assert File.exist?(a1_cache_gem)
end
@@ -282,7 +258,12 @@ class TestGemRemoteFetcher < Gem::TestCase
FileUtils.chmod 0o555, @a1.cache_dir
FileUtils.chmod 0o555, @gemhome
- fetcher = util_fuck_with_fetcher File.read(@a1_gem)
+ fetcher = Gem::RemoteFetcher.fetcher
+ def fetcher.fetch_path(uri, *rest)
+ File.read File.join(@test_gem_dir, "a-1.gem")
+ end
+ fetcher.instance_variable_set(:@test_gem_dir, File.dirname(@a1_gem))
+
fetcher.download(@a1, "http://gems.example.com")
a1_cache_gem = File.join Gem.user_dir, "cache", @a1.file_name
assert File.exist? a1_cache_gem
@@ -301,19 +282,21 @@ class TestGemRemoteFetcher < Gem::TestCase
end
e1.loaded_from = File.join(@gemhome, "specifications", e1.full_name)
- e1_data = nil
- File.open e1_gem, "rb" do |fp|
- e1_data = fp.read
- end
+ e1_data = File.open e1_gem, "rb", &:read
- fetcher = util_fuck_with_fetcher e1_data, :blow_chunks
+ fetcher = Gem::RemoteFetcher.fetcher
+ def fetcher.fetch_path(uri, *rest)
+ @call_count ||= 0
+ @call_count += 1
+ raise Gem::RemoteFetcher::FetchError.new("error", uri) if @call_count == 1
+ @test_data
+ end
+ fetcher.instance_variable_set(:@test_data, e1_data)
e1_cache_gem = e1.cache_file
assert_equal e1_cache_gem, fetcher.download(e1, "http://gems.example.com")
- assert_equal("http://gems.example.com/gems/#{e1.original_name}.gem",
- fetcher.instance_variable_get(:@test_arg).to_s)
assert File.exist?(e1_cache_gem)
end
@@ -592,7 +575,112 @@ class TestGemRemoteFetcher < Gem::TestCase
end
end
- def assert_error(exception_class=Exception)
+ def test_download_with_global_gem_cache
+ # Use a temp directory to safely test global cache behavior
+ test_cache_dir = File.join(@tempdir, "global_gem_cache_test")
+
+ Gem.stub :global_gem_cache_path, test_cache_dir do
+ Gem.configuration.global_gem_cache = true
+
+ # Use the real RemoteFetcher with stubbed fetch_path
+ fetcher = Gem::RemoteFetcher.fetcher
+ def fetcher.fetch_path(uri, *rest)
+ File.binread File.join(@test_gem_dir, "a-1.gem")
+ end
+ fetcher.instance_variable_set(:@test_gem_dir, File.dirname(@a1_gem))
+
+ # With global cache enabled, gem goes directly to global cache
+ global_cache_gem = File.join(test_cache_dir, @a1.file_name)
+ assert_equal global_cache_gem, fetcher.download(@a1, "http://gems.example.com")
+ assert File.exist?(global_cache_gem), "Gem should be in global cache"
+ end
+ ensure
+ Gem.configuration.global_gem_cache = false
+ end
+
+ def test_download_uses_global_gem_cache
+ # Use a temp directory to safely test global cache behavior
+ test_cache_dir = File.join(@tempdir, "global_gem_cache_test")
+
+ Gem.stub :global_gem_cache_path, test_cache_dir do
+ Gem.configuration.global_gem_cache = true
+
+ # Pre-populate global cache
+ FileUtils.mkdir_p test_cache_dir
+ global_cache_gem = File.join(test_cache_dir, @a1.file_name)
+ FileUtils.cp @a1_gem, global_cache_gem
+
+ fetcher = Gem::RemoteFetcher.fetcher
+
+ # Should return global cache path without downloading
+ result = fetcher.download(@a1, "http://gems.example.com")
+ assert_equal global_cache_gem, result
+ end
+ ensure
+ Gem.configuration.global_gem_cache = false
+ end
+
+ def test_download_without_global_gem_cache
+ # Use a temp directory to safely test global cache behavior
+ test_cache_dir = File.join(@tempdir, "global_gem_cache_test")
+
+ Gem.stub :global_gem_cache_path, test_cache_dir do
+ Gem.configuration.global_gem_cache = false
+
+ # Use the real RemoteFetcher with stubbed fetch_path
+ fetcher = Gem::RemoteFetcher.fetcher
+ def fetcher.fetch_path(uri, *rest)
+ File.binread File.join(@test_gem_dir, "a-1.gem")
+ end
+ fetcher.instance_variable_set(:@test_gem_dir, File.dirname(@a1_gem))
+
+ a1_cache_gem = @a1.cache_file
+ assert_equal a1_cache_gem, fetcher.download(@a1, "http://gems.example.com")
+
+ # Verify gem was NOT copied to global cache
+ global_cache_gem = File.join(test_cache_dir, @a1.file_name)
+ refute File.exist?(global_cache_gem), "Gem should not be copied to global cache when disabled"
+ end
+ end
+
+ def test_fetch_http_with_custom_error_header
+ fetcher = Gem::RemoteFetcher.new nil
+ @fetcher = fetcher
+ url = "http://gems.example.com/error"
+
+ def fetcher.request(uri, request_class, last_modified = nil)
+ res = Gem::Net::HTTPBadRequest.new nil, 403, "Forbidden"
+ res.add_field "X-Error-Message", "Component blocked by policy"
+ res
+ end
+
+ e = assert_raise Gem::RemoteFetcher::FetchError do
+ fetcher.fetch_http Gem::URI.parse(url)
+ end
+
+ assert_equal "Bad response Component blocked by policy 403 (#{url})", e.message
+ end
+
+ def test_fetch_http_without_custom_error_header
+ fetcher = Gem::RemoteFetcher.new nil
+ @fetcher = fetcher
+ url = "http://gems.example.com/error"
+
+ def fetcher.request(uri, request_class, last_modified = nil)
+ res = Gem::Net::HTTPBadRequest.new nil, 403, "Forbidden"
+ res
+ end
+
+ e = assert_raise Gem::RemoteFetcher::FetchError do
+ fetcher.fetch_http Gem::URI.parse(url)
+ end
+
+ assert_equal "Bad response Forbidden 403 (#{url})", e.message
+ end
+
+ private
+
+ def assert_error(exception_class = Exception)
got_exception = false
begin
@@ -603,4 +691,13 @@ class TestGemRemoteFetcher < Gem::TestCase
assert got_exception, "Expected exception conforming to #{exception_class}"
end
+
+ def fake_fetcher(url, data)
+ original_fetcher = Gem::RemoteFetcher.fetcher
+ fetcher = Gem::FakeFetcher.new
+ fetcher.data[url] = data
+ Gem::RemoteFetcher.fetcher = fetcher
+ ensure
+ Gem::RemoteFetcher.fetcher = original_fetcher
+ end
end
diff --git a/test/rubygems/test_gem_remote_fetcher_s3.rb b/test/rubygems/test_gem_remote_fetcher_s3.rb
index fe7eb7ec01..4a5acc5a86 100644
--- a/test/rubygems/test_gem_remote_fetcher_s3.rb
+++ b/test/rubygems/test_gem_remote_fetcher_s3.rb
@@ -8,6 +8,100 @@ require "rubygems/package"
class TestGemRemoteFetcherS3 < Gem::TestCase
include Gem::DefaultUserInteraction
+ class FakeGemRequest < Gem::Request
+ attr_reader :last_request, :uri
+
+ # Override perform_request to stub things
+ def perform_request(request)
+ @last_request = request
+ @response
+ end
+
+ def set_response(response)
+ @response = response
+ end
+ end
+
+ class FakeS3URISigner < Gem::S3URISigner
+ class << self
+ attr_accessor :return_token, :instance_profile
+ end
+
+ # Convenience method to output the recent aws iam queries made in tests
+ # this outputs the verb, path, and any non-generic headers
+ def recent_aws_query_logs
+ sreqs = @aws_iam_calls.map do |c|
+ r = c.last_request
+ s = +"#{r.method} #{c.uri}\n"
+ r.each_header do |key, v|
+ # Only include headers that start with x-
+ next unless key.start_with?("x-")
+ s << " #{key}=#{v}\n"
+ end
+ s
+ end
+
+ sreqs.join("")
+ end
+
+ def initialize(uri, method)
+ @aws_iam_calls = []
+ super
+ end
+
+ def ec2_iam_request(uri, verb)
+ fake_s3_request = FakeGemRequest.new(uri, verb, nil, nil)
+ @aws_iam_calls << fake_s3_request
+
+ case uri.to_s
+ when "http://169.254.169.254/latest/api/token"
+ if FakeS3URISigner.return_token.nil?
+ res = Gem::Net::HTTPUnauthorized.new nil, 401, nil
+ def res.body = "you got a 401! panic!"
+ else
+ res = Gem::Net::HTTPOK.new nil, 200, nil
+ def res.body = FakeS3URISigner.return_token
+ end
+ when "http://169.254.169.254/latest/meta-data/iam/info"
+ res = Gem::Net::HTTPOK.new nil, 200, nil
+ def res.body
+ <<~JSON
+ {
+ "Code": "Success",
+ "LastUpdated": "2023-05-27:05:05",
+ "InstanceProfileArn": "arn:aws:iam::somesecretid:instance-profile/TestRole",
+ "InstanceProfileId": "SOMEPROFILEID"
+ }
+ JSON
+ end
+
+ when "http://169.254.169.254/latest/meta-data/iam/security-credentials/TestRole"
+ res = Gem::Net::HTTPOK.new nil, 200, nil
+ def res.body = FakeS3URISigner.instance_profile
+ else
+ raise "Unexpected request to #{uri}"
+ end
+
+ fake_s3_request.set_response(res)
+ fake_s3_request
+ end
+ end
+
+ class FakeGemFetcher < Gem::RemoteFetcher
+ attr_reader :fetched_uri, :last_s3_uri_signer
+
+ def request(uri, request_class, last_modified = nil)
+ @fetched_uri = uri
+ res = Gem::Net::HTTPOK.new nil, 200, nil
+ def res.body = "success"
+ res
+ end
+
+ def s3_uri_signer(uri, method)
+ @last_s3_uri_signer = FakeS3URISigner.new(uri, method)
+ end
+ end
+
def setup
super
@@ -18,39 +112,61 @@ class TestGemRemoteFetcherS3 < Gem::TestCase
@a1.loaded_from = File.join(@gemhome, "specifications", @a1.full_name)
end
- def assert_fetch_s3(url, signature, token=nil, region="us-east-1", instance_profile_json=nil)
- fetcher = Gem::RemoteFetcher.new nil
- @fetcher = fetcher
- $fetched_uri = nil
- $instance_profile = instance_profile_json
+ def assert_fetched_s3_with_imds_v2(expected_token)
+ # Three API requests:
+ # 1. Get the token
+ # 2. Lookup profile details
+ # 3. Query the credentials
+ expected = <<~TEXT
+ PUT http://169.254.169.254/latest/api/token
+ x-aws-ec2-metadata-token-ttl-seconds=60
+ GET http://169.254.169.254/latest/meta-data/iam/info
+ x-aws-ec2-metadata-token=#{expected_token}
+ GET http://169.254.169.254/latest/meta-data/iam/security-credentials/TestRole
+ x-aws-ec2-metadata-token=#{expected_token}
+ TEXT
+ recent_aws_query_logs = @fetcher.last_s3_uri_signer.recent_aws_query_logs
+ assert_equal(expected.strip, recent_aws_query_logs.strip)
+ end
- def fetcher.request(uri, request_class, last_modified = nil)
- $fetched_uri = uri
- res = Gem::Net::HTTPOK.new nil, 200, nil
- def res.body
- "success"
- end
- res
- end
+ def assert_fetched_s3_with_imds_v1
+ # Three API requests:
+ # 1. Get the token (which fails)
+ # 2. Lookup profile details without token
+ # 3. Query the credentials without token
+ expected = <<~TEXT
+ PUT http://169.254.169.254/latest/api/token
+ x-aws-ec2-metadata-token-ttl-seconds=60
+ GET http://169.254.169.254/latest/meta-data/iam/info
+ GET http://169.254.169.254/latest/meta-data/iam/security-credentials/TestRole
+ TEXT
+ recent_aws_query_logs = @fetcher.last_s3_uri_signer.recent_aws_query_logs
+ assert_equal(expected.strip, recent_aws_query_logs.strip)
+ end
- def fetcher.s3_uri_signer(uri)
- require "json"
- s3_uri_signer = Gem::S3URISigner.new(uri)
- def s3_uri_signer.ec2_metadata_credentials_json
- JSON.parse($instance_profile)
- end
- # Running sign operation to make sure uri.query is not mutated
- s3_uri_signer.sign
- raise "URI query is not empty: #{uri.query}" unless uri.query.nil?
- s3_uri_signer
- end
+ def with_imds_v2_failure
+ FakeS3URISigner.should_fail = true
+ yield(fetcher)
+ ensure
+ FakeS3URISigner.should_fail = false
+ end
- data = fetcher.fetch_s3 Gem::URI.parse(url)
+ def assert_fetch_s3(url:, signature:, token: nil, region: "us-east-1", instance_profile_json: nil, fetcher: nil, method: "GET")
+ FakeS3URISigner.instance_profile = instance_profile_json
+ FakeS3URISigner.return_token = token
- assert_equal "https://my-bucket.s3.#{region}.amazonaws.com/gems/specs.4.8.gz?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=testuser%2F20190624%2F#{region}%2Fs3%2Faws4_request&X-Amz-Date=20190624T050641Z&X-Amz-Expires=86400#{token ? "&X-Amz-Security-Token=" + token : ""}&X-Amz-SignedHeaders=host&X-Amz-Signature=#{signature}", $fetched_uri.to_s
- assert_equal "success", data
+ @fetcher = fetcher || FakeGemFetcher.new(nil)
+ res = @fetcher.fetch_s3 Gem::URI.parse(url), nil, (method == "HEAD")
+
+ assert_equal "https://my-bucket.s3.#{region}.amazonaws.com/gems/specs.4.8.gz?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=testuser%2F20190624%2F#{region}%2Fs3%2Faws4_request&X-Amz-Date=20190624T051941Z&X-Amz-Expires=86400#{token ? "&X-Amz-Security-Token=" + token : ""}&X-Amz-SignedHeaders=host&X-Amz-Signature=#{signature}", @fetcher.fetched_uri.to_s
+ if method == "HEAD"
+ assert_equal 200, res.code
+ else
+ assert_equal "success", res
+ end
ensure
- $fetched_uri = nil
+ FakeS3URISigner.instance_profile = nil
+ FakeS3URISigner.return_token = nil
end
def test_fetch_s3_config_creds
@@ -59,7 +175,34 @@ class TestGemRemoteFetcherS3 < Gem::TestCase
}
url = "s3://my-bucket/gems/specs.4.8.gz"
Time.stub :now, Time.at(1_561_353_581) do
- assert_fetch_s3 url, "20f974027db2f3cd6193565327a7c73457a138efb1a63ea248d185ce6827d41b"
+ assert_fetch_s3(
+ url: url,
+ signature: "b5cb80c1301f7b1c50c4af54f1f6c034f80b56d32f000a855f0a903dc5a8413c",
+ )
+ end
+ ensure
+ Gem.configuration[:s3_source] = nil
+ end
+
+ def test_fetch_s3_head_request
+ Gem.configuration[:s3_source] = {
+ "my-bucket" => { id: "testuser", secret: "testpass" },
+ }
+ url = "s3://my-bucket/gems/specs.4.8.gz"
+ Time.stub :now, Time.at(1_561_353_581) do
+ token = nil
+ region = "us-east-1"
+ instance_profile_json = nil
+ method = "HEAD"
+
+ assert_fetch_s3(
+ url: url,
+ signature: "a3c6cf9a2db62e85f4e57f8fc8ac8b5ff5c1fdd4aeef55935d05e05174d9c885",
+ token: token,
+ region: region,
+ instance_profile_json: instance_profile_json,
+ method: method
+ )
end
ensure
Gem.configuration[:s3_source] = nil
@@ -71,7 +214,11 @@ class TestGemRemoteFetcherS3 < Gem::TestCase
}
url = "s3://my-bucket/gems/specs.4.8.gz"
Time.stub :now, Time.at(1_561_353_581) do
- assert_fetch_s3 url, "4afc3010757f1fd143e769f1d1dabd406476a4fc7c120e9884fd02acbb8f26c9", nil, "us-west-2"
+ assert_fetch_s3(
+ url: url,
+ signature: "ef07487bfd8e3ca594f8fc29775b70c0a0636f51318f95d4f12b2e6e1fd8c716",
+ region: "us-west-2"
+ )
end
ensure
Gem.configuration[:s3_source] = nil
@@ -83,7 +230,11 @@ class TestGemRemoteFetcherS3 < Gem::TestCase
}
url = "s3://my-bucket/gems/specs.4.8.gz"
Time.stub :now, Time.at(1_561_353_581) do
- assert_fetch_s3 url, "935160a427ef97e7630f799232b8f208c4a4e49aad07d0540572a2ad5fe9f93c", "testtoken"
+ assert_fetch_s3(
+ url: url,
+ signature: "e709338735f9077edf8f6b94b247171c266a9605975e08e4a519a123c3322625",
+ token: "testtoken"
+ )
end
ensure
Gem.configuration[:s3_source] = nil
@@ -98,7 +249,10 @@ class TestGemRemoteFetcherS3 < Gem::TestCase
}
url = "s3://my-bucket/gems/specs.4.8.gz"
Time.stub :now, Time.at(1_561_353_581) do
- assert_fetch_s3 url, "20f974027db2f3cd6193565327a7c73457a138efb1a63ea248d185ce6827d41b"
+ assert_fetch_s3(
+ url: url,
+ signature: "b5cb80c1301f7b1c50c4af54f1f6c034f80b56d32f000a855f0a903dc5a8413c"
+ )
end
ensure
ENV.each_key {|key| ENV.delete(key) if key.start_with?("AWS") }
@@ -114,7 +268,12 @@ class TestGemRemoteFetcherS3 < Gem::TestCase
}
url = "s3://my-bucket/gems/specs.4.8.gz"
Time.stub :now, Time.at(1_561_353_581) do
- assert_fetch_s3 url, "4afc3010757f1fd143e769f1d1dabd406476a4fc7c120e9884fd02acbb8f26c9", nil, "us-west-2"
+ assert_fetch_s3(
+ url: url,
+ signature: "ef07487bfd8e3ca594f8fc29775b70c0a0636f51318f95d4f12b2e6e1fd8c716",
+ token: nil,
+ region: "us-west-2"
+ )
end
ensure
ENV.each_key {|key| ENV.delete(key) if key.start_with?("AWS") }
@@ -130,7 +289,11 @@ class TestGemRemoteFetcherS3 < Gem::TestCase
}
url = "s3://my-bucket/gems/specs.4.8.gz"
Time.stub :now, Time.at(1_561_353_581) do
- assert_fetch_s3 url, "935160a427ef97e7630f799232b8f208c4a4e49aad07d0540572a2ad5fe9f93c", "testtoken"
+ assert_fetch_s3(
+ url: url,
+ signature: "e709338735f9077edf8f6b94b247171c266a9605975e08e4a519a123c3322625",
+ token: "testtoken"
+ )
end
ensure
ENV.each_key {|key| ENV.delete(key) if key.start_with?("AWS") }
@@ -140,7 +303,10 @@ class TestGemRemoteFetcherS3 < Gem::TestCase
def test_fetch_s3_url_creds
url = "s3://testuser:testpass@my-bucket/gems/specs.4.8.gz"
Time.stub :now, Time.at(1_561_353_581) do
- assert_fetch_s3 url, "20f974027db2f3cd6193565327a7c73457a138efb1a63ea248d185ce6827d41b"
+ assert_fetch_s3(
+ url: url,
+ signature: "b5cb80c1301f7b1c50c4af54f1f6c034f80b56d32f000a855f0a903dc5a8413c"
+ )
end
end
@@ -151,8 +317,14 @@ class TestGemRemoteFetcherS3 < Gem::TestCase
url = "s3://my-bucket/gems/specs.4.8.gz"
Time.stub :now, Time.at(1_561_353_581) do
- assert_fetch_s3 url, "20f974027db2f3cd6193565327a7c73457a138efb1a63ea248d185ce6827d41b", nil, "us-east-1",
- '{"AccessKeyId": "testuser", "SecretAccessKey": "testpass"}'
+ assert_fetch_s3(
+ url: url,
+ signature: "da82e098bdaed0d3087047670efc98eaadc20559a473b5eac8d70190d2a9e8fd",
+ region: "us-east-1",
+ token: "mysecrettoken",
+ instance_profile_json: '{"AccessKeyId": "testuser", "SecretAccessKey": "testpass", "Token": "mysecrettoken"}'
+ )
+ assert_fetched_s3_with_imds_v2("mysecrettoken")
end
ensure
Gem.configuration[:s3_source] = nil
@@ -165,8 +337,14 @@ class TestGemRemoteFetcherS3 < Gem::TestCase
url = "s3://my-bucket/gems/specs.4.8.gz"
Time.stub :now, Time.at(1_561_353_581) do
- assert_fetch_s3 url, "4afc3010757f1fd143e769f1d1dabd406476a4fc7c120e9884fd02acbb8f26c9", nil, "us-west-2",
- '{"AccessKeyId": "testuser", "SecretAccessKey": "testpass"}'
+ assert_fetch_s3(
+ url: url,
+ signature: "532960594dbfe31d1bbfc0e8e7a666c3cbdd8b00a143774da51b7f920704afd2",
+ region: "us-west-2",
+ token: "mysecrettoken",
+ instance_profile_json: '{"AccessKeyId": "testuser", "SecretAccessKey": "testpass", "Token": "mysecrettoken"}'
+ )
+ assert_fetched_s3_with_imds_v2("mysecrettoken")
end
ensure
Gem.configuration[:s3_source] = nil
@@ -179,14 +357,40 @@ class TestGemRemoteFetcherS3 < Gem::TestCase
url = "s3://my-bucket/gems/specs.4.8.gz"
Time.stub :now, Time.at(1_561_353_581) do
- assert_fetch_s3 url, "935160a427ef97e7630f799232b8f208c4a4e49aad07d0540572a2ad5fe9f93c", "testtoken", "us-east-1",
- '{"AccessKeyId": "testuser", "SecretAccessKey": "testpass", "Token": "testtoken"}'
+ assert_fetch_s3(
+ url: url,
+ signature: "e709338735f9077edf8f6b94b247171c266a9605975e08e4a519a123c3322625",
+ token: "testtoken",
+ region: "us-east-1",
+ instance_profile_json: '{"AccessKeyId": "testuser", "SecretAccessKey": "testpass", "Token": "testtoken"}'
+ )
+ assert_fetched_s3_with_imds_v2("testtoken")
+ end
+ ensure
+ Gem.configuration[:s3_source] = nil
+ end
+
+ def test_fetch_s3_instance_profile_creds_with_fallback
+ Gem.configuration[:s3_source] = {
+ "my-bucket" => { provider: "instance_profile" },
+ }
+
+ url = "s3://my-bucket/gems/specs.4.8.gz"
+ Time.stub :now, Time.at(1_561_353_581) do
+ assert_fetch_s3(
+ url: url,
+ signature: "b5cb80c1301f7b1c50c4af54f1f6c034f80b56d32f000a855f0a903dc5a8413c",
+ token: nil,
+ region: "us-east-1",
+ instance_profile_json: '{"AccessKeyId": "testuser", "SecretAccessKey": "testpass"}'
+ )
+ assert_fetched_s3_with_imds_v1
end
ensure
Gem.configuration[:s3_source] = nil
end
- def refute_fetch_s3(url, expected_message)
+ def refute_fetch_s3(url:, expected_message:)
fetcher = Gem::RemoteFetcher.new nil
@fetcher = fetcher
@@ -199,7 +403,7 @@ class TestGemRemoteFetcherS3 < Gem::TestCase
def test_fetch_s3_no_source_key
url = "s3://my-bucket/gems/specs.4.8.gz"
- refute_fetch_s3 url, "no s3_source key exists in .gemrc"
+ refute_fetch_s3(url: url, expected_message: "no s3_source key exists in .gemrc")
end
def test_fetch_s3_no_host
@@ -208,7 +412,7 @@ class TestGemRemoteFetcherS3 < Gem::TestCase
}
url = "s3://other-bucket/gems/specs.4.8.gz"
- refute_fetch_s3 url, "no key for host other-bucket in s3_source in .gemrc"
+ refute_fetch_s3(url: url, expected_message: "no key for host other-bucket in s3_source in .gemrc")
ensure
Gem.configuration[:s3_source] = nil
end
@@ -217,7 +421,7 @@ class TestGemRemoteFetcherS3 < Gem::TestCase
Gem.configuration[:s3_source] = { "my-bucket" => { secret: "testpass" } }
url = "s3://my-bucket/gems/specs.4.8.gz"
- refute_fetch_s3 url, "s3_source for my-bucket missing id or secret"
+ refute_fetch_s3(url: url, expected_message: "s3_source for my-bucket missing id or secret")
ensure
Gem.configuration[:s3_source] = nil
end
@@ -226,7 +430,7 @@ class TestGemRemoteFetcherS3 < Gem::TestCase
Gem.configuration[:s3_source] = { "my-bucket" => { id: "testuser" } }
url = "s3://my-bucket/gems/specs.4.8.gz"
- refute_fetch_s3 url, "s3_source for my-bucket missing id or secret"
+ refute_fetch_s3(url: url, expected_message: "s3_source for my-bucket missing id or secret")
ensure
Gem.configuration[:s3_source] = nil
end
diff --git a/test/rubygems/test_gem_request.rb b/test/rubygems/test_gem_request.rb
index eb15eed749..cd0a416e79 100644
--- a/test/rubygems/test_gem_request.rb
+++ b/test/rubygems/test_gem_request.rb
@@ -248,7 +248,7 @@ class TestGemRequest < Gem::TestCase
auth_header = conn.payload["Authorization"]
assert_equal "Basic #{base64_encode64("{DEScede}pass:x-oauth-basic")}".strip, auth_header
- assert_includes @ui.output, "GET https://REDACTED:x-oauth-basic@example.rubygems/specs.#{Gem.marshal_version}"
+ assert_includes @ui.output, "GET https://REDACTED@example.rubygems/specs.#{Gem.marshal_version}"
end
def test_fetch_head
@@ -363,19 +363,19 @@ class TestGemRequest < Gem::TestCase
def test_verify_certificate_extra_message
pend if Gem.java_platform?
- error_number = OpenSSL::X509::V_ERR_INVALID_CA
+ error_number = OpenSSL::X509::V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY
store = OpenSSL::X509::Store.new
- context = OpenSSL::X509::StoreContext.new store
- context.error = error_number
+ context = OpenSSL::X509::StoreContext.new store, CHILD_CERT
+ context.verify
use_ui @ui do
Gem::Request.verify_certificate context
end
expected = <<-ERROR
-ERROR: SSL verification error at depth 0: invalid CA certificate (#{error_number})
-ERROR: Certificate is an invalid CA certificate
+ERROR: SSL verification error at depth 0: unable to get local issuer certificate (#{error_number})
+ERROR: You must add #{CHILD_CERT.issuer} to your local trusted store
ERROR
assert_equal expected, @ui.error
diff --git a/test/rubygems/test_gem_request_connection_pools.rb b/test/rubygems/test_gem_request_connection_pools.rb
index 966447bff6..2860deabf7 100644
--- a/test/rubygems/test_gem_request_connection_pools.rb
+++ b/test/rubygems/test_gem_request_connection_pools.rb
@@ -148,4 +148,16 @@ class TestGemRequestConnectionPool < Gem::TestCase
end
end.join
end
+
+ def test_checkouts_multiple_connections_from_the_pool
+ uri = Gem::URI.parse("http://example/some_endpoint")
+ pools = Gem::Request::ConnectionPools.new nil, [], 2
+ pool = pools.pool_for uri
+
+ pool.checkout
+
+ Thread.new do
+ assert_not_nil(pool.checkout)
+ end.join
+ end
end
diff --git a/test/rubygems/test_gem_request_set.rb b/test/rubygems/test_gem_request_set.rb
index 9aa244892c..33054aa8e5 100644
--- a/test/rubygems/test_gem_request_set.rb
+++ b/test/rubygems/test_gem_request_set.rb
@@ -93,6 +93,34 @@ Gems to install:
end
end
+ def test_install_from_gemdeps_explain_verbose
+ spec_fetcher do |fetcher|
+ fetcher.gem "a", 2
+ end
+
+ rs = Gem::RequestSet.new
+
+ verbose = Gem.configuration.verbose
+ Gem.configuration.verbose = :really
+
+ File.open "gem.deps.rb", "w" do |io|
+ io.puts 'gem "a"'
+ io.flush
+
+ expected = <<-EXPECTED
+Gems to install:
+ a-2
+ EXPECTED
+
+ actual, _ = capture_output do
+ rs.install_from_gemdeps gemdeps: io.path, explain: true
+ end
+ assert_equal(expected, actual)
+ end
+ ensure
+ Gem.configuration.verbose = verbose
+ end
+
def test_install_from_gemdeps_install_dir
spec_fetcher do |fetcher|
fetcher.gem "a", 2
@@ -311,6 +339,110 @@ ruby "0"
assert_empty rs.dependencies
end
+ def test_load_gemdeps_with_lockfile_gem_section
+ rs = Gem::RequestSet.new
+
+ File.open "gem.deps.rb", "w" do |io|
+ io.puts 'gem "b"'
+ end
+
+ File.open "gem.deps.rb.lock", "w" do |io|
+ io.puts <<~LOCKFILE
+ GEM
+ remote: #{@gem_repo}
+ specs:
+ a (1)
+ b (1)
+ a (~> 1.0)
+
+ PLATFORMS
+ #{Gem::Platform::RUBY}
+
+ DEPENDENCIES
+ b
+ LOCKFILE
+ end
+
+ rs.load_gemdeps "gem.deps.rb"
+
+ lock_set = rs.sets.find {|set| Gem::Resolver::LockSet === set }
+ refute_nil lock_set, "LockSet should be created from GEM section"
+ assert_equal %w[a-1 b-1], lock_set.specs.map(&:full_name).sort
+ end
+
+ def test_load_gemdeps_with_lockfile_git_section
+ rs = Gem::RequestSet.new
+
+ File.open "gem.deps.rb", "w" do |io|
+ io.puts 'gem "a", :git => "git://example/a.git"'
+ end
+
+ File.open "gem.deps.rb.lock", "w" do |io|
+ io.puts <<~LOCKFILE
+ GIT
+ remote: git://example/a.git
+ revision: deadbeef
+ specs:
+ a (1)
+
+ PLATFORMS
+ #{Gem::Platform::RUBY}
+
+ DEPENDENCIES
+ a!
+ LOCKFILE
+ end
+
+ rs.load_gemdeps "gem.deps.rb"
+
+ git_set = rs.sets.find {|set| Gem::Resolver::GitSet === set }
+ refute_nil git_set, "GitSet should be created from GIT section"
+ assert_includes git_set.specs.keys, "a"
+ end
+
+ def test_load_gemdeps_with_lockfile_path_section
+ _, _, directory = vendor_gem
+
+ rs = Gem::RequestSet.new
+
+ File.open "gem.deps.rb", "w" do |io|
+ io.puts "gem \"a\", :path => #{directory.inspect}"
+ end
+
+ File.open "gem.deps.rb.lock", "w" do |io|
+ io.puts <<~LOCKFILE
+ PATH
+ remote: #{directory}
+ specs:
+ a (1)
+
+ PLATFORMS
+ #{Gem::Platform::RUBY}
+
+ DEPENDENCIES
+ a!
+ LOCKFILE
+ end
+
+ rs.load_gemdeps "gem.deps.rb"
+
+ vendor_set = rs.sets.find {|set| Gem::Resolver::VendorSet === set }
+ refute_nil vendor_set, "VendorSet should be created from PATH section"
+ assert_equal %w[a-1], vendor_set.specs.values.map(&:full_name)
+ end
+
+ def test_load_gemdeps_with_missing_lockfile
+ rs = Gem::RequestSet.new
+
+ File.open "gem.deps.rb", "w" do |io|
+ io.puts 'gem "a"'
+ end
+
+ rs.load_gemdeps "gem.deps.rb"
+
+ assert_equal [dep("a")], rs.dependencies
+ end
+
def test_resolve
a = util_spec "a", "2", "b" => ">= 2"
b = util_spec "b", "2"
diff --git a/test/rubygems/test_gem_request_set_lockfile_parser.rb b/test/rubygems/test_gem_request_set_lockfile_parser.rb
deleted file mode 100644
index 253a59b243..0000000000
--- a/test/rubygems/test_gem_request_set_lockfile_parser.rb
+++ /dev/null
@@ -1,544 +0,0 @@
-# frozen_string_literal: true
-
-require_relative "helper"
-require "rubygems/request_set"
-require "rubygems/request_set/lockfile"
-require "rubygems/request_set/lockfile/tokenizer"
-require "rubygems/request_set/lockfile/parser"
-
-class TestGemRequestSetLockfileParser < Gem::TestCase
- def setup
- super
- @gem_deps_file = "gem.deps.rb"
- @lock_file = File.expand_path "#{@gem_deps_file}.lock"
- @set = Gem::RequestSet.new
- end
-
- def test_get
- tokenizer = Gem::RequestSet::Lockfile::Tokenizer.new "\n"
- parser = tokenizer.make_parser nil, nil
-
- assert_equal :newline, parser.get.first
- end
-
- def test_get_type_mismatch
- filename = File.expand_path("#{@gem_deps_file}.lock")
- tokenizer = Gem::RequestSet::Lockfile::Tokenizer.new "foo", filename, 1, 0
- parser = tokenizer.make_parser nil, nil
-
- e = assert_raise Gem::RequestSet::Lockfile::ParseError do
- parser.get :section
- end
-
- expected =
- 'unexpected token [:text, "foo"], expected :section (at line 1 column 0)'
-
- assert_equal expected, e.message
-
- assert_equal 1, e.line
- assert_equal 0, e.column
- assert_equal filename, e.path
- end
-
- def test_get_type_multiple
- filename = File.expand_path("#{@gem_deps_file}.lock")
- tokenizer = Gem::RequestSet::Lockfile::Tokenizer.new "x", filename, 1
- parser = tokenizer.make_parser nil, nil
-
- assert parser.get [:text, :section]
- end
-
- def test_get_type_value_mismatch
- filename = File.expand_path("#{@gem_deps_file}.lock")
- tokenizer = Gem::RequestSet::Lockfile::Tokenizer.new "x", filename, 1
- parser = tokenizer.make_parser nil, nil
-
- e = assert_raise Gem::RequestSet::Lockfile::ParseError do
- parser.get :text, "y"
- end
-
- expected =
- 'unexpected token [:text, "x"], expected [:text, "y"] (at line 1 column 0)'
-
- assert_equal expected, e.message
-
- assert_equal 1, e.line
- assert_equal 0, e.column
- assert_equal File.expand_path("#{@gem_deps_file}.lock"), e.path
- end
-
- def test_parse
- write_lockfile <<-LOCKFILE.strip
-GEM
- remote: #{@gem_repo}
- specs:
- a (2)
-
-PLATFORMS
- #{Gem::Platform::RUBY}
-
-DEPENDENCIES
- a
- LOCKFILE
-
- platforms = []
- parse_lockfile @set, platforms
-
- assert_equal [dep("a")], @set.dependencies
-
- assert_equal [Gem::Platform::RUBY], platforms
-
- lockfile_set = @set.sets.find do |set|
- Gem::Resolver::LockSet === set
- end
-
- assert lockfile_set, "could not find a LockSet"
-
- assert_equal %w[a-2], lockfile_set.specs.map(&:full_name)
- end
-
- def test_parse_dependencies
- write_lockfile <<-LOCKFILE
-GEM
- remote: #{@gem_repo}
- specs:
- a (2)
-
-PLATFORMS
- #{Gem::Platform::RUBY}
-
-DEPENDENCIES
- a (>= 1, <= 2)
- LOCKFILE
-
- platforms = []
- parse_lockfile @set, platforms
-
- assert_equal [dep("a", ">= 1", "<= 2")], @set.dependencies
-
- assert_equal [Gem::Platform::RUBY], platforms
-
- lockfile_set = @set.sets.find do |set|
- Gem::Resolver::LockSet === set
- end
-
- assert lockfile_set, "could not find a LockSet"
-
- assert_equal %w[a-2], lockfile_set.specs.map(&:full_name)
- end
-
- def test_parse_DEPENDENCIES_git
- write_lockfile <<-LOCKFILE
-GIT
- remote: git://git.example/josevalim/rails-footnotes.git
- revision: 3a6ac1971e91d822f057650cc5916ebfcbd6ee37
- specs:
- rails-footnotes (3.7.9)
- rails (>= 3.0.0)
-
-GIT
- remote: git://git.example/svenfuchs/i18n-active_record.git
- revision: 55507cf59f8f2173d38e07e18df0e90d25b1f0f6
- specs:
- i18n-active_record (0.0.2)
- i18n (>= 0.5.0)
-
-GEM
- remote: http://gems.example/
- specs:
- i18n (0.6.9)
- rails (4.0.0)
-
-PLATFORMS
- ruby
-
-DEPENDENCIES
- i18n-active_record!
- rails-footnotes!
- LOCKFILE
-
- parse_lockfile @set, []
-
- expected = [
- dep("i18n-active_record", "= 0.0.2"),
- dep("rails-footnotes", "= 3.7.9"),
- ]
-
- assert_equal expected, @set.dependencies
- end
-
- def test_parse_DEPENDENCIES_git_version
- write_lockfile <<-LOCKFILE
-GIT
- remote: git://github.com/progrium/ruby-jwt.git
- revision: 8d74770c6cd92ea234b428b5d0c1f18306a4f41c
- specs:
- jwt (1.1)
-
-GEM
- remote: http://gems.example/
- specs:
-
-PLATFORMS
- ruby
-
-DEPENDENCIES
- jwt (= 1.1)!
- LOCKFILE
-
- parse_lockfile @set, []
-
- expected = [
- dep("jwt", "= 1.1"),
- ]
-
- assert_equal expected, @set.dependencies
- end
-
- def test_parse_GEM
- write_lockfile <<-LOCKFILE
-GEM
- specs:
- a (2)
-
-PLATFORMS
- ruby
-
-DEPENDENCIES
- a
- LOCKFILE
-
- parse_lockfile @set, []
-
- assert_equal [dep("a", ">= 0")], @set.dependencies
-
- lockfile_set = @set.sets.find do |set|
- Gem::Resolver::LockSet === set
- end
-
- assert lockfile_set, "found a LockSet"
-
- assert_equal %w[a-2], lockfile_set.specs.map(&:full_name)
- end
-
- def test_parse_GEM_remote_multiple
- write_lockfile <<-LOCKFILE
-GEM
- remote: https://gems.example/
- remote: https://other.example/
- specs:
- a (2)
-
-PLATFORMS
- ruby
-
-DEPENDENCIES
- a
- LOCKFILE
-
- parse_lockfile @set, []
-
- assert_equal [dep("a", ">= 0")], @set.dependencies
-
- lockfile_set = @set.sets.find do |set|
- Gem::Resolver::LockSet === set
- end
-
- assert lockfile_set, "found a LockSet"
-
- assert_equal %w[a-2], lockfile_set.specs.map(&:full_name)
-
- assert_equal %w[https://gems.example/ https://other.example/],
- lockfile_set.specs.flat_map {|s| s.sources.map {|src| src.uri.to_s } }
- end
-
- def test_parse_GIT
- @set.instance_variable_set :@install_dir, "install_dir"
-
- write_lockfile <<-LOCKFILE
-GIT
- remote: git://example/a.git
- revision: abranch
- specs:
- a (2)
- b (>= 3)
- c
-
-DEPENDENCIES
- a!
- LOCKFILE
-
- parse_lockfile @set, []
-
- assert_equal [dep("a", "= 2")], @set.dependencies
-
- lockfile_set = @set.sets.find do |set|
- Gem::Resolver::LockSet === set
- end
-
- refute lockfile_set, "found a LockSet"
-
- git_set = @set.sets.find do |set|
- Gem::Resolver::GitSet === set
- end
-
- assert git_set, "could not find a GitSet"
-
- assert_equal %w[a-2], git_set.specs.values.map(&:full_name)
-
- assert_equal [dep("b", ">= 3"), dep("c")],
- git_set.specs.values.first.dependencies
-
- expected = {
- "a" => %w[git://example/a.git abranch],
- }
-
- assert_equal expected, git_set.repositories
- assert_equal "install_dir", git_set.root_dir
- end
-
- def test_parse_GIT_branch
- write_lockfile <<-LOCKFILE
-GIT
- remote: git://example/a.git
- revision: 1234abc
- branch: 0-9-12-stable
- specs:
- a (2)
- b (>= 3)
-
-DEPENDENCIES
- a!
- LOCKFILE
-
- parse_lockfile @set, []
-
- assert_equal [dep("a", "= 2")], @set.dependencies
-
- lockfile_set = @set.sets.find do |set|
- Gem::Resolver::LockSet === set
- end
-
- refute lockfile_set, "found a LockSet"
-
- git_set = @set.sets.find do |set|
- Gem::Resolver::GitSet === set
- end
-
- assert git_set, "could not find a GitSet"
-
- expected = {
- "a" => %w[git://example/a.git 1234abc],
- }
-
- assert_equal expected, git_set.repositories
- end
-
- def test_parse_GIT_ref
- write_lockfile <<-LOCKFILE
-GIT
- remote: git://example/a.git
- revision: 1234abc
- ref: 1234abc
- specs:
- a (2)
- b (>= 3)
-
-DEPENDENCIES
- a!
- LOCKFILE
-
- parse_lockfile @set, []
-
- assert_equal [dep("a", "= 2")], @set.dependencies
-
- lockfile_set = @set.sets.find do |set|
- Gem::Resolver::LockSet === set
- end
-
- refute lockfile_set, "found a LockSet"
-
- git_set = @set.sets.find do |set|
- Gem::Resolver::GitSet === set
- end
-
- assert git_set, "could not find a GitSet"
-
- expected = {
- "a" => %w[git://example/a.git 1234abc],
- }
-
- assert_equal expected, git_set.repositories
- end
-
- def test_parse_GIT_tag
- write_lockfile <<-LOCKFILE
-GIT
- remote: git://example/a.git
- revision: 1234abc
- tag: v0.9.12
- specs:
- a (2)
- b (>= 3)
-
-DEPENDENCIES
- a!
- LOCKFILE
-
- parse_lockfile @set, []
-
- assert_equal [dep("a", "= 2")], @set.dependencies
-
- lockfile_set = @set.sets.find do |set|
- Gem::Resolver::LockSet === set
- end
-
- refute lockfile_set, "found a LockSet"
-
- git_set = @set.sets.find do |set|
- Gem::Resolver::GitSet === set
- end
-
- assert git_set, "could not find a GitSet"
-
- expected = {
- "a" => %w[git://example/a.git 1234abc],
- }
-
- assert_equal expected, git_set.repositories
- end
-
- def test_parse_PATH
- _, _, directory = vendor_gem
-
- write_lockfile <<-LOCKFILE
-PATH
- remote: #{directory}
- specs:
- a (1)
- b (2)
-
-DEPENDENCIES
- a!
- LOCKFILE
-
- parse_lockfile @set, []
-
- assert_equal [dep("a", "= 1")], @set.dependencies
-
- lockfile_set = @set.sets.find do |set|
- Gem::Resolver::LockSet === set
- end
-
- refute lockfile_set, "found a LockSet"
-
- vendor_set = @set.sets.find do |set|
- Gem::Resolver::VendorSet === set
- end
-
- assert vendor_set, "could not find a VendorSet"
-
- assert_equal %w[a-1], vendor_set.specs.values.map(&:full_name)
-
- spec = vendor_set.load_spec "a", nil, nil, nil
-
- assert_equal [dep("b", "= 2")], spec.dependencies
- end
-
- def test_parse_dependency
- write_lockfile " 1)"
-
- tokenizer = Gem::RequestSet::Lockfile::Tokenizer.from_file @lock_file
- parser = tokenizer.make_parser nil, nil
-
- parsed = parser.parse_dependency "a", "="
-
- assert_equal dep("a", "= 1"), parsed
-
- write_lockfile ")"
-
- tokenizer = Gem::RequestSet::Lockfile::Tokenizer.from_file @lock_file
- parser = tokenizer.make_parser nil, nil
-
- parsed = parser.parse_dependency "a", "2"
-
- assert_equal dep("a", "= 2"), parsed
- end
-
- def test_parse_gem_specs_dependency
- write_lockfile <<-LOCKFILE
-GEM
- remote: #{@gem_repo}
- specs:
- a (2)
- b (= 3)
- c (~> 4)
- d
- e (~> 5.0, >= 5.0.1)
- b (3-x86_64-linux)
-
-PLATFORMS
- #{Gem::Platform::RUBY}
-
-DEPENDENCIES
- a
- LOCKFILE
-
- platforms = []
- parse_lockfile @set, platforms
-
- assert_equal [dep("a")], @set.dependencies
-
- assert_equal [Gem::Platform::RUBY], platforms
-
- lockfile_set = @set.sets.find do |set|
- Gem::Resolver::LockSet === set
- end
-
- assert lockfile_set, "could not find a LockSet"
-
- assert_equal %w[a-2 b-3], lockfile_set.specs.map(&:full_name)
-
- expected = [
- Gem::Platform::RUBY,
- Gem::Platform.new("x86_64-linux"),
- ]
-
- assert_equal expected, lockfile_set.specs.map(&:platform)
-
- spec = lockfile_set.specs.first
-
- expected = [
- dep("b", "= 3"),
- dep("c", "~> 4"),
- dep("d"),
- dep("e", "~> 5.0", ">= 5.0.1"),
- ]
-
- assert_equal expected, spec.dependencies
- end
-
- def test_parse_missing
- assert_raise(Errno::ENOENT) do
- parse_lockfile @set, []
- end
-
- lockfile_set = @set.sets.find do |set|
- Gem::Resolver::LockSet === set
- end
-
- refute lockfile_set
- end
-
- def write_lockfile(lockfile)
- File.open @lock_file, "w" do |io|
- io.write lockfile
- end
- end
-
- def parse_lockfile(set, platforms)
- tokenizer = Gem::RequestSet::Lockfile::Tokenizer.from_file @lock_file
- parser = tokenizer.make_parser set, platforms
- parser.parse
- end
-end
diff --git a/test/rubygems/test_gem_request_set_lockfile_tokenizer.rb b/test/rubygems/test_gem_request_set_lockfile_tokenizer.rb
deleted file mode 100644
index dce8c9ada5..0000000000
--- a/test/rubygems/test_gem_request_set_lockfile_tokenizer.rb
+++ /dev/null
@@ -1,307 +0,0 @@
-# frozen_string_literal: true
-
-require_relative "helper"
-require "rubygems/request_set"
-require "rubygems/request_set/lockfile"
-require "rubygems/request_set/lockfile/tokenizer"
-require "rubygems/request_set/lockfile/parser"
-
-class TestGemRequestSetLockfileTokenizer < Gem::TestCase
- def setup
- super
-
- @gem_deps_file = "gem.deps.rb"
- @lock_file = File.expand_path "#{@gem_deps_file}.lock"
- end
-
- def test_peek
- tokenizer = Gem::RequestSet::Lockfile::Tokenizer.new "\n"
-
- assert_equal :newline, tokenizer.peek.first
-
- assert_equal :newline, tokenizer.next_token.first
-
- assert_equal :EOF, tokenizer.peek.first
- end
-
- def test_skip
- tokenizer = Gem::RequestSet::Lockfile::Tokenizer.new "\n"
-
- refute_predicate tokenizer, :empty?
-
- tokenizer.skip :newline
-
- assert_empty tokenizer
- end
-
- def test_token_pos
- tokenizer = Gem::RequestSet::Lockfile::Tokenizer.new ""
- assert_equal [5, 0], tokenizer.token_pos(5)
-
- tokenizer = Gem::RequestSet::Lockfile::Tokenizer.new "", nil, 1, 2
- assert_equal [3, 1], tokenizer.token_pos(5)
- end
-
- def test_tokenize
- write_lockfile <<-LOCKFILE
-GEM
- remote: #{@gem_repo}
- specs:
- a (2)
- b (= 2)
- c (!= 3)
- d (> 4)
- e (< 5)
- f (>= 6)
- g (<= 7)
- h (~> 8)
-
-PLATFORMS
- #{Gem::Platform::RUBY}
-
-DEPENDENCIES
- a
- LOCKFILE
-
- expected = [
- [:section, "GEM", 0, 0],
- [:newline, nil, 3, 0],
-
- [:entry, "remote", 2, 1],
- [:text, @gem_repo, 10, 1],
- [:newline, nil, 34, 1],
-
- [:entry, "specs", 2, 2],
- [:newline, nil, 8, 2],
-
- [:text, "a", 4, 3],
- [:l_paren, nil, 6, 3],
- [:text, "2", 7, 3],
- [:r_paren, nil, 8, 3],
- [:newline, nil, 9, 3],
-
- [:text, "b", 6, 4],
- [:l_paren, nil, 8, 4],
- [:requirement, "=", 9, 4],
- [:text, "2", 11, 4],
- [:r_paren, nil, 12, 4],
- [:newline, nil, 13, 4],
-
- [:text, "c", 6, 5],
- [:l_paren, nil, 8, 5],
- [:requirement, "!=", 9, 5],
- [:text, "3", 12, 5],
- [:r_paren, nil, 13, 5],
- [:newline, nil, 14, 5],
-
- [:text, "d", 6, 6],
- [:l_paren, nil, 8, 6],
- [:requirement, ">", 9, 6],
- [:text, "4", 11, 6],
- [:r_paren, nil, 12, 6],
- [:newline, nil, 13, 6],
-
- [:text, "e", 6, 7],
- [:l_paren, nil, 8, 7],
- [:requirement, "<", 9, 7],
- [:text, "5", 11, 7],
- [:r_paren, nil, 12, 7],
- [:newline, nil, 13, 7],
-
- [:text, "f", 6, 8],
- [:l_paren, nil, 8, 8],
- [:requirement, ">=", 9, 8],
- [:text, "6", 12, 8],
- [:r_paren, nil, 13, 8],
- [:newline, nil, 14, 8],
-
- [:text, "g", 6, 9],
- [:l_paren, nil, 8, 9],
- [:requirement, "<=", 9, 9],
- [:text, "7", 12, 9],
- [:r_paren, nil, 13, 9],
- [:newline, nil, 14, 9],
-
- [:text, "h", 6, 10],
- [:l_paren, nil, 8, 10],
- [:requirement, "~>", 9, 10],
- [:text, "8", 12, 10],
- [:r_paren, nil, 13, 10],
- [:newline, nil, 14, 10],
-
- [:newline, nil, 0, 11],
-
- [:section, "PLATFORMS", 0, 12],
- [:newline, nil, 9, 12],
-
- [:text, Gem::Platform::RUBY, 2, 13],
- [:newline, nil, 6, 13],
-
- [:newline, nil, 0, 14],
-
- [:section, "DEPENDENCIES", 0, 15],
- [:newline, nil, 12, 15],
-
- [:text, "a", 2, 16],
- [:newline, nil, 3, 16],
- ]
-
- assert_equal expected, tokenize_lockfile
- end
-
- def test_tokenize_capitals
- write_lockfile <<-LOCKFILE
-GEM
- remote: #{@gem_repo}
- specs:
- Ab (2)
-
-PLATFORMS
- #{Gem::Platform::RUBY}
-
-DEPENDENCIES
- Ab
- LOCKFILE
-
- expected = [
- [:section, "GEM", 0, 0],
- [:newline, nil, 3, 0],
- [:entry, "remote", 2, 1],
- [:text, @gem_repo, 10, 1],
- [:newline, nil, 34, 1],
- [:entry, "specs", 2, 2],
- [:newline, nil, 8, 2],
- [:text, "Ab", 4, 3],
- [:l_paren, nil, 7, 3],
- [:text, "2", 8, 3],
- [:r_paren, nil, 9, 3],
- [:newline, nil, 10, 3],
- [:newline, nil, 0, 4],
- [:section, "PLATFORMS", 0, 5],
- [:newline, nil, 9, 5],
- [:text, Gem::Platform::RUBY, 2, 6],
- [:newline, nil, 6, 6],
- [:newline, nil, 0, 7],
- [:section, "DEPENDENCIES", 0, 8],
- [:newline, nil, 12, 8],
- [:text, "Ab", 2, 9],
- [:newline, nil, 4, 9],
- ]
-
- assert_equal expected, tokenize_lockfile
- end
-
- def test_tokenize_conflict_markers
- write_lockfile "<<<<<<<"
-
- e = assert_raise Gem::RequestSet::Lockfile::ParseError do
- tokenize_lockfile
- end
-
- assert_equal "your #{@lock_file} contains merge conflict markers (at line 0 column 0)",
- e.message
-
- write_lockfile "|||||||"
-
- e = assert_raise Gem::RequestSet::Lockfile::ParseError do
- tokenize_lockfile
- end
-
- assert_equal "your #{@lock_file} contains merge conflict markers (at line 0 column 0)",
- e.message
-
- write_lockfile "======="
-
- e = assert_raise Gem::RequestSet::Lockfile::ParseError do
- tokenize_lockfile
- end
-
- assert_equal "your #{@lock_file} contains merge conflict markers (at line 0 column 0)",
- e.message
-
- write_lockfile ">>>>>>>"
-
- e = assert_raise Gem::RequestSet::Lockfile::ParseError do
- tokenize_lockfile
- end
-
- assert_equal "your #{@lock_file} contains merge conflict markers (at line 0 column 0)",
- e.message
- end
-
- def test_tokenize_git
- write_lockfile <<-LOCKFILE
-DEPENDENCIES
- a!
- LOCKFILE
-
- expected = [
- [:section, "DEPENDENCIES", 0, 0],
- [:newline, nil, 12, 0],
-
- [:text, "a", 2, 1],
- [:bang, nil, 3, 1],
- [:newline, nil, 4, 1],
- ]
-
- assert_equal expected, tokenize_lockfile
- end
-
- def test_tokenize_multiple
- write_lockfile <<-LOCKFILE
-GEM
- remote: #{@gem_repo}
- specs:
- a (2)
- b (~> 3.0, >= 3.0.1)
- LOCKFILE
-
- expected = [
- [:section, "GEM", 0, 0],
- [:newline, nil, 3, 0],
-
- [:entry, "remote", 2, 1],
- [:text, @gem_repo, 10, 1],
- [:newline, nil, 34, 1],
-
- [:entry, "specs", 2, 2],
- [:newline, nil, 8, 2],
-
- [:text, "a", 4, 3],
- [:l_paren, nil, 6, 3],
- [:text, "2", 7, 3],
- [:r_paren, nil, 8, 3],
- [:newline, nil, 9, 3],
-
- [:text, "b", 6, 4],
- [:l_paren, nil, 8, 4],
- [:requirement, "~>", 9, 4],
- [:text, "3.0", 12, 4],
- [:comma, nil, 15, 4],
- [:requirement, ">=", 17, 4],
- [:text, "3.0.1", 20, 4],
- [:r_paren, nil, 25, 4],
- [:newline, nil, 26, 4],
- ]
-
- assert_equal expected, tokenize_lockfile
- end
-
- def test_unget
- tokenizer = Gem::RequestSet::Lockfile::Tokenizer.new "\n"
- tokenizer.unshift :token
- parser = tokenizer.make_parser nil, nil
-
- assert_equal :token, parser.get
- end
-
- def write_lockfile(lockfile)
- File.open @lock_file, "w" do |io|
- io.write lockfile
- end
- end
-
- def tokenize_lockfile
- Gem::RequestSet::Lockfile::Tokenizer.from_file(@lock_file).to_a
- end
-end
diff --git a/test/rubygems/test_gem_requirement.rb b/test/rubygems/test_gem_requirement.rb
index de0d11ec00..00634dc7f4 100644
--- a/test/rubygems/test_gem_requirement.rb
+++ b/test/rubygems/test_gem_requirement.rb
@@ -137,11 +137,7 @@ class TestGemRequirement < Gem::TestCase
refute_satisfied_by "1.2", r
assert_satisfied_by "1.3", r
- assert_raise ArgumentError do
- Gem::Deprecate.skip_during do
- assert_satisfied_by nil, r
- end
- end
+ assert_satisfied_by nil, r
end
def test_satisfied_by_eh_blank
@@ -151,11 +147,7 @@ class TestGemRequirement < Gem::TestCase
assert_satisfied_by "1.2", r
refute_satisfied_by "1.3", r
- assert_raise ArgumentError do
- Gem::Deprecate.skip_during do
- assert_satisfied_by nil, r
- end
- end
+ refute_satisfied_by nil, r
end
def test_satisfied_by_eh_equal
@@ -165,11 +157,7 @@ class TestGemRequirement < Gem::TestCase
assert_satisfied_by "1.2", r
refute_satisfied_by "1.3", r
- assert_raise ArgumentError do
- Gem::Deprecate.skip_during do
- assert_satisfied_by nil, r
- end
- end
+ refute_satisfied_by nil, r
end
def test_satisfied_by_eh_gt
@@ -179,9 +167,7 @@ class TestGemRequirement < Gem::TestCase
refute_satisfied_by "1.2", r
assert_satisfied_by "1.3", r
- assert_raise ArgumentError do
- r.satisfied_by? nil
- end
+ refute_satisfied_by nil, r
end
def test_satisfied_by_eh_gte
diff --git a/test/rubygems/test_gem_resolver.rb b/test/rubygems/test_gem_resolver.rb
index 4990d5d2dd..84ede36b6c 100644
--- a/test/rubygems/test_gem_resolver.rb
+++ b/test/rubygems/test_gem_resolver.rb
@@ -86,63 +86,6 @@ class TestGemResolver < Gem::TestCase
assert_same index_set, composed
end
- def test_requests
- a1 = util_spec "a", 1, "b" => 2
-
- r1 = Gem::Resolver::DependencyRequest.new dep("a", "= 1"), nil
-
- act = Gem::Resolver::ActivationRequest.new a1, r1
-
- res = Gem::Resolver.new [a1]
-
- reqs = []
-
- res.requests a1, act, reqs
-
- assert_equal ["b (= 2)"], reqs.map(&:to_s)
- end
-
- def test_requests_development
- a1 = util_spec "a", 1, "b" => 2
-
- spec = Gem::Resolver::SpecSpecification.new nil, a1
- def spec.fetch_development_dependencies
- @called = true
- end
-
- r1 = Gem::Resolver::DependencyRequest.new dep("a", "= 1"), nil
-
- act = Gem::Resolver::ActivationRequest.new spec, r1
-
- res = Gem::Resolver.new [act]
- res.development = true
-
- reqs = []
-
- res.requests spec, act, reqs
-
- assert_equal ["b (= 2)"], reqs.map(&:to_s)
-
- assert spec.instance_variable_defined? :@called
- end
-
- def test_requests_ignore_dependencies
- a1 = util_spec "a", 1, "b" => 2
-
- r1 = Gem::Resolver::DependencyRequest.new dep("a", "= 1"), nil
-
- act = Gem::Resolver::ActivationRequest.new a1, r1
-
- res = Gem::Resolver.new [a1]
- res.ignore_dependencies = true
-
- reqs = []
-
- res.requests a1, act, reqs
-
- assert_empty reqs
- end
-
def test_resolve_conservative
a1_spec = util_spec "a", 1
@@ -197,6 +140,34 @@ class TestGemResolver < Gem::TestCase
assert_resolves_to [a2_spec, b2_spec, c1_spec, d2_spec, e1_spec], res
end
+ def test_conservative_upgrades_when_installed_blocked
+ # Conservative mode floats the installed (skip) version to the front but
+ # keeps newer versions selectable. When the installed version cannot be
+ # used because its own dependency is unsatisfiable, the solver backtracks
+ # to a newer version instead of failing. This intentionally diverges from
+ # Molinillo (which hard-restricted to skip versions and raised) and reaches
+ # Bundler's upgrade-over-raise outcome. See the comment in
+ # Gem::Resolver#all_versions_for.
+ a1_spec = util_spec "a", 1 do |s|
+ s.add_dependency "b", ">= 2"
+ end
+ a2_spec = util_spec "a", 2 do |s|
+ s.add_dependency "b", ">= 1"
+ end
+ b1_spec = util_spec "b", 1
+
+ # b-2 is intentionally absent, so a-1's `b >= 2` cannot be satisfied.
+ deps = [make_dep("a", ">= 1")]
+ s = set a1_spec, a2_spec, b1_spec
+
+ res = Gem::Resolver.new deps, s
+ # a-1 is already installed and satisfies `a >= 1`, so conservative mode
+ # prefers it - but it is blocked by the missing b-2, forcing an upgrade.
+ res.skip_gems = { "a" => [a1_spec] }
+
+ assert_resolves_to [a2_spec, b1_spec], res
+ end
+
def test_resolve_development
a_spec = util_spec "a", 1 do |s|
s.add_development_dependency "b"
@@ -511,19 +482,10 @@ class TestGemResolver < Gem::TestCase
r.resolve
end
- deps = [make_dep("c", "= 2"), make_dep("c", "= 1")]
- assert_equal deps, e.conflicting_dependencies
-
- con = e.conflict
-
- act = con.activated
- assert_equal "c-1", act.spec.full_name
-
- parent = act.parent
- assert_equal "a-1", parent.spec.full_name
-
- act = con.requester
- assert_equal "b-1", act.spec.full_name
+ assert_nil e.conflict
+ assert_match(/your request/, e.message)
+ assert_match(/a depends on c/, e.message)
+ assert_match(/b depends on c/, e.message)
end
def test_raises_when_a_gem_is_missing
@@ -578,12 +540,11 @@ class TestGemResolver < Gem::TestCase
r = Gem::Resolver.new([ad], set(a1))
- e = assert_raise Gem::UnsatisfiableDependencyError do
+ e = assert_raise Gem::DependencyResolutionError do
r.resolve
end
- assert_equal "Unable to resolve dependency: 'a (= 1)' requires 'b (= 2)'",
- e.message
+ assert_match(/depends on b = 2 which could not be found in any repository/, e.message)
end
def test_raises_when_possibles_are_exhausted
@@ -605,18 +566,9 @@ class TestGemResolver < Gem::TestCase
r.resolve
end
- dependency = e.conflict.dependency
-
- assert_includes %w[a b], dependency.name
- assert_equal req(">= 0"), dependency.requirement
-
- activated = e.conflict.activated
- assert_equal "c-1", activated.full_name
-
- assert_equal dep("c", "= 1"), activated.request.dependency
-
- assert_equal [dep("c", ">= 2"), dep("c", "= 1")],
- e.conflict.conflicting_dependencies
+ assert_nil e.conflict
+ assert_match(/a depends on c/, e.message)
+ assert_match(/b depends on c/, e.message)
end
def test_keeps_resolving_after_seeing_satisfied_dep
@@ -772,7 +724,7 @@ class TestGemResolver < Gem::TestCase
assert_resolves_to [b1, c1, d2], r
end
- def test_sorts_by_source_then_version
+ def test_picks_highest_version_across_sources
source_a = Gem::Source.new "http://example.com/a"
source_b = Gem::Source.new "http://example.com/b"
source_c = Gem::Source.new "http://example.com/c"
@@ -795,7 +747,43 @@ class TestGemResolver < Gem::TestCase
resolver = Gem::Resolver.new [dependency], set
- assert_resolves_to [spec_b_2], resolver
+ assert_resolves_to [spec_a_2], resolver
+ end
+
+ def test_same_version_prefers_earlier_source
+ source_a = Gem::Source.new "http://example.com/a"
+ source_b = Gem::Source.new "http://example.com/b"
+
+ spec_a = util_spec "some-dep", "1.0.0"
+ spec_b = util_spec "some-dep", "1.0.0"
+
+ set = StaticSet.new [
+ Gem::Resolver::SpecSpecification.new(nil, spec_a, source_a),
+ Gem::Resolver::SpecSpecification.new(nil, spec_b, source_b),
+ ]
+
+ resolver = Gem::Resolver.new [make_dep("some-dep", "> 0")], set
+ result = resolver.resolve
+
+ assert_equal source_a, result.first.spec.source
+ end
+
+ def test_same_version_prefers_earlier_source_when_order_flipped
+ source_a = Gem::Source.new "http://example.com/a"
+ source_b = Gem::Source.new "http://example.com/b"
+
+ spec_a = util_spec "some-dep", "1.0.0"
+ spec_b = util_spec "some-dep", "1.0.0"
+
+ set = StaticSet.new [
+ Gem::Resolver::SpecSpecification.new(nil, spec_b, source_b),
+ Gem::Resolver::SpecSpecification.new(nil, spec_a, source_a),
+ ]
+
+ resolver = Gem::Resolver.new [make_dep("some-dep", "> 0")], set
+ result = resolver.resolve
+
+ assert_equal source_b, result.first.spec.source
end
def test_select_local_platforms
@@ -850,4 +838,338 @@ class TestGemResolver < Gem::TestCase
assert_match "No match for 'a (= 1)' on this platform. Found: c-p-1",
e.message
end
+
+ def test_resolve_prerelease_not_considered_when_stable_exists
+ # a-1.0 depends on b ~> 2.0 - only b-2.0.pre satisfies that, but
+ # b also has a stable version (1.0), so prereleases are filtered out.
+ # The resolver must fail, not silently use b-2.0.pre during propagation.
+ a_stable = util_spec "a", "1.0" do |s|
+ s.add_dependency "b", "~> 2.0"
+ end
+
+ b_stable = util_spec "b", "1.0"
+ b_pre = util_spec "b", "2.0.pre"
+
+ s = set(a_stable, b_stable, b_pre)
+
+ ad = make_dep "a"
+ r = Gem::Resolver.new([ad], s)
+
+ assert_raise Gem::DependencyResolutionError do
+ r.resolve
+ end
+ end
+
+ def test_resolve_prerelease_considered_when_enabled
+ a_stable = util_spec "a", "1.0" do |s|
+ s.add_dependency "b", ">= 1.0"
+ end
+
+ b_pre = util_spec "b", "2.0.pre"
+
+ s = set(a_stable, b_pre)
+ s.prerelease = true
+
+ ad = make_dep "a"
+ r = Gem::Resolver.new([ad], s)
+
+ assert_resolves_to [a_stable, b_pre], r
+ end
+
+ def test_resolve_prerelease_used_when_no_stable_versions_exist
+ a_stable = util_spec "a", "1.0" do |s|
+ s.add_dependency "b", ">= 1.0"
+ end
+
+ b_pre = util_spec "b", "2.0.pre"
+ b_other_pre = util_spec "b", "1.0.pre"
+
+ s = set(a_stable, b_pre, b_other_pre)
+
+ ad = make_dep "a"
+ r = Gem::Resolver.new([ad], s)
+
+ assert_resolves_to [a_stable, b_pre], r
+ end
+
+ def test_resolve_prerelease_required_by_exact_requirement
+ # A root dep with an exact prerelease version must resolve to that
+ # version even when stable versions of the same gem are in the set.
+ # Gem.finish_resolve hits this: it imports loaded_specs as exact-version
+ # deps, so the currently-activated prerelease bundler becomes a root dep.
+ a_stable = util_spec "a", "1.0"
+ a_pre = util_spec "a", "2.0.pre"
+
+ s = set(a_stable, a_pre)
+
+ ad = make_dep "a", "= 2.0.pre"
+ r = Gem::Resolver.new([ad], s)
+
+ assert_resolves_to [a_pre], r
+ end
+
+ def test_resolve_transitive_prerelease_required_by_exact_requirement
+ # A transitive dep with an exact prerelease version must resolve to that
+ # version even when stable versions of the same gem are in the set.
+ # The gate on prereleases lives in versions_for and is per-constraint:
+ # `= 2.0.pre` carries a prerelease bound, so prereleases are admitted for
+ # this range even though the global prerelease flag is off.
+ a = util_spec "a", "1.0" do |s|
+ s.add_dependency "b", "= 2.0.pre"
+ end
+
+ b_stable = util_spec "b", "1.0"
+ b_pre = util_spec "b", "2.0.pre"
+
+ s = set(a, b_stable, b_pre)
+
+ ad = make_dep "a"
+ r = Gem::Resolver.new([ad], s)
+
+ assert_resolves_to [a, b_pre], r
+ end
+
+ def test_error_includes_platform_hint_when_specs_exist_for_other_platforms
+ a = util_spec "a", "1.0" do |s|
+ s.add_dependency "b", ">= 1.0"
+ end
+
+ b_foreign = util_spec "b", "1.0" do |s|
+ s.platform = "java"
+ end
+
+ s = set(a, b_foreign)
+
+ ad = make_dep "a"
+ r = Gem::Resolver.new([ad], s)
+
+ e = assert_raise Gem::DependencyResolutionError do
+ r.resolve
+ end
+
+ assert_match(/could not be found in any repository/, e.message)
+ assert_match(/b-1.0-java/, e.message)
+ end
+
+ def test_error_includes_ruby_version_hint_when_filtered
+ a = util_spec "a", "1.0" do |s|
+ s.add_dependency "b", ">= 1.0"
+ end
+
+ b = util_spec "b", "1.0" do |s|
+ s.required_ruby_version = ">= 999.0"
+ end
+
+ s = set(a, b)
+
+ ad = make_dep "a"
+ r = Gem::Resolver.new([ad], s)
+
+ e = assert_raise Gem::DependencyResolutionError do
+ r.resolve
+ end
+
+ assert_match(/requires Ruby/, e.message)
+ assert_match(/you have/, e.message)
+ end
+
+ def test_root_gem_incompatible_ruby_version_names_ruby_requirement
+ # A requested (root) gem available only for an incompatible Ruby version
+ # flows through the solver to a DependencyResolutionError whose message
+ # names the Ruby requirement. This matches Bundler (which models Ruby as a
+ # synthetic dependency and reports a solve failure) and is clearer than the
+ # platform-oriented UnsatisfiableDependencyError. Contrast the foreign-
+ # *platform* case (test_raises_and_explains_when_platform_prevents_install),
+ # which is genuinely "not found" and does raise UnsatisfiableDependencyError.
+ a = util_spec "a", "1.0" do |s|
+ s.required_ruby_version = ">= 999.0"
+ end
+
+ ad = make_dep "a", "= 1.0"
+ r = Gem::Resolver.new([ad], set(a))
+
+ e = assert_raise Gem::DependencyResolutionError do
+ r.resolve
+ end
+
+ assert_match(/requires Ruby >= 999.0/, e.message)
+ end
+
+ def test_self_dependency_does_not_crash
+ a = util_spec "a", "1.0" do |s|
+ s.add_dependency "a"
+ end
+
+ s = set(a)
+ ad = make_dep "a"
+ r = Gem::Resolver.new([ad], s)
+
+ assert_resolves_to [a], r
+ end
+
+ def test_contradictory_root_requirements_give_clear_error
+ a1 = util_spec "a", "1"
+ a2 = util_spec "a", "2"
+
+ s = set(a1, a2)
+ r = Gem::Resolver.new([make_dep("a", "= 1"), make_dep("a", "= 2")], s)
+
+ e = assert_raise Gem::DependencyResolutionError do
+ r.resolve
+ end
+
+ assert_match(/contradictory/, e.message)
+ refute_match(/unknown package/, e.message)
+ end
+
+ def test_empty_range_transitive_dep_does_not_say_unknown
+ a = util_spec "a", "1.0" do |s|
+ s.add_dependency "b", "> 2", "< 1"
+ end
+
+ b = util_spec "b", "1.5"
+
+ s = set(a, b)
+ ad = make_dep "a"
+ r = Gem::Resolver.new([ad], s)
+
+ e = assert_raise Gem::DependencyResolutionError do
+ r.resolve
+ end
+
+ assert_match(/contradictory/, e.message)
+ refute_match(/unknown package/, e.message)
+ end
+
+ def test_error_hints_about_prerelease_when_filtered
+ a = util_spec "a", "1.0" do |s|
+ s.add_dependency "b", "~> 2.0"
+ end
+
+ b_stable = util_spec "b", "1.0"
+ b_pre = util_spec "b", "2.0.pre"
+
+ s = set(a, b_stable, b_pre)
+ ad = make_dep "a"
+ r = Gem::Resolver.new([ad], s)
+
+ e = assert_raise Gem::DependencyResolutionError do
+ r.resolve
+ end
+
+ assert_match(/pre-release/, e.message)
+ assert_match(/--prerelease/, e.message)
+ end
+
+ def test_soft_missing_skips_dep_with_wrong_version
+ a = util_spec "a", "1.0" do |s|
+ s.add_dependency "b", ">= 2.0"
+ end
+
+ b = util_spec "b", "1.0"
+
+ s = set(a, b)
+ ad = make_dep "a"
+ r = Gem::Resolver.new([ad], s)
+ r.soft_missing = true
+
+ # b exists but only 1.0, which doesn't satisfy >= 2.0.
+ # With soft_missing (--force), the dep should be skipped.
+ assert_resolves_to [a], r
+ end
+
+ def test_backtracks_to_clean_sibling_when_higher_version_has_missing_dep
+ a1 = util_spec "a", "1"
+ a2 = util_spec "a", "2" do |s|
+ s.add_dependency "zzz", ">= 1"
+ end
+
+ r = Gem::Resolver.new([make_dep("a")], set(a1, a2))
+
+ # 'zzz' has zero specs anywhere, so a-2 is unusable, but a-1 is clean
+ # and resolution must backtrack to it rather than declaring every
+ # version of 'a' invalid.
+ assert_resolves_to [a1], r
+ end
+
+ def test_backtracks_over_band_of_bad_high_versions_to_clean_lower
+ a1 = util_spec "a", "1"
+ a2 = util_spec "a", "2" do |s|
+ s.add_dependency "zzz", ">= 1"
+ end
+ a3 = util_spec "a", "3" do |s|
+ s.add_dependency "zzz", ">= 1"
+ end
+
+ r = Gem::Resolver.new([make_dep("a")], set(a1, a2, a3))
+
+ # Only the a-2..a-3 band shares the missing 'zzz' dep and should be
+ # eliminated; band scoping is load-bearing here, not just sibling
+ # presence.
+ assert_resolves_to [a1], r
+ end
+
+ def test_backtracks_when_one_of_several_deps_is_missing
+ good = util_spec "good", "1"
+ a1 = util_spec "a", "1" do |s|
+ s.add_dependency "good", ">= 1"
+ end
+ a2 = util_spec "a", "2" do |s|
+ s.add_dependency "good", ">= 1"
+ s.add_dependency "zzz", ">= 1"
+ end
+
+ r = Gem::Resolver.new([make_dep("a")], set(a1, a2, good))
+
+ # Only a-2, which carries the missing 'zzz' dep, is eliminated; the
+ # per-dep check inside a multi-dep version must not poison a-1.
+ assert_resolves_to [a1, good], r
+ end
+
+ def test_fails_when_every_version_depends_on_missing_package
+ a1 = util_spec "a", "1" do |s|
+ s.add_dependency "zzz", ">= 1"
+ end
+ a2 = util_spec "a", "2" do |s|
+ s.add_dependency "zzz", ">= 1"
+ end
+
+ r = Gem::Resolver.new([make_dep("a")], set(a1, a2))
+
+ e = assert_raise Gem::DependencyResolutionError do
+ r.resolve
+ end
+
+ assert_match(/every version of a depends on zzz >= 1 which could not be found in any repository/, e.message)
+ end
+
+ def test_resolves_when_only_lowest_version_has_missing_dep
+ a1 = util_spec "a", "1" do |s|
+ s.add_dependency "zzz", ">= 1"
+ end
+ a2 = util_spec "a", "2"
+
+ r = Gem::Resolver.new([make_dep("a")], set(a1, a2))
+
+ # a-2 is preferred/tried first, so this is already green; it guards
+ # against the bug being re-introduced in an order-sensitive way.
+ assert_resolves_to [a2], r
+ end
+
+ def test_filtered_platform_dep_lets_clean_sibling_backtrack
+ a1 = util_spec "a", "1"
+ a2 = util_spec "a", "2" do |s|
+ s.add_dependency "b", ">= 1.0"
+ end
+ b_java = util_spec "b", "1.0" do |s|
+ s.platform = "java"
+ end
+
+ r = Gem::Resolver.new([make_dep("a")], set(a1, a2, b_java))
+
+ # 'b' EXISTS in the unfiltered specs but is platform-filtered, so a-2
+ # is unusable via NoVersions (not InvalidDependency). Resolution must
+ # backtrack to the clean a-1 rather than eliminating it.
+ assert_resolves_to [a1], r
+ end
end
diff --git a/test/rubygems/test_gem_resolver_best_set.rb b/test/rubygems/test_gem_resolver_best_set.rb
index 02f542efc0..ac186884d1 100644
--- a/test/rubygems/test_gem_resolver_best_set.rb
+++ b/test/rubygems/test_gem_resolver_best_set.rb
@@ -31,6 +31,20 @@ class TestGemResolverBestSet < Gem::TestCase
assert_equal %w[a-1], found.map(&:full_name)
end
+ def test_pick_sets_prerelease
+ set = Gem::Resolver::BestSet.new
+ set.prerelease = true
+
+ set.pick_sets
+
+ sets = set.sets
+
+ assert_equal 1, sets.count
+
+ source_set = sets.first
+ assert_equal true, source_set.prerelease
+ end
+
def test_find_all_local
spec_fetcher do |fetcher|
fetcher.spec "a", 1
diff --git a/test/rubygems/test_gem_resolver_conflict.rb b/test/rubygems/test_gem_resolver_conflict.rb
deleted file mode 100644
index 5696ff266d..0000000000
--- a/test/rubygems/test_gem_resolver_conflict.rb
+++ /dev/null
@@ -1,80 +0,0 @@
-# frozen_string_literal: true
-
-require_relative "helper"
-
-class TestGemResolverConflict < Gem::TestCase
- def test_explanation
- root =
- dependency_request dep("net-ssh", ">= 2.0.13"), "rye", "0.9.8"
- child =
- dependency_request dep("net-ssh", ">= 2.6.5"), "net-ssh", "2.2.2", root
-
- dep = Gem::Resolver::DependencyRequest.new dep("net-ssh", ">= 2.0.13"), nil
-
- spec = util_spec "net-ssh", "2.2.2"
- active =
- Gem::Resolver::ActivationRequest.new spec, dep
-
- conflict =
- Gem::Resolver::Conflict.new child, active
-
- expected = <<-EXPECTED
- Activated net-ssh-2.2.2
- which does not match conflicting dependency (>= 2.6.5)
-
- Conflicting dependency chains:
- net-ssh (>= 2.0.13), 2.2.2 activated
-
- versus:
- rye (= 0.9.8), 0.9.8 activated, depends on
- net-ssh (>= 2.0.13), 2.2.2 activated, depends on
- net-ssh (>= 2.6.5)
-
- EXPECTED
-
- assert_equal expected, conflict.explanation
- end
-
- def test_explanation_user_request
- spec = util_spec "a", 2
-
- a1_req = Gem::Resolver::DependencyRequest.new dep("a", "= 1"), nil
- a2_req = Gem::Resolver::DependencyRequest.new dep("a", "= 2"), nil
-
- activated = Gem::Resolver::ActivationRequest.new spec, a2_req
-
- conflict = Gem::Resolver::Conflict.new a1_req, activated
-
- expected = <<-EXPECTED
- Activated a-2
- which does not match conflicting dependency (= 1)
-
- Conflicting dependency chains:
- a (= 2), 2 activated
-
- versus:
- a (= 1)
-
- EXPECTED
-
- assert_equal expected, conflict.explanation
- end
-
- def test_request_path
- root =
- dependency_request dep("net-ssh", ">= 2.0.13"), "rye", "0.9.8"
-
- child =
- dependency_request dep("other", ">= 1.0"), "net-ssh", "2.2.2", root
-
- conflict =
- Gem::Resolver::Conflict.new nil, nil
-
- expected = [
- "net-ssh (>= 2.0.13), 2.2.2 activated",
- "rye (= 0.9.8), 0.9.8 activated",
- ]
-
- assert_equal expected, conflict.request_path(child.requester)
- end
-end
diff --git a/test/rubygems/test_gem_resolver_git_specification.rb b/test/rubygems/test_gem_resolver_git_specification.rb
index 621333d3bf..e03c61e27d 100644
--- a/test/rubygems/test_gem_resolver_git_specification.rb
+++ b/test/rubygems/test_gem_resolver_git_specification.rb
@@ -97,6 +97,44 @@ class TestGemResolverGitSpecification < Gem::TestCase
assert_path_exist File.join git_spec.spec.extension_dir, "b.rb"
end
+ def test_install_no_build_extension
+ pend if Gem.java_platform?
+ pend "terminates on mswin" if vc_windows? && ruby_repo?
+ name, _, repository, = git_gem "a", 1 do |s|
+ s.extensions << "ext/extconf.rb"
+ end
+
+ Dir.chdir "git/a" do
+ FileUtils.mkdir_p "ext/lib"
+
+ File.open "ext/extconf.rb", "w" do |io|
+ io.puts 'require "mkmf"'
+ io.puts 'create_makefile "a"'
+ end
+
+ FileUtils.touch "ext/lib/b.rb"
+
+ system @git, "add", "ext/extconf.rb"
+ system @git, "add", "ext/lib/b.rb"
+
+ system @git, "commit", "--quiet", "-m", "Add extension files"
+ end
+
+ source = Gem::Source::Git.new name, repository, nil, true
+
+ spec = source.specs.first
+
+ git_spec = Gem::Resolver::GitSpecification.new @set, spec, source
+
+ use_ui @ui do
+ git_spec.install(build_extension: false)
+ end
+
+ assert_path_not_exist File.join(git_spec.spec.extension_dir, "b.rb")
+ assert_match "contains native extensions that were not built", @ui.error
+ assert_match "gem pristine #{git_spec.spec.name} --extensions", @ui.error
+ end
+
def test_install_installed
git_gem "a", 1
diff --git a/test/rubygems/test_gem_resolver_strategy.rb b/test/rubygems/test_gem_resolver_strategy.rb
new file mode 100644
index 0000000000..57c9aadde8
--- /dev/null
+++ b/test/rubygems/test_gem_resolver_strategy.rb
@@ -0,0 +1,163 @@
+# frozen_string_literal: true
+
+require_relative "helper"
+
+class TestGemResolverStrategy < Gem::TestCase
+ # Minimal source that implements the two methods Strategy calls:
+ # all_versions_for(package) - returns versions in preference order
+ # versions_for(package, range) - returns versions matching a range
+ #
+ # Tracks call counts so we can assert on caching behavior.
+ class StubSource
+ attr_reader :versions_for_calls
+
+ def initialize(versions_by_package)
+ @versions_by_package = versions_by_package
+ @versions_for_calls = 0
+ end
+
+ def all_versions_for(package)
+ @versions_by_package.fetch(package.to_s, [])
+ end
+
+ def versions_for(package, range)
+ @versions_for_calls += 1
+ all = @versions_by_package.fetch(package.to_s, [])
+ all.select {|v| range.include?(v) }
+ end
+ end
+
+ def v(version_string)
+ Gem::Version.new(version_string)
+ end
+
+ def make_package(name)
+ Gem::PubGrub::Package.new(name)
+ end
+
+ def make_range_any
+ Gem::PubGrub::VersionRange.any
+ end
+
+ # A range >= min (unbounded above)
+ def make_range_gte(version)
+ Gem::PubGrub::VersionRange.new(min: version, include_min: true)
+ end
+
+ # A range >= min AND < max
+ def make_range_between(min, max)
+ Gem::PubGrub::VersionRange.new(
+ min: min, max: max,
+ include_min: true, include_max: false
+ )
+ end
+
+ def test_most_preferred_version_respects_all_versions_for_ordering
+ # all_versions_for returns [2.0, 1.0, 3.0] - so 2.0 is most preferred
+ # even though 3.0 is numerically highest.
+ pkg = make_package("a")
+ source = StubSource.new("a" => [v("2.0"), v("1.0"), v("3.0")])
+
+ strategy = Gem::Resolver::Strategy.new(source)
+ unsatisfied = { pkg => make_range_any }
+
+ _package, version = strategy.next_package_and_version(unsatisfied)
+
+ assert_equal v("2.0"), version
+ end
+
+ def test_picks_most_constrained_package
+ # "a" has 3 matching versions, "b" has 1 matching version.
+ # Strategy should pick "b" because it's more constrained.
+ pkg_a = make_package("a")
+ pkg_b = make_package("b")
+
+ source = StubSource.new(
+ "a" => [v("3.0"), v("2.0"), v("1.0")],
+ "b" => [v("1.0")]
+ )
+
+ strategy = Gem::Resolver::Strategy.new(source)
+
+ unsatisfied = {
+ pkg_a => make_range_any,
+ pkg_b => make_range_any,
+ }
+
+ package, _version = strategy.next_package_and_version(unsatisfied)
+
+ assert_equal pkg_b, package
+ end
+
+ def test_picks_package_with_fewer_higher_versions_as_tiebreaker
+ # Both "a" and "b" have 2 matching versions (so both get priority [1, ...]).
+ # "a" has matching [2.0, 1.0] with higher (above range) = [] (0 higher)
+ # "b" has matching [2.0, 1.0] with higher [3.0] (1 higher)
+ # Tiebreaker: fewer higher versions wins, so "a" is picked.
+ pkg_a = make_package("a")
+ pkg_b = make_package("b")
+
+ range = make_range_between(v("0.5"), v("2.5"))
+
+ source = StubSource.new(
+ "a" => [v("2.0"), v("1.0")],
+ "b" => [v("3.0"), v("2.0"), v("1.0")]
+ )
+
+ strategy = Gem::Resolver::Strategy.new(source)
+
+ unsatisfied = {
+ pkg_a => range,
+ pkg_b => range,
+ }
+
+ package, _version = strategy.next_package_and_version(unsatisfied)
+
+ assert_equal pkg_a, package
+ end
+
+ def test_cache_prevents_redundant_versions_for_calls
+ pkg = make_package("a")
+ source = StubSource.new("a" => [v("2.0"), v("1.0")])
+
+ strategy = Gem::Resolver::Strategy.new(source)
+
+ range = make_range_any
+ unsatisfied = { pkg => range }
+
+ # First call: should call versions_for for matching + upper_invert + most_preferred
+ strategy.next_package_and_version(unsatisfied)
+ calls_after_first = source.versions_for_calls
+
+ # Second call with same package+range: next_term_to_try_from should
+ # hit the cache, so only most_preferred_version_of adds a call.
+ strategy.next_package_and_version(unsatisfied)
+ calls_after_second = source.versions_for_calls
+
+ # The cached path saves the 2 calls in next_term_to_try_from,
+ # so only the 1 call from most_preferred_version_of is added.
+ assert_equal 1, calls_after_second - calls_after_first
+ end
+
+ def test_cache_is_keyed_by_package_and_range
+ pkg = make_package("a")
+ source = StubSource.new("a" => [v("3.0"), v("2.0"), v("1.0")])
+
+ strategy = Gem::Resolver::Strategy.new(source)
+
+ range_any = make_range_any
+ range_gte = make_range_gte(v("2.0"))
+
+ # First call with range_any
+ strategy.next_package_and_version({ pkg => range_any })
+ calls_after_first = source.versions_for_calls
+
+ # Second call with different range - cache miss, so versions_for is called again
+ strategy.next_package_and_version({ pkg => range_gte })
+ calls_after_second = source.versions_for_calls
+
+ # A cache miss means 2 new versions_for calls (matching + upper_invert)
+ # plus 1 from most_preferred_version_of = 3 total new calls
+ assert_equal 3, calls_after_second - calls_after_first
+ end
+end
diff --git a/test/rubygems/test_gem_safe_marshal.rb b/test/rubygems/test_gem_safe_marshal.rb
index deeb8205bc..7e3a046c4e 100644
--- a/test/rubygems/test_gem_safe_marshal.rb
+++ b/test/rubygems/test_gem_safe_marshal.rb
@@ -234,8 +234,6 @@ class TestGemSafeMarshal < Gem::TestCase
end
def test_link_after_float
- pend "Marshal.load of links and floats is broken on truffleruby, see https://github.com/oracle/truffleruby/issues/3747" if RUBY_ENGINE == "truffleruby"
-
a = []
a << a
assert_safe_load_as [0.0, a, 1.0, a]
@@ -254,6 +252,8 @@ class TestGemSafeMarshal < Gem::TestCase
end
def test_hash_with_compare_by_identity
+ pend "Marshal.dump of a compare_by_identity Hash emits an unexpected ivar on jruby" if RUBY_ENGINE == "jruby"
+
with_const(Gem::SafeMarshal, :PERMITTED_CLASSES, %w[Hash]) do
assert_safe_load_as Hash.new.compare_by_identity.tap {|h|
h[+"a"] = 1
diff --git a/test/rubygems/test_gem_safe_yaml.rb b/test/rubygems/test_gem_safe_yaml.rb
index 02df9f97da..8d0ac63c41 100644
--- a/test/rubygems/test_gem_safe_yaml.rb
+++ b/test/rubygems/test_gem_safe_yaml.rb
@@ -5,6 +5,28 @@ require_relative "helper"
Gem.load_yaml
class TestGemSafeYAML < Gem::TestCase
+ def yaml_load(input, permitted_classes: Gem::SafeYAML::PERMITTED_CLASSES,
+ permitted_symbols: Gem::SafeYAML::PERMITTED_SYMBOLS,
+ aliases: true)
+ if Gem.use_psych?
+ Psych.safe_load(input, permitted_classes: permitted_classes,
+ permitted_symbols: permitted_symbols,
+ aliases: aliases)
+ else
+ Gem::YAMLSerializer.load(input, permitted_classes: permitted_classes,
+ permitted_symbols: permitted_symbols,
+ aliases: aliases)
+ end
+ end
+
+ def yaml_dump(obj)
+ if Gem.use_psych?
+ obj.to_yaml
+ else
+ Gem::YAMLSerializer.dump(obj)
+ end
+ end
+
def test_aliases_enabled_by_default
assert_predicate Gem::SafeYAML, :aliases_enabled?
assert_equal({ "a" => "a", "b" => "a" }, Gem::SafeYAML.safe_load("a: &a a\nb: *a\n"))
@@ -21,4 +43,1284 @@ class TestGemSafeYAML < Gem::TestCase
ensure
Gem::SafeYAML.aliases_enabled = aliases_enabled
end
+
+ def test_specification_version_is_integer
+ yaml = <<~YAML
+ --- !ruby/object:Gem::Specification
+ name: test
+ version: !ruby/object:Gem::Version
+ version: 1.0.0
+ specification_version: 4
+ YAML
+
+ spec = Gem::SafeYAML.safe_load(yaml)
+ assert_kind_of Integer, spec.specification_version
+ assert_equal 4, spec.specification_version
+ end
+
+ def test_disallowed_class_rejected
+ yaml = <<~YAML
+ --- !ruby/object:SomeDisallowedClass
+ foo: bar
+ YAML
+
+ exception = assert_raise(Psych::DisallowedClass) do
+ Gem::SafeYAML.safe_load(yaml)
+ end
+ assert_match(/unspecified class/, exception.message)
+ end
+
+ def test_plain_tag_key_does_not_construct_specification
+ yaml = <<~YAML
+ tag: "!ruby/object:Gem::Specification"
+ name: pwned
+ arbitrary_ivar: hello
+ YAML
+
+ result = Gem::SafeYAML.safe_load(yaml)
+ assert_kind_of Hash, result
+ assert_equal "!ruby/object:Gem::Specification", result["tag"]
+ assert_equal "pwned", result["name"]
+ end
+
+ def test_disallowed_symbol_rejected
+ yaml = <<~YAML
+ --- !ruby/object:Gem::Dependency
+ name: test
+ requirement: !ruby/object:Gem::Requirement
+ requirements:
+ - - ">="
+ - !ruby/object:Gem::Version
+ version: 0
+ type: :invalid_type
+ prerelease: false
+ version_requirements: !ruby/object:Gem::Requirement
+ requirements:
+ - - ">="
+ - !ruby/object:Gem::Version
+ version: 0
+ YAML
+
+ exception = assert_raise(Psych::DisallowedClass) do
+ Gem::SafeYAML.safe_load(yaml)
+ end
+ assert_match(/unspecified class/, exception.message)
+ end
+
+ def test_disallowed_symbol_not_interned
+ unique = "rejected_symbol_#{rand(1 << 30)}"
+ yaml = <<~YAML
+ --- !ruby/object:Gem::Dependency
+ name: test
+ requirement: !ruby/object:Gem::Requirement
+ requirements:
+ - - ">="
+ - !ruby/object:Gem::Version
+ version: 0
+ type: :#{unique}
+ prerelease: false
+ version_requirements: !ruby/object:Gem::Requirement
+ requirements:
+ - - ">="
+ - !ruby/object:Gem::Version
+ version: 0
+ YAML
+
+ assert_raise(Psych::DisallowedClass) do
+ Gem::YAMLSerializer.load(yaml,
+ permitted_classes: Gem::SafeYAML::PERMITTED_CLASSES,
+ permitted_symbols: Gem::SafeYAML::PERMITTED_SYMBOLS)
+ end
+ refute_includes Symbol.all_symbols.map(&:to_s), unique
+ end
+
+ def test_inline_array_nesting_capped
+ depth = Gem::YAMLSerializer::Parser::MAX_NESTING_DEPTH + 1
+ yaml = "x: " + ("[" * depth) + "a" + ("]" * depth) + "\n"
+
+ expected = [Psych::SyntaxError]
+ # JRuby's JVM stack overflows before the Ruby-level nesting cap fires.
+ expected << ::Java::JavaLang::StackOverflowError if RUBY_ENGINE == "jruby"
+
+ assert_raise(*expected) do
+ Gem::YAMLSerializer.load(yaml, permitted_classes: [])
+ end
+ end
+
+ def test_unknown_alias_raises
+ yaml = <<~YAML
+ foo: 1
+ bar: *missing
+ YAML
+
+ expected_error = defined?(Psych::AnchorNotDefined) ? Psych::AnchorNotDefined : Psych::BadAlias
+ assert_raise(expected_error) { Gem::SafeYAML.safe_load(yaml) }
+ end
+
+ def test_unused_anchor_with_aliases_disabled_is_allowed
+ aliases_enabled = Gem::SafeYAML.aliases_enabled?
+ Gem::SafeYAML.aliases_enabled = false
+
+ result = Gem::SafeYAML.safe_load("foo: &unused 1\nbar: 2\n")
+ assert_equal({ "foo" => 1, "bar" => 2 }, result)
+ ensure
+ Gem::SafeYAML.aliases_enabled = aliases_enabled
+ end
+
+ def test_yaml_serializer_aliases_disabled
+ aliases_enabled = Gem::SafeYAML.aliases_enabled?
+ Gem::SafeYAML.aliases_enabled = false
+ refute_predicate Gem::SafeYAML, :aliases_enabled?
+
+ yaml = "a: &anchor value\nb: *anchor\n"
+
+ assert_raise(Psych::AliasesNotEnabled) do
+ Gem::SafeYAML.safe_load(yaml)
+ end
+ ensure
+ Gem::SafeYAML.aliases_enabled = aliases_enabled
+ end
+
+ def test_real_gemspec_fileutils
+ yaml = <<~YAML
+ --- !ruby/object:Gem::Specification
+ name: fileutils
+ version: !ruby/object:Gem::Version
+ version: 1.8.0
+ platform: ruby
+ authors:
+ - Minero Aoki
+ bindir: bin
+ cert_chain: []
+ date: 1980-01-02 00:00:00.000000000 Z
+ dependencies: []
+ description: Several file utility methods for copying, moving, removing, etc.
+ email:
+ -
+ executables: []
+ extensions: []
+ extra_rdoc_files: []
+ files:
+ - BSDL
+ - COPYING
+ - README.md
+ - Rakefile
+ - fileutils.gemspec
+ - lib/fileutils.rb
+ homepage: https://github.com/ruby/fileutils
+ licenses:
+ - Ruby
+ - BSD-2-Clause
+ metadata:
+ source_code_uri: https://github.com/ruby/fileutils
+ rdoc_options: []
+ require_paths:
+ - lib
+ required_ruby_version: !ruby/object:Gem::Requirement
+ requirements:
+ - - ">="
+ - !ruby/object:Gem::Version
+ version: 2.5.0
+ required_rubygems_version: !ruby/object:Gem::Requirement
+ requirements:
+ - - ">="
+ - !ruby/object:Gem::Version
+ version: '0'
+ requirements: []
+ rubygems_version: 3.6.9
+ specification_version: 4
+ summary: Several file utility methods for copying, moving, removing, etc.
+ test_files: []
+ YAML
+
+ spec = Gem::SafeYAML.safe_load(yaml)
+ assert_kind_of Gem::Specification, spec
+ assert_equal "fileutils", spec.name
+ assert_equal Gem::Version.new("1.8.0"), spec.version
+ assert_kind_of Integer, spec.specification_version
+ assert_equal 4, spec.specification_version
+ end
+
+ def test_yaml_anchor_and_alias_enabled
+ aliases_enabled = Gem::SafeYAML.aliases_enabled?
+ Gem::SafeYAML.aliases_enabled = true
+
+ yaml = <<~YAML
+ dependencies:
+ - &req !ruby/object:Gem::Requirement
+ requirements:
+ - - ">="
+ - !ruby/object:Gem::Version
+ version: '0'
+ - *req
+ YAML
+
+ result = Gem::SafeYAML.safe_load(yaml)
+ assert_kind_of Hash, result
+ assert_kind_of Array, result["dependencies"]
+ assert_equal 2, result["dependencies"].size
+ assert_kind_of Gem::Requirement, result["dependencies"][0]
+ assert_kind_of Gem::Requirement, result["dependencies"][1]
+ assert_equal result["dependencies"][0].requirements, result["dependencies"][1].requirements
+ ensure
+ Gem::SafeYAML.aliases_enabled = aliases_enabled
+ end
+
+ def test_real_gemspec_rubygems_bundler
+ yaml = <<~YAML
+ --- !ruby/object:Gem::Specification
+ name: rubygems-bundler
+ version: !ruby/object:Gem::Version
+ version: 1.4.5
+ platform: ruby
+ authors:
+ - Josh Hull
+ - Michal Papis
+ autorequire:
+ bindir: bin
+ cert_chain: []
+ date: 2018-06-24 00:00:00.000000000 Z
+ dependencies:
+ - !ruby/object:Gem::Dependency
+ name: bundler-unload
+ requirement: !ruby/object:Gem::Requirement
+ requirements:
+ - - ">="
+ - !ruby/object:Gem::Version
+ version: 1.0.2
+ type: :runtime
+ prerelease: false
+ version_requirements: !ruby/object:Gem::Requirement
+ requirements:
+ - - ">="
+ - !ruby/object:Gem::Version
+ version: 1.0.2
+ description: Stop using bundle exec.
+ email:
+ - joshbuddy@gmail.com
+ - mpapis@gmail.com
+ executables: []
+ extensions: []
+ extra_rdoc_files: []
+ files:
+ - ".gem.config"
+ homepage: http://mpapis.github.com/rubygems-bundler
+ licenses:
+ - Apache-2.0
+ metadata: {}
+ post_install_message:
+ rdoc_options: []
+ require_paths:
+ - lib
+ required_ruby_version: !ruby/object:Gem::Requirement
+ requirements:
+ - - ">="
+ - !ruby/object:Gem::Version
+ version: '0'
+ rubyforge_project:
+ rubygems_version: 2.7.6
+ signing_key:
+ specification_version: 4
+ summary: Stop using bundle exec
+ test_files: []
+ YAML
+
+ spec = Gem::SafeYAML.safe_load(yaml)
+ assert_kind_of Gem::Specification, spec
+ assert_equal "rubygems-bundler", spec.name
+ assert_equal Gem::Version.new("1.4.5"), spec.version
+ assert_equal 1, spec.dependencies.size
+
+ dep = spec.dependencies.first
+ assert_equal "bundler-unload", dep.name
+ assert_kind_of Gem::Requirement, dep.requirement
+ assert_kind_of Gem::Requirement, dep.instance_variable_get(:@version_requirements)
+ assert_equal dep.requirement.requirements, [[">=", Gem::Version.new("1.0.2")]]
+
+ # Empty fields should be nil
+ assert_nil spec.autorequire
+ assert_nil spec.post_install_message
+
+ # Metadata should be empty hash
+ assert_equal({}, spec.metadata)
+
+ # specification_version should be Integer
+ assert_kind_of Integer, spec.specification_version
+ assert_equal 4, spec.specification_version
+ end
+
+ def test_empty_requirements_array
+ yaml = <<~YAML
+ --- !ruby/object:Gem::Specification
+ name: test
+ dependencies:
+ - !ruby/object:Gem::Dependency
+ name: foo
+ requirement: !ruby/object:Gem::Requirement
+ requirements:
+ type: :runtime
+ version_requirements: !ruby/object:Gem::Requirement
+ requirements:
+ YAML
+
+ spec = Gem::SafeYAML.safe_load(yaml)
+ assert_kind_of Gem::Specification, spec
+ assert_equal "test", spec.name
+ assert_equal 1, spec.dependencies.size
+
+ dep = spec.dependencies.first
+ assert_equal "foo", dep.name
+ assert_kind_of Gem::Requirement, dep.requirement
+
+ reqs = dep.requirement.instance_variable_get(:@requirements)
+ assert_nil reqs
+ end
+
+ def test_requirements_hash_converted_to_array
+ # Malformed YAML where requirements is a Hash instead of Array
+ yaml = <<~YAML
+ !ruby/object:Gem::Requirement
+ requirements:
+ foo: bar
+ YAML
+
+ req = yaml_load(yaml, permitted_classes: ["Gem::Requirement"])
+ assert_kind_of Gem::Requirement, req
+
+ reqs = req.instance_variable_get(:@requirements)
+ assert_kind_of Hash, reqs
+ end
+
+ def test_requirement_quote
+ yaml = <<~YAML
+ requirements:
+ - "system: arrow-glib>=25.0.0: amazon_linux: arrow-glib-devel"
+ - 'system: arrow-glib>=25.0.0: fedora: libarrow-glib-devel'
+ YAML
+
+ expected = [
+ "system: arrow-glib>=25.0.0: amazon_linux: arrow-glib-devel",
+ "system: arrow-glib>=25.0.0: fedora: libarrow-glib-devel",
+ ]
+ assert_equal expected, yaml_load(yaml)["requirements"]
+ end
+
+ def test_rdoc_options_hash_converted_to_array
+ # Some gemspecs incorrectly have rdoc_options: {} instead of rdoc_options: []
+ yaml = <<~YAML
+ --- !ruby/object:Gem::Specification
+ name: test-gem
+ version: !ruby/object:Gem::Version
+ version: 1.0.0
+ rdoc_options: {}
+ YAML
+
+ spec = Gem::SafeYAML.safe_load(yaml)
+ assert_kind_of Gem::Specification, spec
+ assert_equal "test-gem", spec.name
+
+ assert_equal [], spec.rdoc_options
+ end
+
+ def test_load_returns_nil_for_comment_only_yaml
+ # Bundler config files may contain only comments after deleting all keys
+ result = yaml_load("---\n# BUNDLE_FOO: \"bar\"\n")
+ assert_nil result
+ end
+
+ def test_load_returns_nil_for_empty_document
+ assert_nil yaml_load("---\n")
+ assert_nil yaml_load("")
+ assert_raise(TypeError) { yaml_load(nil) }
+ end
+
+ def test_load_returns_hash_for_flow_empty_hash
+ # yaml_dump({}) produces "--- {}\n"
+ result = yaml_load("--- {}\n")
+ assert_kind_of Hash, result
+ assert_empty result
+ end
+
+ def test_load_parses_flow_empty_hash_as_value
+ result = yaml_load("metadata: {}\n")
+ assert_kind_of Hash, result
+ assert_kind_of Hash, result["metadata"]
+ assert_empty result["metadata"]
+ end
+
+ def test_yaml_non_specific_tag_stripped
+ # Legacy RubyGems (1.x) generated YAML with ! non-specific tags like:
+ # - ! '>='
+ # The ! prefix should be ignored.
+ yaml = <<~YAML
+ --- !ruby/object:Gem::Specification
+ name: legacy-gem
+ version: !ruby/object:Gem::Version
+ version: 0.1.0
+ required_ruby_version: !ruby/object:Gem::Requirement
+ none: false
+ requirements:
+ - - ! '>='
+ - !ruby/object:Gem::Version
+ version: '0'
+ required_rubygems_version: !ruby/object:Gem::Requirement
+ none: false
+ requirements:
+ - - ! '>='
+ - !ruby/object:Gem::Version
+ version: 1.3.5
+ YAML
+
+ spec = Gem::SafeYAML.safe_load(yaml)
+ assert_kind_of Gem::Specification, spec
+ assert_equal "legacy-gem", spec.name
+ assert_equal Gem::Requirement.new(">= 0"), spec.required_ruby_version
+ assert_equal Gem::Requirement.new(">= 1.3.5"), spec.required_rubygems_version
+ end
+
+ def test_legacy_gemspec_with_anchors_and_non_specific_tags
+ aliases_enabled = Gem::SafeYAML.aliases_enabled?
+ Gem::SafeYAML.aliases_enabled = true
+
+ # Real-world pattern from gems like vegas-0.1.11 that combine
+ # YAML anchors/aliases with ! non-specific tags
+ yaml = <<~YAML
+ --- !ruby/object:Gem::Specification
+ name: legacy-gem
+ version: !ruby/object:Gem::Version
+ version: 0.1.11
+ dependencies:
+ - !ruby/object:Gem::Dependency
+ name: rack
+ requirement: &id001 !ruby/object:Gem::Requirement
+ none: false
+ requirements:
+ - - ! '>='
+ - !ruby/object:Gem::Version
+ version: 1.0.0
+ type: :runtime
+ prerelease: false
+ version_requirements: *id001
+ - !ruby/object:Gem::Dependency
+ name: mocha
+ requirement: &id002 !ruby/object:Gem::Requirement
+ none: false
+ requirements:
+ - - ~>
+ - !ruby/object:Gem::Version
+ version: 0.9.8
+ type: :development
+ prerelease: false
+ version_requirements: *id002
+ YAML
+
+ spec = Gem::SafeYAML.safe_load(yaml)
+ assert_kind_of Gem::Specification, spec
+ assert_equal "legacy-gem", spec.name
+
+ assert_equal 2, spec.dependencies.size
+
+ rack_dep = spec.dependencies.find {|d| d.name == "rack" }
+ assert_kind_of Gem::Dependency, rack_dep
+ assert_equal :runtime, rack_dep.type
+ assert_equal Gem::Requirement.new(">= 1.0.0"), rack_dep.requirement
+
+ mocha_dep = spec.dependencies.find {|d| d.name == "mocha" }
+ assert_kind_of Gem::Dependency, mocha_dep
+ assert_equal :development, mocha_dep.type
+ assert_equal Gem::Requirement.new("~> 0.9.8"), mocha_dep.requirement
+ ensure
+ Gem::SafeYAML.aliases_enabled = aliases_enabled
+ end
+
+ def test_non_specific_tag_on_plain_value
+ # ! tag on a bracketed value like rubyforge_project: ! '[none]'
+ result = yaml_load("key: ! '[none]'\n")
+ assert_equal({ "key" => "[none]" }, result)
+ end
+
+ def test_dump_quotes_dollar_sign_values
+ # Values starting with $ should be quoted to preserve them as strings
+ yaml = yaml_dump({ "BUNDLE_FOO" => "$BUILD_DIR", "BUNDLE_BAR" => "baz" })
+ assert_include yaml, 'BUNDLE_FOO: "$BUILD_DIR"'
+ assert_include yaml, "BUNDLE_BAR: baz"
+
+ # Round-trip: ensure the quoted value is parsed back correctly
+ result = yaml_load(yaml)
+ assert_equal "$BUILD_DIR", result["BUNDLE_FOO"]
+ assert_equal "baz", result["BUNDLE_BAR"]
+ end
+
+ def test_dump_quotes_special_characters
+ # Various special characters that should trigger quoting
+ special_values = {
+ "dollar" => "$HOME",
+ "exclamation" => "!important",
+ "ampersand" => "&anchor",
+ "asterisk" => "*ref",
+ "colon_prefix" => ":symbol",
+ "at_sign" => "@mention",
+ "percent" => "%encoded",
+ }
+
+ yaml = yaml_dump(special_values)
+ special_values.each do |key, value|
+ assert_include yaml, "#{key}: #{value.inspect}", "Value #{value.inspect} for key #{key} should be quoted"
+ end
+
+ # Round-trip
+ result = yaml_load(yaml)
+ special_values.each do |key, value|
+ assert_equal value, result[key], "Round-trip failed for key #{key}"
+ end
+ end
+
+ def test_load_ambiguous_value_with_colon
+ # "invalid: yaml: hah" is ambiguous YAML - our parser treats it as
+ # {"invalid" => "yaml: hah"}, but the value looks like a nested mapping.
+ # config_file.rb's load_file should detect this and reject it.
+ if Gem.use_psych?
+ # Psych raises a syntax error for this ambiguous YAML
+ assert_raise(Psych::SyntaxError) do
+ yaml_load("invalid: yaml: hah")
+ end
+ else
+ result = yaml_load("invalid: yaml: hah")
+ assert_kind_of Hash, result
+ assert_equal "yaml: hah", result["invalid"]
+ end
+ end
+
+ def test_nested_anchor_in_array_item
+ # Ensure aliases are enabled for this test
+ aliases_enabled = Gem::SafeYAML.aliases_enabled?
+ Gem::SafeYAML.aliases_enabled = true
+
+ yaml = <<~YAML
+ --- !ruby/object:Gem::Specification
+ name: test-gem
+ version: !ruby/object:Gem::Version
+ version: 1.0.0
+ dependencies:
+ - !ruby/object:Gem::Dependency
+ name: foo
+ requirement: !ruby/object:Gem::Requirement
+ requirements:
+ - &id002
+ - ">="
+ - !ruby/object:Gem::Version
+ version: "0"
+ type: :runtime
+ YAML
+
+ spec = Gem::SafeYAML.safe_load(yaml)
+ assert_kind_of Gem::Specification, spec
+ assert_equal "test-gem", spec.name
+
+ dep = spec.dependencies.first
+ assert_kind_of Gem::Dependency, dep
+
+ # Requirements should be parsed as nested arrays, not strings
+ assert_kind_of Array, dep.requirement.requirements
+ assert_equal 1, dep.requirement.requirements.size
+
+ req_item = dep.requirement.requirements.first
+ assert_kind_of Array, req_item
+ assert_equal ">=", req_item[0]
+ assert_kind_of Gem::Version, req_item[1]
+ assert_equal "0", req_item[1].version
+ ensure
+ Gem::SafeYAML.aliases_enabled = aliases_enabled
+ end
+
+ def test_roundtrip_specification
+ spec = Gem::Specification.new do |s|
+ s.name = "round-trip-test"
+ s.version = "2.3.4"
+ s.platform = "ruby"
+ s.authors = ["Test Author"]
+ s.summary = "A test gem for round-trip"
+ s.description = "Longer description of the test gem"
+ s.files = ["lib/foo.rb", "README.md"]
+ s.require_paths = ["lib"]
+ s.homepage = "https://example.com"
+ s.licenses = ["MIT"]
+ s.metadata = { "source_code_uri" => "https://example.com/src" }
+ s.add_dependency "rake", ">= 1.0"
+ end
+
+ yaml = yaml_dump(spec)
+ loaded = Gem::SafeYAML.safe_load(yaml)
+
+ assert_kind_of Gem::Specification, loaded
+ assert_equal "round-trip-test", loaded.name
+ assert_equal Gem::Version.new("2.3.4"), loaded.version
+ assert_equal ["Test Author"], loaded.authors
+ assert_equal "A test gem for round-trip", loaded.summary
+ assert_equal ["README.md", "lib/foo.rb"], loaded.files
+ assert_equal ["lib"], loaded.require_paths
+ assert_equal "https://example.com", loaded.homepage
+ assert_equal ["MIT"], loaded.licenses
+ assert_equal({ "source_code_uri" => "https://example.com/src" }, loaded.metadata)
+ assert_equal 1, loaded.dependencies.size
+
+ dep = loaded.dependencies.first
+ assert_equal "rake", dep.name
+ assert_equal :runtime, dep.type
+ end
+
+ def test_roundtrip_specification_with_extensions
+ spec = Gem::Specification.new do |s|
+ s.name = "native-ext-test"
+ s.version = "1.0.0"
+ s.authors = ["Test"]
+ s.summary = "A gem with native extensions"
+ s.files = ["lib/native.rb", "ext/native/extconf.rb", "ext/native/native.c"]
+ s.extensions = ["ext/native/extconf.rb"]
+ s.require_paths = ["lib"]
+ end
+
+ yaml = yaml_dump(spec)
+ loaded = Gem::SafeYAML.safe_load(yaml)
+
+ assert_kind_of Gem::Specification, loaded
+ assert_equal ["ext/native/extconf.rb"], loaded.extensions
+ assert_equal ["ext/native/extconf.rb", "ext/native/native.c", "lib/native.rb"], loaded.files
+ end
+
+ def test_roundtrip_specification_with_windows_paths
+ spec = Gem::Specification.new do |s|
+ s.name = "win-path-test"
+ s.version = "1.0.0"
+ s.authors = ["Test"]
+ s.summary = "A gem with Windows-style paths"
+ s.files = ["lib/foo.rb", "lib/foo/bar.rb"]
+ s.require_paths = ["lib"]
+ s.description = 'Installed in D:\ruby\lib\ruby\gems'
+ s.post_install_message = "Installed to C:\\Program Files\\Ruby\\lib\\rdoc"
+ end
+
+ yaml = yaml_dump(spec)
+ loaded = Gem::SafeYAML.safe_load(yaml)
+
+ assert_kind_of Gem::Specification, loaded
+ assert_equal 'Installed in D:\ruby\lib\ruby\gems', loaded.description
+ assert_equal "Installed to C:\\Program Files\\Ruby\\lib\\rdoc", loaded.post_install_message
+ end
+
+ def test_roundtrip_specification_with_metadata
+ spec = Gem::Specification.new do |s|
+ s.name = "metadata-test"
+ s.version = "1.0.0"
+ s.authors = ["Test"]
+ s.summary = "A gem with metadata"
+ s.files = ["lib/foo.rb"]
+ s.require_paths = ["lib"]
+ s.metadata = {
+ "changelog_uri" => "https://example.com/CHANGELOG.md",
+ "source_code_uri" => "https://github.com/example/metadata-test",
+ "bug_tracker_uri" => "https://github.com/example/metadata-test/issues",
+ "allowed_push_host" => "https://rubygems.org",
+ }
+ end
+
+ yaml = yaml_dump(spec)
+ loaded = Gem::SafeYAML.safe_load(yaml)
+
+ assert_kind_of Gem::Specification, loaded
+ assert_kind_of Hash, loaded.metadata
+ assert_equal 4, loaded.metadata.size
+ assert_equal "https://example.com/CHANGELOG.md", loaded.metadata["changelog_uri"]
+ assert_equal "https://github.com/example/metadata-test", loaded.metadata["source_code_uri"]
+ assert_equal "https://github.com/example/metadata-test/issues", loaded.metadata["bug_tracker_uri"]
+ assert_equal "https://rubygems.org", loaded.metadata["allowed_push_host"]
+ end
+
+ def test_roundtrip_version
+ ver = Gem::Version.new("1.2.3")
+ yaml = yaml_dump(ver)
+ loaded = yaml_load(yaml, permitted_classes: Gem::SafeYAML::PERMITTED_CLASSES)
+
+ assert_kind_of Gem::Version, loaded
+ assert_equal ver, loaded
+ end
+
+ def test_roundtrip_platform
+ plat = Gem::Platform.new("x86_64-linux")
+ yaml = yaml_dump(plat)
+ loaded = yaml_load(yaml, permitted_classes: Gem::SafeYAML::PERMITTED_CLASSES)
+
+ assert_kind_of Gem::Platform, loaded
+ assert_equal plat.cpu, loaded.cpu
+ assert_equal plat.os, loaded.os
+ assert_equal plat.version, loaded.version
+ end
+
+ def test_roundtrip_requirement
+ req = Gem::Requirement.new(">= 1.0", "< 2.0")
+ yaml = yaml_dump(req)
+ loaded = yaml_load(yaml, permitted_classes: Gem::SafeYAML::PERMITTED_CLASSES)
+
+ assert_kind_of Gem::Requirement, loaded
+ assert_equal req.requirements.sort_by(&:to_s), loaded.requirements.sort_by(&:to_s)
+ end
+
+ def test_roundtrip_dependency
+ dep = Gem::Dependency.new("foo", ">= 1.0", :development)
+ yaml = yaml_dump(dep)
+ loaded = yaml_load(yaml, permitted_classes: Gem::SafeYAML::PERMITTED_CLASSES)
+
+ assert_kind_of Gem::Dependency, loaded
+ assert_equal "foo", loaded.name
+ assert_equal :development, loaded.type
+ assert_equal dep.requirement.requirements, loaded.requirement.requirements
+ end
+
+ def test_roundtrip_nested_hash
+ obj = { "a" => { "b" => "c", "d" => [1, 2, 3] } }
+ yaml = yaml_dump(obj)
+ loaded = yaml_load(yaml)
+
+ assert_equal obj, loaded
+ end
+
+ def test_roundtrip_block_scalar
+ obj = { "text" => "line1\nline2\n" }
+ yaml = yaml_dump(obj)
+ loaded = yaml_load(yaml)
+
+ assert_equal "line1\nline2\n", loaded["text"]
+ end
+
+ def test_roundtrip_special_characters
+ obj = {
+ "dollar" => "$HOME",
+ "exclamation" => "!important",
+ "ampersand" => "&anchor",
+ "asterisk" => "*ref",
+ "colon_prefix" => ":symbol",
+ "hash_char" => "value#comment",
+ "brackets" => "[item]",
+ "braces" => "{key}",
+ "comma" => "a,b,c",
+ }
+ yaml = yaml_dump(obj)
+ loaded = yaml_load(yaml)
+
+ obj.each do |key, value|
+ assert_equal value, loaded[key], "Round-trip failed for key #{key}"
+ end
+ end
+
+ def test_roundtrip_boolean_nil_integer
+ obj = { "flag" => true, "count" => 42, "empty" => nil, "off" => false }
+ yaml = yaml_dump(obj)
+ loaded = yaml_load(yaml)
+
+ assert_equal true, loaded["flag"]
+ assert_equal 42, loaded["count"]
+ assert_nil loaded["empty"]
+ assert_equal false, loaded["off"]
+ end
+
+ def test_roundtrip_time
+ time = Time.utc(2024, 6, 15, 12, 30, 45)
+ obj = { "created" => time }
+ yaml = yaml_dump(obj)
+ loaded = yaml_load(yaml)
+
+ assert_kind_of Time, loaded["created"]
+ assert_equal time.year, loaded["created"].year
+ assert_equal time.month, loaded["created"].month
+ assert_equal time.day, loaded["created"].day
+ end
+
+ def test_roundtrip_empty_collections
+ obj = { "arr" => [], "hash" => {} }
+ yaml = yaml_dump(obj)
+ loaded = yaml_load(yaml)
+
+ assert_equal [], loaded["arr"]
+ assert_equal({}, loaded["hash"])
+ end
+
+ def test_load_double_quoted_escape_sequences
+ result = yaml_load("newline: \"hello\\nworld\"")
+ assert_equal "hello\nworld", result["newline"]
+
+ result = yaml_load("tab: \"col1\\tcol2\"")
+ assert_equal "col1\tcol2", result["tab"]
+
+ result = yaml_load("cr: \"line\\rend\"")
+ assert_equal "line\rend", result["cr"]
+
+ result = yaml_load("quote: \"say\\\"hi\\\"\"")
+ assert_equal "say\"hi\"", result["quote"]
+ end
+
+ def test_load_double_quoted_backslash_before_escape_chars
+ # \\r in YAML should become literal backslash + r, not carriage return
+ result = yaml_load('path: "D:\\\\ruby-mswin\\\\lib"')
+ assert_equal "D:\\ruby-mswin\\lib", result["path"]
+
+ # \\n should become literal backslash + n, not newline
+ result = yaml_load('path: "C:\\\\new_folder"')
+ assert_equal "C:\\new_folder", result["path"]
+
+ # \\t should become literal backslash + t, not tab
+ result = yaml_load('path: "C:\\\\tmp\\\\test"')
+ assert_equal "C:\\tmp\\test", result["path"]
+
+ # \\\\ should become two literal backslashes
+ result = yaml_load('val: "a\\\\\\\\b"')
+ assert_equal "a\\\\b", result["val"]
+ end
+
+ def test_load_single_quoted_escape
+ result = yaml_load("key: 'it''s'")
+ assert_equal "it's", result["key"]
+
+ result = yaml_load("key: 'no escape \\n here'")
+ assert_equal "no escape \\n here", result["key"]
+ end
+
+ def test_load_quoted_numeric_stays_string
+ result = yaml_load("key: \"42\"")
+ assert_equal "42", result["key"]
+ assert_kind_of String, result["key"]
+
+ result = yaml_load("key: '99'")
+ assert_equal "99", result["key"]
+ assert_kind_of String, result["key"]
+ end
+
+ def test_load_empty_string_value
+ result = yaml_load("key: \"\"")
+ assert_equal "", result["key"]
+ end
+
+ def test_load_unquoted_integer
+ result = yaml_load("key: 42")
+ assert_equal 42, result["key"]
+ assert_kind_of Integer, result["key"]
+
+ result = yaml_load("key: -7")
+ assert_equal(-7, result["key"])
+ end
+
+ def test_load_boolean_values
+ result = yaml_load("a: true\nb: false")
+ assert_equal true, result["a"]
+ assert_equal false, result["b"]
+ end
+
+ def test_load_nil_value
+ # YAML 1.2: "nil" is not a null value, only ~ and null are
+ result = yaml_load("key: nil")
+ assert_equal "nil", result["key"]
+
+ result = yaml_load("key: ~")
+ assert_nil result["key"]
+
+ result = yaml_load("key: null")
+ assert_nil result["key"]
+ end
+
+ def test_load_time_value
+ result = yaml_load("date: 2024-06-15 12:30:45.000000000 Z")
+ assert_kind_of Time, result["date"]
+ assert_equal 2024, result["date"].year
+ assert_equal 6, result["date"].month
+ assert_equal 15, result["date"].day
+ end
+
+ def test_load_block_scalar_keep_trailing_newline
+ yaml = "text: |\n line1\n line2\n"
+ result = yaml_load(yaml)
+ assert_equal "line1\nline2\n", result["text"]
+ end
+
+ def test_load_block_scalar_strip_trailing_newline
+ yaml = "text: |-\n no trailing newline\n"
+ result = yaml_load(yaml)
+ assert_equal "no trailing newline", result["text"]
+ refute result["text"].end_with?("\n")
+ end
+
+ def test_load_flow_array
+ result = yaml_load("items: [a, b, c]")
+ assert_equal ["a", "b", "c"], result["items"]
+ end
+
+ def test_load_flow_empty_array
+ result = yaml_load("items: []")
+ assert_equal [], result["items"]
+ end
+
+ def test_load_mapping_key_with_no_value
+ result = yaml_load("key:")
+ assert_kind_of Hash, result
+ assert_nil result["key"]
+ end
+
+ def test_load_sequence_item_as_mapping
+ yaml = "items:\n- name: foo\n ver: 1\n- name: bar\n ver: 2"
+ result = yaml_load(yaml)
+ assert_equal [{ "name" => "foo", "ver" => 1 }, { "name" => "bar", "ver" => 2 }], result["items"]
+ end
+
+ def test_load_nested_sequence
+ yaml = "matrix:\n- - a\n - b\n- - c\n - d"
+ result = yaml_load(yaml)
+ assert_equal [["a", "b"], ["c", "d"]], result["matrix"]
+ end
+
+ def test_load_comment_stripped_from_value
+ result = yaml_load("key: value # this is a comment")
+ assert_equal "value", result["key"]
+ end
+
+ def test_load_comment_in_quoted_string_preserved
+ result = yaml_load("key: \"value # not a comment\"")
+ assert_equal "value # not a comment", result["key"]
+
+ result = yaml_load("key: 'value # not a comment'")
+ assert_equal "value # not a comment", result["key"]
+ end
+
+ def test_load_crlf_line_endings
+ result = yaml_load("key: value\r\nother: data\r\n")
+ assert_equal "value", result["key"]
+ assert_equal "data", result["other"]
+ end
+
+ def test_load_version_requirement_old_tag
+ yaml = <<~YAML
+ !ruby/object:Gem::Version::Requirement
+ requirements:
+ - - ">="
+ - !ruby/object:Gem::Version
+ version: "1.0"
+ YAML
+
+ req = yaml_load(yaml, permitted_classes: Gem::SafeYAML::PERMITTED_CLASSES)
+ assert_kind_of Gem::Requirement, req
+ assert_equal [[">=", Gem::Version.new("1.0")]], req.requirements
+ end
+
+ def test_load_dependency_version_version_requirement_old_tag
+ yaml = <<~YAML
+ - !ruby/object:Gem::Dependency
+ name: test-unit
+ type: :development
+ version_requirement:
+ version_requirements: !ruby/object:Gem::Requirement
+ requirements:
+ - - ">="
+ - !ruby/object:Gem::Version
+ version: 2.0.2
+ version:
+ YAML
+
+ deps = yaml_load(yaml, permitted_classes: Gem::SafeYAML::PERMITTED_CLASSES)
+ assert_not_nil(deps.first)
+
+ assert_equal [[">=", Gem::Version.new("2.0.2")]], deps.first.requirement.requirements
+ end
+
+ def test_load_platform_from_value_field
+ yaml = "!ruby/object:Gem::Platform\nvalue: x86-linux\n"
+ plat = yaml_load(yaml, permitted_classes: Gem::SafeYAML::PERMITTED_CLASSES)
+ assert_kind_of Gem::Platform, plat
+ assert_nil plat.cpu
+ end
+
+ def test_load_platform_from_cpu_os_version_fields
+ yaml = "!ruby/object:Gem::Platform\ncpu: x86_64\nos: darwin\nversion: nil\n"
+ plat = yaml_load(yaml, permitted_classes: Gem::SafeYAML::PERMITTED_CLASSES)
+ assert_kind_of Gem::Platform, plat
+ assert_equal "x86_64", plat.cpu
+ assert_equal "darwin", plat.os
+ end
+
+ def test_load_platform_malicious_sequence
+ yaml = "!ruby/object:Gem::Platform\n- \"x86-mswin32\\n system('id')#\"\n"
+ result = yaml_load(yaml, permitted_classes: Gem::SafeYAML::PERMITTED_CLASSES)
+ refute_kind_of Gem::Platform, result
+ assert_kind_of Array, result
+ end
+
+ def test_load_dependency_missing_requirement_uses_default
+ yaml = <<~YAML
+ !ruby/object:Gem::Dependency
+ name: foo
+ type: :runtime
+ YAML
+
+ dep = yaml_load(yaml, permitted_classes: Gem::SafeYAML::PERMITTED_CLASSES)
+ assert_kind_of Gem::Dependency, dep
+ assert_equal "foo", dep.name
+ assert_equal :runtime, dep.type
+ assert_nil dep.instance_variable_get(:@requirement)
+ end
+
+ def test_load_dependency_missing_type_defaults_to_runtime
+ yaml = <<~YAML
+ !ruby/object:Gem::Dependency
+ name: bar
+ requirement: !ruby/object:Gem::Requirement
+ requirements:
+ - - ">="
+ - !ruby/object:Gem::Version
+ version: '0'
+ YAML
+
+ dep = yaml_load(yaml, permitted_classes: Gem::SafeYAML::PERMITTED_CLASSES)
+ assert_equal :runtime, dep.type
+ end
+
+ def test_specification_version_non_numeric_string_not_converted
+ yaml = <<~YAML
+ --- !ruby/object:Gem::Specification
+ name: test
+ version: !ruby/object:Gem::Version
+ version: 1.0.0
+ specification_version: abc
+ YAML
+
+ spec = Gem::SafeYAML.safe_load(yaml)
+ assert_kind_of Gem::Specification, spec
+ # Non-numeric string should not be converted to Integer
+ assert_equal "abc", spec.specification_version
+ end
+
+ def test_unknown_permitted_tag_raises_argument_error
+ yaml = "!ruby/object:MyCustomClass\nfoo: bar\n"
+ assert_raise(ArgumentError) do
+ yaml_load(yaml, permitted_classes: ["MyCustomClass"])
+ end
+ end
+
+ def test_dump_block_scalar_with_trailing_newline
+ yaml = yaml_dump({ "text" => "line1\nline2\n" })
+ assert_include yaml, " |\n"
+ refute_includes yaml, " |-\n"
+ end
+
+ def test_dump_block_scalar_without_trailing_newline
+ yaml = yaml_dump({ "text" => "line1\nline2" })
+ assert_include yaml, " |-\n"
+ end
+
+ def test_dump_nil_value
+ yaml = yaml_dump({ "key" => nil })
+
+ loaded = yaml_load(yaml)
+ assert_nil loaded["key"]
+ end
+
+ def test_dump_symbol_keys_quoted
+ yaml = yaml_dump({ foo: "bar" })
+ # Symbol keys should use inspect format
+ assert_include yaml, ":foo:"
+
+ # Symbol values in hash with symbol keys should be quoted
+ yaml = yaml_dump({ type: ":runtime" })
+ assert_include yaml, "\":runtime\""
+ end
+
+ def test_regression_flow_empty_hash_as_root
+ # Previously returned Mapping struct instead of Hash
+ result = yaml_load("--- {}")
+ assert_kind_of Hash, result
+ assert_empty result
+ end
+
+ def test_regression_alias_check_in_builder_not_parser
+ # Previously aliases were resolved in Parser, bypassing Builder's policy check.
+ # The Builder must enforce aliases: false.
+ aliases_enabled = Gem::SafeYAML.aliases_enabled?
+ Gem::SafeYAML.aliases_enabled = false
+
+ # Alias in mapping value
+ assert_raise(Psych::AliasesNotEnabled) do
+ yaml_load("a: &x val\nb: *x", aliases: false)
+ end
+
+ # Alias in sequence item
+ assert_raise(Psych::AliasesNotEnabled) do
+ yaml_load("items:\n- &x val\n- *x", aliases: false)
+ end
+ ensure
+ Gem::SafeYAML.aliases_enabled = aliases_enabled
+ end
+
+ def test_regression_anchored_mapping_stored_for_alias_resolution
+ # Previously build_mapping didn't call store_anchor, so anchored
+ # Gem types (Requirement, etc.) couldn't be resolved via aliases.
+ aliases_enabled = Gem::SafeYAML.aliases_enabled?
+ Gem::SafeYAML.aliases_enabled = true
+
+ yaml = <<~YAML
+ a: &req !ruby/object:Gem::Requirement
+ requirements:
+ - - ">="
+ - !ruby/object:Gem::Version
+ version: '0'
+ b: *req
+ YAML
+
+ result = Gem::SafeYAML.safe_load(yaml)
+ assert_kind_of Gem::Requirement, result["a"]
+ assert_kind_of Gem::Requirement, result["b"]
+ assert_equal result["a"].requirements, result["b"].requirements
+ ensure
+ Gem::SafeYAML.aliases_enabled = aliases_enabled
+ end
+
+ def test_regression_register_anchor_sets_node_anchor
+ # Previously register_anchor only stored node in @anchors hash but
+ # didn't set node.anchor, so Builder couldn't track anchored values.
+ aliases_enabled = Gem::SafeYAML.aliases_enabled?
+ Gem::SafeYAML.aliases_enabled = true
+
+ yaml = <<~YAML
+ items:
+ - &item !ruby/object:Gem::Version
+ version: '1.0'
+ - *item
+ YAML
+
+ result = Gem::SafeYAML.safe_load(yaml)
+ assert_kind_of Array, result["items"]
+ assert_equal 2, result["items"].size
+ assert_kind_of Gem::Version, result["items"][0]
+ assert_kind_of Gem::Version, result["items"][1]
+ assert_equal result["items"][0], result["items"][1]
+ ensure
+ Gem::SafeYAML.aliases_enabled = aliases_enabled
+ end
+
+ def test_regression_coerce_empty_hash_not_wrapped_in_scalar
+ # Previously coerce("{}") returned Mapping but parse_plain_scalar
+ # wrapped it in Scalar.new(value: Mapping), causing type mismatch.
+ result = yaml_load("--- {}")
+ assert_kind_of Hash, result
+
+ result = yaml_load("key: {}")
+ assert_kind_of Hash, result["key"]
+ end
+
+ def test_regression_rdoc_options_normalized_to_array
+ # rdoc_options as Hash (malformed gemspec)
+ yaml = <<~YAML
+ --- !ruby/object:Gem::Specification
+ name: test
+ version: !ruby/object:Gem::Version
+ version: 1.0.0
+ rdoc_options:
+ --title: MyGem
+ --main: README
+ YAML
+
+ spec = Gem::SafeYAML.safe_load(yaml)
+ assert_equal ["--title", "MyGem", "--main", "README"], spec.rdoc_options
+ end
+
+ def test_regression_requirements_field_normalized_to_array
+ # The "requirements" field in a Specification (not Requirement)
+ # should be normalized from Hash to Array if malformed
+ yaml = <<~YAML
+ --- !ruby/object:Gem::Specification
+ name: test
+ version: !ruby/object:Gem::Version
+ version: 1.0.0
+ requirements:
+ foo: bar
+ YAML
+
+ spec = Gem::SafeYAML.safe_load(yaml)
+ assert_equal [["foo", "bar"]], spec.requirements
+ end
+
+ def test_binary_tag_decoded_in_mapping_key
+ yaml = <<~YAML
+ ---
+ !binary "U0hBMQ==":
+ metadata.gz: abc123
+ YAML
+
+ result = yaml_load(yaml)
+ assert_equal "SHA1", result.keys.first
+ assert_equal "abc123", result["SHA1"]["metadata.gz"]
+ end
+
+ def test_binary_tag_decoded_in_block_scalar_value
+ yaml = <<~YAML
+ ---
+ SHA256:
+ metadata.gz: !binary |-
+ OWY4YTM5Y2MxOTc3Mzc5MWYzNzk1NjRmZjVlYzljYjY1MDQwYWIwMg==
+ YAML
+
+ result = yaml_load(yaml)
+ assert_equal "9f8a39cc19773791f379564ff5ec9cb65040ab02", result["SHA256"]["metadata.gz"]
+ end
+
+ def test_binary_tag_decoded_in_inline_value
+ yaml = <<~YAML
+ ---
+ key: !binary "U0hBMQ=="
+ YAML
+
+ result = yaml_load(yaml)
+ assert_equal "SHA1", result["key"]
+ end
+
+ def test_binary_tag_checksums_yaml_roundtrip
+ # Simulates the checksums.yaml.gz format from older gems
+ yaml = <<~YAML
+ ---
+ !binary "U0hBMQ==":
+ metadata.gz: !binary |-
+ OWY4YTM5Y2MxOTc3Mzc5MWYzNzk1NjRmZjVlYzljYjY1MDQwYWIwMg==
+ data.tar.gz: !binary |-
+ ZTRmZGRhNjc1MWM5NmIwYzRhODFkYjI0OTlkMjY3ZjQ2MWNkMGM1ZA==
+ YAML
+
+ result = yaml_load(yaml)
+ assert_equal ["SHA1"], result.keys
+ assert_equal "9f8a39cc19773791f379564ff5ec9cb65040ab02", result["SHA1"]["metadata.gz"]
+ assert_equal "e4fdda6751c96b0c4a81db2499d267f461cd0c5d", result["SHA1"]["data.tar.gz"]
+ end
+
+ def test_binary_tag_decoded_in_sequence_item_inline
+ yaml = <<~YAML
+ ---
+ - !binary "U0hBMQ=="
+ YAML
+
+ result = yaml_load(yaml)
+ assert_equal ["SHA1"], result
+ end
+
+ def test_version_requirement_tag_always_permitted
+ yaml = <<~YAML
+ --- !ruby/object:Gem::Specification
+ name: escape
+ version: !ruby/object:Gem::Version
+ version: 0.0.4
+ required_ruby_version: !ruby/object:Gem::Version::Requirement
+ requirements:
+ - - ">"
+ - !ruby/object:Gem::Version
+ version: 0.0.0
+ version:
+ YAML
+
+ result = yaml_load(yaml)
+ assert_kind_of Gem::Specification, result
+ assert_equal "escape", result.name
+ assert_kind_of Gem::Requirement, result.required_ruby_version
+ end
end
diff --git a/test/rubygems/test_gem_security_trust_dir.rb b/test/rubygems/test_gem_security_trust_dir.rb
index cfde8e9d48..bd3dfb86c2 100644
--- a/test/rubygems/test_gem_security_trust_dir.rb
+++ b/test/rubygems/test_gem_security_trust_dir.rb
@@ -56,7 +56,7 @@ class TestGemSecurityTrustDir < Gem::TestCase
assert_path_exist trusted
- mask = 0o100600 & (~File.umask)
+ mask = 0o100600 & ~File.umask
assert_equal mask, File.stat(trusted).mode unless Gem.win_platform?
@@ -70,7 +70,7 @@ class TestGemSecurityTrustDir < Gem::TestCase
assert_path_exist @dest_dir
- mask = 0o040700 & (~File.umask)
+ mask = 0o040700 & ~File.umask
mask |= 0o200000 if RUBY_PLATFORM.include?("aix")
assert_equal mask, File.stat(@dest_dir).mode unless Gem.win_platform?
@@ -91,7 +91,7 @@ class TestGemSecurityTrustDir < Gem::TestCase
@trust_dir.verify
- mask = 0o40700 & (~File.umask)
+ mask = 0o40700 & ~File.umask
mask |= 0o200000 if RUBY_PLATFORM.include?("aix")
assert_equal mask, File.stat(@dest_dir).mode unless Gem.win_platform?
diff --git a/test/rubygems/test_gem_source_git.rb b/test/rubygems/test_gem_source_git.rb
index fef79a0743..b7b2c52f9a 100644
--- a/test/rubygems/test_gem_source_git.rb
+++ b/test/rubygems/test_gem_source_git.rb
@@ -65,6 +65,8 @@ class TestGemSourceGit < Gem::TestCase
end
def test_checkout_submodules
+ omit "JRuby on Windows hits git submodule path differences" if Gem.win_platform? && Gem.java_platform?
+
# We need to allow to checkout submodules with file:// protocol
# CVE-2022-39253
# https://lore.kernel.org/lkml/xmqq4jw1uku5.fsf@gitster.g/
diff --git a/test/rubygems/test_gem_source_list.rb b/test/rubygems/test_gem_source_list.rb
index 64353f8f90..5327b14db8 100644
--- a/test/rubygems/test_gem_source_list.rb
+++ b/test/rubygems/test_gem_source_list.rb
@@ -1,8 +1,7 @@
# frozen_string_literal: true
-require "rubygems"
-require "rubygems/source_list"
require_relative "helper"
+require "rubygems/source_list"
class TestGemSourceList < Gem::TestCase
def setup
@@ -116,4 +115,128 @@ class TestGemSourceList < Gem::TestCase
@sl.delete Gem::Source.new(@uri)
assert_equal @sl.sources, []
end
+
+ def test_prepend_new_source
+ uri2 = "http://example2"
+ source2 = Gem::Source.new(uri2)
+
+ result = @sl.prepend(uri2)
+
+ assert_kind_of Gem::Source, result
+ assert_kind_of Gem::URI, result.uri
+ assert_equal uri2, result.uri.to_s
+ assert_equal [source2, @source], @sl.sources
+ end
+
+ def test_prepend_existing_source
+ uri2 = "http://example2"
+ source2 = Gem::Source.new(uri2)
+ @sl << uri2
+
+ assert_equal [@source, source2], @sl.sources
+
+ result = @sl.prepend(uri2)
+
+ assert_kind_of Gem::Source, result
+ assert_kind_of Gem::URI, result.uri
+ assert_equal uri2, result.uri.to_s
+ assert_equal [source2, @source], @sl.sources
+ end
+
+ def test_prepend_alias_behaves_like_unshift
+ sl = Gem::SourceList.new
+
+ uri1 = "http://one"
+ uri2 = "http://two"
+
+ source1 = sl << uri1
+ source2 = sl << uri2
+
+ # move existing to front
+ result = sl.prepend(uri2)
+
+ assert_kind_of Gem::Source, result
+ assert_equal [source2, source1], sl.sources
+
+ # and again with the other
+ result = sl.prepend(uri1)
+ assert_equal [source1, source2], sl.sources
+ end
+
+ def test_append_method_new_source
+ sl = Gem::SourceList.new
+
+ uri1 = "http://example1"
+
+ result = sl.append(uri1)
+
+ assert_kind_of Gem::Source, result
+ assert_kind_of Gem::URI, result.uri
+ assert_equal uri1, result.uri.to_s
+ assert_equal [result], sl.sources
+ end
+
+ def test_append_method_existing_moves_to_end
+ sl = Gem::SourceList.new
+
+ uri1 = "http://example1"
+ uri2 = "http://example2"
+
+ s1 = sl << uri1
+ s2 = sl << uri2
+
+ # list is [s1, s2]; appending s1 should move it to end => [s2, s1]
+ result = sl.append(uri1)
+
+ assert_equal s1, result
+ assert_equal [s2, s1], sl.sources
+ end
+
+ def test_prepend_with_gem_source_object
+ sl = Gem::SourceList.new
+
+ uri1 = "http://example1"
+ uri2 = "http://example2"
+ source1 = Gem::Source.new(uri1)
+ source2 = Gem::Source.new(uri2)
+
+ # Add first source
+ sl << source1
+
+ # Prepend with Gem::Source object
+ result = sl.prepend(source2)
+
+ assert_equal source2, result
+ assert_equal [source2, source1], sl.sources
+
+ # Prepend existing source - should move to front
+ result = sl.prepend(source1)
+
+ assert_equal source1, result
+ assert_equal [source1, source2], sl.sources
+ end
+
+ def test_append_with_gem_source_object
+ sl = Gem::SourceList.new
+
+ uri1 = "http://example1"
+ uri2 = "http://example2"
+ source1 = Gem::Source.new(uri1)
+ source2 = Gem::Source.new(uri2)
+
+ # Add first source
+ sl << source1
+
+ # Append with Gem::Source object
+ result = sl.append(source2)
+
+ assert_equal source2, result
+ assert_equal [source1, source2], sl.sources
+
+ # Append existing source - should move to end
+ result = sl.append(source1)
+
+ assert_equal source1, result
+ assert_equal [source2, source1], sl.sources
+ end
end
diff --git a/test/rubygems/test_gem_source_local.rb b/test/rubygems/test_gem_source_local.rb
index e9d7f45482..6062173629 100644
--- a/test/rubygems/test_gem_source_local.rb
+++ b/test/rubygems/test_gem_source_local.rb
@@ -63,6 +63,30 @@ class TestGemSourceLocal < Gem::TestCase
assert_equal "a-2.a", @sl.find_gem("a", req, true).full_name
end
+ def test_find_all_gems
+ _, a2_gem = util_gem "a", "2"
+ FileUtils.mv a2_gem, @tempdir
+
+ results = @sl.find_all_gems("a")
+ assert_equal ["a-1", "a-2"], results.map(&:full_name).sort
+ end
+
+ def test_find_all_gems_excludes_prerelease_by_default
+ results = @sl.find_all_gems("a")
+ assert_equal ["a-1"], results.map(&:full_name)
+ end
+
+ def test_find_all_gems_includes_prerelease_when_requested
+ results = @sl.find_all_gems("a", Gem::Requirement.create(">= 0"), true)
+ assert_equal ["a-1", "a-2.a"], results.map(&:full_name).sort
+ end
+
+ def test_find_all_gems_includes_prerelease_when_requirement_is_prerelease
+ req = Gem::Requirement.create("= 2.a")
+ results = @sl.find_all_gems("a", req)
+ assert_equal ["a-2.a"], results.map(&:full_name)
+ end
+
def test_fetch_spec
s = @sl.fetch_spec @a.name_tuple
assert_equal s, @a
diff --git a/test/rubygems/test_gem_specification.rb b/test/rubygems/test_gem_specification.rb
index 697a26338c..79be0c996d 100644
--- a/test/rubygems/test_gem_specification.rb
+++ b/test/rubygems/test_gem_specification.rb
@@ -16,7 +16,7 @@ rubygems_version: "1.0"
name: keyedlist
version: !ruby/object:Gem::Version
version: 0.4.0
-date: 2004-03-28 15:37:49.828000 +02:00
+date: 1980-01-02 00:00:00 UTC
platform:
summary: A Hash which automatically computes keys.
require_paths:
@@ -33,7 +33,6 @@ has_rdoc: true
Gem::Specification.new do |s|
s.name = %q{keyedlist}
s.version = %q{0.4.0}
- s.has_rdoc = true
s.summary = %q{A Hash which automatically computes keys.}
s.files = [%q{lib/keyedlist.rb}]
s.require_paths = [%q{lib}]
@@ -75,7 +74,7 @@ end
def assert_date(date)
assert_kind_of Time, date
assert_equal [0, 0, 0], [date.hour, date.min, date.sec]
- assert_operator (Gem::Specification::TODAY..Time.now), :cover?, date
+ assert_equal Time.at(Gem::DEFAULT_SOURCE_DATE_EPOCH).utc, date
end
def setup
@@ -818,7 +817,7 @@ dependencies: []
write_file full_path do |io|
io.write @a2.to_ruby_for_cache
end
- rescue Errno::EINVAL
+ rescue Errno::EINVAL, Errno::EACCES
pend "cannot create '#{full_path}' on this platform"
end
@@ -837,7 +836,7 @@ dependencies: []
write_file full_path do |io|
io.write @a2.to_ruby_for_cache
end
- rescue Errno::EINVAL
+ rescue Errno::EINVAL, Errno::EACCES
pend "cannot create '#{full_path}' on this platform"
end
@@ -856,7 +855,7 @@ dependencies: []
write_file full_path do |io|
io.write @a2.to_ruby_for_cache
end
- rescue Errno::EINVAL
+ rescue Errno::EINVAL, Errno::EACCES
pend "cannot create '#{full_path}' on this platform"
end
@@ -1029,7 +1028,7 @@ dependencies: []
gem = "mingw"
v = "1.1.1"
- platforms = ["x86-mingw32", "x64-mingw32"]
+ platforms = ["x86-mingw32", "x64-mingw-ucrt"]
# create specs
platforms.each do |plat|
@@ -1248,12 +1247,37 @@ dependencies: []
end
def test_initialize_nil_version
- expected = "nil versions are discouraged and will be deprecated in Rubygems 4\n"
- actual_stdout, actual_stderr = capture_output do
- Gem::Specification.new.version = nil
+ spec = Gem::Specification.new
+ spec.name = "test-name"
+
+ assert_nil spec.version
+ spec.version = nil
+ assert_nil spec.version
+
+ spec.summary = "test gem"
+ spec.authors = ["test author"]
+ e = assert_raise Gem::InvalidSpecificationException do
+ spec.validate
end
- assert_empty actual_stdout
- assert_equal(expected, actual_stderr)
+ assert_match("missing value for attribute version", e.message)
+ end
+
+ def test_set_version_to_nil_after_setting_version
+ spec = Gem::Specification.new
+ spec.name = "test-name"
+
+ assert_nil spec.version
+ spec.version = "1.0.0"
+ assert_equal "1.0.0", spec.version.to_s
+ spec.version = nil
+ assert_nil spec.version
+
+ spec.summary = "test gem"
+ spec.authors = ["test author"]
+ e = assert_raise Gem::InvalidSpecificationException do
+ spec.validate
+ end
+ assert_match("missing value for attribute version", e.message)
end
def test__dump
@@ -1554,13 +1578,21 @@ dependencies: []
ext_spec
_, err = capture_output do
- refute @ext.contains_requirable_file? "nonexistent"
+ if RUBY_ENGINE == "jruby"
+ refute @ext.ignored?
+ else
+ refute @ext.contains_requirable_file? "nonexistent"
+ end
end
- expected = "Ignoring ext-1 because its extensions are not built. " \
- "Try: gem pristine ext --version 1\n"
+ if RUBY_ENGINE == "jruby"
+ assert_equal "", err
+ else
+ expected = "Ignoring ext-1 because its extensions are not built. " \
+ "Try: gem pristine ext --version 1\n"
- assert_equal expected, err
+ assert_equal expected, err
+ end
end
def test_contains_requirable_file_eh_extension_java_platform
@@ -2216,9 +2248,9 @@ dependencies: []
s1 = util_spec "a", "1"
s2 = util_spec "b", "1"
- assert_equal(-1, (s1 <=> s2))
- assert_equal(0, (s1 <=> s1)) # rubocop:disable Lint/BinaryOperatorWithIdenticalOperands
- assert_equal(1, (s2 <=> s1))
+ assert_equal(-1, s1 <=> s2)
+ assert_equal(0, s1 <=> s1) # rubocop:disable Lint/BinaryOperatorWithIdenticalOperands
+ assert_equal(1, s2 <=> s1)
end
def test_spaceship_platform
@@ -2227,18 +2259,18 @@ dependencies: []
s.platform = Gem::Platform.new "x86-my_platform1"
end
- assert_equal(-1, (s1 <=> s2))
- assert_equal(0, (s1 <=> s1)) # rubocop:disable Lint/BinaryOperatorWithIdenticalOperands
- assert_equal(1, (s2 <=> s1))
+ assert_equal(-1, s1 <=> s2)
+ assert_equal(0, s1 <=> s1) # rubocop:disable Lint/BinaryOperatorWithIdenticalOperands
+ assert_equal(1, s2 <=> s1)
end
def test_spaceship_version
s1 = util_spec "a", "1"
s2 = util_spec "a", "2"
- assert_equal(-1, (s1 <=> s2))
- assert_equal(0, (s1 <=> s1)) # rubocop:disable Lint/BinaryOperatorWithIdenticalOperands
- assert_equal(1, (s2 <=> s1))
+ assert_equal(-1, s1 <=> s2)
+ assert_equal(0, s1 <=> s1) # rubocop:disable Lint/BinaryOperatorWithIdenticalOperands
+ assert_equal(1, s2 <=> s1)
end
def test_spec_file
@@ -2671,27 +2703,7 @@ end
@a1.validate
end
- expected = <<-EXPECTED
-#{w}: prerelease dependency on b (>= 1.0.rc1) is not recommended
-#{w}: prerelease dependency on c (>= 2.0.rc2, development) is not recommended
-#{w}: open-ended dependency on i (>= 1.2) is not recommended
- if i is semantically versioned, use:
- add_runtime_dependency "i", "~> 1.2"
-#{w}: open-ended dependency on j (>= 1.2.3) is not recommended
- if j is semantically versioned, use:
- add_runtime_dependency "j", "~> 1.2", ">= 1.2.3"
-#{w}: open-ended dependency on k (> 1.2) is not recommended
- if k is semantically versioned, use:
- add_runtime_dependency "k", "~> 1.2", "> 1.2"
-#{w}: open-ended dependency on l (> 1.2.3) is not recommended
- if l is semantically versioned, use:
- add_runtime_dependency "l", "~> 1.2", "> 1.2.3"
-#{w}: open-ended dependency on o (>= 0) is not recommended
- use a bounded requirement, such as "~> x.y"
-#{w}: See https://guides.rubygems.org/specification-reference/ for help
- EXPECTED
-
- assert_equal expected, @ui.error, "warning"
+ assert_equal "", @ui.error, "warning"
end
end
@@ -2808,14 +2820,13 @@ duplicate dependency on c (>= 1.2.3, development), (~> 1.2) use:
Dir.chdir @tempdir do
@a1.add_dependency @a1.name, "1"
- use_ui @ui do
+ e = assert_raise Gem::InvalidSpecificationException do
@a1.validate
end
- assert_equal <<-EXPECTED, @ui.error
-#{w}: Self referencing dependency is unnecessary and strongly discouraged.
-#{w}: See https://guides.rubygems.org/specification-reference/ for help
- EXPECTED
+ expected = "Dependencies of this gem include a self-reference."
+
+ assert_equal expected, e.message
end
end
@@ -2883,6 +2894,61 @@ duplicate dependency on c (>= 1.2.3, development), (~> 1.2) use:
end
end
+ def test_validate_extension_require_relative_warning
+ util_setup_validate
+
+ Dir.chdir @tempdir do
+ @a1.extensions = ["ext/a/extconf.rb"]
+ @a1.files = %w[lib/code.rb lib/a.rb ext/a/extconf.rb]
+
+ File.write File.join("lib", "a.rb"), 'require_relative "a/a"'
+
+ use_ui @ui do
+ @a1.validate
+ end
+
+ assert_match(%r{require_relative "a/a"}, @ui.error)
+ assert_match(/will break in RubyGems 4\.2/, @ui.error)
+ assert_match(/Use `require` instead of `require_relative`/, @ui.error)
+ end
+ end
+
+ def test_validate_extension_require_relative_no_warning_when_rb_exists
+ util_setup_validate
+
+ Dir.chdir @tempdir do
+ @a1.extensions = ["ext/a/extconf.rb"]
+ @a1.files = %w[lib/code.rb lib/a.rb lib/a/a.rb ext/a/extconf.rb]
+
+ FileUtils.mkdir_p File.join("lib", "a")
+ File.write File.join("lib", "a.rb"), 'require_relative "a/a"'
+ File.write File.join("lib", "a", "a.rb"), ""
+
+ use_ui @ui do
+ @a1.validate
+ end
+
+ refute_match(/require_relative/, @ui.error)
+ end
+ end
+
+ def test_validate_extension_require_relative_no_warning_without_extensions
+ util_setup_validate
+
+ Dir.chdir @tempdir do
+ @a1.extensions = []
+ @a1.files = %w[lib/code.rb lib/a.rb]
+
+ File.write File.join("lib", "a.rb"), 'require_relative "a/a"'
+
+ use_ui @ui do
+ @a1.validate
+ end
+
+ refute_match(/require_relative/, @ui.error)
+ end
+ end
+
def test_validate_description
util_setup_validate
@@ -3009,6 +3075,65 @@ duplicate dependency on c (>= 1.2.3, development), (~> 1.2) use:
assert_match "#{w}: bin/exec is missing #! line\n", @ui.error, "error"
end
+ def test_validate_executables_with_space
+ util_setup_validate
+
+ FileUtils.mkdir_p File.join(@tempdir, "bin")
+ File.write File.join(@tempdir, "bin", "echo hax"), "#!/usr/bin/env ruby\n"
+
+ @a1.executables = ["echo hax"]
+
+ e = assert_raise Gem::InvalidSpecificationException do
+ use_ui @ui do
+ Dir.chdir @tempdir do
+ @a1.validate
+ end
+ end
+ end
+
+ assert_match "executable \"echo hax\" contains invalid characters", e.message
+ end
+
+ def test_validate_executables_with_path_separator
+ util_setup_validate
+
+ FileUtils.mkdir_p File.join(@tempdir, "bin")
+ File.write File.join(@tempdir, "exe"), "#!/usr/bin/env ruby\n"
+
+ @a1.executables = Gem.win_platform? ? ["..\\exe"] : ["../exe"]
+
+ e = assert_raise Gem::InvalidSpecificationException do
+ use_ui @ui do
+ Dir.chdir @tempdir do
+ @a1.validate
+ end
+ end
+ end
+
+ assert_match "executable \"#{Gem.win_platform? ? "..\\exe" : "../exe"}\" contains invalid characters", e.message
+ end
+
+ def test_validate_executables_with_path_list_separator
+ sep = Gem.win_platform? ? ";" : ":"
+
+ util_setup_validate
+
+ FileUtils.mkdir_p File.join(@tempdir, "bin")
+ File.write File.join(@tempdir, "bin", "foo#{sep}bar"), "#!/usr/bin/env ruby\n"
+
+ @a1.executables = ["foo#{sep}bar"]
+
+ e = assert_raise Gem::InvalidSpecificationException do
+ use_ui @ui do
+ Dir.chdir @tempdir do
+ @a1.validate
+ end
+ end
+ end
+
+ assert_match "executable \"foo#{sep}bar\" contains invalid characters", e.message
+ end
+
def test_validate_empty_require_paths
util_setup_validate
@@ -3664,8 +3789,6 @@ Did you mean 'Ruby'?
end
def test__load_fixes_Date_objects
- pend "Marshal.load of links and floats is broken on truffleruby, see https://github.com/oracle/truffleruby/issues/3747" if RUBY_ENGINE == "truffleruby"
-
spec = util_spec "a", 1
spec.instance_variable_set :@date, Date.today
@@ -3892,7 +4015,11 @@ end
def test_missing_extensions_eh
ext_spec
- assert @ext.missing_extensions?
+ if RUBY_ENGINE == "jruby"
+ refute @ext.missing_extensions?
+ else
+ assert @ext.missing_extensions?
+ end
extconf_rb = File.join @ext.gem_dir, @ext.extensions.first
FileUtils.mkdir_p File.dirname extconf_rb
diff --git a/test/rubygems/test_gem_stub_specification.rb b/test/rubygems/test_gem_stub_specification.rb
index 4b2d4c570a..6c07480c7f 100644
--- a/test/rubygems/test_gem_stub_specification.rb
+++ b/test/rubygems/test_gem_stub_specification.rb
@@ -68,13 +68,21 @@ class TestStubSpecification < Gem::TestCase
def test_contains_requirable_file_eh_extension
stub_with_extension do |stub|
_, err = capture_output do
- refute stub.contains_requirable_file? "nonexistent"
+ if RUBY_ENGINE == "jruby"
+ refute stub.ignored?
+ else
+ refute stub.contains_requirable_file? "nonexistent"
+ end
end
- expected = "Ignoring stub_e-2 because its extensions are not built. " \
- "Try: gem pristine stub_e --version 2\n"
+ if RUBY_ENGINE == "jruby"
+ assert_equal "", err
+ else
+ expected = "Ignoring stub_e-2 because its extensions are not built. " \
+ "Try: gem pristine stub_e --version 2\n"
- assert_equal expected, err
+ assert_equal expected, err
+ end
end
end
@@ -137,7 +145,11 @@ class TestStubSpecification < Gem::TestCase
end
end
- assert stub.missing_extensions?
+ if RUBY_ENGINE == "jruby"
+ refute stub.missing_extensions?
+ else
+ assert stub.missing_extensions?
+ end
stub.build_extensions
@@ -209,7 +221,7 @@ class TestStubSpecification < Gem::TestCase
end
def stub_with_version
- spec = File.join @gemhome, "specifications", "stub_e-2.gemspec"
+ spec = File.join @gemhome, "specifications", "stub_v-with-version.gemspec"
File.open spec, "w" do |io|
io.write <<~STUB
# -*- encoding: utf-8 -*-
@@ -232,7 +244,7 @@ class TestStubSpecification < Gem::TestCase
end
def stub_without_version
- spec = File.join @gemhome, "specifications", "stub-2.gemspec"
+ spec = File.join @gemhome, "specifications", "stub_v-without-version.gemspec"
File.open spec, "w" do |io|
io.write <<~STUB
# -*- encoding: utf-8 -*-
diff --git a/test/rubygems/test_gem_text.rb b/test/rubygems/test_gem_text.rb
index 8e99610946..60739e6131 100644
--- a/test/rubygems/test_gem_text.rb
+++ b/test/rubygems/test_gem_text.rb
@@ -100,4 +100,21 @@ Without the wrapping, the text might not look good in the RSS feed.
def test_clean_text
assert_equal ".]2;nyan.", clean_text("\e]2;nyan\a")
end
+
+ def test_clean_text_strips_c1_control_characters
+ text = [0x41, 0x9b, 0x42].pack("U*") # "A", CSI (U+009B), "B"
+ assert_equal "A.B", clean_text(text)
+ end
+
+ def test_clean_text_preserves_multibyte_characters
+ # U+0400 encodes to bytes D0 80, whose 0x80 continuation byte must not be
+ # mistaken for a C1 control byte. NEL (U+0085) is stripped.
+ text = [0x400, 0x85].pack("U*")
+ assert_equal [0x400, 0x2e].pack("U*"), clean_text(text)
+ end
+
+ def test_clean_text_passes_through_non_unicode_encodings
+ text = "x\x9by".dup.force_encoding("ISO-8859-1")
+ assert_equal text, clean_text(text)
+ end
end
diff --git a/test/rubygems/test_gem_uri.rb b/test/rubygems/test_gem_uri.rb
index 1253ebc6de..ce633c99b6 100644
--- a/test/rubygems/test_gem_uri.rb
+++ b/test/rubygems/test_gem_uri.rb
@@ -21,7 +21,7 @@ class TestUri < Gem::TestCase
end
def test_redacted_with_user_x_oauth_basic
- assert_equal "https://REDACTED:x-oauth-basic@example.com", Gem::Uri.new("https://token:x-oauth-basic@example.com").redacted.to_s
+ assert_equal "https://REDACTED@example.com", Gem::Uri.new("https://token:x-oauth-basic@example.com").redacted.to_s
end
def test_redacted_without_credential
diff --git a/test/rubygems/test_gem_util.rb b/test/rubygems/test_gem_util.rb
index 608210a903..9688d066db 100644
--- a/test/rubygems/test_gem_util.rb
+++ b/test/rubygems/test_gem_util.rb
@@ -13,17 +13,6 @@ class TestGemUtil < Gem::TestCase
end
end
- def test_silent_system
- pend if Gem.java_platform?
- Gem::Deprecate.skip_during do
- out, err = capture_output do
- Gem::Util.silent_system(*ruby_with_rubygems_in_load_path, "-e", 'puts "hello"; warn "hello"')
- end
- assert_empty out
- assert_empty err
- end
- end
-
def test_traverse_parents
FileUtils.mkdir_p "a/b/c"
diff --git a/test/rubygems/test_gem_util_atomic_file_writer.rb b/test/rubygems/test_gem_util_atomic_file_writer.rb
new file mode 100644
index 0000000000..e011a38ad4
--- /dev/null
+++ b/test/rubygems/test_gem_util_atomic_file_writer.rb
@@ -0,0 +1,12 @@
+# frozen_string_literal: true
+
+require_relative "helper"
+require "rubygems/util/atomic_file_writer"
+
+class TestGemUtilAtomicFileWriter < Gem::TestCase
+ def test_external_encoding
+ Gem::AtomicFileWriter.open(File.join(@tempdir, "test.txt")) do |file|
+ assert_equal(Encoding::ASCII_8BIT, file.external_encoding)
+ end
+ end
+end
diff --git a/test/rubygems/test_gem_version.rb b/test/rubygems/test_gem_version.rb
index cf771bc5a1..f58359e54c 100644
--- a/test/rubygems/test_gem_version.rb
+++ b/test/rubygems/test_gem_version.rb
@@ -7,6 +7,11 @@ class TestGemVersion < Gem::TestCase
class V < ::Gem::Version
end
+ def test_nil_is_zero
+ zero = Gem::Version.create nil
+ assert_equal Gem::Version.create(0), zero
+ end
+
def test_bump
assert_bumped_version_equal "5.3", "5.2.4"
end
@@ -35,13 +40,6 @@ class TestGemVersion < Gem::TestCase
assert_same real, Gem::Version.create(real)
- expected = "nil versions are discouraged and will be deprecated in Rubygems 4\n"
- actual_stdout, actual_stderr = capture_output do
- assert_nil Gem::Version.create(nil)
- end
- assert_empty actual_stdout
- assert_equal(expected, actual_stderr)
-
assert_equal v("5.1"), Gem::Version.create("5.1")
ver = "1.1"
@@ -51,13 +49,7 @@ class TestGemVersion < Gem::TestCase
def test_class_correct
assert_equal true, Gem::Version.correct?("5.1")
assert_equal false, Gem::Version.correct?("an incorrect version")
-
- expected = "nil versions are discouraged and will be deprecated in Rubygems 4\n"
- actual_stdout, actual_stderr = capture_output do
- Gem::Version.correct?(nil)
- end
- assert_empty actual_stdout
- assert_equal(expected, actual_stderr)
+ assert_equal true, Gem::Version.correct?(nil)
end
def test_class_new_subclass
@@ -162,33 +154,36 @@ class TestGemVersion < Gem::TestCase
assert_equal(-1, v("5.a") <=> v("5.0.0.rc2"))
assert_equal(1, v("5.x") <=> v("5.0.0.rc2"))
- assert_equal(0, v("1.9.3") <=> "1.9.3")
- assert_equal(1, v("1.9.3") <=> "1.9.2.99")
- assert_equal(-1, v("1.9.3") <=> "1.9.3.1")
-
- assert_nil v("1.0") <=> "whatever"
+ [
+ [0, "1.9.3"],
+ [1, "1.9.2.99"],
+ [-1, "1.9.3.1"],
+ [nil, "whatever"],
+ ].each do |cmp, string_ver|
+ assert_equal(cmp, v("1.9.3") <=> string_ver)
+ end
end
def test_approximate_recommendation
- assert_approximate_equal "~> 1.0", "1"
+ assert_approximate_equal ">= 1.0", "1"
assert_approximate_satisfies_itself "1"
- assert_approximate_equal "~> 1.0", "1.0"
+ assert_approximate_equal ">= 1.0", "1.0"
assert_approximate_satisfies_itself "1.0"
- assert_approximate_equal "~> 1.2", "1.2"
+ assert_approximate_equal ">= 1.2", "1.2"
assert_approximate_satisfies_itself "1.2"
- assert_approximate_equal "~> 1.2", "1.2.0"
+ assert_approximate_equal ">= 1.2", "1.2.0"
assert_approximate_satisfies_itself "1.2.0"
- assert_approximate_equal "~> 1.2", "1.2.3"
+ assert_approximate_equal ">= 1.2", "1.2.3"
assert_approximate_satisfies_itself "1.2.3"
- assert_approximate_equal "~> 1.2.a", "1.2.3.a.4"
+ assert_approximate_equal ">= 1.2.a", "1.2.3.a.4"
assert_approximate_satisfies_itself "1.2.3.a.4"
- assert_approximate_equal "~> 1.9.a", "1.9.0.dev"
+ assert_approximate_equal ">= 1.9.a", "1.9.0.dev"
assert_approximate_satisfies_itself "1.9.0.dev"
end
@@ -205,6 +200,51 @@ class TestGemVersion < Gem::TestCase
assert_less_than "1.0.0-1", "1"
end
+ def test_sort_key_is_computed_on_regular_release
+ refute_nil v("9.8.7").send(:sort_key)
+ end
+
+ def test_sort_key_is_computed_on_security_release
+ refute_nil v("9.8.7.1").send(:sort_key)
+ end
+
+ def test_sort_key_is_not_computed_on_prerelease
+ assert_nil v("9.8.7.pre1").send(:sort_key)
+ end
+
+ def test_sort_key_is_not_computed_on_version_with_more_segments
+ assert_nil v("1.1.1.1.1.1.1").send(:sort_key)
+ end
+
+ def test_sort_key_is_not_computed_on_huge_numbers
+ assert_nil v("2.30.1.250000").send(:sort_key)
+ end
+
+ def test_sort_key_on_timestamped_version
+ a = v("1.0.0")
+ b = v("0.0.1.20220404083012")
+
+ assert_operator a, :>, b
+ end
+
+ def test_sort_key_when_segment_is_higher_than_radix
+ a = v("0.7.0")
+ b = v("0.6.63000")
+
+ assert_operator(a, :>, b)
+ end
+
+ def test_sort_key_is_used_for_comparison
+ a = v("18.0.1")
+ b = v("18.0.2")
+
+ # Ensure the slow path isn't getting hit
+ a.instance_variable_set(:@version, nil)
+ a.instance_variable_set(:@canonical_segments, nil)
+
+ assert_operator(a, :<, b)
+ end
+
# modifying the segments of a version should not affect the segments of the cached version object
def test_segments
v("9.8.7").segments[2] += 1
diff --git a/test/rubygems/test_project_sanity.rb b/test/rubygems/test_project_sanity.rb
index 8f23b2d8c0..3b08d1ec7b 100644
--- a/test/rubygems/test_project_sanity.rb
+++ b/test/rubygems/test_project_sanity.rb
@@ -12,6 +12,7 @@ class TestGemProjectSanity < Gem::TestCase
def test_manifest_is_up_to_date
pend unless File.exist?("#{root}/Rakefile")
+ omit "JRuby on Windows cannot exec the bin/rake shebang" if Gem.win_platform? && Gem.java_platform?
rake = "#{root}/bin/rake"
_, status = Open3.capture2e(rake, "check_manifest")
@@ -37,6 +38,8 @@ class TestGemProjectSanity < Gem::TestCase
end
def test_require_rubygems_package
+ omit "JRuby on Windows fails to spawn ruby --disable-gems here" if Gem.win_platform? && Gem.java_platform?
+
err, status = Open3.capture2e(*ruby_with_rubygems_in_load_path, "--disable-gems", "-e", "require \"rubygems/package\"")
assert status.success?, err
diff --git a/test/rubygems/test_require.rb b/test/rubygems/test_require.rb
index f63c23c315..db86a30905 100644
--- a/test/rubygems/test_require.rb
+++ b/test/rubygems/test_require.rb
@@ -431,6 +431,22 @@ class TestGemRequire < Gem::TestCase
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")
@@ -460,6 +476,7 @@ class TestGemRequire < Gem::TestCase
def test_realworld_default_gem
omit "this test can't work under ruby-core setup" if ruby_repo?
+ omit "JRuby on Windows does not register json as a default gem the same way" if Gem.win_platform? && Gem.java_platform?
cmd = <<-RUBY
$stderr = $stdout
@@ -770,6 +787,8 @@ class TestGemRequire < Gem::TestCase
end
def test_require_does_not_crash_when_utilizing_bundler_version_finder
+ omit "JRuby on Windows hits a different require path" if Gem.win_platform? && Gem.java_platform?
+
a1 = util_spec "a", "1.1", { "bundler" => ">= 0" }
a2 = util_spec "a", "1.2", { "bundler" => ">= 0" }
b1 = util_spec "bundler", "2.3.7"
diff --git a/test/rubygems/test_rubygems.rb b/test/rubygems/test_rubygems.rb
index ec195b65cd..6566b5981e 100644
--- a/test/rubygems/test_rubygems.rb
+++ b/test/rubygems/test_rubygems.rb
@@ -10,6 +10,7 @@ class GemTest < Gem::TestCase
def test_operating_system_other_exceptions
pend "does not apply to truffleruby" if RUBY_ENGINE == "truffleruby"
+ omit "JRuby on Windows loads a different operating_system defaults file" if Gem.win_platform? && Gem.java_platform?
path = util_install_operating_system_rb <<-RUBY
intentionally_not_implemented_method
diff --git a/test/rubygems/test_webauthn_listener.rb b/test/rubygems/test_webauthn_listener.rb
index 08edabceb2..ded4128928 100644
--- a/test/rubygems/test_webauthn_listener.rb
+++ b/test/rubygems/test_webauthn_listener.rb
@@ -17,7 +17,7 @@ class WebauthnListenerTest < Gem::TestCase
super
end
- def test_listener_thread_retreives_otp_code
+ def test_listener_thread_retrieves_otp_code
thread = Gem::GemcutterUtilities::WebauthnListener.listener_thread(Gem.host, @server)
Gem::MockBrowser.get Gem::URI("http://localhost:#{@port}?code=xyz")