From 85d461456c154d7b4a72b20369e0d65d7880ce02 Mon Sep 17 00:00:00 2001 From: hsbt Date: Mon, 27 Aug 2018 10:05:04 +0000 Subject: [PATCH] Merge master branch from rubygems upstream. * It's preparation to release RubyGems 3.0.0.beta2 and Ruby 2.6.0 preview 3. * https://github.com/rubygems/rubygems/compare/v3.0.0.beta1...fad2eb15a282b19dfcb4b48bc95b8b39ebb4511f git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@64555 b2dd03c8-39d4-4d8f-98ff-823fe69b080e --- lib/rubygems.rb | 7 +- lib/rubygems/basic_specification.rb | 4 +- lib/rubygems/command_manager.rb | 11 ++ lib/rubygems/commands/build_command.rb | 6 +- lib/rubygems/commands/cert_command.rb | 2 +- lib/rubygems/commands/cleanup_command.rb | 10 +- lib/rubygems/commands/install_command.rb | 7 + lib/rubygems/commands/open_command.rb | 6 + lib/rubygems/commands/pristine_command.rb | 9 + lib/rubygems/commands/push_command.rb | 39 ++++- lib/rubygems/commands/setup_command.rb | 6 +- lib/rubygems/commands/signin_command.rb | 2 +- lib/rubygems/commands/uninstall_command.rb | 2 +- lib/rubygems/dependency_installer.rb | 2 +- lib/rubygems/ext/builder.rb | 1 - lib/rubygems/indexer.rb | 2 +- lib/rubygems/install_update_options.rb | 2 +- lib/rubygems/installer.rb | 35 +++- lib/rubygems/package.rb | 10 +- lib/rubygems/package/tar_reader/entry.rb | 20 ++- lib/rubygems/path_support.rb | 16 +- lib/rubygems/remote_fetcher.rb | 25 ++- lib/rubygems/request.rb | 1 - lib/rubygems/request/connection_pools.rb | 1 - lib/rubygems/request_set.rb | 2 +- lib/rubygems/requirement.rb | 2 +- lib/rubygems/security/signer.rb | 41 +++-- lib/rubygems/spec_fetcher.rb | 2 +- lib/rubygems/specification.rb | 41 +++-- lib/rubygems/specification_policy.rb | 132 +++++++-------- lib/rubygems/test_case.rb | 18 ++ lib/rubygems/util.rb | 14 +- lib/rubygems/version.rb | 9 +- test/rubygems/test_gem.rb | 2 +- test/rubygems/test_gem_command_manager.rb | 6 + .../test_gem_commands_build_command.rb | 95 +++++++++-- .../test_gem_commands_cleanup_command.rb | 27 +++ .../test_gem_commands_install_command.rb | 18 +- .../test_gem_commands_open_command.rb | 29 ++++ .../test_gem_commands_pristine_command.rb | 33 ++++ .../test_gem_commands_push_command.rb | 20 +++ .../test_gem_commands_query_command.rb | 2 +- test/rubygems/test_gem_ext_cmake_builder.rb | 1 + test/rubygems/test_gem_gemcutter_utilities.rb | 4 - test/rubygems/test_gem_installer.rb | 10 +- test/rubygems/test_gem_package.rb | 77 ++++++--- .../test_gem_package_tar_reader_entry.rb | 11 ++ test/rubygems/test_gem_path_support.rb | 17 ++ test/rubygems/test_gem_remote_fetcher.rb | 64 +++++++- test/rubygems/test_gem_resolver.rb | 2 +- test/rubygems/test_gem_security_signer.rb | 4 +- test/rubygems/test_gem_server.rb | 8 +- test/rubygems/test_gem_specification.rb | 155 ++++++++++++++++-- test/rubygems/test_gem_text.rb | 4 + test/rubygems/test_gem_util.rb | 2 + test/rubygems/test_gem_version.rb | 32 +++- 56 files changed, 868 insertions(+), 242 deletions(-) diff --git a/lib/rubygems.rb b/lib/rubygems.rb index d263f29dd2..858d910610 100644 --- a/lib/rubygems.rb +++ b/lib/rubygems.rb @@ -7,7 +7,6 @@ #++ require 'rbconfig' -require 'thread' module Gem VERSION = "3.0.0.beta1" @@ -526,8 +525,9 @@ An Array (#{env.inspect}) was passed in from #{caller[3]} end def self.find_files_from_load_path glob # :nodoc: + glob_with_suffixes = "#{glob}#{Gem.suffix_pattern}" $LOAD_PATH.map { |load_path| - Dir["#{File.expand_path glob, load_path}#{Gem.suffix_pattern}"] + Gem::Util.glob_files_in_dir(glob_with_suffixes, load_path) }.flatten.select { |file| File.file? file.untaint } end @@ -1119,8 +1119,9 @@ An Array (#{env.inspect}) was passed in from #{caller[3]} path = "rubygems_plugin" files = [] + glob = "#{path}#{Gem.suffix_pattern}" $LOAD_PATH.each do |load_path| - globbed = Dir["#{File.expand_path path, load_path}#{Gem.suffix_pattern}"] + globbed = Gem::Util.glob_files_in_dir(glob, load_path) globbed.each do |load_path_file| files << load_path_file if File.file?(load_path_file.untaint) diff --git a/lib/rubygems/basic_specification.rb b/lib/rubygems/basic_specification.rb index 72954a7863..54242983ff 100644 --- a/lib/rubygems/basic_specification.rb +++ b/lib/rubygems/basic_specification.rb @@ -152,7 +152,7 @@ class Gem::BasicSpecification # The path to the data directory for this gem. def datadir -# TODO: drop the extra ", gem_name" which is uselessly redundant + # TODO: drop the extra ", gem_name" which is uselessly redundant File.expand_path(File.join(gems_dir, full_name, "data", name)).untaint end @@ -282,7 +282,7 @@ class Gem::BasicSpecification self.raw_require_paths.first end else - "lib" # default value for require_paths for bundler/inline + "lib" # default value for require_paths for bundler/inline end "#{self.full_gem_path}/#{dirs}".dup.untaint diff --git a/lib/rubygems/command_manager.rb b/lib/rubygems/command_manager.rb index e22dc5deb3..3dc5779c91 100644 --- a/lib/rubygems/command_manager.rb +++ b/lib/rubygems/command_manager.rb @@ -71,6 +71,10 @@ class Gem::CommandManager :yank, ] + ALIAS_COMMANDS = { + 'i' => 'install' + } + ## # Return the authoritative instance of the command manager. @@ -174,6 +178,8 @@ class Gem::CommandManager end def find_command(cmd_name) + cmd_name = find_alias_command cmd_name + possibilities = find_command_possibilities cmd_name if possibilities.size > 1 then @@ -186,6 +192,11 @@ class Gem::CommandManager self[possibilities.first] end + def find_alias_command(cmd_name) + alias_name = ALIAS_COMMANDS[cmd_name] + alias_name ? alias_name : cmd_name + end + def find_command_possibilities(cmd_name) len = cmd_name.length diff --git a/lib/rubygems/commands/build_command.rb b/lib/rubygems/commands/build_command.rb index 0ba24e5ea3..f1d700349f 100644 --- a/lib/rubygems/commands/build_command.rb +++ b/lib/rubygems/commands/build_command.rb @@ -10,6 +10,10 @@ class Gem::Commands::BuildCommand < Gem::Command add_option '--force', 'skip validation of the spec' do |value, options| options[:force] = true end + + add_option '--strict', 'consider warnings as errors when validating the spec' do |value, options| + options[:strict] = true + end end def arguments # :nodoc: @@ -51,7 +55,7 @@ with gem spec: spec = Gem::Specification.load File.basename(gemspec) if spec then - Gem::Package.build spec, options[:force] + Gem::Package.build spec, options[:force], options[:strict] else alert_error "Error loading gemspec. Aborting." terminate_interaction 1 diff --git a/lib/rubygems/commands/cert_command.rb b/lib/rubygems/commands/cert_command.rb index 5542262a50..aa26f340ff 100644 --- a/lib/rubygems/commands/cert_command.rb +++ b/lib/rubygems/commands/cert_command.rb @@ -87,7 +87,7 @@ class Gem::Commands::CertCommand < Gem::Command add_option('-d', '--days NUMBER_OF_DAYS', 'Days before the certificate expires') do |days, options| - options[:expiration_length_days] = days.to_i + options[:expiration_length_days] = days.to_i end end diff --git a/lib/rubygems/commands/cleanup_command.rb b/lib/rubygems/commands/cleanup_command.rb index 79c23c840d..fe85deddda 100644 --- a/lib/rubygems/commands/cleanup_command.rb +++ b/lib/rubygems/commands/cleanup_command.rb @@ -22,6 +22,12 @@ class Gem::Commands::CleanupCommand < Gem::Command options[:check_dev] = value end + add_option('--[no-]user-install', + 'Cleanup in user\'s home directory instead', + 'of GEM_HOME.') do |value, options| + options[:user_install] = value + end + @candidate_gems = nil @default_gems = [] @full = nil @@ -124,8 +130,10 @@ If no gems are named all gems in GEM_HOME are cleaned. spec.default_gem? } + uninstall_from = options[:user_install] ? Gem.user_dir : @original_home + gems_to_cleanup = gems_to_cleanup.select { |spec| - spec.base_dir == @original_home + spec.base_dir == uninstall_from } @default_gems += default_gems diff --git a/lib/rubygems/commands/install_command.rb b/lib/rubygems/commands/install_command.rb index c6abf9cd7c..9fe131c290 100644 --- a/lib/rubygems/commands/install_command.rb +++ b/lib/rubygems/commands/install_command.rb @@ -117,6 +117,13 @@ to write the specification by hand. For example: some_extension_gem (1.0) $ +Command Alias +========================== + +You can use `i` command instead of `install`. + + $ gem i GEMNAME + EOF end diff --git a/lib/rubygems/commands/open_command.rb b/lib/rubygems/commands/open_command.rb index 059635e835..fdac19dc1f 100644 --- a/lib/rubygems/commands/open_command.rb +++ b/lib/rubygems/commands/open_command.rb @@ -60,8 +60,14 @@ class Gem::Commands::OpenCommand < Gem::Command def open_gem name spec = spec_for name + return false unless spec + if spec.default_gem? + say "'#{name}' is a default gem and can't be opened." + return false + end + open_editor(spec.full_gem_path) end diff --git a/lib/rubygems/commands/pristine_command.rb b/lib/rubygems/commands/pristine_command.rb index 817e752266..575c344130 100644 --- a/lib/rubygems/commands/pristine_command.rb +++ b/lib/rubygems/commands/pristine_command.rb @@ -46,6 +46,12 @@ class Gem::Commands::PristineCommand < Gem::Command options[:env_shebang] = value end + add_option('-n', '--bindir DIR', + 'Directory where executables are', + 'located') do |value, options| + options[:bin_dir] = File.expand_path(value) + end + add_version_option('restore to', 'pristine condition') end @@ -160,12 +166,15 @@ extensions will be restored. install_defaults.to_s['--env-shebang'] end + bin_dir = options[:bin_dir] if options[:bin_dir] + installer_options = { :wrappers => true, :force => true, :install_dir => spec.base_dir, :env_shebang => env_shebang, :build_args => spec.build_args, + :bin_dir => bin_dir } if options[:only_executables] then diff --git a/lib/rubygems/commands/push_command.rb b/lib/rubygems/commands/push_command.rb index 613c4e6ddf..83c7131afc 100644 --- a/lib/rubygems/commands/push_command.rb +++ b/lib/rubygems/commands/push_command.rb @@ -29,6 +29,8 @@ command. For further discussion see the help for the yank command. def initialize super 'push', 'Push a gem up to the gem server', :host => self.host + @user_defined_host = false + add_proxy_option add_key_option @@ -36,20 +38,41 @@ command. For further discussion see the help for the yank command. 'Push to another gemcutter-compatible host', ' (e.g. https://rubygems.org)') do |value, options| options[:host] = value + @user_defined_host = true end @host = nil end def execute - @host = options[:host] + gem_name = get_one_gem_name + default_gem_server, push_host = get_hosts_for(gem_name) + + default_host = nil + user_defined_host = nil + + if @user_defined_host + user_defined_host = options[:host] + else + default_host = options[:host] + end + + @host = if user_defined_host + user_defined_host + elsif default_gem_server + default_gem_server + elsif push_host + push_host + else + default_host + end sign_in @host - send_gem get_one_gem_name + send_gem(gem_name) end - def send_gem name + def send_gem(name) args = [:post, "api/v1/gems"] latest_rubygems_version = Gem.latest_rubygems_version @@ -100,5 +123,15 @@ You can upgrade or downgrade to the latest release version with: with_response response end + private + + def get_hosts_for(name) + gem_metadata = Gem::Package.new(name).spec.metadata + + [ + gem_metadata["default_gem_server"], + gem_metadata["allowed_push_host"] + ] + end end diff --git a/lib/rubygems/commands/setup_command.rb b/lib/rubygems/commands/setup_command.rb index fc87063fe3..281108ea1f 100644 --- a/lib/rubygems/commands/setup_command.rb +++ b/lib/rubygems/commands/setup_command.rb @@ -84,7 +84,7 @@ class Gem::Commands::SetupCommand < Gem::Command add_option '--[no-]regenerate-binstubs', 'Regenerate gem binstubs' do |value, options| - options[:regenerate_binstubs] = value + options[:regenerate_binstubs] = value end add_option('-E', '--[no-]env-shebang', @@ -468,8 +468,8 @@ By default, this RubyGems will install gem as: (prefix == RbConfig::CONFIG['libdir'] or # this one is important prefix == File.join(RbConfig::CONFIG['libdir'], 'ruby')) then - lib_dir = RbConfig::CONFIG[site_or_vendor] - bin_dir = RbConfig::CONFIG['bindir'] + lib_dir = RbConfig::CONFIG[site_or_vendor] + bin_dir = RbConfig::CONFIG['bindir'] else lib_dir = File.join prefix, 'lib' bin_dir = File.join prefix, 'bin' diff --git a/lib/rubygems/commands/signin_command.rb b/lib/rubygems/commands/signin_command.rb index 6556db5a89..a48c32b52e 100644 --- a/lib/rubygems/commands/signin_command.rb +++ b/lib/rubygems/commands/signin_command.rb @@ -10,7 +10,7 @@ class Gem::Commands::SigninCommand < Gem::Command 'It defaults to https://rubygems.org' add_option('--host HOST', 'Push to another gemcutter-compatible host') do |value, options| - options[:host] = value + options[:host] = value end end diff --git a/lib/rubygems/commands/uninstall_command.rb b/lib/rubygems/commands/uninstall_command.rb index 55a052284a..1ddc12c737 100644 --- a/lib/rubygems/commands/uninstall_command.rb +++ b/lib/rubygems/commands/uninstall_command.rb @@ -48,7 +48,7 @@ class Gem::Commands::UninstallCommand < Gem::Command end add_option('-n', '--bindir DIR', - 'Directory to remove binaries from') do |value, options| + 'Directory to remove executables from') do |value, options| options[:bin_dir] = File.expand_path(value) end diff --git a/lib/rubygems/dependency_installer.rb b/lib/rubygems/dependency_installer.rb index 4b474e1d19..89ce9afe29 100644 --- a/lib/rubygems/dependency_installer.rb +++ b/lib/rubygems/dependency_installer.rb @@ -458,7 +458,7 @@ class Gem::DependencyInstaller rescue Gem::Package::FormatError end end - # else This is a dependency. InstallerSet handles this case + # else This is a dependency. InstallerSet handles this case end end diff --git a/lib/rubygems/ext/builder.rb b/lib/rubygems/ext/builder.rb index 805ef02422..6382a8f5c7 100644 --- a/lib/rubygems/ext/builder.rb +++ b/lib/rubygems/ext/builder.rb @@ -6,7 +6,6 @@ #++ require 'rubygems/user_interaction' -require 'thread' class Gem::Ext::Builder diff --git a/lib/rubygems/indexer.rb b/lib/rubygems/indexer.rb index 2e59e790d4..5607bf3c77 100644 --- a/lib/rubygems/indexer.rb +++ b/lib/rubygems/indexer.rb @@ -271,7 +271,7 @@ class Gem::Indexer # List of gem file names to index. def gem_file_list - Dir[File.join(@dest_directory, "gems", '*.gem')] + Gem::Util.glob_files_in_dir("*.gem", File.join(@dest_directory, "gems")) end ## diff --git a/lib/rubygems/install_update_options.rb b/lib/rubygems/install_update_options.rb index 190372739b..75968605f1 100644 --- a/lib/rubygems/install_update_options.rb +++ b/lib/rubygems/install_update_options.rb @@ -25,7 +25,7 @@ module Gem::InstallUpdateOptions end add_option(:"Install/Update", '-n', '--bindir DIR', - 'Directory where binary files are', + 'Directory where executables are', 'located') do |value, options| options[:bin_dir] = File.expand_path(value) end diff --git a/lib/rubygems/installer.rb b/lib/rubygems/installer.rb index b142454c09..e9ad4d03d4 100644 --- a/lib/rubygems/installer.rb +++ b/lib/rubygems/installer.rb @@ -187,6 +187,8 @@ class Gem::Installer @package.prog_mode = options[:prog_mode] @package.data_mode = options[:data_mode] + @bin_dir = options[:bin_dir] if options[:bin_dir] + if options[:user_install] and not options[:unpack] then @gem_home = Gem.user_dir @bin_dir = Gem.bindir gem_home unless options[:bin_dir] @@ -379,7 +381,7 @@ class Gem::Installer @specs ||= begin specs = [] - Dir[File.join(gem_home, "specifications", "*.gemspec")].each do |path| + Gem::Util.glob_files_in_dir("*.gemspec", File.join(gem_home, "specifications")).each do |path| spec = Gem::Specification.load path.untaint specs << spec if spec end @@ -769,15 +771,30 @@ TEXT # return the stub script text used to launch the true Ruby script def windows_stub_script(bindir, bin_file_name) - ruby = Gem.ruby.gsub(/^\"|\"$/, "").tr(File::SEPARATOR, "\\") - return <<-TEXT + # All comparisons should be case insensitive + if bindir.downcase == RbConfig::CONFIG["bindir"].downcase + # stub & ruby.exe withing same folder. Portable + <<-TEXT @ECHO OFF -IF NOT "%~f0" == "~f0" GOTO :WinNT -@"#{ruby}" "#{File.join(bindir, bin_file_name)}" %1 %2 %3 %4 %5 %6 %7 %8 %9 -GOTO :EOF -:WinNT -@"#{ruby}" "%~dpn0" %* -TEXT +@"%~dp0ruby.exe" "%~dpn0" %* + TEXT + elsif bindir.downcase.start_with? RbConfig::TOPDIR.downcase + # stub within ruby folder, but not standard bin. Not portable + require 'pathname' + from = Pathname.new bindir + to = Pathname.new RbConfig::CONFIG["bindir"] + rel = to.relative_path_from from + <<-TEXT +@ECHO OFF +@"%~dp0#{rel}/ruby.exe" "%~dpn0" %* + TEXT + else + # outside ruby folder, maybe -user-install or bundler. Portable + <<-TEXT +@ECHO OFF +@ruby.exe "%~dpn0" %* + TEXT + end end ## diff --git a/lib/rubygems/package.rb b/lib/rubygems/package.rb index b20334c8ca..ec9541d19b 100644 --- a/lib/rubygems/package.rb +++ b/lib/rubygems/package.rb @@ -119,12 +119,12 @@ class Gem::Package # Permission for other files attr_accessor :data_mode - def self.build spec, skip_validation=false + def self.build spec, skip_validation=false, strict_validation=false gem_file = spec.file_name package = new gem_file package.spec = spec - package.build skip_validation + package.build skip_validation, strict_validation gem_file end @@ -254,12 +254,14 @@ class Gem::Package ## # Builds this package based on the specification set by #spec= - def build skip_validation = false + def build skip_validation = false, strict_validation = false + raise ArgumentError, "skip_validation = true and strict_validation = true are incompatible" if skip_validation && strict_validation + Gem.load_yaml require 'rubygems/security' @spec.mark_version - @spec.validate unless skip_validation + @spec.validate true, strict_validation unless skip_validation setup_signer diff --git a/lib/rubygems/package/tar_reader/entry.rb b/lib/rubygems/package/tar_reader/entry.rb index b6fb8c3a3a..77b06af233 100644 --- a/lib/rubygems/package/tar_reader/entry.rb +++ b/lib/rubygems/package/tar_reader/entry.rb @@ -119,6 +119,12 @@ class Gem::Package::TarReader::Entry bytes_read end + def size + @header.size + end + + alias length size + ## # Reads +len+ bytes from the tar file entry, or the rest of the entry if # nil @@ -137,7 +143,19 @@ class Gem::Package::TarReader::Entry ret end - alias readpartial read # :nodoc: + def readpartial(maxlen = nil, outbuf = "".b) + check_closed + + raise EOFError if @read >= @header.size + + maxlen ||= @header.size - @read + max_read = [maxlen, @header.size - @read].min + + @io.readpartial(max_read, outbuf) + @read += outbuf.size + + outbuf + end ## # Rewinds to the beginning of the tar file entry diff --git a/lib/rubygems/path_support.rb b/lib/rubygems/path_support.rb index 618bc793c4..02332cef80 100644 --- a/lib/rubygems/path_support.rb +++ b/lib/rubygems/path_support.rb @@ -23,12 +23,14 @@ class Gem::PathSupport # hashtable, or defaults to ENV, the system environment. # def initialize(env) - @home = env["GEM_HOME"] || Gem.default_dir + @home = env["GEM_HOME"] || Gem.default_dir if File::ALT_SEPARATOR then - @home = @home.gsub(File::ALT_SEPARATOR, File::SEPARATOR) + @home = @home.gsub(File::ALT_SEPARATOR, File::SEPARATOR) end + @home = expand(@home) + @path = split_gem_path env["GEM_PATH"], @home @spec_cache_dir = env["GEM_SPEC_CACHE"] || Gem.default_spec_cache_dir @@ -65,7 +67,7 @@ class Gem::PathSupport gem_path = default_path end - gem_path.uniq + gem_path.map { |path| expand(path) }.uniq end # Return the default Gem path @@ -77,4 +79,12 @@ class Gem::PathSupport end gem_path end + + def expand(path) + if File.directory?(path) + File.realpath(path) + else + path + end + end end diff --git a/lib/rubygems/remote_fetcher.rb b/lib/rubygems/remote_fetcher.rb index a829268f39..940523f246 100644 --- a/lib/rubygems/remote_fetcher.rb +++ b/lib/rubygems/remote_fetcher.rb @@ -384,17 +384,15 @@ class Gem::RemoteFetcher require 'base64' require 'openssl' - unless uri.user && uri.password - raise FetchError.new("credentials needed in s3 source, like s3://key:secret@bucket-name/", uri.to_s) - end + id, secret = s3_source_auth uri expiration ||= s3_expiration canonical_path = "/#{uri.host}#{uri.path}" payload = "GET\n\n\n#{expiration}\n#{canonical_path}" - digest = OpenSSL::HMAC.digest('sha1', uri.password, payload) + digest = OpenSSL::HMAC.digest('sha1', secret, payload) # URI.escape is deprecated, and there isn't yet a replacement that does quite what we want signature = Base64.encode64(digest).gsub("\n", '').gsub(/[\+\/=]/) { |c| BASE64_URI_TRANSLATE[c] } - URI.parse("https://#{uri.host}.s3.amazonaws.com#{uri.path}?AWSAccessKeyId=#{uri.user}&Expires=#{expiration}&Signature=#{signature}") + URI.parse("https://#{uri.host}.s3.amazonaws.com#{uri.path}?AWSAccessKeyId=#{id}&Expires=#{expiration}&Signature=#{signature}") end def s3_expiration @@ -414,4 +412,21 @@ class Gem::RemoteFetcher @pools[proxy] ||= Gem::Request::ConnectionPools.new proxy, @cert_files end end + + def s3_source_auth(uri) + return [uri.user, uri.password] if uri.user && uri.password + + s3_source = Gem.configuration[:s3_source] || Gem.configuration['s3_source'] + host = uri.host + raise FetchError.new("no s3_source key exists in .gemrc", "s3://#{host}") unless s3_source + + auth = s3_source[host] || s3_source[host.to_sym] + raise FetchError.new("no key for host #{host} in s3_source in .gemrc", "s3://#{host}") unless auth + + id = auth[:id] || auth['id'] + secret = auth[:secret] || auth['secret'] + raise FetchError.new("s3_source for #{host} missing id or secret", "s3://#{host}") unless id and secret + + [id, secret] + end end diff --git a/lib/rubygems/request.rb b/lib/rubygems/request.rb index 81699b98fe..d8d5d1bc31 100644 --- a/lib/rubygems/request.rb +++ b/lib/rubygems/request.rb @@ -1,6 +1,5 @@ # frozen_string_literal: true require 'net/http' -require 'thread' require 'time' require 'rubygems/user_interaction' diff --git a/lib/rubygems/request/connection_pools.rb b/lib/rubygems/request/connection_pools.rb index d70dea785a..f95dd8aabf 100644 --- a/lib/rubygems/request/connection_pools.rb +++ b/lib/rubygems/request/connection_pools.rb @@ -1,5 +1,4 @@ # frozen_string_literal: true -require 'thread' class Gem::Request::ConnectionPools # :nodoc: diff --git a/lib/rubygems/request_set.rb b/lib/rubygems/request_set.rb index 89f47616ec..ed99d29295 100644 --- a/lib/rubygems/request_set.rb +++ b/lib/rubygems/request_set.rb @@ -417,7 +417,7 @@ class Gem::RequestSet end def specs_in dir - Dir["#{dir}/specifications/*.gemspec"].map do |g| + Gem::Util.glob_files_in_dir("*.gemspec", File.join(dir, "specifications")).map do |g| Gem::Specification.load g end end diff --git a/lib/rubygems/requirement.rb b/lib/rubygems/requirement.rb index 0717739dc0..430f351280 100644 --- a/lib/rubygems/requirement.rb +++ b/lib/rubygems/requirement.rb @@ -284,7 +284,7 @@ class Gem::Requirement end def sort_requirements! # :nodoc: - @requirements.sort! do |l, r| + @requirements.sort! do |l, r| comp = l.last <=> r.last # first, sort by the requirement's version next comp unless comp == 0 l.first <=> r.first # then, sort by the operator (for stability) diff --git a/lib/rubygems/security/signer.rb b/lib/rubygems/security/signer.rb index 0c6ef60a9a..1ee9c31be6 100644 --- a/lib/rubygems/security/signer.rb +++ b/lib/rubygems/security/signer.rb @@ -2,8 +2,12 @@ ## # Basic OpenSSL-based package signing class. +require "rubygems/user_interaction" + class Gem::Security::Signer + include Gem::UserInteraction + ## # The chain of certificates for signing including the signing certificate @@ -33,6 +37,7 @@ class Gem::Security::Signer def initialize key, cert_chain, passphrase = nil @cert_chain = cert_chain @key = key + @passphrase = passphrase unless @key then default_key = File.join Gem.default_key_path @@ -47,8 +52,10 @@ class Gem::Security::Signer @digest_algorithm = Gem::Security::DIGEST_ALGORITHM @digest_name = Gem::Security::DIGEST_NAME - @key = OpenSSL::PKey::RSA.new File.read(@key), passphrase if - @key and not OpenSSL::PKey::RSA === @key + if @key && !@key.is_a?(OpenSSL::PKey::RSA) + @passphrase ||= ask_for_password("Enter PEM pass phrase:") + @key = OpenSSL::PKey::RSA.new(File.read(@key), @passphrase) + end if @cert_chain then @cert_chain = @cert_chain.compact.map do |cert| @@ -121,6 +128,7 @@ class Gem::Security::Signer # The key will be re-signed if: # * The expired certificate is self-signed # * The expired certificate is saved at ~/.gem/gem-public_cert.pem + # and the private key is saved at ~/.gem/gem-private_key.pem # * There is no file matching the expiry date at # ~/.gem/gem-public_cert.pem.expired.%Y%m%d%H%M%S # @@ -131,22 +139,29 @@ class Gem::Security::Signer def re_sign_key # :nodoc: old_cert = @cert_chain.last - disk_cert_path = File.join Gem.default_cert_path - disk_cert = File.read disk_cert_path rescue nil - disk_key = - File.read File.join(Gem.default_key_path) rescue nil + disk_cert_path = File.join(Gem.default_cert_path) + disk_cert = File.read(disk_cert_path) rescue nil - if disk_key == @key.to_pem and disk_cert == old_cert.to_pem then - expiry = old_cert.not_after.strftime '%Y%m%d%H%M%S' + disk_key_path = File.join(Gem.default_key_path) + disk_key = + OpenSSL::PKey::RSA.new(File.read(disk_key_path), @passphrase) rescue nil + + return unless disk_key + + if disk_key.to_pem == @key.to_pem && disk_cert == old_cert.to_pem + expiry = old_cert.not_after.strftime('%Y%m%d%H%M%S') old_cert_file = "gem-public_cert.pem.expired.#{expiry}" - old_cert_path = File.join Gem.user_home, ".gem", old_cert_file + old_cert_path = File.join(Gem.user_home, ".gem", old_cert_file) - unless File.exist? old_cert_path then - Gem::Security.write old_cert, old_cert_path + unless File.exist?(old_cert_path) + Gem::Security.write(old_cert, old_cert_path) - cert = Gem::Security.re_sign old_cert, @key + cert = Gem::Security.re_sign(old_cert, @key) - Gem::Security.write cert, disk_cert_path + Gem::Security.write(cert, disk_cert_path) + + alert("Your cert: #{disk_cert_path} has been auto re-signed with the key: #{disk_key_path}") + alert("Your expired cert will be located at: #{old_cert_path}") @cert_chain = [cert] end diff --git a/lib/rubygems/spec_fetcher.rb b/lib/rubygems/spec_fetcher.rb index 919276e113..4d224ca173 100644 --- a/lib/rubygems/spec_fetcher.rb +++ b/lib/rubygems/spec_fetcher.rb @@ -202,7 +202,7 @@ class Gem::SpecFetcher }.compact matches = if matches.empty? && type != :prerelease - suggest_gems_from_name gem_name, :prerelease + suggest_gems_from_name gem_name, :prerelease else matches.uniq.sort_by { |name, dist| dist } end diff --git a/lib/rubygems/specification.rb b/lib/rubygems/specification.rb index 2fe315f4f6..1ec4ed9227 100644 --- a/lib/rubygems/specification.rb +++ b/lib/rubygems/specification.rb @@ -172,9 +172,9 @@ class Gem::Specification < Gem::BasicSpecification when String v.dump when Numeric - "default_value(:#{k})" + "default_value(:#{k})" else - "default_value(:#{k}).dup" + "default_value(:#{k}).dup" end end @@ -761,14 +761,14 @@ class Gem::Specification < Gem::BasicSpecification def self.each_gemspec(dirs) # :nodoc: dirs.each do |dir| - Dir[File.join(dir, "*.gemspec")].each do |path| + Gem::Util.glob_files_in_dir("*.gemspec", dir).each do |path| yield path.untaint end end end def self.gemspec_stubs_in dir, pattern - Dir[File.join(dir, pattern)].map { |path| yield path }.select(&:valid?) + Gem::Util.glob_files_in_dir(pattern, dir).map { |path| yield path }.select(&:valid?) end private_class_method :gemspec_stubs_in @@ -820,11 +820,11 @@ class Gem::Specification < Gem::BasicSpecification def self.stubs @@stubs ||= begin pattern = "*.gemspec" - stubs = default_stubs(pattern).concat installed_stubs(dirs, pattern) + stubs = Gem.loaded_specs.values + default_stubs(pattern) + installed_stubs(dirs, pattern) stubs = uniq_by(stubs) { |stub| stub.full_name } _resort!(stubs) - @@stubs_by_name = stubs.group_by(&:name) + @@stubs_by_name = stubs.select { |s| Gem::Platform.match s.platform }.group_by(&:name) stubs end end @@ -833,13 +833,15 @@ class Gem::Specification < Gem::BasicSpecification ## # Returns a Gem::StubSpecification for installed gem named +name+ + # only returns stubs that match Gem.platforms def self.stubs_for name if @@stubs @@stubs_by_name[name] || [] else pattern = "#{name}-*.gemspec" - stubs = default_stubs(pattern) + installed_stubs(dirs, pattern) + stubs = Gem.loaded_specs.values + default_stubs(pattern) + + installed_stubs(dirs, pattern).select { |s| Gem::Platform.match s.platform } stubs = uniq_by(stubs) { |stub| stub.full_name }.group_by(&:name) stubs.each_value { |v| _resort!(v) } @@ -1280,11 +1282,17 @@ class Gem::Specification < Gem::BasicSpecification unresolved = unresolved_deps unless unresolved.empty? then w = "W" + "ARN" - warn "#{w}: Unresolved specs during Gem::Specification.reset:" + warn "#{w}: Unresolved or ambigious specs during Gem::Specification.reset:" unresolved.values.each do |dep| warn " #{dep}" + + versions = find_all_by_name(dep.name) + unless versions.empty? + warn " Available/installed versions of this gem:" + versions.each { |s| warn " - #{s.version}" } + end end - warn "#{w}: Clearing out unresolved specs." + warn "#{w}: Clearing out unresolved specs. Try 'gem cleanup '" warn "Please report a bug if this causes problems." unresolved.clear end @@ -2645,19 +2653,14 @@ class Gem::Specification < Gem::BasicSpecification # Raises InvalidSpecificationException if the spec does not pass the # checks.. - def validate packaging = true - @warnings = 0 + def validate packaging = true, strict = false require 'rubygems/user_interaction' extend Gem::UserInteraction normalize validation_policy = Gem::SpecificationPolicy.new(self) validation_policy.packaging = packaging - validation_policy.validate - ensure - if $! or @warnings > 0 then - alert_warning "See http://guides.rubygems.org/specification-reference/ for help" - end + validation_policy.validate(strict) end def keep_only_files_and_directories @@ -2744,12 +2747,6 @@ class Gem::Specification < Gem::BasicSpecification @installed_by_version ||= nil end - def warning statement # :nodoc: - @warnings += 1 - - alert_warning statement - end - def raw_require_paths # :nodoc: @require_paths end diff --git a/lib/rubygems/specification_policy.rb b/lib/rubygems/specification_policy.rb index cb8457ed62..853cca5126 100644 --- a/lib/rubygems/specification_policy.rb +++ b/lib/rubygems/specification_policy.rb @@ -16,6 +16,12 @@ class Gem::SpecificationPolicy < SimpleDelegator wiki_uri ] # :nodoc: + def initialize(specification) + @warnings = 0 + + super(specification) + end + ## # If set to true, run packaging-specific checks, as well. @@ -28,7 +34,7 @@ class Gem::SpecificationPolicy < SimpleDelegator # Raises InvalidSpecificationException if the spec does not pass the # checks. - def validate + def validate(strict = false) validate_nil_attributes validate_rubygems_version @@ -64,6 +70,15 @@ class Gem::SpecificationPolicy < SimpleDelegator validate_values validate_dependencies + + if @warnings > 0 + if strict + error "specification has warnings" + else + alert_warning help_text + end + end + true end @@ -72,35 +87,29 @@ class Gem::SpecificationPolicy < SimpleDelegator def validate_metadata unless Hash === metadata then - raise Gem::InvalidSpecificationException, - 'metadata must be a hash' + error 'metadata must be a hash' end metadata.each do |key, value| if !key.kind_of?(String) then - raise Gem::InvalidSpecificationException, - "metadata keys must be a String" + error "metadata keys must be a String" end if key.size > 128 then - raise Gem::InvalidSpecificationException, - "metadata key too large (#{key.size} > 128)" + error "metadata key too large (#{key.size} > 128)" end if !value.kind_of?(String) then - raise Gem::InvalidSpecificationException, - "metadata values must be a String" + error "metadata values must be a String" end if value.size > 1024 then - raise Gem::InvalidSpecificationException, - "metadata value too large (#{value.size} > 1024)" + error "metadata value too large (#{value.size} > 1024)" end if METADATA_LINK_KEYS.include? key then if value !~ VALID_URI_PATTERN then - raise Gem::InvalidSpecificationException, - "metadata['#{key}'] has invalid link: #{value.inspect}" + error "metadata['#{key}'] has invalid link: #{value.inspect}" end end end @@ -132,30 +141,6 @@ duplicate dependency on #{dep}, (#{prev.requirement}) use: warning_messages << "prerelease dependency on #{dep} is not recommended" if prerelease_dep && !version.prerelease? - overly_strict = dep.requirement.requirements.length == 1 && - dep.requirement.requirements.any? do |op, version| - op == '~>' and - not version.prerelease? and - version.segments.length > 2 and - version.segments.first != 0 - end - - if overly_strict then - _, dep_version = dep.requirement.requirements.first - - base = dep_version.segments.first 2 - upper_bound = dep_version.segments.first(dep_version.segments.length - 1) - upper_bound[-1] += 1 - - warning_messages << <<-WARNING -pessimistic dependency on #{dep} may be overly strict - if #{dep.name} is semantically versioned, use: - add_#{dep.type}_dependency '#{dep.name}', '~> #{base.join '.'}', '>= #{dep_version}' - if #{dep.name} is not semantically versioned, you can bypass this warning with: - add_#{dep.type}_dependency '#{dep.name}', '>= #{dep_version}', '< #{upper_bound.join '.'}.a' - WARNING - end - open_ended = dep.requirement.requirements.all? do |op, version| not version.prerelease? and (op == '>' or op == '>=') end @@ -179,7 +164,7 @@ open-ended dependency on #{dep} is not recommended end end if error_messages.any? then - raise Gem::InvalidSpecificationException, error_messages.join + error error_messages.join end if warning_messages.any? then warning_messages.each { |warning_message| warning warning_message } @@ -215,45 +200,38 @@ open-ended dependency on #{dep} is not recommended __getobj__.instance_variable_get("@#{attrname}").nil? end return if nil_attributes.empty? - raise Gem::InvalidSpecificationException, - "#{nil_attributes.join ', '} must not be nil" + error "#{nil_attributes.join ', '} must not be nil" end def validate_rubygems_version return unless packaging return if rubygems_version == Gem::VERSION - raise Gem::InvalidSpecificationException, - "expected RubyGems version #{Gem::VERSION}, was #{rubygems_version}" + error "expected RubyGems version #{Gem::VERSION}, was #{rubygems_version}" end def validate_required_attributes Gem::Specification.required_attributes.each do |symbol| unless send symbol then - raise Gem::InvalidSpecificationException, - "missing value for attribute #{symbol}" + error "missing value for attribute #{symbol}" end end end def validate_name if !name.is_a?(String) then - raise Gem::InvalidSpecificationException, - "invalid value for attribute name: \"#{name.inspect}\" must be a string" + error "invalid value for attribute name: \"#{name.inspect}\" must be a string" elsif name !~ /[a-zA-Z]/ then - raise Gem::InvalidSpecificationException, - "invalid value for attribute name: #{name.dump} must include at least one letter" + error "invalid value for attribute name: #{name.dump} must include at least one letter" elsif name !~ VALID_NAME_PATTERN then - raise Gem::InvalidSpecificationException, - "invalid value for attribute name: #{name.dump} can only include letters, numbers, dashes, and underscores" + error "invalid value for attribute name: #{name.dump} can only include letters, numbers, dashes, and underscores" end end def validate_require_paths return unless raw_require_paths.empty? - raise Gem::InvalidSpecificationException, - 'specification must have at least one require_path' + error 'specification must have at least one require_path' end def validate_non_files @@ -261,31 +239,27 @@ open-ended dependency on #{dep} is not recommended non_files = files.reject {|x| File.file?(x) || File.symlink?(x)} unless non_files.empty? then - raise Gem::InvalidSpecificationException, - "[\"#{non_files.join "\", \""}\"] are not files" + error "[\"#{non_files.join "\", \""}\"] are not files" end end def validate_self_inclusion_in_files_list return unless files.include?(file_name) - - raise Gem::InvalidSpecificationException, - "#{full_name} contains itself (#{file_name}), check your files list" + + error "#{full_name} contains itself (#{file_name}), check your files list" end def validate_specification_version return if specification_version.is_a?(Integer) - - raise Gem::InvalidSpecificationException, - 'specification_version must be an Integer (did you mean version?)' + + error 'specification_version must be an Integer (did you mean version?)' end def validate_platform case platform when Gem::Platform, Gem::Platform::RUBY then # ok else - raise Gem::InvalidSpecificationException, - "invalid platform #{platform.inspect}, see Gem::Platform" + error "invalid platform #{platform.inspect}, see Gem::Platform" end end @@ -313,15 +287,13 @@ open-ended dependency on #{dep} is not recommended def validate_authors_field return unless authors.empty? - raise Gem::InvalidSpecificationException, - "authors may not be empty" + error "authors may not be empty" end def validate_licenses licenses.each { |license| if license.length > 64 then - raise Gem::InvalidSpecificationException, - "each license must be 64 characters or less" + error "each license must be 64 characters or less" end if !Gem::Licenses.match?(license) then @@ -347,19 +319,19 @@ http://spdx.org/licenses or '#{Gem::Licenses::NONSTANDARD}' for a nonstandard li def validate_lazy_metadata unless authors.grep(LAZY_PATTERN).empty? then - raise Gem::InvalidSpecificationException, "#{LAZY} is not an author" + error "#{LAZY} is not an author" end unless Array(email).grep(LAZY_PATTERN).empty? then - raise Gem::InvalidSpecificationException, "#{LAZY} is not an email" + error "#{LAZY} is not an email" end if description =~ LAZY_PATTERN then - raise Gem::InvalidSpecificationException, "#{LAZY} is not a description" + error "#{LAZY} is not a description" end if summary =~ LAZY_PATTERN then - raise Gem::InvalidSpecificationException, "#{LAZY} is not a summary" + error "#{LAZY} is not a summary" end # Make sure a homepage is valid HTTP/HTTPS URI @@ -367,10 +339,10 @@ http://spdx.org/licenses or '#{Gem::Licenses::NONSTANDARD}' for a nonstandard li begin homepage_uri = URI.parse(homepage) unless [URI::HTTP, URI::HTTPS].member? homepage_uri.class - raise Gem::InvalidSpecificationException, "\"#{homepage}\" is not a valid HTTP URI" + error "\"#{homepage}\" is not a valid HTTP URI" end rescue URI::InvalidURIError - raise Gem::InvalidSpecificationException, "\"#{homepage}\" is not a valid HTTP URI" + error "\"#{homepage}\" is not a valid HTTP URI" end end end @@ -407,4 +379,20 @@ http://spdx.org/licenses or '#{Gem::Licenses::NONSTANDARD}' for a nonstandard li warning "#{executable_path} is missing #! line" end + + def warning statement # :nodoc: + @warnings += 1 + + alert_warning statement + end + + def error statement # :nodoc: + raise Gem::InvalidSpecificationException, statement + ensure + alert_warning help_text + end + + def help_text # :nodoc: + "See http://guides.rubygems.org/specification-reference/ for help" + end end diff --git a/lib/rubygems/test_case.rb b/lib/rubygems/test_case.rb index f1cd3d274c..ced33c4d11 100644 --- a/lib/rubygems/test_case.rb +++ b/lib/rubygems/test_case.rb @@ -13,6 +13,15 @@ else require 'rubygems' end +# If bundler gemspec exists, add to stubs +bundler_gemspec = File.expand_path("../../../bundler/bundler.gemspec", __FILE__) +if File.exist?(bundler_gemspec) + Gem::Specification.dirs.unshift File.dirname(bundler_gemspec) + Gem::Specification.class_variable_set :@@stubs, nil + Gem::Specification.stubs + Gem::Specification.dirs.shift +end + begin gem 'minitest' rescue Gem::LoadError @@ -382,6 +391,11 @@ class Gem::TestCase < (defined?(Minitest::Test) ? Minitest::Test : MiniTest::Uni util_set_arch 'i686-darwin8.10.1' end + @orig_hooks = {} + %w[post_install_hooks done_installing_hooks post_uninstall_hooks pre_uninstall_hooks pre_install_hooks pre_reset_hooks post_reset_hooks post_build_hooks].each do |name| + @orig_hooks[name] = Gem.send(name).dup + end + @marshal_version = "#{Marshal::MAJOR_VERSION}.#{Marshal::MINOR_VERSION}" @orig_LOADED_FEATURES = $LOADED_FEATURES.dup end @@ -449,6 +463,10 @@ class Gem::TestCase < (defined?(Minitest::Test) ? Minitest::Test : MiniTest::Uni Gem::Specification.unresolved_deps.clear Gem::refresh + @orig_hooks.each do |name, hooks| + Gem.send(name).replace hooks + end + @back_ui.close end diff --git a/lib/rubygems/util.rb b/lib/rubygems/util.rb index 9f5b9a2239..6dbcd4ba21 100644 --- a/lib/rubygems/util.rb +++ b/lib/rubygems/util.rb @@ -80,8 +80,6 @@ module Gem::Util end return system(*(cmds << opt)) rescue TypeError - require 'thread' - @silent_mutex ||= Mutex.new @silent_mutex.synchronize do @@ -118,4 +116,16 @@ module Gem::Util end end + ## + # Globs for files matching +pattern+ inside of +directory+, + # returning absolute paths to the matching files. + + def self.glob_files_in_dir(glob, base_path) + if RUBY_VERSION >= "2.5" + Dir.glob(glob, base: base_path).map! {|f| File.join(base_path, f) } + else + Dir.glob(File.expand_path(glob, base_path)) + end + end + end diff --git a/lib/rubygems/version.rb b/lib/rubygems/version.rb index fbf75996cd..08f0d1e7a5 100644 --- a/lib/rubygems/version.rb +++ b/lib/rubygems/version.rb @@ -170,7 +170,10 @@ class Gem::Version # True if the +version+ string matches RubyGems' requirements. def self.correct? version - return false if version.nil? + unless Gem::Deprecate.skip + warn "nil versions are discouraged and will be deprecated in Rubygems 4" if version.nil? + end + !!(version.to_s =~ ANCHORED_VERSION_PATTERN) end @@ -325,7 +328,9 @@ class Gem::Version segments.pop while segments.size > 2 segments.push 0 while segments.size < 2 - "~> #{segments.join(".")}" + recommendation = "~> #{segments.join(".")}" + recommendation += ".a" if prerelease? + recommendation end ## diff --git a/test/rubygems/test_gem.rb b/test/rubygems/test_gem.rb index f383d5af58..ddf0f7e04c 100644 --- a/test/rubygems/test_gem.rb +++ b/test/rubygems/test_gem.rb @@ -1758,7 +1758,7 @@ class TestGem < Gem::TestCase platform = " #{platform}" end expected = if Gem::USE_BUNDLER_FOR_GEMDEPS - <<-EXPECTED + <<-EXPECTED Could not find gem 'a#{platform}' in any of the gem sources listed in your Gemfile. You may need to `gem install -g` to install missing gems diff --git a/test/rubygems/test_gem_command_manager.rb b/test/rubygems/test_gem_command_manager.rb index c3aa01503a..51a14184ff 100644 --- a/test/rubygems/test_gem_command_manager.rb +++ b/test/rubygems/test_gem_command_manager.rb @@ -29,6 +29,12 @@ class TestGemCommandManager < Gem::TestCase e.message end + def test_find_alias_command + command = @command_manager.find_command 'i' + + assert_kind_of Gem::Commands::InstallCommand, command + end + def test_find_command_ambiguous_exact ins_command = Class.new Gem::Commands.send :const_set, :InsCommand, ins_command diff --git a/test/rubygems/test_gem_commands_build_command.rb b/test/rubygems/test_gem_commands_build_command.rb index 19ca03bb70..8048f3e8f5 100644 --- a/test/rubygems/test_gem_commands_build_command.rb +++ b/test/rubygems/test_gem_commands_build_command.rb @@ -9,13 +9,35 @@ class TestGemCommandsBuildCommand < Gem::TestCase def setup super + readme_file = File.join(@tempdir, 'README.md') + + File.open readme_file, 'w' do |f| + f.write 'My awesome gem' + end + @gem = util_spec 'some_gem' do |s| s.rubyforge_project = 'example' + s.license = 'AGPL-3.0' + s.files = ['README.md'] end @cmd = Gem::Commands::BuildCommand.new end + def test_handle_options + @cmd.handle_options %w[--force --strict] + + assert @cmd.options[:force] + assert @cmd.options[:strict] + end + + def test_handle_options_defaults + @cmd.handle_options [] + + refute @cmd.options[:force] + refute @cmd.options[:strict] + end + def test_execute gemspec_file = File.join(@tempdir, @gem.spec_name) @@ -23,7 +45,55 @@ class TestGemCommandsBuildCommand < Gem::TestCase gs.write @gem.to_ruby end - util_test_build_gem @gem, gemspec_file + @cmd.options[:args] = [gemspec_file] + + util_test_build_gem @gem + end + + def test_execute_strict_without_warnings + gemspec_file = File.join(@tempdir, @gem.spec_name) + + File.open gemspec_file, 'w' do |gs| + gs.write @gem.to_ruby + end + + @cmd.options[:strict] = true + @cmd.options[:args] = [gemspec_file] + + util_test_build_gem @gem + end + + def test_execute_strict_with_warnings + bad_gem = util_spec 'some_bad_gem' do |s| + s.rubyforge_project = 'example' + s.files = ['README.md'] + end + + gemspec_file = File.join(@tempdir, bad_gem.spec_name) + + File.open gemspec_file, 'w' do |gs| + gs.write bad_gem.to_ruby + end + + @cmd.options[:args] = [gemspec_file] + @cmd.options[:strict] = true + + use_ui @ui do + Dir.chdir @tempdir do + assert_raises Gem::InvalidSpecificationException do + @cmd.execute + end + end + end + + error = @ui.error.split "\n" + assert_equal "WARNING: licenses is empty, but is recommended. Use a license identifier from", error.shift + assert_equal "http://spdx.org/licenses or 'Nonstandard' for a nonstandard license.", error.shift + assert_equal "WARNING: See http://guides.rubygems.org/specification-reference/ for help", error.shift + assert_equal [], error + + gem_file = File.join @tempdir, File.basename(@gem.cache_file) + refute File.exist?(gem_file) end def test_execute_bad_spec @@ -67,9 +137,14 @@ class TestGemCommandsBuildCommand < Gem::TestCase def test_execute_outside_dir gemspec_dir = File.join @tempdir, 'build_command_gem' gemspec_file = File.join gemspec_dir, @gem.spec_name + readme_file = File.join gemspec_dir, 'README.md' FileUtils.mkdir_p gemspec_dir + File.open readme_file, 'w' do |f| + f.write "My awesome gem" + end + File.open gemspec_file, 'w' do |gs| gs.write @gem.to_ruby end @@ -103,12 +178,12 @@ class TestGemCommandsBuildCommand < Gem::TestCase gs.write @gem.to_ruby end - util_test_build_gem @gem, gemspec_file - end - - def util_test_build_gem(gem, gemspec_file, check_licenses=true) @cmd.options[:args] = [gemspec_file] + util_test_build_gem @gem + end + + def util_test_build_gem(gem) use_ui @ui do Dir.chdir @tempdir do @cmd.execute @@ -122,10 +197,6 @@ class TestGemCommandsBuildCommand < Gem::TestCase assert_equal " File: some_gem-2.gem", output.shift assert_equal [], output - if check_licenses - assert_match "WARNING: licenses is empty", @ui.error - end - gem_file = File.join @tempdir, File.basename(gem.cache_file) assert File.exist?(gem_file) @@ -147,7 +218,7 @@ class TestGemCommandsBuildCommand < Gem::TestCase @cmd.options[:args] = [gemspec_file] @cmd.options[:force] = true - util_test_build_gem @gem, gemspec_file, false + util_test_build_gem @gem end CERT_FILE = cert_path 'public3072' @@ -169,7 +240,9 @@ class TestGemCommandsBuildCommand < Gem::TestCase gs.write spec.to_ruby end - util_test_build_gem spec, gemspec_file + @cmd.options[:args] = [gemspec_file] + + util_test_build_gem spec trust_dir.trust_cert OpenSSL::X509::Certificate.new(File.read(CERT_FILE)) diff --git a/test/rubygems/test_gem_commands_cleanup_command.rb b/test/rubygems/test_gem_commands_cleanup_command.rb index 60d208fcc0..7024e59fb9 100644 --- a/test/rubygems/test_gem_commands_cleanup_command.rb +++ b/test/rubygems/test_gem_commands_cleanup_command.rb @@ -236,5 +236,32 @@ class TestGemCommandsCleanupCommand < Gem::TestCase refute_path_exists d_1.gem_dir refute_path_exists e_1.gem_dir end + + def test_execute_user_install + c_1, = util_gem 'c', '1.0' + c_2, = util_gem 'c', '1.1' + + d_1, = util_gem 'd', '1.0' + d_2, = util_gem 'd', '1.1' + + c_1 = install_gem c_1, :user_install => true # pick up user install path + c_2 = install_gem c_2, :user_install => true # pick up user install path + + d_1 = install_gem d_1 + d_2 = install_gem d_2 + + Gem::Specification.dirs = [Gem.dir, Gem.user_dir] + + @cmd.handle_options %w[--user-install] + @cmd.options[:args] = [] + + @cmd.execute + + refute_path_exists c_1.gem_dir + assert_path_exists c_2.gem_dir + + assert_path_exists d_1.gem_dir + assert_path_exists d_2.gem_dir + end end diff --git a/test/rubygems/test_gem_commands_install_command.rb b/test/rubygems/test_gem_commands_install_command.rb index a0851fc288..03ceec94b4 100644 --- a/test/rubygems/test_gem_commands_install_command.rb +++ b/test/rubygems/test_gem_commands_install_command.rb @@ -229,7 +229,7 @@ ERROR: Could not find a valid gem 'bar' (= 0.5) (required by 'foo' (>= 0)) in a @cmd.handle_options %w[-p=foo.bar.com] end - assert_match "Invalid uri scheme for =foo.bar.com\nPreface URLs with one of [\"http://\", \"https://\", \"file://\", \"s3://\"]", e.message + assert_match "Invalid uri scheme for =foo.bar.com\nPreface URLs with one of [\"http://\", \"https://\", \"file://\", \"s3://\"]", e.message end end @@ -451,23 +451,23 @@ ERROR: Possible alternatives: non_existent_with_hint specs = spec_fetcher do |fetcher| fetcher.gem 'a', 2 end - + Gem.done_installing(&Gem::RDoc.method(:generation_hook)) - + @cmd.options[:document] = %w[rdoc ri] @cmd.options[:domain] = :local @cmd.options[:install_dir] = 'whatever' - + a2 = specs['a-2'] FileUtils.mv a2.cache_file, @tempdir - + @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 assert_raises Gem::MockGemUi::SystemExitException, @ui.error do @@ -477,9 +477,9 @@ ERROR: Possible alternatives: non_existent_with_hint Dir.chdir old end end - + wait_for_child_process_to_exit - + assert_path_exists 'whatever/doc/a-2', 'documentation not installed' end diff --git a/test/rubygems/test_gem_commands_open_command.rb b/test/rubygems/test_gem_commands_open_command.rb index a96fa6ea23..e73a138204 100644 --- a/test/rubygems/test_gem_commands_open_command.rb +++ b/test/rubygems/test_gem_commands_open_command.rb @@ -68,4 +68,33 @@ class TestGemCommandsOpenCommand < Gem::TestCase assert_equal "", @ui.error end + def test_default_gem + @cmd.options[:version] = "1.0" + @cmd.options[:args] = %w[foo] + + version = @cmd.options[:version] + @cmd.define_singleton_method(:spec_for) do |name| + spec = Gem::Specification.find_all_by_name(name, version).first + + spec.define_singleton_method(:default_gem?) do + true + end + + return spec if spec + + say "Unable to find gem '#{name}'" + end + + gem("foo", "1.0") + + assert_raises Gem::MockGemUi::TermError do + use_ui @ui do + @cmd.execute + end + end + + assert_match %r|'foo' is a default gem and can't be opened\.| , @ui.output + assert_equal "", @ui.error + end + end diff --git a/test/rubygems/test_gem_commands_pristine_command.rb b/test/rubygems/test_gem_commands_pristine_command.rb index 806ed87007..4586159af6 100644 --- a/test/rubygems/test_gem_commands_pristine_command.rb +++ b/test/rubygems/test_gem_commands_pristine_command.rb @@ -433,6 +433,39 @@ class TestGemCommandsPristineCommand < Gem::TestCase refute File.exist? gem_lib end + def test_execute_bindir + a = util_spec 'a' do |s| + s.name = "test_gem" + s.executables = %w[foo] + s.files = %w[bin/foo] + end + + write_file File.join(@tempdir, 'bin', 'foo') do |fp| + fp.puts "#!/usr/bin/ruby" + end + + write_file File.join(@tempdir, 'test_bin', 'foo') do |fp| + fp.puts "#!/usr/bin/ruby" + end + + install_gem a + + gem_exec = File.join @gemhome, 'bin', 'foo' + gem_bindir = File.join @tempdir, 'test_bin', 'foo' + + FileUtils.rm gem_exec + FileUtils.rm gem_bindir + + @cmd.handle_options ["--all", "--only-executables", "--bindir", "#{gem_bindir}"] + + use_ui @ui do + @cmd.execute + end + + refute File.exist? gem_exec + assert File.exist? gem_bindir + end + def test_execute_unknown_gem_at_remote_source install_specs util_spec 'a' diff --git a/test/rubygems/test_gem_commands_push_command.rb b/test/rubygems/test_gem_commands_push_command.rb index 1c5dbfe23e..ccc46d4f45 100644 --- a/test/rubygems/test_gem_commands_push_command.rb +++ b/test/rubygems/test_gem_commands_push_command.rb @@ -95,6 +95,26 @@ class TestGemCommandsPushCommand < Gem::TestCase @fetcher.last_request["Content-Type"] 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: freewill (1.0.0)" + @fetcher.data["#{@spec.metadata['allowed_push_host']}/api/v1/gems"] = [@response, 200, 'OK'] + @fetcher.data["#{Gem.host}/api/v1/gems"] = + ['fail', 500, 'Internal Server Error'] + + @cmd.options[:args] = [@path] + + @cmd.execute + + assert_equal Net::HTTP::Post, @fetcher.last_request.class + assert_equal Gem.read_binary(@path), @fetcher.last_request.body + assert_equal "application/octet-stream", + @fetcher.last_request["Content-Type"] + end + def test_sending_when_default_host_disabled Gem.configuration.disable_default_gem_server = true response = "You must specify a gem server" diff --git a/test/rubygems/test_gem_commands_query_command.rb b/test/rubygems/test_gem_commands_query_command.rb index 7957689db4..db6c16e91b 100644 --- a/test/rubygems/test_gem_commands_query_command.rb +++ b/test/rubygems/test_gem_commands_query_command.rb @@ -11,7 +11,7 @@ module TestGemCommandsQueryCommandSetup @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 diff --git a/test/rubygems/test_gem_ext_cmake_builder.rb b/test/rubygems/test_gem_ext_cmake_builder.rb index 76d3cb2afe..2d449fc2fd 100644 --- a/test/rubygems/test_gem_ext_cmake_builder.rb +++ b/test/rubygems/test_gem_ext_cmake_builder.rb @@ -25,6 +25,7 @@ class TestGemExtCmakeBuilder < Gem::TestCase File.open File.join(@ext, 'CMakeLists.txt'), 'w' do |cmakelists| cmakelists.write <<-eo_cmake cmake_minimum_required(VERSION 2.6) +project(self_build LANGUAGES NONE) install (FILES test.txt DESTINATION bin) eo_cmake end diff --git a/test/rubygems/test_gem_gemcutter_utilities.rb b/test/rubygems/test_gem_gemcutter_utilities.rb index 7b3e273957..90f9142171 100644 --- a/test/rubygems/test_gem_gemcutter_utilities.rb +++ b/test/rubygems/test_gem_gemcutter_utilities.rb @@ -179,8 +179,6 @@ class TestGemGemcutterUtilities < Gem::TestCase end def test_sign_in_with_bad_credentials - skip 'Always uses $stdin on windows' if Gem.win_platform? - assert_raises Gem::MockGemUi::TermError do util_sign_in ['Access Denied.', 403, 'Forbidden'] end @@ -190,8 +188,6 @@ class TestGemGemcutterUtilities < Gem::TestCase end def util_sign_in response, host = nil, args = [] - skip 'Always uses $stdin on windows' if Gem.win_platform? - email = 'you@example.com' password = 'secret' diff --git a/test/rubygems/test_gem_installer.rb b/test/rubygems/test_gem_installer.rb index 062d366665..0ff4954d82 100644 --- a/test/rubygems/test_gem_installer.rb +++ b/test/rubygems/test_gem_installer.rb @@ -141,7 +141,7 @@ end end File.open File.join(util_inst_bindir, 'executable'), 'w' do |io| - io.write <<-EXEC + io.write <<-EXEC #!/usr/local/bin/ruby # # This file was generated by RubyGems @@ -336,6 +336,9 @@ gem 'other', version bin_dir = Gem.win_platform? ? File.expand_path(ENV["WINDIR"]).upcase : "/usr/bin" + old_path = ENV["PATH"] + ENV["PATH"] = [ENV["PATH"], bin_dir].compact.join(File::PATH_SEPARATOR) + options = { :bin_dir => bin_dir, :install_dir => "/non/existent" @@ -350,6 +353,9 @@ gem 'other', version end assert_equal "", @ui.error + + ensure + ENV["PATH"] = old_path end def test_generate_bin_script @@ -1409,7 +1415,7 @@ gem 'other', version def spec.full_name # so the spec is buildable "malicious-1" end - def spec.validate; end + def spec.validate packaging, strict; end util_build_gem spec diff --git a/test/rubygems/test_gem_package.rb b/test/rubygems/test_gem_package.rb index 09ef27ee22..a53cda1274 100644 --- a/test/rubygems/test_gem_package.rb +++ b/test/rubygems/test_gem_package.rb @@ -150,7 +150,7 @@ class TestGemPackage < Gem::Package::TarTestCase end def test_add_files_symlink - skip 'symlink not supported' if Gem.win_platform? + skip 'symlink not supported' if Gem.win_platform? && RUBY_VERSION < '2.3' spec = Gem::Specification.new spec.files = %w[lib/code.rb lib/code_sym.rb] @@ -159,7 +159,15 @@ class TestGemPackage < Gem::Package::TarTestCase File.open 'lib/code.rb', 'w' do |io| io.write '# lib/code.rb' end # NOTE: 'code.rb' is correct, because it's relative to lib/code_sym.rb - File.symlink('code.rb', 'lib/code_sym.rb') + begin + File.symlink('code.rb', 'lib/code_sym.rb') + rescue Errno::EACCES => e + if win_platform? + skip "symlink - must be admin with no UAC on Windows" + else + raise e + end + end package = Gem::Package.new 'bogus.gem' package.spec = spec @@ -315,6 +323,19 @@ class TestGemPackage < Gem::Package::TarTestCase assert_equal 'missing value for attribute summary', e.message end + def test_build_invalid_arguments + spec = Gem::Specification.new 'build', '1' + + package = Gem::Package.new spec.file_name + package.spec = spec + + e = assert_raises ArgumentError do + package.build true, true + end + + assert_equal "skip_validation = true and strict_validation = true are incompatible", e.message + end + def test_build_signed skip 'openssl is missing' unless defined?(OpenSSL::SSL) @@ -451,7 +472,7 @@ class TestGemPackage < Gem::Package::TarTestCase end def test_extract_tar_gz_symlink_relative_path - skip 'symlink not supported' if Gem.win_platform? + skip 'symlink not supported' if Gem.win_platform? && RUBY_VERSION < '2.3' package = Gem::Package.new @gem @@ -461,7 +482,15 @@ class TestGemPackage < Gem::Package::TarTestCase tar.add_symlink 'lib/foo.rb', '../relative.rb', 0644 end - package.extract_tar_gz tgz_io, @destination + begin + package.extract_tar_gz tgz_io, @destination + rescue Errno::EACCES => e + if win_platform? + skip "symlink - must be admin with no UAC on Windows" + else + raise e + end + end extracted = File.join @destination, 'lib/foo.rb' assert_path_exists extracted @@ -472,28 +501,34 @@ class TestGemPackage < Gem::Package::TarTestCase end def test_extract_symlink_parent - skip 'symlink not supported' if Gem.win_platform? + skip 'symlink not supported' if Gem.win_platform? && RUBY_VERSION < '2.3' - package = Gem::Package.new @gem + package = Gem::Package.new @gem - tgz_io = util_tar_gz do |tar| - tar.mkdir 'lib', 0755 - tar.add_symlink 'lib/link', '../..', 0644 - tar.add_file 'lib/link/outside.txt', 0644 do |io| io.write 'hi' end - end + tgz_io = util_tar_gz do |tar| + tar.mkdir 'lib', 0755 + tar.add_symlink 'lib/link', '../..', 0644 + tar.add_file 'lib/link/outside.txt', 0644 do |io| io.write 'hi' end + end - # Extract into a subdirectory of @destination; if this test fails it writes - # a file outside destination_subdir, but we want the file to remain inside - # @destination so it will be cleaned up. - destination_subdir = File.join @destination, 'subdir' - FileUtils.mkdir_p destination_subdir + # Extract into a subdirectory of @destination; if this test fails it writes + # a file outside destination_subdir, but we want the file to remain inside + # @destination so it will be cleaned up. + destination_subdir = File.join @destination, 'subdir' + FileUtils.mkdir_p destination_subdir - e = assert_raises Gem::Package::PathError do - package.extract_tar_gz tgz_io, destination_subdir - end + e = assert_raises(Gem::Package::PathError, Errno::EACCES) do + package.extract_tar_gz tgz_io, destination_subdir + end - assert_equal("installing into parent path lib/link/outside.txt of " + - "#{destination_subdir} is not allowed", e.message) + if Gem::Package::PathError === e + assert_equal("installing into parent path lib/link/outside.txt of " + + "#{destination_subdir} is not allowed", e.message) + elsif win_platform? + skip "symlink - must be admin with no UAC on Windows" + else + raise e + end end def test_extract_tar_gz_directory diff --git a/test/rubygems/test_gem_package_tar_reader_entry.rb b/test/rubygems/test_gem_package_tar_reader_entry.rb index dba1987d4d..ef088e32be 100644 --- a/test/rubygems/test_gem_package_tar_reader_entry.rb +++ b/test/rubygems/test_gem_package_tar_reader_entry.rb @@ -34,6 +34,10 @@ class TestGemPackageTarReaderEntry < Gem::Package::TarTestCase assert_equal 1, @entry.bytes_read end + def test_size + assert_equal @contents.size, @entry.size + end + def test_close @entry.close @@ -129,6 +133,13 @@ class TestGemPackageTarReaderEntry < Gem::Package::TarTestCase assert_equal @contents[0...100], @entry.read(100) end + def test_readpartial + assert_raises(EOFError) do + @entry.read(@contents.size) + @entry.readpartial(1) + end + end + def test_rewind char = @entry.getc diff --git a/test/rubygems/test_gem_path_support.rb b/test/rubygems/test_gem_path_support.rb index ccb46d6b8e..90d50a269f 100644 --- a/test/rubygems/test_gem_path_support.rb +++ b/test/rubygems/test_gem_path_support.rb @@ -118,4 +118,21 @@ class TestGemPathSupport < Gem::TestCase ps = Gem::PathSupport.new "GEM_SPEC_CACHE" => "foo" assert_equal "foo", ps.spec_cache_dir end + + def test_gem_paths_do_not_contain_symlinks + dir = "#{@tempdir}/realgemdir" + symlink = "#{@tempdir}/symdir" + Dir.mkdir dir + begin + File.symlink(dir, symlink) + rescue NotImplementedError, SystemCallError + skip 'symlinks not supported' + end + not_existing = "#{@tempdir}/does_not_exist" + path = "#{symlink}#{File::PATH_SEPARATOR}#{not_existing}" + + ps = Gem::PathSupport.new "GEM_PATH" => path, "GEM_HOME" => symlink + assert_equal dir, ps.home + assert_equal [dir, not_existing], ps.path + end end diff --git a/test/rubygems/test_gem_remote_fetcher.rb b/test/rubygems/test_gem_remote_fetcher.rb index 1eed0c7670..dfef55c7ca 100644 --- a/test/rubygems/test_gem_remote_fetcher.rb +++ b/test/rubygems/test_gem_remote_fetcher.rb @@ -731,10 +731,9 @@ PeIQQkFng2VVot/WAQbv3ePqWq07g1BBcwIBAg== assert_equal "murphy", fetcher.fetch_path(@server_uri) end - def test_fetch_s3 + def assert_fetch_s3(url) fetcher = Gem::RemoteFetcher.new nil @fetcher = fetcher - url = 's3://testuser:testpass@my-bucket/gems/specs.4.8.gz' $fetched_uri = nil def fetcher.request(uri, request_class, last_modified = nil) @@ -756,15 +755,64 @@ PeIQQkFng2VVot/WAQbv3ePqWq07g1BBcwIBAg== $fetched_uri = nil end - def test_fetch_s3_no_creds + def test_fetch_s3_config_creds + Gem.configuration[:s3_source] = { + 'my-bucket' => {:id => 'testuser', :secret => 'testpass'} + } + url = 's3://my-bucket/gems/specs.4.8.gz' + assert_fetch_s3 url + ensure + Gem.configuration[:s3_source] = nil + end + + def test_fetch_s3_url_creds + url = 's3://testuser:testpass@my-bucket/gems/specs.4.8.gz' + assert_fetch_s3 url + end + + def refute_fetch_s3(url, expected_message) fetcher = Gem::RemoteFetcher.new nil @fetcher = fetcher - url = 's3://my-bucket/gems/specs.4.8.gz' + e = assert_raises Gem::RemoteFetcher::FetchError do fetcher.fetch_s3 URI.parse(url) end - assert_match "credentials needed", e.message + assert_match expected_message, e.message + end + + 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' + end + + def test_fetch_s3_no_host + Gem.configuration[:s3_source] = { + 'my-bucket' => {:id => 'testuser', :secret => 'testpass'} + } + + url = 's3://other-bucket/gems/specs.4.8.gz' + refute_fetch_s3 url, 'no key for host other-bucket in s3_source in .gemrc' + ensure + Gem.configuration[:s3_source] = nil + end + + def test_fetch_s3_no_id + 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' + ensure + Gem.configuration[:s3_source] = nil + end + + def test_fetch_s3_no_secret + 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' + ensure + Gem.configuration[:s3_source] = nil end def test_observe_no_proxy_env_single_host @@ -846,9 +894,9 @@ PeIQQkFng2VVot/WAQbv3ePqWq07g1BBcwIBAg== with_configured_fetcher( ":ssl_ca_cert: #{temp_ca_cert}\n" + ":ssl_client_cert: #{temp_client_cert}\n") do |fetcher| - assert_raises Gem::RemoteFetcher::FetchError do - fetcher.fetch_path("https://localhost:#{ssl_server.config[:Port]}/yaml") - end + assert_raises Gem::RemoteFetcher::FetchError do + fetcher.fetch_path("https://localhost:#{ssl_server.config[:Port]}/yaml") + end end end diff --git a/test/rubygems/test_gem_resolver.rb b/test/rubygems/test_gem_resolver.rb index b083622da4..99cb77afaf 100644 --- a/test/rubygems/test_gem_resolver.rb +++ b/test/rubygems/test_gem_resolver.rb @@ -299,7 +299,7 @@ class TestGemResolver < Gem::TestCase a2_p1 = a3_p2 = nil spec_fetcher do |fetcher| - fetcher.spec 'a', 2 + fetcher.spec 'a', 2 a2_p1 = fetcher.spec 'a', 2 do |s| s.platform = Gem::Platform.local end a3_p2 = fetcher.spec 'a', 3 do |s| s.platform = unknown end end diff --git a/test/rubygems/test_gem_security_signer.rb b/test/rubygems/test_gem_security_signer.rb index 79dfea099d..8a0b7cfde9 100644 --- a/test/rubygems/test_gem_security_signer.rb +++ b/test/rubygems/test_gem_security_signer.rb @@ -135,9 +135,11 @@ toqvglr0kdbknSRRjBVLK6tsgr07aLT9gNP7mTW2PA== def test_sign_expired signer = Gem::Security::Signer.new PRIVATE_KEY, [EXPIRED_CERT] - assert_raises Gem::Security::Exception do + e = assert_raises Gem::Security::Exception do signer.sign 'hello' end + + assert_match "certificate /CN=nobody/DC=example not valid after 1970-01-01 00:00:00 UTC", e.message end def test_sign_expired_auto_update diff --git a/test/rubygems/test_gem_server.rb b/test/rubygems/test_gem_server.rb index 8a3e6410ae..6886ec4858 100644 --- a/test/rubygems/test_gem_server.rb +++ b/test/rubygems/test_gem_server.rb @@ -377,9 +377,9 @@ class TestGemServer < Gem::TestCase assert_equal 200, @res.status assert_match 'xsshomepagegem 1', @res.body - # This verifies that the homepage for this spec is not displayed and is set to ".", because it's not a + # This verifies that the homepage for this spec is not displayed and is set to ".", because it's not a # valid HTTP/HTTPS URL and could be unsafe in an HTML context. We would prefer to throw an exception here, - # but spec.homepage is currently free form and not currently required to be a URL, this behavior may be + # but spec.homepage is currently free form and not currently required to be a URL, this behavior may be # validated in future versions of Gem::Specification. # # There are two variant we're checking here, one where rdoc is not present, and one where rdoc is present in the same regex: @@ -432,9 +432,9 @@ class TestGemServer < Gem::TestCase assert_equal 200, @res.status assert_match 'invalidhomepagegem 1', @res.body - # This verifies that the homepage for this spec is not displayed and is set to ".", because it's not a + # This verifies that the homepage for this spec is not displayed and is set to ".", because it's not a # valid HTTP/HTTPS URL and could be unsafe in an HTML context. We would prefer to throw an exception here, - # but spec.homepage is currently free form and not currently required to be a URL, this behavior may be + # but spec.homepage is currently free form and not currently required to be a URL, this behavior may be # validated in future versions of Gem::Specification. # # There are two variant we're checking here, one where rdoc is not present, and one where rdoc is present in the same regex: diff --git a/test/rubygems/test_gem_specification.rb b/test/rubygems/test_gem_specification.rb index 845914d33a..edf4d08d93 100644 --- a/test/rubygems/test_gem_specification.rb +++ b/test/rubygems/test_gem_specification.rb @@ -108,7 +108,7 @@ end # objects are present in the @stubs collection. This test verifies that # this scenario works correctly. Gem::Specification.all = [spec] - Gem::Specification.find_active_stub_by_path('foo') + assert_equal spec, Gem::Specification.find_active_stub_by_path('foo') end def test_self_activate @@ -387,8 +387,8 @@ end def test_self_activate_checks_dependencies a = util_spec 'a', '1.0' - a.add_dependency 'c', '= 1.0' - a.add_dependency 'b', '~> 1.0' + a.add_dependency 'c', '= 1.0' + a.add_dependency 'b', '~> 1.0' b1 = util_spec 'b', '1.0' b2 = util_spec 'b', '2.0' @@ -1126,6 +1126,88 @@ dependencies: [] refute_includes Gem::Specification.stubs.map { |s| s.full_name }, 'a-1' end + def test_self_stubs + Gem.loaded_specs.clear + Gem::Specification.class_variable_set(:@@stubs, nil) + + dir_standard_specs = File.join Gem.dir, 'specifications' + dir_default_specs = Gem::BasicSpecification.default_specifications_dir + + # Create gemspecs in three locations used in stubs + loaded_spec = Gem::Specification.new 'a', '3' + Gem.loaded_specs['a'] = loaded_spec + save_gemspec 'a', '2', dir_default_specs + save_gemspec 'a', '1', dir_standard_specs + + full_names = ['a-3', 'a-2', 'a-1'] + assert_equal full_names, Gem::Specification.stubs.map { |s| s.full_name } + + Gem.loaded_specs.delete 'a' + Gem::Specification.class_variable_set(:@@stubs, nil) + end + + def test_self_stubs_for + Gem.loaded_specs.clear + Gem::Specification.class_variable_set(:@@stubs, nil) + + dir_standard_specs = File.join Gem.dir, 'specifications' + dir_default_specs = Gem::BasicSpecification.default_specifications_dir + + # Create gemspecs in three locations used in stubs + loaded_spec = Gem::Specification.new 'a', '3' + Gem.loaded_specs['a'] = loaded_spec + save_gemspec 'a', '2', dir_default_specs + save_gemspec 'a', '1', dir_standard_specs + + full_names = ['a-3', 'a-2', 'a-1'] + + full_names = Gem::Specification.stubs_for('a').map { |s| s.full_name } + assert_equal full_names, Gem::Specification.stubs_for('a').map { |s| s.full_name } + assert_equal 1, Gem::Specification.class_variable_get(:@@stubs_by_name).length + + Gem.loaded_specs.delete 'a' + Gem::Specification.class_variable_set(:@@stubs, nil) + end + + def test_self_stubs_for_mult_platforms + # gems for two different platforms are installed with --user-install + # the correct one should be returned in the array + + orig_platform = Gem.platforms.dup + + # create user spec + user_spec_dir = File.join Gem.user_dir, 'specifications' + FileUtils.mkdir_p(user_spec_dir) unless Dir.exist? user_spec_dir + # dirs doesn't include user ? + Gem::Specification.dirs << user_spec_dir + + gem = 'mingw' + v = '1.1.1' + platforms = ['x86-mingw32', 'x64-mingw32'] + + #create specs + platforms.each do |plat| + spec = Gem::Specification.new(gem, v) { |s| s.platform = plat } + File.open File.join(user_spec_dir, "#{gem}-#{v}-#{plat}.gemspec"), 'w' do |io| + io.write spec.to_ruby + end + end + + platforms.each do |plat| + cur_plat = Gem::Platform.new plat + Gem.platforms = ['ruby', cur_plat] + + Gem::Specification.class_variable_set :@@stubs, nil + Gem::Specification.stubs if plat == platforms.last # test loading via stubs + t = Gem::Specification.stubs_for 'mingw' + + assert_equal 1, t.length + assert_equal cur_plat, t.first.platform + end + + Gem.platforms = orig_platform + end + DATA_PATH = File.expand_path "../data", __FILE__ def test_handles_private_null_type @@ -2615,16 +2697,6 @@ 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}: pessimistic dependency on d (~> 1.2.3) may be overly strict - if d is semantically versioned, use: - add_runtime_dependency 'd', '~> 1.2', '>= 1.2.3' - if d is not semantically versioned, you can bypass this warning with: - add_runtime_dependency 'd', '>= 1.2.3', '< 1.3.a' -#{w}: pessimistic dependency on e (~> 1.2.3.4) may be overly strict - if e is semantically versioned, use: - add_runtime_dependency 'e', '~> 1.2', '>= 1.2.3.4' - if e is not semantically versioned, you can bypass this warning with: - add_runtime_dependency 'e', '>= 1.2.3.4', '< 1.2.4.a' #{w}: open-ended dependency on i (>= 1.2) is not recommended if i is semantically versioned, use: add_runtime_dependency 'i', '~> 1.2' @@ -2637,11 +2709,6 @@ end #{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}: pessimistic dependency on m (~> 2.1.0) may be overly strict - if m is semantically versioned, use: - add_runtime_dependency 'm', '~> 2.1', '>= 2.1.0' - if m is not semantically versioned, you can bypass this warning with: - add_runtime_dependency 'm', '>= 2.1.0', '< 2.2.a' #{w}: See http://guides.rubygems.org/specification-reference/ for help EXPECTED @@ -2844,6 +2911,58 @@ duplicate dependency on c (>= 1.2.3, development), (~> 1.2) use: @a1.files end + def test_unresolved_specs + specification = Gem::Specification.clone + + specification.define_singleton_method(:unresolved_deps) do + { b: Gem::Dependency.new("x","1") } + end + + specification.define_singleton_method(:find_all_by_name) do |dep_name| + [] + end + + expected = <<-EXPECTED +WARN: Unresolved or ambigious specs during Gem::Specification.reset: + x (= 1) +WARN: Clearing out unresolved specs. Try 'gem cleanup ' +Please report a bug if this causes problems. + EXPECTED + + assert_output nil, expected do + specification.reset + end + end + + def test_unresolved_specs_with_versions + specification = Gem::Specification.clone + + specification.define_singleton_method(:unresolved_deps) do + { b: Gem::Dependency.new("x","1") } + end + + specification.define_singleton_method(:find_all_by_name) do |dep_name| + [ + specification.new { |s| s.name = "z", s.version = Gem::Version.new("1") }, + specification.new { |s| s.name = "z", s.version = Gem::Version.new("2") } + ] + end + + expected = <<-EXPECTED +WARN: Unresolved or ambigious specs during Gem::Specification.reset: + x (= 1) + Available/installed versions of this gem: + - 1 + - 2 +WARN: Clearing out unresolved specs. Try 'gem cleanup ' +Please report a bug if this causes problems. + EXPECTED + + assert_output nil, expected do + specification.reset + end + end + def test_validate_files_recursive util_setup_validate FileUtils.touch @a1.file_name diff --git a/test/rubygems/test_gem_text.rb b/test/rubygems/test_gem_text.rb index 04f3f605e8..0249c47f83 100644 --- a/test/rubygems/test_gem_text.rb +++ b/test/rubygems/test_gem_text.rb @@ -21,6 +21,10 @@ class TestGemText < Gem::TestCase assert_equal " text to wrap", format_text("text to wrap", 40, 2) end + def test_format_text_no_space + assert_equal "texttowr\nap", format_text("texttowrap", 8) + end + def test_format_text_trailing # for two spaces after . text = <<-TEXT This line is really, really long. So long, in fact, that it is more than eighty characters long! The purpose of this line is for testing wrapping behavior because sometimes people don't wrap their text to eighty characters. Without the wrapping, the text might not look good in the RSS feed. diff --git a/test/rubygems/test_gem_util.rb b/test/rubygems/test_gem_util.rb index 71a26c06ae..205cd89611 100644 --- a/test/rubygems/test_gem_util.rb +++ b/test/rubygems/test_gem_util.rb @@ -38,6 +38,8 @@ class TestGemUtil < Gem::TestCase # impossible to cd into it and its children FileUtils.chmod(0666, 'd/e') + skip 'skipped in root privilege' if Process.uid.zero? + paths = Gem::Util.traverse_parents('d/e/f').to_a assert_equal File.join(@tempdir, 'd'), paths[0] diff --git a/test/rubygems/test_gem_version.rb b/test/rubygems/test_gem_version.rb index bddae7fdc3..a2572fb611 100644 --- a/test/rubygems/test_gem_version.rb +++ b/test/rubygems/test_gem_version.rb @@ -46,7 +46,11 @@ 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") - assert_equal false, Gem::Version.correct?(nil) + + expected = "nil versions are discouraged and will be deprecated in Rubygems 4\n" + assert_output nil, expected do + Gem::Version.correct?(nil) + end end def test_class_new_subclass @@ -158,11 +162,25 @@ class TestGemVersion < Gem::TestCase def test_approximate_recommendation assert_approximate_equal "~> 1.0", "1" + assert_approximate_satisfies_itself "1" + assert_approximate_equal "~> 1.0", "1.0" + assert_approximate_satisfies_itself "1.0" + assert_approximate_equal "~> 1.2", "1.2" + assert_approximate_satisfies_itself "1.2" + 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.a.4" + assert_approximate_satisfies_itself "1.2.3" + + 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_satisfies_itself "1.9.0.dev" end def test_to_s @@ -198,12 +216,20 @@ class TestGemVersion < Gem::TestCase assert v(version).prerelease?, "#{version} is a prerelease" end - # Assert that +expected+ is the "approximate" recommendation for +version". + # Assert that +expected+ is the "approximate" recommendation for +version+. def assert_approximate_equal expected, version assert_equal expected, v(version).approximate_recommendation end + # Assert that the "approximate" recommendation for +version+ satifies +version+. + + def assert_approximate_satisfies_itself version + gem_version = v(version) + + assert Gem::Requirement.new(gem_version.approximate_recommendation).satisfied_by?(gem_version) + end + # Assert that bumping the +unbumped+ version yields the +expected+. def assert_bumped_version_equal expected, unbumped