From effdbf5936cc090a618e13c8f9a1b5412ebab2fa Mon Sep 17 00:00:00 2001 From: hsbt Date: Wed, 1 Jul 2015 21:50:14 +0000 Subject: * lib/rubygems: Update to RubyGems HEAD(c202db2). this version contains many enhancements see http://git.io/vtNwF * test/rubygems: ditto. git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@51092 b2dd03c8-39d4-4d8f-98ff-823fe69b080e --- ChangeLog | 6 + lib/rubygems.rb | 31 +- lib/rubygems/basic_specification.rb | 40 +- lib/rubygems/commands/dependency_command.rb | 40 +- lib/rubygems/commands/install_command.rb | 2 +- lib/rubygems/commands/list_command.rb | 2 +- lib/rubygems/commands/pristine_command.rb | 2 +- lib/rubygems/commands/query_command.rb | 2 +- lib/rubygems/core_ext/kernel_require.rb | 4 +- lib/rubygems/dependency.rb | 9 +- lib/rubygems/dependency_list.rb | 3 + lib/rubygems/ext/builder.rb | 2 + lib/rubygems/ext/ext_conf_builder.rb | 7 +- lib/rubygems/indexer.rb | 26 +- lib/rubygems/installer.rb | 84 ++-- lib/rubygems/installer_test_case.rb | 4 +- lib/rubygems/package.rb | 24 +- lib/rubygems/package/old.rb | 4 +- lib/rubygems/package/tar_reader/entry.rb | 8 +- lib/rubygems/package/tar_test_case.rb | 15 +- lib/rubygems/package/tar_writer.rb | 20 +- lib/rubygems/platform.rb | 1 + lib/rubygems/rdoc.rb | 3 +- lib/rubygems/request/connection_pools.rb | 12 +- lib/rubygems/request_set.rb | 7 +- lib/rubygems/request_set/gem_dependency_api.rb | 2 +- lib/rubygems/request_set/lockfile.rb | 2 +- lib/rubygems/resolver.rb | 286 +++----------- lib/rubygems/resolver/activation_request.rb | 3 +- lib/rubygems/resolver/conflict.rb | 1 - lib/rubygems/resolver/dependency_request.rb | 5 +- lib/rubygems/resolver/git_specification.rb | 3 +- lib/rubygems/resolver/molinillo.rb | 1 + lib/rubygems/resolver/molinillo/lib/molinillo.rb | 5 + .../molinillo/lib/molinillo/dependency_graph.rb | 266 +++++++++++++ .../resolver/molinillo/lib/molinillo/errors.rb | 69 ++++ .../molinillo/lib/molinillo/gem_metadata.rb | 3 + .../molinillo/modules/specification_provider.rb | 99 +++++ .../resolver/molinillo/lib/molinillo/modules/ui.rb | 63 +++ .../resolver/molinillo/lib/molinillo/resolution.rb | 430 +++++++++++++++++++++ .../resolver/molinillo/lib/molinillo/resolver.rb | 43 +++ .../resolver/molinillo/lib/molinillo/state.rb | 51 +++ lib/rubygems/resolver/specification.rb | 2 +- lib/rubygems/specification.rb | 237 ++++++++---- lib/rubygems/stub_specification.rb | 39 +- lib/rubygems/test_case.rb | 49 ++- lib/rubygems/test_utilities.rb | 36 +- lib/rubygems/uninstaller.rb | 2 +- lib/rubygems/util.rb | 8 +- lib/rubygems/util/list.rb | 30 +- lib/rubygems/util/stringio.rb | 34 -- lib/rubygems/version.rb | 12 +- test/rubygems/simple_gem.rb | 2 +- test/rubygems/test_gem.rb | 65 +++- test/rubygems/test_gem_available_set.rb | 3 +- test/rubygems/test_gem_commands_cleanup_command.rb | 11 +- .../test_gem_commands_dependency_command.rb | 10 +- test/rubygems/test_gem_commands_install_command.rb | 44 +-- test/rubygems/test_gem_commands_mirror.rb | 13 - .../rubygems/test_gem_commands_outdated_command.rb | 5 +- .../rubygems/test_gem_commands_pristine_command.rb | 14 +- test/rubygems/test_gem_commands_query_command.rb | 281 ++++++-------- .../test_gem_commands_specification_command.rb | 8 +- test/rubygems/test_gem_commands_stale_command.rb | 2 + .../test_gem_commands_uninstall_command.rb | 8 +- test/rubygems/test_gem_commands_unpack_command.rb | 10 +- test/rubygems/test_gem_commands_update_command.rb | 74 ++-- test/rubygems/test_gem_commands_which_command.rb | 1 + test/rubygems/test_gem_dependency.rb | 10 +- test/rubygems/test_gem_dependency_installer.rb | 10 +- test/rubygems/test_gem_doctor.rb | 2 +- test/rubygems/test_gem_ext_configure_builder.rb | 12 +- test/rubygems/test_gem_ext_ext_conf_builder.rb | 40 +- test/rubygems/test_gem_indexer.rb | 2 +- test/rubygems/test_gem_install_update_options.rb | 4 +- test/rubygems/test_gem_installer.rb | 58 +-- test/rubygems/test_gem_package.rb | 47 ++- test/rubygems/test_gem_package_tar_reader_entry.rb | 9 +- test/rubygems/test_gem_package_tar_writer.rb | 11 +- test/rubygems/test_gem_request_connection_pools.rb | 9 + test/rubygems/test_gem_request_set.rb | 13 +- test/rubygems/test_gem_request_set_lockfile.rb | 6 +- test/rubygems/test_gem_resolver.rb | 43 +-- test/rubygems/test_gem_resolver_installer_set.rb | 18 +- .../test_gem_resolver_lock_specification.rb | 5 +- test/rubygems/test_gem_specification.rb | 334 +++++++++++++--- test/rubygems/test_gem_stub_specification.rb | 27 +- test/rubygems/test_gem_uninstaller.rb | 3 +- test/rubygems/test_gem_util.rb | 8 + test/rubygems/test_require.rb | 251 ++++++------ 90 files changed, 2471 insertions(+), 1141 deletions(-) create mode 100644 lib/rubygems/resolver/molinillo.rb create mode 100644 lib/rubygems/resolver/molinillo/lib/molinillo.rb create mode 100644 lib/rubygems/resolver/molinillo/lib/molinillo/dependency_graph.rb create mode 100644 lib/rubygems/resolver/molinillo/lib/molinillo/errors.rb create mode 100644 lib/rubygems/resolver/molinillo/lib/molinillo/gem_metadata.rb create mode 100644 lib/rubygems/resolver/molinillo/lib/molinillo/modules/specification_provider.rb create mode 100644 lib/rubygems/resolver/molinillo/lib/molinillo/modules/ui.rb create mode 100644 lib/rubygems/resolver/molinillo/lib/molinillo/resolution.rb create mode 100644 lib/rubygems/resolver/molinillo/lib/molinillo/resolver.rb create mode 100644 lib/rubygems/resolver/molinillo/lib/molinillo/state.rb delete mode 100644 lib/rubygems/util/stringio.rb diff --git a/ChangeLog b/ChangeLog index 56a002f57d..1d021801b7 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,9 @@ +Thu Jul 2 06:49:44 2015 SHIBATA Hiroshi + + * lib/rubygems: Update to RubyGems HEAD(c202db2). + this version contains many enhancements see http://git.io/vtNwF + * test/rubygems: ditto. + Wed Jul 1 23:50:34 2015 Kazuhiro NISHIYAMA * test/net/http/test_httpresponse.rb diff --git a/lib/rubygems.rb b/lib/rubygems.rb index 817805c1f4..7af0694402 100644 --- a/lib/rubygems.rb +++ b/lib/rubygems.rb @@ -9,7 +9,7 @@ require 'rbconfig' require 'thread' module Gem - VERSION = '2.4.8' + VERSION = '2.5.0' end # Must be first since it unloads the prelude from 1.9.2 @@ -26,12 +26,12 @@ require 'rubygems/errors' # For user documentation, see: # # * gem help and gem help [command] -# * {RubyGems User Guide}[http://docs.rubygems.org/read/book/1] -# * {Frequently Asked Questions}[http://docs.rubygems.org/read/book/3] +# * {RubyGems User Guide}[http://guides.rubygems.org/] +# * {Frequently Asked Questions}[http://guides.rubygems.org/faqs] # # For gem developer documentation see: # -# * {Creating Gems}[http://docs.rubygems.org/read/chapter/5] +# * {Creating Gems}[http://guides.rubygems.org/make-your-own-gem] # * Gem::Specification # * Gem::Version for version dependency notes # @@ -156,6 +156,7 @@ module Gem @@win_platform = nil @configuration = nil + @gemdeps = nil @loaded_specs = {} LOADED_SPECS_MUTEX = Mutex.new @path_to_default_spec_map = {} @@ -184,13 +185,9 @@ module Gem # or if it was ambiguous (and thus unresolved) the code in our custom # require will try to activate the more specific version. - spec = Gem::Specification.find_inactive_by_path path - - unless spec - spec = Gem::Specification.find_by_path path - return true if spec && spec.activated? - return false - end + spec = Gem::Specification.find_by_path path + return false unless spec + return true if spec.activated? begin spec.activate @@ -433,7 +430,7 @@ module Gem files = find_files_from_load_path glob if check_load_path - files.concat Gem::Specification.map { |spec| + files.concat Gem::Specification.stubs.map { |spec| spec.matches_for_glob("#{glob}#{Gem.suffix_pattern}") }.flatten @@ -598,7 +595,7 @@ module Gem unless test_syck begin - gem 'psych', '~> 1.2', '>= 1.2.1' + gem 'psych', '>= 1.2.1' rescue Gem::LoadError # It's OK if the user does not have the psych gem installed. We will # attempt to require the stdlib version @@ -1052,7 +1049,7 @@ module Gem end rs = Gem::RequestSet.new - rs.load_gemdeps path + @gemdeps = rs.load_gemdeps path rs.resolve_current.map do |s| sp = s.full_spec @@ -1082,6 +1079,12 @@ module Gem attr_reader :loaded_specs + ## + # GemDependencyAPI object, which is set when .use_gemdeps is called. + # This contains all the information from the Gemfile. + + attr_reader :gemdeps + ## # Register a Gem::Specification for default gem. # diff --git a/lib/rubygems/basic_specification.rb b/lib/rubygems/basic_specification.rb index e27b261d7e..78a45c1190 100644 --- a/lib/rubygems/basic_specification.rb +++ b/lib/rubygems/basic_specification.rb @@ -22,13 +22,17 @@ class Gem::BasicSpecification ## # The path this gemspec was loaded from. This attribute is not persisted. - attr_reader :loaded_from + attr_accessor :loaded_from ## # Allows correct activation of git: and path: gems. attr_writer :full_gem_path # :nodoc: + def initialize + internal_init + end + def self.default_specifications_dir File.join(Gem.default_dir, "specifications", "default") end @@ -141,7 +145,7 @@ class Gem::BasicSpecification @full_require_paths ||= begin full_paths = raw_require_paths.map do |path| - File.join full_gem_path, path + File.join full_gem_path, path.untaint end full_paths << extension_dir unless @extensions.nil? || @extensions.empty? @@ -189,13 +193,7 @@ class Gem::BasicSpecification @gems_dir ||= File.join(loaded_from && base_dir || Gem.dir, "gems") end - ## - # Set the path the Specification was loaded from. +path+ is converted to a - # String. - - def loaded_from= path - @loaded_from = path && path.to_s - + def internal_init # :nodoc: @extension_dir = nil @extensions_dir = nil @full_gem_path = nil @@ -263,6 +261,30 @@ class Gem::BasicSpecification paths.uniq end + ## + # Return all files in this gem that match for +glob+. + + def matches_for_glob glob # TODO: rename? + # TODO: do we need these?? Kill it + glob = File.join(self.lib_dirs_glob, glob) + + Dir[glob].map { |f| f.untaint } # FIX our tests are broken, run w/ SAFE=1 + end + + ## + # Returns a string usable in Dir.glob to match all requirable paths + # for this spec. + + def lib_dirs_glob + dirs = if self.require_paths.size > 1 then + "{#{self.require_paths.join(',')}}" + else + self.require_paths.first + end + + "#{self.full_gem_path}/#{dirs}" + end + ## # Return a Gem::Specification from this gem diff --git a/lib/rubygems/commands/dependency_command.rb b/lib/rubygems/commands/dependency_command.rb index 4a54a3e385..9d4b3e0f50 100644 --- a/lib/rubygems/commands/dependency_command.rb +++ b/lib/rubygems/commands/dependency_command.rb @@ -61,10 +61,16 @@ use with other commands. ss.map { |spec, _| spec } end - def fetch_specs dependency # :nodoc: + def fetch_specs name_pattern, dependency # :nodoc: specs = [] - specs.concat dependency.matching_specs if local? + if local? + specs.concat Gem::Specification.stubs.find_all { |spec| + name_pattern =~ spec.name and + dependency.requirement.satisfied_by? spec.version + }.map(&:to_spec) + end + specs.concat fetch_remote_specs dependency if remote? ensure_specs specs @@ -72,16 +78,7 @@ use with other commands. specs.uniq.sort end - def gem_dependency args, version, prerelease # :nodoc: - args << '' if args.empty? - - pattern = if args.length == 1 and args.first =~ /\A\/(.*)\/(i)?\z/m then - flags = $2 ? Regexp::IGNORECASE : nil - Regexp.new $1, flags - else - /\A#{Regexp.union(*args)}/ - end - + def gem_dependency pattern, version, prerelease # :nodoc: dependency = Gem::Deprecate.skip_during { Gem::Dependency.new pattern, version } @@ -121,10 +118,12 @@ use with other commands. def execute ensure_local_only_reverse_dependencies + pattern = name_pattern options[:args] + dependency = - gem_dependency options[:args], options[:version], options[:prerelease] + gem_dependency pattern, options[:version], options[:prerelease] - specs = fetch_specs dependency + specs = fetch_specs pattern, dependency reverse = reverse_dependencies specs @@ -203,5 +202,16 @@ use with other commands. result end -end + private + def name_pattern args + args << '' if args.empty? + + if args.length == 1 and args.first =~ /\A\/(.*)\/(i)?\z/m then + flags = $2 ? Regexp::IGNORECASE : nil + Regexp.new $1, flags + else + /\A#{Regexp.union(*args)}/ + end + end +end diff --git a/lib/rubygems/commands/install_command.rb b/lib/rubygems/commands/install_command.rb index 1bf5928ebb..7e3a953d19 100644 --- a/lib/rubygems/commands/install_command.rb +++ b/lib/rubygems/commands/install_command.rb @@ -275,7 +275,7 @@ to write the specification by hand. For example: gem = fetcher.download_to_cache dependency end - inst = Gem::Installer.new gem, options + inst = Gem::Installer.at gem, options inst.install require 'rubygems/dependency_installer' diff --git a/lib/rubygems/commands/list_command.rb b/lib/rubygems/commands/list_command.rb index c6ff237311..1842aaacca 100644 --- a/lib/rubygems/commands/list_command.rb +++ b/lib/rubygems/commands/list_command.rb @@ -33,7 +33,7 @@ To search for remote gems use the search command. end def usage # :nodoc: - "#{program_name} [STRING ...]" + "#{program_name} [REGEXP ...]" end end diff --git a/lib/rubygems/commands/pristine_command.rb b/lib/rubygems/commands/pristine_command.rb index 91308d83f6..5a36b12358 100644 --- a/lib/rubygems/commands/pristine_command.rb +++ b/lib/rubygems/commands/pristine_command.rb @@ -156,7 +156,7 @@ extensions will be restored. install_defaults.to_s['--env-shebang'] end - installer = Gem::Installer.new(gem, + installer = Gem::Installer.at(gem, :wrappers => true, :force => true, :install_dir => spec.base_dir, diff --git a/lib/rubygems/commands/query_command.rb b/lib/rubygems/commands/query_command.rb index 432250e033..0d28711de8 100644 --- a/lib/rubygems/commands/query_command.rb +++ b/lib/rubygems/commands/query_command.rb @@ -227,7 +227,7 @@ is too hard to use. name_tuple, spec = detail_tuple - spec = spec.fetch_spec name_tuple unless Gem::Specification === spec + spec = spec.fetch_spec name_tuple if spec.respond_to? :fetch_spec entry << "\n" diff --git a/lib/rubygems/core_ext/kernel_require.rb b/lib/rubygems/core_ext/kernel_require.rb index 8f2cddee4d..0a073adb7b 100755 --- a/lib/rubygems/core_ext/kernel_require.rb +++ b/lib/rubygems/core_ext/kernel_require.rb @@ -66,7 +66,7 @@ module Kernel begin RUBYGEMS_ACTIVATION_MONITOR.exit - return gem_original_require(spec.to_fullpath(path) || path) + return gem_original_require(path) end if spec # Attempt to find +path+ in any unresolved gems... @@ -105,7 +105,7 @@ module Kernel # Ok, now find a gem that has no conflicts, starting # at the highest version. - valid = found_specs.select { |s| s.conflicts.empty? }.last + valid = found_specs.reject { |s| s.has_conflicts? }.last unless valid then le = Gem::LoadError.new "unable to find a version of '#{names.first}' to activate" diff --git a/lib/rubygems/dependency.rb b/lib/rubygems/dependency.rb index 5924d2fc6b..b4a6dd8673 100644 --- a/lib/rubygems/dependency.rb +++ b/lib/rubygems/dependency.rb @@ -164,6 +164,10 @@ class Gem::Dependency @type ||= :runtime end + def runtime? + @type == :runtime || !@type + end + def == other # :nodoc: Gem::Dependency === other && self.name == other.name && @@ -270,9 +274,8 @@ class Gem::Dependency end def matching_specs platform_only = false - matches = Gem::Specification.stubs.find_all { |spec| - self.name === spec.name and # TODO: == instead of === - requirement.satisfied_by? spec.version + matches = Gem::Specification.stubs_for(name).find_all { |spec| + requirement.satisfied_by? spec.version }.map(&:to_spec) if platform_only diff --git a/lib/rubygems/dependency_list.rb b/lib/rubygems/dependency_list.rb index ad7a82a86e..c034bb6589 100644 --- a/lib/rubygems/dependency_list.rb +++ b/lib/rubygems/dependency_list.rb @@ -141,6 +141,9 @@ class Gem::DependencyList def ok_to_remove?(full_name, check_dev=true) gem_to_remove = find_name full_name + # If the state is inconsistent, at least don't crash + return true unless gem_to_remove + siblings = @specs.find_all { |s| s.name == gem_to_remove.name && s.full_name != gem_to_remove.full_name diff --git a/lib/rubygems/ext/builder.rb b/lib/rubygems/ext/builder.rb index 548f1262a8..abcc09ce57 100644 --- a/lib/rubygems/ext/builder.rb +++ b/lib/rubygems/ext/builder.rb @@ -66,9 +66,11 @@ class Gem::Ext::Builder # TODO use Process.spawn when ruby 1.8 support is dropped. rubygems_gemdeps, ENV['RUBYGEMS_GEMDEPS'] = ENV['RUBYGEMS_GEMDEPS'], nil if verbose + puts("current directory: #{Dir.pwd}") puts(command) system(command) else + results << "current directory: #{Dir.pwd}" results << command results << `#{command} #{redirector}` end diff --git a/lib/rubygems/ext/ext_conf_builder.rb b/lib/rubygems/ext/ext_conf_builder.rb index d11d1ac328..f5c4c8ce84 100644 --- a/lib/rubygems/ext/ext_conf_builder.rb +++ b/lib/rubygems/ext/ext_conf_builder.rb @@ -35,7 +35,12 @@ class Gem::Ext::ExtConfBuilder < Gem::Ext::Builder begin run cmd, results ensure - FileUtils.mv 'mkmf.log', dest_path if File.exist? 'mkmf.log' + if File.exist? 'mkmf.log' + results << "To see why this extension failed to compile, please check" \ + " the mkmf.log which can be found here:\n" + results << " " + File.join(dest_path, 'mkmf.log') + "\n" + FileUtils.mv 'mkmf.log', dest_path + end siteconf.unlink end diff --git a/lib/rubygems/indexer.rb b/lib/rubygems/indexer.rb index 99938d013a..23279f40ec 100644 --- a/lib/rubygems/indexer.rb +++ b/lib/rubygems/indexer.rb @@ -96,11 +96,10 @@ class Gem::Indexer # Build various indices def build_indices - Gem::Specification.dirs = [] - Gem::Specification.add_specs(*map_gems_to_specs(gem_file_list)) - - build_marshal_gemspecs - build_modern_indices if @build_modern + specs = map_gems_to_specs gem_file_list + Gem::Specification._resort! specs + build_marshal_gemspecs specs + build_modern_indices specs if @build_modern compress_indices end @@ -108,8 +107,8 @@ class Gem::Indexer ## # Builds Marshal quick index gemspecs. - def build_marshal_gemspecs - count = Gem::Specification.count { |s| not s.default_gem? } + def build_marshal_gemspecs specs + count = specs.count progress = ui.progress_reporter count, "Generating Marshal quick index gemspecs for #{count} gems", "Complete" @@ -117,7 +116,7 @@ class Gem::Indexer files = [] Gem.time 'Generated Marshal quick index gemspecs' do - Gem::Specification.each do |spec| + specs.each do |spec| next if spec.default_gem? spec_file_name = "#{spec.original_name}.gemspec.rz" marshal_name = File.join @quick_marshal_dir, spec_file_name @@ -171,14 +170,12 @@ class Gem::Indexer ## # Builds indices for RubyGems 1.2 and newer. Handles full, latest, prerelease - def build_modern_indices - specs = Gem::Specification.reject { |s| s.default_gem? } - + def build_modern_indices specs prerelease, released = specs.partition { |s| s.version.prerelease? } latest_specs = - Gem::Specification.latest_specs.reject { |s| s.default_gem? } + Gem::Specification._latest_specs specs build_modern_index(released.sort, @specs_index, 'specs') build_modern_index(latest_specs.sort, @latest_specs_index, 'latest specs') @@ -376,10 +373,7 @@ class Gem::Indexer specs = map_gems_to_specs updated_gems prerelease, released = specs.partition { |s| s.version.prerelease? } - Gem::Specification.dirs = [] - Gem::Specification.add_specs(*specs) - - files = build_marshal_gemspecs + files = build_marshal_gemspecs specs Gem.time 'Updated indexes' do update_specs_index released, @dest_specs_index, @specs_index diff --git a/lib/rubygems/installer.rb b/lib/rubygems/installer.rb index 10fc1a34a5..efaea83187 100644 --- a/lib/rubygems/installer.rb +++ b/lib/rubygems/installer.rb @@ -61,11 +61,6 @@ class Gem::Installer attr_reader :options - ## - # Sets the specification for .gem-less installs. - - attr_writer :spec - @path_warning = false @install_lock = Mutex.new @@ -99,6 +94,46 @@ class Gem::Installer end + ## + # Construct an installer object for the gem file located at +path+ + + def self.at path, options = {} + security_policy = options[:security_policy] + package = Gem::Package.new path, security_policy + new package, options + end + + class FakePackage + attr_accessor :spec + + def initialize(spec) + @spec = spec + end + + def extract_files destination_dir, pattern = '*' + FileUtils.mkdir_p destination_dir + + spec.files.each do |file| + file = File.join destination_dir, file + next if File.exist? file + FileUtils.mkdir_p File.dirname(file) + File.open file, 'w' do |fp| fp.puts "# #{file}" end + end + end + + def copy_to path + end + end + + ## + # Construct an installer object for an ephemeral gem (one where we don't + # actually have a .gem file, just a spec) + + def self.for_spec spec, options = {} + # FIXME: we should have a real Package class for this + new FakePackage.new(spec), options + end + ## # Constructs an Installer instance that will install the gem located at # +gem+. +options+ is a Hash with the following keys: @@ -122,17 +157,22 @@ class Gem::Installer # :build_args:: An Array of arguments to pass to the extension builder # process. If not set, then Gem::Command.build_args is used - def initialize(gem, options={}) + def initialize(package, options={}) require 'fileutils' - @gem = gem @options = options - @package = Gem::Package.new @gem + if package.is_a? String + security_policy = options[:security_policy] + @package = Gem::Package.new package, security_policy + if $VERBOSE + warn "constructing an Installer object with a string is deprecated. Please use Gem::Installer.at (called from: #{caller.first})" + end + else + @package = package + end process_options - @package.security_policy = @security_policy - if options[:user_install] and not options[:unpack] then @gem_home = Gem.user_dir @bin_dir = Gem.bindir gem_home unless options[:bin_dir] @@ -211,7 +251,7 @@ class Gem::Installer # Lazy accessor for the installer's spec. def spec - @spec ||= @package.spec + @package.spec rescue Gem::Package::Error => e raise Gem::InstallError, "invalid gem: #{e.message}" end @@ -230,7 +270,7 @@ class Gem::Installer def install pre_install_checks - FileUtils.rm_f File.join gem_home, 'specifications', @spec.spec_name + FileUtils.rm_f File.join gem_home, 'specifications', spec.spec_name run_pre_install_hooks @@ -239,12 +279,12 @@ class Gem::Installer FileUtils.mkdir_p gem_dir - spec.loaded_from = spec_file - if @options[:install_as_default] + spec.loaded_from = default_spec_file extract_bin write_default_spec else + spec.loaded_from = spec_file extract_files build_extensions @@ -258,7 +298,7 @@ class Gem::Installer say spec.post_install_message unless spec.post_install_message.nil? - Gem::Installer.install_lock.synchronize { Gem::Specification.add_spec spec } + Gem::Installer.install_lock.synchronize { Gem::Specification.reset } run_post_install_hooks @@ -363,7 +403,7 @@ class Gem::Installer # def default_spec_file - File.join gem_home, "specifications/default", "#{spec.full_name}.gemspec" + File.join Gem::Specification.default_specifications_dir, "#{spec.full_name}.gemspec" end ## @@ -595,7 +635,6 @@ class Gem::Installer @gem_home = options[:install_dir] || Gem.dir @ignore_dependencies = options[:ignore_dependencies] @format_executable = options[:format_executable] - @security_policy = options[:security_policy] @wrappers = options[:wrappers] @only_install_dir = options[:only_install_dir] @@ -661,7 +700,7 @@ class Gem::Installer require 'rubygems' -version = "#{Gem::Requirement.default}" +version = "#{Gem::Requirement.default}.a" if ARGV.first str = ARGV.first @@ -764,11 +803,6 @@ TEXT def pre_install_checks verify_gem_home options[:unpack] - # If we're forcing the install then disable security unless the security - # policy says that we only install signed gems. - @security_policy = nil if - @force and @security_policy and not @security_policy.only_signed - ensure_loadable_spec if options[:install_as_default] @@ -811,9 +845,7 @@ TEXT def write_cache_file cache_file = File.join gem_home, 'cache', spec.file_name - - FileUtils.cp @gem, cache_file unless File.exist? cache_file + @package.copy_to cache_file end end - diff --git a/lib/rubygems/installer_test_case.rb b/lib/rubygems/installer_test_case.rb index 549de011e4..f4aa773114 100644 --- a/lib/rubygems/installer_test_case.rb +++ b/lib/rubygems/installer_test_case.rb @@ -176,7 +176,7 @@ class Gem::InstallerTestCase < Gem::TestCase end end - @installer = Gem::Installer.new @gem + @installer = Gem::Installer.at @gem end ## @@ -184,7 +184,7 @@ class Gem::InstallerTestCase < Gem::TestCase # +user+ is true a user-install will be performed. def util_installer(spec, gem_home, user=false) - Gem::Installer.new(spec.cache_file, + Gem::Installer.at(spec.cache_file, :install_dir => gem_home, :user_install => user) end diff --git a/lib/rubygems/package.rb b/lib/rubygems/package.rb index e8b8b38b06..64beae5550 100644 --- a/lib/rubygems/package.rb +++ b/lib/rubygems/package.rb @@ -123,7 +123,7 @@ class Gem::Package # If +gem+ is an existing file in the old format a Gem::Package::Old will be # returned. - def self.new gem + def self.new gem, security_policy = nil gem = if gem.is_a?(Gem::Package::Source) gem elsif gem.respond_to? :read @@ -132,7 +132,7 @@ class Gem::Package Gem::Package::FileSource.new gem end - return super(gem) unless Gem::Package == self + return super unless Gem::Package == self return super unless gem.present? return super unless gem.start @@ -144,7 +144,7 @@ class Gem::Package ## # Creates a new package that will read or write to the file +gem+. - def initialize gem # :notnew: + def initialize gem, security_policy # :notnew: @gem = gem @build_time = Time.now @@ -152,12 +152,19 @@ class Gem::Package @contents = nil @digests = Hash.new { |h, algorithm| h[algorithm] = {} } @files = nil - @security_policy = nil + @security_policy = security_policy @signatures = {} @signer = nil @spec = nil end + ## + # Copies this package to +path+ (if possible) + + def copy_to path + FileUtils.cp @gem.path, path unless File.exist? path + end + ## # Adds a checksum for each entry in the gem to checksums.yaml.gz. @@ -200,7 +207,11 @@ class Gem::Package def add_files tar # :nodoc: @spec.files.each do |file| - stat = File.stat file + stat = File.lstat file + + if stat.symlink? + tar.add_symlink file, File.readlink(file), stat.mode + end next unless stat.file? @@ -371,6 +382,8 @@ EOM FileUtils.chmod entry.header.mode, destination end if entry.file? + File.symlink(install_location(entry.header.linkname, destination_dir), destination) if entry.symlink? + verbose destination end end @@ -611,4 +624,3 @@ require 'rubygems/package/tar_header' require 'rubygems/package/tar_reader' require 'rubygems/package/tar_reader/entry' require 'rubygems/package/tar_writer' - diff --git a/lib/rubygems/package/old.rb b/lib/rubygems/package/old.rb index d7b228d893..bcf60a00c9 100644 --- a/lib/rubygems/package/old.rb +++ b/lib/rubygems/package/old.rb @@ -18,14 +18,14 @@ class Gem::Package::Old < Gem::Package # Creates a new old-format package reader for +gem+. Old-format packages # cannot be written. - def initialize gem + def initialize gem, security_policy require 'fileutils' require 'zlib' Gem.load_yaml @contents = nil @gem = gem - @security_policy = nil + @security_policy = security_policy @spec = nil end diff --git a/lib/rubygems/package/tar_reader/entry.rb b/lib/rubygems/package/tar_reader/entry.rb index 737c7639c6..1d917a81ac 100644 --- a/lib/rubygems/package/tar_reader/entry.rb +++ b/lib/rubygems/package/tar_reader/entry.rb @@ -102,6 +102,13 @@ class Gem::Package::TarReader::Entry @header.typeflag == "0" end + ## + # Is this tar entry a symlink? + + def symlink? + @header.typeflag == "2" + end + ## # The position in the tar entry @@ -144,4 +151,3 @@ class Gem::Package::TarReader::Entry end end - diff --git a/lib/rubygems/package/tar_test_case.rb b/lib/rubygems/package/tar_test_case.rb index 5253e32f36..b2d6f4ea77 100644 --- a/lib/rubygems/package/tar_test_case.rb +++ b/lib/rubygems/package/tar_test_case.rb @@ -71,7 +71,7 @@ class Gem::Package::TarTestCase < Gem::TestCase SP(Z(to_oct(sum, 6))) end - def header(type, fname, dname, length, mode, mtime, checksum = nil) + def header(type, fname, dname, length, mode, mtime, checksum = nil, linkname = "") checksum ||= " " * 8 arr = [ # struct tarfile_entry_posix @@ -83,7 +83,7 @@ class Gem::Package::TarTestCase < Gem::TestCase Z(to_oct(mtime, 11)), # char mtime[12]; 0 padded, octal, null checksum, # char checksum[8]; 0 padded, octal, null, space type, # char typeflag[1]; file: "0" dir: "5" - "\0" * 100, # char linkname[100]; ASCII + (Z unless filled) + ASCIIZ(linkname, 100), # char linkname[100]; ASCII + (Z unless filled) "ustar\0", # char magic[6]; "ustar\0" "00", # char version[2]; "00" ASCIIZ("wheel", 32), # char uname[32]; ASCIIZ @@ -117,6 +117,12 @@ class Gem::Package::TarTestCase < Gem::TestCase header("0", fname, dname, length, mode, mtime, checksum) end + def tar_symlink_header(fname, prefix, mode, mtime, linkname) + h = header("2", fname, prefix, 0, mode, mtime, nil, linkname) + checksum = calc_checksum(h) + header("2", fname, prefix, 0, mode, mtime, checksum, linkname) + end + def to_oct(n, pad_size) "%0#{pad_size}o" % n end @@ -133,5 +139,8 @@ class Gem::Package::TarTestCase < Gem::TestCase util_entry tar_dir_header("foo", "bar", 0, Time.now) end -end + def util_symlink_entry + util_entry tar_symlink_header("foo", "bar", 0, Time.now, "link") + end +end diff --git a/lib/rubygems/package/tar_writer.rb b/lib/rubygems/package/tar_writer.rb index dfd635724b..fff02e9235 100644 --- a/lib/rubygems/package/tar_writer.rb +++ b/lib/rubygems/package/tar_writer.rb @@ -233,6 +233,25 @@ class Gem::Package::TarWriter self end + ## + # Adds symlink +name+ with permissions +mode+, linking to +target+. + + def add_symlink(name, target, mode) + check_closed + + name, prefix = split_name name + + header = Gem::Package::TarHeader.new(:name => name, :mode => mode, + :size => 0, :typeflag => "2", + :linkname => target, + :prefix => prefix, + :mtime => Time.now).to_s + + @io.write header + + self + end + ## # Raises IOError if the TarWriter is closed @@ -323,4 +342,3 @@ class Gem::Package::TarWriter end end - diff --git a/lib/rubygems/platform.rb b/lib/rubygems/platform.rb index fa56141631..10d699d6b9 100644 --- a/lib/rubygems/platform.rb +++ b/lib/rubygems/platform.rb @@ -95,6 +95,7 @@ class Gem::Platform [os, version] when /netbsdelf/ then [ 'netbsdelf', nil ] when /openbsd(\d+\.\d+)?/ then [ 'openbsd', $1 ] + when /bitrig(\d+\.\d+)?/ then [ 'bitrig', $1 ] when /solaris(\d+\.\d+)?/ then [ 'solaris', $1 ] # test when /^(\w+_platform)(\d+)?/ then [ $1, $2 ] diff --git a/lib/rubygems/rdoc.rb b/lib/rubygems/rdoc.rb index 180b95fbf3..7591346306 100644 --- a/lib/rubygems/rdoc.rb +++ b/lib/rubygems/rdoc.rb @@ -20,7 +20,7 @@ begin require 'rdoc/rubygems_hook' loaded_hook = true module Gem - RDoc = RDoc::RubygemsHook + RDoc = ::RDoc::RubygemsHook end rescue LoadError end @@ -332,4 +332,3 @@ class Gem::RDoc # :nodoc: all end unless loaded_hook Gem.done_installing(&Gem::RDoc.method(:generation_hook)) - diff --git a/lib/rubygems/request/connection_pools.rb b/lib/rubygems/request/connection_pools.rb index 7a0a6e6e74..271b32b2b1 100644 --- a/lib/rubygems/request/connection_pools.rb +++ b/lib/rubygems/request/connection_pools.rb @@ -61,18 +61,22 @@ class Gem::Request::ConnectionPools # :nodoc: end def net_http_args uri, proxy_uri - net_http_args = [uri.host, uri.port] + # URI::Generic#hostname was added in ruby 1.9.3, use it if exists, otherwise + # don't support IPv6 literals and use host. + hostname = uri.respond_to?(:hostname) ? uri.hostname : uri.host + net_http_args = [hostname, uri.port] no_proxy = get_no_proxy_from_env - if proxy_uri and not no_proxy?(uri.host, no_proxy) then + if proxy_uri and not no_proxy?(hostname, no_proxy) then + proxy_hostname = proxy_uri.respond_to?(:hostname) ? proxy_uri.hostname : proxy_uri.host net_http_args + [ - proxy_uri.host, + proxy_hostname, proxy_uri.port, Gem::UriFormatter.new(proxy_uri.user).unescape, Gem::UriFormatter.new(proxy_uri.password).unescape, ] - elsif no_proxy? uri.host, no_proxy then + elsif no_proxy? hostname, no_proxy then net_http_args + [nil, nil] else net_http_args diff --git a/lib/rubygems/request_set.rb b/lib/rubygems/request_set.rb index cfbc955ed9..12806836aa 100644 --- a/lib/rubygems/request_set.rb +++ b/lib/rubygems/request_set.rb @@ -159,16 +159,13 @@ class Gem::RequestSet path = req.download cache_dir - inst = Gem::Installer.new path, options + inst = Gem::Installer.at path, options yield req, inst if block_given? requests << inst.install end - requests - ensure - raise if $! return requests if options[:gemdeps] specs = requests.map do |request| @@ -187,6 +184,8 @@ class Gem::RequestSet Gem.done_installing_hooks.each do |hook| hook.call inst, specs end unless Gem.done_installing_hooks.empty? + + requests end ## diff --git a/lib/rubygems/request_set/gem_dependency_api.rb b/lib/rubygems/request_set/gem_dependency_api.rb index 6dd7892b4c..cf5c2185d9 100644 --- a/lib/rubygems/request_set/gem_dependency_api.rb +++ b/lib/rubygems/request_set/gem_dependency_api.rb @@ -174,7 +174,7 @@ class Gem::RequestSet::GemDependencyAPI ## # A Hash containing gem names and files to require from those gems. - attr_reader :requires # :nodoc: + attr_reader :requires ## # A set of gems that are loaded via the +:path+ option to #gem diff --git a/lib/rubygems/request_set/lockfile.rb b/lib/rubygems/request_set/lockfile.rb index 3c54785aad..b79a377248 100644 --- a/lib/rubygems/request_set/lockfile.rb +++ b/lib/rubygems/request_set/lockfile.rb @@ -185,7 +185,7 @@ class Gem::RequestSet::Lockfile platforms = platforms.sort_by { |platform| platform.to_s } - platforms.sort.each do |platform| + platforms.each do |platform| out << " #{platform}" end diff --git a/lib/rubygems/resolver.rb b/lib/rubygems/resolver.rb index ef17d682ac..dcd1ade0d6 100644 --- a/lib/rubygems/resolver.rb +++ b/lib/rubygems/resolver.rb @@ -12,6 +12,7 @@ require 'net/http' # all the requirements. class Gem::Resolver + require 'rubygems/resolver/molinillo' ## # If the DEBUG_RESOLVER environment variable is set then debugging mode is @@ -20,13 +21,6 @@ class Gem::Resolver DEBUG_RESOLVER = !ENV['DEBUG_RESOLVER'].nil? - require 'pp' if DEBUG_RESOLVER - - ## - # Contains all the conflicts encountered while doing resolution - - attr_reader :conflicts - ## # Set to true if all development dependencies should be considered. @@ -110,7 +104,6 @@ class Gem::Resolver @set = set || Gem::Resolver::IndexSet.new @needed = needed - @conflicts = [] @development = false @development_shallow = false @ignore_dependencies = false @@ -153,7 +146,7 @@ class Gem::Resolver return spec, activation_request end - def requests s, act, reqs=nil # :nodoc: + def requests s, act, reqs=[] # :nodoc: return reqs if @ignore_dependencies s.fetch_development_dependencies if @development @@ -165,7 +158,7 @@ class Gem::Resolver next if d.type == :development and @development_shallow and act.parent - reqs.add Gem::Resolver::DependencyRequest.new(d, act) + reqs << Gem::Resolver::DependencyRequest.new(d, act) @stats.requirement! end @@ -176,29 +169,27 @@ class Gem::Resolver reqs end - ## - # Proceed with resolution! Returns an array of ActivationRequest objects. - - def resolve - @conflicts = [] - - needed = Gem::Resolver::RequirementList.new + include Molinillo::UI - @needed.reverse_each do |n| - request = Gem::Resolver::DependencyRequest.new n, nil - - needed.add request - @stats.requirement! - end + def output + @output ||= debug? ? $stdout : File.open('/dev/null', 'w') + end - @stats.record_requirements needed + def debug? + DEBUG_RESOLVER + end - res = resolve_for needed, nil + include Molinillo::SpecificationProvider - raise Gem::DependencyResolutionError, res if - res.kind_of? Gem::Resolver::Conflict + ## + # Proceed with resolution! Returns an array of ActivationRequest objects. - res.to_a + def resolve + locking_dg = Molinillo::DependencyGraph.new + Molinillo::Resolver.new(self, self).resolve(@needed.map { |d| DependencyRequest.new d, nil }, locking_dg).tsort.map(&:payload) + rescue Molinillo::VersionConflict => e + conflict = e.conflicts.values.first + raise Gem::DependencyResolutionError, Conflict.new(conflict.requirement_trees.first.first, conflict.existing, conflict.requirement) end ## @@ -221,232 +212,44 @@ class Gem::Resolver return matching_platform, all end - def handle_conflict(dep, existing) # :nodoc: - # There is a conflict! We return the conflict object which will be seen by - # the caller and be handled at the right level. - - # If the existing activation indicates that there are other possibles for - # it, then issue the conflict on the dependency for the activation itself. - # Otherwise, if there was a requester, issue it on the requester's - # request itself. - # Finally, if the existing request has no requester (toplevel) unwind to - # it anyway. - - if existing.others_possible? - conflict = - Gem::Resolver::Conflict.new dep, existing - elsif dep.requester - depreq = dep.requester.request - conflict = - Gem::Resolver::Conflict.new depreq, existing, dep - elsif existing.request.requester.nil? - conflict = - Gem::Resolver::Conflict.new dep, existing - else - raise Gem::DependencyError, "Unable to figure out how to unwind conflict" - end - - @conflicts << conflict unless @conflicts.include? conflict - - return conflict - end - - # Contains the state for attempting activation of a set of possible specs. - # +needed+ is a Gem::List of DependencyRequest objects that, well, need - # to be satisfied. - # +specs+ is the List of ActivationRequest that are being tested. - # +dep+ is the DependencyRequest that was used to generate this state. - # +spec+ is the Specification for this state. - # +possible+ is List of DependencyRequest objects that can be tried to - # find a complete set. - # +conflicts+ is a [DependencyRequest, Conflict] hit tried to - # activate the state. - # - State = Struct.new(:needed, :specs, :dep, :spec, :possibles, :conflicts) do - def summary # :nodoc: - nd = needed.map { |s| s.to_s }.sort if nd - - if specs then - ss = specs.map { |s| s.full_name }.sort - ss.unshift ss.length - end - - d = dep.to_s - d << " from #{dep.requester.full_name}" if dep.requester - - ps = possibles.map { |p| p.full_name }.sort - ps.unshift ps.length - - cs = conflicts.map do |(s, c)| - [s.full_name, c.conflicting_dependencies.map { |cd| cd.to_s }] - end - - { :needed => nd, :specs => ss, :dep => d, :spec => spec.full_name, - :possibles => ps, :conflicts => cs } - end - end - ## - # The meat of the algorithm. Given +needed+ DependencyRequest objects and - # +specs+ being a list to ActivationRequest, calculate a new list of - # ActivationRequest objects. - - def resolve_for needed, specs # :nodoc: - # The State objects that are used to attempt the activation tree. - states = [] - - while !needed.empty? - @stats.iteration! - - dep = needed.remove - explain :try, [dep, dep.requester ? dep.requester.request : :toplevel] - explain_list(:next5) { needed.next5 } - explain_list(:specs) { Array(specs).map { |x| x.full_name }.sort } - - # If there is already a spec activated for the requested name... - if specs && existing = specs.find { |s| dep.name == s.name } - # then we're done since this new dep matches the existing spec. - next if dep.matches_spec? existing - - conflict = handle_conflict dep, existing - - return conflict unless dep.requester - - explain :conflict, dep, :existing, existing.full_name - - depreq = dep.requester.request - - state = nil - until states.empty? - x = states.pop - - i = existing.request.requester - explain :consider, x.spec.full_name, [depreq.name, dep.name, i ? i.name : :top] - - if x.spec.name == depreq.name or - x.spec.name == dep.name or - (i && (i.name == x.spec.name)) - explain :found, x.spec.full_name - state = x - break - end - end - - return conflict unless state - - @stats.backtracking! - - needed, specs = resolve_for_conflict needed, specs, state - - states << state unless state.possibles.empty? - - next - end - - matching, all = find_possible dep + # Returns the gems in +specs+ that match the local platform. - case matching.size - when 0 - resolve_for_zero dep, all - when 1 - needed, specs = - resolve_for_single needed, specs, dep, matching - else - needed, specs = - resolve_for_multiple needed, specs, states, dep, matching - end + def select_local_platforms specs # :nodoc: + specs.select do |spec| + Gem::Platform.installable? spec end - - specs - end - - ## - # Rewinds +needed+ and +specs+ to a previous state in +state+ for a conflict - # between +dep+ and +existing+. - - def resolve_for_conflict needed, specs, state # :nodoc: - # We exhausted the possibles so it's definitely not going to work out, - # bail out. - raise Gem::ImpossibleDependenciesError.new state.dep, state.conflicts if - state.possibles.empty? - - # Retry resolution with this spec and add it's dependencies - spec, act = activation_request state.dep, state.possibles - - needed = requests spec, act, state.needed.dup - specs = Gem::List.prepend state.specs, act - - return needed, specs end - ## - # There are multiple +possible+ specifications for this +dep+. Updates - # +needed+, +specs+ and +states+ for further resolution of the +possible+ - # choices. - - def resolve_for_multiple needed, specs, states, dep, possible # :nodoc: - # Sort them so that we try the highest versions first. - possible = possible.sort_by do |s| - [s.source, s.version, s.platform == Gem::Platform::RUBY ? -1 : 1] + def search_for(dependency) + possibles, all = find_possible(dependency) + if !@soft_missing && possibles.empty? + @missing << dependency + exc = Gem::UnsatisfiableDependencyError.new dependency, all + exc.errors = @set.errors + raise exc end - - spec, act = activation_request dep, possible - - # We may need to try all of +possible+, so we setup state to unwind back - # to current +needed+ and +specs+ so we can try another. This is code is - # what makes conflict resolution possible. - states << State.new(needed.dup, specs, dep, spec, possible, []) - - @stats.record_depth states - - explain :states, states.map { |s| s.dep } - - needed = requests spec, act, needed - specs = Gem::List.prepend specs, act - - return needed, specs + possibles.sort_by { |s| [s.source, s.version, s.platform == Gem::Platform.local.to_s ? 1 : 0] }. + map { |s| ActivationRequest.new s, dependency, [] } end - ## - # Add the spec from the +possible+ list to +specs+ and process the spec's - # dependencies by adding them to +needed+. - - def resolve_for_single needed, specs, dep, possible # :nodoc: - spec, act = activation_request dep, possible - - specs = Gem::List.prepend specs, act - - # Put the deps for at the beginning of needed - # rather than the end to match the depth first - # searching done by the multiple case code below. - # - # This keeps the error messages consistent. - needed = requests spec, act, needed - - return needed, specs + def dependencies_for(specification) + return [] if @ignore_dependencies + spec = specification.spec + requests(spec, specification) end - ## - # When there are no possible specifications for +dep+ our work is done. - - def resolve_for_zero dep, platform_mismatch # :nodoc: - @missing << dep - - unless @soft_missing - exc = Gem::UnsatisfiableDependencyError.new dep, platform_mismatch - exc.errors = @set.errors - - raise exc - end + def requirement_satisfied_by?(requirement, activated, spec) + requirement.matches_spec? spec end - ## - # Returns the gems in +specs+ that match the local platform. + def name_for(dependency) + dependency.name + end - def select_local_platforms specs # :nodoc: - specs.select do |spec| - Gem::Platform.installable? spec - end + def allow_missing?(dependency) + @missing << dependency + @soft_missing end end @@ -482,4 +285,3 @@ require 'rubygems/resolver/installed_specification' require 'rubygems/resolver/local_specification' require 'rubygems/resolver/lock_specification' require 'rubygems/resolver/vendor_specification' - diff --git a/lib/rubygems/resolver/activation_request.rb b/lib/rubygems/resolver/activation_request.rb index 56c6363e4f..03dd8d083b 100644 --- a/lib/rubygems/resolver/activation_request.rb +++ b/lib/rubygems/resolver/activation_request.rb @@ -67,6 +67,8 @@ class Gem::Resolver::ActivationRequest @spec.full_name end + alias_method :to_s, :full_name + ## # The Gem::Specification for this activation request. @@ -169,4 +171,3 @@ class Gem::Resolver::ActivationRequest end end - diff --git a/lib/rubygems/resolver/conflict.rb b/lib/rubygems/resolver/conflict.rb index 902c286b6b..0b6c704d6a 100644 --- a/lib/rubygems/resolver/conflict.rb +++ b/lib/rubygems/resolver/conflict.rb @@ -157,4 +157,3 @@ end # TODO: Remove in RubyGems 3 Gem::Resolver::DependencyConflict = Gem::Resolver::Conflict # :nodoc: - diff --git a/lib/rubygems/resolver/dependency_request.rb b/lib/rubygems/resolver/dependency_request.rb index 79690bec4c..6c6ea8f4da 100644 --- a/lib/rubygems/resolver/dependency_request.rb +++ b/lib/rubygems/resolver/dependency_request.rb @@ -67,6 +67,10 @@ class Gem::Resolver::DependencyRequest @dependency.name end + def type + @dependency.type + end + ## # Indicate that the request is for a gem explicitly requested by the user @@ -113,4 +117,3 @@ class Gem::Resolver::DependencyRequest end end - diff --git a/lib/rubygems/resolver/git_specification.rb b/lib/rubygems/resolver/git_specification.rb index 55e180e525..dcfb2ad855 100644 --- a/lib/rubygems/resolver/git_specification.rb +++ b/lib/rubygems/resolver/git_specification.rb @@ -23,8 +23,7 @@ class Gem::Resolver::GitSpecification < Gem::Resolver::SpecSpecification def install options = {} require 'rubygems/installer' - installer = Gem::Installer.new '', options - installer.spec = spec + installer = Gem::Installer.for_spec spec, options yield installer if block_given? diff --git a/lib/rubygems/resolver/molinillo.rb b/lib/rubygems/resolver/molinillo.rb new file mode 100644 index 0000000000..24ac0f9b2d --- /dev/null +++ b/lib/rubygems/resolver/molinillo.rb @@ -0,0 +1 @@ +require 'rubygems/resolver/molinillo/lib/molinillo' diff --git a/lib/rubygems/resolver/molinillo/lib/molinillo.rb b/lib/rubygems/resolver/molinillo/lib/molinillo.rb new file mode 100644 index 0000000000..47b4518321 --- /dev/null +++ b/lib/rubygems/resolver/molinillo/lib/molinillo.rb @@ -0,0 +1,5 @@ +require 'rubygems/resolver/molinillo/lib/molinillo/gem_metadata' +require 'rubygems/resolver/molinillo/lib/molinillo/errors' +require 'rubygems/resolver/molinillo/lib/molinillo/resolver' +require 'rubygems/resolver/molinillo/lib/molinillo/modules/ui' +require 'rubygems/resolver/molinillo/lib/molinillo/modules/specification_provider' diff --git a/lib/rubygems/resolver/molinillo/lib/molinillo/dependency_graph.rb b/lib/rubygems/resolver/molinillo/lib/molinillo/dependency_graph.rb new file mode 100644 index 0000000000..b6db1b7417 --- /dev/null +++ b/lib/rubygems/resolver/molinillo/lib/molinillo/dependency_graph.rb @@ -0,0 +1,266 @@ +require 'set' +require 'tsort' + +module Gem::Resolver::Molinillo + # A directed acyclic graph that is tuned to hold named dependencies + class DependencyGraph + include Enumerable + + # Enumerates through the vertices of the graph. + # @return [Array] The graph's vertices. + def each + vertices.values.each { |v| yield v } + end + + include TSort + + alias_method :tsort_each_node, :each + + def tsort_each_child(vertex, &block) + vertex.successors.each(&block) + end + + # Topologically sorts the given vertices. + # @param [Enumerable] vertices the vertices to be sorted, which must + # all belong to the same graph. + # @return [Array] The sorted vertices. + def self.tsort(vertices) + TSort.tsort( + lambda { |b| vertices.each(&b) }, + lambda { |v, &b| (v.successors & vertices).each(&b) } + ) + end + + # A directed edge of a {DependencyGraph} + # @attr [Vertex] origin The origin of the directed edge + # @attr [Vertex] destination The destination of the directed edge + # @attr [Array] requirements The requirements the directed edge represents + Edge = Struct.new(:origin, :destination, :requirements) + + # @return [{String => Vertex}] vertices that have no {Vertex#predecessors}, + # keyed by by {Vertex#name} + attr_reader :root_vertices + # @return [{String => Vertex}] the vertices of the dependency graph, keyed + # by {Vertex#name} + attr_reader :vertices + # @return [Set] the edges of the dependency graph + attr_reader :edges + + def initialize + @vertices = {} + @edges = Set.new + @root_vertices = {} + end + + # Initializes a copy of a {DependencyGraph}, ensuring that all {#vertices} + # have the correct {Vertex#graph} set + def initialize_copy(other) + super + @vertices = other.vertices.reduce({}) do |vertices, (name, vertex)| + vertices.tap do |hash| + hash[name] = vertex.dup.tap { |v| v.graph = self } + end + end + @root_vertices = Hash[@vertices.select { |n, _v| other.root_vertices[n] }] + @edges = other.edges.map do |edge| + Edge.new( + vertex_named(edge.origin.name), + vertex_named(edge.destination.name), + edge.requirements.dup + ) + end + end + + # @return [String] a string suitable for debugging + def inspect + "#{self.class}:#{vertices.values.inspect}" + end + + # @return [Boolean] whether the two dependency graphs are equal, determined + # by a recursive traversal of each {#root_vertices} and its + # {Vertex#successors} + def ==(other) + root_vertices == other.root_vertices + end + + # @param [String] name + # @param [Object] payload + # @param [Array] parent_names + # @param [Object] requirement the requirement that is requiring the child + # @return [void] + def add_child_vertex(name, payload, parent_names, requirement) + is_root = parent_names.include?(nil) + parent_nodes = parent_names.compact.map { |n| vertex_named(n) } + vertex = vertex_named(name) || if is_root + add_root_vertex(name, payload) + else + add_vertex(name, payload) + end + vertex.payload ||= payload + parent_nodes.each do |parent_node| + add_edge(parent_node, vertex, requirement) + end + vertex + end + + # @param [String] name + # @param [Object] payload + # @return [Vertex] the vertex that was added to `self` + def add_vertex(name, payload) + vertex = vertices[name] ||= Vertex.new(self, name, payload) + vertex.tap { |v| v.payload = payload } + end + + # @param [String] name + # @param [Object] payload + # @return [Vertex] the vertex that was added to `self` + def add_root_vertex(name, payload) + add_vertex(name, payload).tap { |v| root_vertices[name] = v } + end + + # Detaches the {#vertex_named} `name` {Vertex} from the graph, recursively + # removing any non-root vertices that were orphaned in the process + # @param [String] name + # @return [void] + def detach_vertex_named(name) + vertex = vertex_named(name) + return unless vertex + successors = vertex.successors + vertices.delete(name) + edges.reject! { |e| e.origin == vertex || e.destination == vertex } + successors.each { |v| detach_vertex_named(v.name) unless root_vertices[v.name] || v.predecessors.any? } + end + + # @param [String] name + # @return [Vertex,nil] the vertex with the given name + def vertex_named(name) + vertices[name] + end + + # @param [String] name + # @return [Vertex,nil] the root vertex with the given name + def root_vertex_named(name) + root_vertices[name] + end + + # Adds a new {Edge} to the dependency graph + # @param [Vertex] origin + # @param [Vertex] destination + # @param [Object] requirement the requirement that this edge represents + # @return [Edge] the added edge + def add_edge(origin, destination, requirement) + if origin == destination || destination.path_to?(origin) + raise CircularDependencyError.new([origin, destination]) + end + Edge.new(origin, destination, [requirement]).tap { |e| edges << e } + end + + # A vertex in a {DependencyGraph} that encapsulates a {#name} and a + # {#payload} + class Vertex + # @return [DependencyGraph] the graph this vertex is a node of + attr_accessor :graph + + # @return [String] the name of the vertex + attr_accessor :name + + # @return [Object] the payload the vertex holds + attr_accessor :payload + + # @return [Arrary] the explicit requirements that required + # this vertex + attr_reader :explicit_requirements + + # @param [DependencyGraph] graph see {#graph} + # @param [String] name see {#name} + # @param [Object] payload see {#payload} + def initialize(graph, name, payload) + @graph = graph + @name = name + @payload = payload + @explicit_requirements = [] + end + + # @return [Array] all of the requirements that required + # this vertex + def requirements + incoming_edges.map(&:requirements).flatten + explicit_requirements + end + + # @return [Array] the edges of {#graph} that have `self` as their + # {Edge#origin} + def outgoing_edges + graph.edges.select { |e| e.origin.shallow_eql?(self) } + end + + # @return [Array] the edges of {#graph} that have `self` as their + # {Edge#destination} + def incoming_edges + graph.edges.select { |e| e.destination.shallow_eql?(self) } + end + + # @return [Set] the vertices of {#graph} that have an edge with + # `self` as their {Edge#destination} + def predecessors + incoming_edges.map(&:origin).to_set + end + + # @return [Set] the vertices of {#graph} that have an edge with + # `self` as their {Edge#origin} + def successors + outgoing_edges.map(&:destination).to_set + end + + # @return [Set] the vertices of {#graph} where `self` is an + # {#ancestor?} + def recursive_successors + successors + successors.map(&:recursive_successors).reduce(Set.new, &:+) + end + + # @return [String] a string suitable for debugging + def inspect + "#{self.class}:#{name}(#{payload.inspect})" + end + + # @return [Boolean] whether the two vertices are equal, determined + # by a recursive traversal of each {Vertex#successors} + def ==(other) + shallow_eql?(other) && + successors == other.successors + end + + # @return [Boolean] whether the two vertices are equal, determined + # solely by {#name} and {#payload} equality + def shallow_eql?(other) + other && + name == other.name && + payload == other.payload + end + + alias_method :eql?, :== + + # @return [Fixnum] a hash for the vertex based upon its {#name} + def hash + name.hash + end + + # Is there a path from `self` to `other` following edges in the + # dependency graph? + # @return true iff there is a path following edges within this {#graph} + def path_to?(other) + successors.include?(other) || successors.any? { |v| v.path_to?(other) } + end + + alias_method :descendent?, :path_to? + + # Is there a path from `other` to `self` following edges in the + # dependency graph? + # @return true iff there is a path following edges within this {#graph} + def ancestor?(other) + predecessors.include?(other) || predecessors.any? { |v| v.ancestor?(other) } + end + + alias_method :is_reachable_from?, :ancestor? + end + end +end diff --git a/lib/rubygems/resolver/molinillo/lib/molinillo/errors.rb b/lib/rubygems/resolver/molinillo/lib/molinillo/errors.rb new file mode 100644 index 0000000000..cc9f636ed5 --- /dev/null +++ b/lib/rubygems/resolver/molinillo/lib/molinillo/errors.rb @@ -0,0 +1,69 @@ +module Gem::Resolver::Molinillo + # An error that occurred during the resolution process + class ResolverError < StandardError; end + + # An error caused by searching for a dependency that is completely unknown, + # i.e. has no versions available whatsoever. + class NoSuchDependencyError < ResolverError + # @return [Object] the dependency that could not be found + attr_accessor :dependency + + # @return [Array] the specifications that depended upon {#dependency} + attr_accessor :required_by + + # @param [Object] dependency @see {#dependency} + # @param [Array] required_by @see {#required_by} + def initialize(dependency, required_by = []) + @dependency = dependency + @required_by = required_by + super() + end + + def message + sources = required_by.map { |r| "`#{r}`" }.join(' and ') + message = "Unable to find a specification for `#{dependency}`" + message << " depended upon by #{sources}" unless sources.empty? + message + end + end + + # An error caused by attempting to fulfil a dependency that was circular + # + # @note This exception will be thrown iff a {Vertex} is added to a + # {DependencyGraph} that has a {DependencyGraph::Vertex#path_to?} an + # existing {DependencyGraph::Vertex} + class CircularDependencyError < ResolverError + # [Set] the dependencies responsible for causing the error + attr_reader :dependencies + + # @param [Array] nodes the nodes in the dependency + # that caused the error + def initialize(nodes) + super "There is a circular dependency between #{nodes.map(&:name).join(' and ')}" + @dependencies = nodes.map(&:payload).to_set + end + end + + # An error caused by conflicts in version + class VersionConflict < ResolverError + # @return [{String => Resolution::Conflict}] the conflicts that caused + # resolution to fail + attr_reader :conflicts + + # @param [{String => Resolution::Conflict}] conflicts see {#conflicts} + def initialize(conflicts) + pairs = [] + conflicts.values.flatten.map(&:requirements).flatten.each do |conflicting| + conflicting.each do |source, conflict_requirements| + conflict_requirements.each do |c| + pairs << [c, source] + end + end + end + + super "Unable to satisfy the following requirements:\n\n" \ + "#{pairs.map { |r, d| "- `#{r}` required by `#{d}`" }.join("\n")}" + @conflicts = conflicts + end + end +end diff --git a/lib/rubygems/resolver/molinillo/lib/molinillo/gem_metadata.rb b/lib/rubygems/resolver/molinillo/lib/molinillo/gem_metadata.rb new file mode 100644 index 0000000000..8568c623e6 --- /dev/null +++ b/lib/rubygems/resolver/molinillo/lib/molinillo/gem_metadata.rb @@ -0,0 +1,3 @@ +module Gem::Resolver::Molinillo + VERSION = '0.3.0' +end diff --git a/lib/rubygems/resolver/molinillo/lib/molinillo/modules/specification_provider.rb b/lib/rubygems/resolver/molinillo/lib/molinillo/modules/specification_provider.rb new file mode 100644 index 0000000000..848392b215 --- /dev/null +++ b/lib/rubygems/resolver/molinillo/lib/molinillo/modules/specification_provider.rb @@ -0,0 +1,99 @@ +module Gem::Resolver::Molinillo + # Provides information about specifcations and dependencies to the resolver, + # allowing the {Resolver} class to remain generic while still providing power + # and flexibility. + # + # This module contains the methods that users of Gem::Resolver::Molinillo must to implement, + # using knowledge of their own model classes. + module SpecificationProvider + # Search for the specifications that match the given dependency. + # The specifications in the returned array will be considered in reverse + # order, so the latest version ought to be last. + # @note This method should be 'pure', i.e. the return value should depend + # only on the `dependency` parameter. + # + # @param [Object] dependency + # @return [Array] the specifications that satisfy the given + # `dependency`. + def search_for(dependency) + [] + end + + # Returns the dependencies of `specification`. + # @note This method should be 'pure', i.e. the return value should depend + # only on the `specification` parameter. + # + # @param [Object] specification + # @return [Array] the dependencies that are required by the given + # `specification`. + def dependencies_for(specification) + [] + end + + # Determines whether the given `requirement` is satisfied by the given + # `spec`, in the context of the current `activated` dependency graph. + # + # @param [Object] requirement + # @param [DependencyGraph] activated the current dependency graph in the + # resolution process. + # @param [Object] spec + # @return [Boolean] whether `requirement` is satisfied by `spec` in the + # context of the current `activated` dependency graph. + def requirement_satisfied_by?(requirement, activated, spec) + true + end + + # Returns the name for the given `dependency`. + # @note This method should be 'pure', i.e. the return value should depend + # only on the `dependency` parameter. + # + # @param [Object] dependency + # @return [String] the name for the given `dependency`. + def name_for(dependency) + dependency.to_s + end + + # @return [String] the name of the source of explicit dependencies, i.e. + # those passed to {Resolver#resolve} directly. + def name_for_explicit_dependency_source + 'user-specified dependency' + end + + # @return [String] the name of the source of 'locked' dependencies, i.e. + # those passed to {Resolver#resolve} directly as the `base` + def name_for_locking_dependency_source + 'Lockfile' + end + + # Sort dependencies so that the ones that are easiest to resolve are first. + # Easiest to resolve is (usually) defined by: + # 1) Is this dependency already activated? + # 2) How relaxed are the requirements? + # 3) Are there any conflicts for this dependency? + # 4) How many possibilities are there to satisfy this dependency? + # + # @param [Array] dependencies + # @param [DependencyGraph] activated the current dependency graph in the + # resolution process. + # @param [{String => Array}] conflicts + # @return [Array] a sorted copy of `dependencies`. + def sort_dependencies(dependencies, activated, conflicts) + dependencies.sort_by do |dependency| + name = name_for(dependency) + [ + activated.vertex_named(name).payload ? 0 : 1, + conflicts[name] ? 0 : 1, + ] + end + end + + # Returns whether this dependency, which has no possible matching + # specifications, can safely be ignored. + # + # @param [Object] dependency + # @return [Boolean] whether this dependency can safely be skipped. + def allow_missing?(dependency) + false + end + end +end diff --git a/lib/rubygems/resolver/molinillo/lib/molinillo/modules/ui.rb b/lib/rubygems/resolver/molinillo/lib/molinillo/modules/ui.rb new file mode 100644 index 0000000000..18f5363950 --- /dev/null +++ b/lib/rubygems/resolver/molinillo/lib/molinillo/modules/ui.rb @@ -0,0 +1,63 @@ +module Gem::Resolver::Molinillo + # Conveys information about the resolution process to a user. + module UI + # The {IO} object that should be used to print output. `STDOUT`, by default. + # + # @return [IO] + def output + STDOUT + end + + # Called roughly every {#progress_rate}, this method should convey progress + # to the user. + # + # @return [void] + def indicate_progress + output.print '.' unless debug? + end + + # How often progress should be conveyed to the user via + # {#indicate_progress}, in seconds. A third of a second, by default. + # + # @return [Float] + def progress_rate + 0.33 + end + + # Called before resolution begins. + # + # @return [void] + def before_resolution + output.print 'Resolving dependencies...' + end + + # Called after resolution ends (either successfully or with an error). + # By default, prints a newline. + # + # @return [void] + def after_resolution + output.puts + end + + # Conveys debug information to the user. + # + # @param [Integer] depth the current depth of the resolution process. + # @return [void] + def debug(depth = 0) + if debug? + debug_info = yield + debug_info = debug_info.inspect unless debug_info.is_a?(String) + output.puts debug_info.split("\n").map { |s| ' ' * depth + s } + end + end + + # Whether or not debug messages should be printed. + # By default, whether or not the `MOLINILLO_DEBUG` environment variable is + # set. + # + # @return [Boolean] + def debug? + @debug_mode ||= ENV['MOLINILLO_DEBUG'] + end + end +end diff --git a/lib/rubygems/resolver/molinillo/lib/molinillo/resolution.rb b/lib/rubygems/resolver/molinillo/lib/molinillo/resolution.rb new file mode 100644 index 0000000000..712864495f --- /dev/null +++ b/lib/rubygems/resolver/molinillo/lib/molinillo/resolution.rb @@ -0,0 +1,430 @@ +module Gem::Resolver::Molinillo + class Resolver + # A specific resolution from a given {Resolver} + class Resolution + # A conflict that the resolution process encountered + # @attr [Object] requirement the requirement that immediately led to the conflict + # @attr [{String,Nil=>[Object]}] requirements the requirements that caused the conflict + # @attr [Object, nil] existing the existing spec that was in conflict with + # the {#possibility} + # @attr [Object] possibility the spec that was unable to be activated due + # to a conflict + # @attr [Object] locked_requirement the relevant locking requirement. + # @attr [Array>] requirement_trees the different requirement + # trees that led to every requirement for the conflicting name. + Conflict = Struct.new( + :requirement, + :requirements, + :existing, + :possibility, + :locked_requirement, + :requirement_trees + ) + + # @return [SpecificationProvider] the provider that knows about + # dependencies, requirements, specifications, versions, etc. + attr_reader :specification_provider + + # @return [UI] the UI that knows how to communicate feedback about the + # resolution process back to the user + attr_reader :resolver_ui + + # @return [DependencyGraph] the base dependency graph to which + # dependencies should be 'locked' + attr_reader :base + + # @return [Array] the dependencies that were explicitly required + attr_reader :original_requested + + # @param [SpecificationProvider] specification_provider + # see {#specification_provider} + # @param [UI] resolver_ui see {#resolver_ui} + # @param [Array] requested see {#original_requested} + # @param [DependencyGraph] base see {#base} + def initialize(specification_provider, resolver_ui, requested, base) + @specification_provider = specification_provider + @resolver_ui = resolver_ui + @original_requested = requested + @base = base + @states = [] + @iteration_counter = 0 + end + + # Resolves the {#original_requested} dependencies into a full dependency + # graph + # @raise [ResolverError] if successful resolution is impossible + # @return [DependencyGraph] the dependency graph of successfully resolved + # dependencies + def resolve + start_resolution + + while state + break unless state.requirements.any? || state.requirement + indicate_progress + if state.respond_to?(:pop_possibility_state) # DependencyState + debug(depth) { "Creating possibility state for #{requirement} (#{possibilities.count} remaining)" } + state.pop_possibility_state.tap { |s| states.push(s) if s } + end + process_topmost_state + end + + activated.freeze + ensure + end_resolution + end + + # @return [Integer] the number of resolver iterations in between calls to + # {#resolver_ui}'s {UI#indicate_progress} method + attr_accessor :iteration_rate + private :iteration_rate + + # @return [Time] the time at which resolution began + attr_accessor :started_at + private :started_at + + # @return [Array] the stack of states for the resolution + attr_accessor :states + private :states + + private + + # Sets up the resolution process + # @return [void] + def start_resolution + @started_at = Time.now + + handle_missing_or_push_dependency_state(initial_state) + + debug { "Starting resolution (#{@started_at})" } + resolver_ui.before_resolution + end + + # Ends the resolution process + # @return [void] + def end_resolution + resolver_ui.after_resolution + debug do + "Finished resolution (#{@iteration_counter} steps) " \ + "(Took #{(ended_at = Time.now) - @started_at} seconds) (#{ended_at})" + end + debug { 'Unactivated: ' + Hash[activated.vertices.reject { |_n, v| v.payload }].keys.join(', ') } if state + debug { 'Activated: ' + Hash[activated.vertices.select { |_n, v| v.payload }].keys.join(', ') } if state + end + + require 'rubygems/resolver/molinillo/lib/molinillo/state' + require 'rubygems/resolver/molinillo/lib/molinillo/modules/specification_provider' + + ResolutionState.new.members.each do |member| + define_method member do |*args, &block| + current_state = state || ResolutionState.empty + current_state.send(member, *args, &block) + end + end + + SpecificationProvider.instance_methods(false).each do |instance_method| + define_method instance_method do |*args, &block| + begin + specification_provider.send(instance_method, *args, &block) + rescue NoSuchDependencyError => error + if state + vertex = activated.vertex_named(name_for error.dependency) + error.required_by += vertex.incoming_edges.map { |e| e.origin.name } + error.required_by << name_for_explicit_dependency_source unless vertex.explicit_requirements.empty? + end + raise + end + end + end + + # Processes the topmost available {RequirementState} on the stack + # @return [void] + def process_topmost_state + if possibility + attempt_to_activate + else + create_conflict if state.is_a? PossibilityState + unwind_for_conflict until possibility && state.is_a?(DependencyState) + end + end + + # @return [Object] the current possibility that the resolution is trying + # to activate + def possibility + possibilities.last + end + + # @return [RequirementState] the current state the resolution is + # operating upon + def state + states.last + end + + # Creates the initial state for the resolution, based upon the + # {#requested} dependencies + # @return [DependencyState] the initial state for the resolution + def initial_state + graph = DependencyGraph.new.tap do |dg| + original_requested.each { |r| dg.add_root_vertex(name_for(r), nil).tap { |v| v.explicit_requirements << r } } + end + + requirements = sort_dependencies(original_requested, graph, {}) + initial_requirement = requirements.shift + DependencyState.new( + initial_requirement && name_for(initial_requirement), + requirements, + graph, + initial_requirement, + initial_requirement && search_for(initial_requirement), + 0, + {} + ) + end + + # Unwinds the states stack because a conflict has been encountered + # @return [void] + def unwind_for_conflict + debug(depth) { "Unwinding for conflict: #{requirement}" } + conflicts.tap do |c| + states.slice!((state_index_for_unwind + 1)..-1) + raise VersionConflict.new(c) unless state + state.conflicts = c + end + end + + # @return [Integer] The index to which the resolution should unwind in the + # case of conflict. + def state_index_for_unwind + current_requirement = requirement + existing_requirement = requirement_for_existing_name(name) + until current_requirement.nil? + current_state = find_state_for(current_requirement) + return states.index(current_state) if state_any?(current_state) + current_requirement = parent_of(current_requirement) + end + + until existing_requirement.nil? + existing_state = find_state_for(existing_requirement) + return states.index(existing_state) if state_any?(existing_state) + existing_requirement = parent_of(existing_requirement) + end + -1 + end + + # @return [Object] the requirement that led to `requirement` being added + # to the list of requirements. + def parent_of(requirement) + return nil unless requirement + seen = false + state = states.reverse_each.find do |s| + seen ||= s.requirement == requirement + seen && s.requirement != requirement && !s.requirements.include?(requirement) + end + state && state.requirement + end + + # @return [Object] the requirement that led to a version of a possibility + # with the given name being activated. + def requirement_for_existing_name(name) + return nil unless activated.vertex_named(name).payload + states.reverse_each.find { |s| !s.activated.vertex_named(name).payload }.requirement + end + + # @return [ResolutionState] the state whose `requirement` is the given + # `requirement`. + def find_state_for(requirement) + return nil unless requirement + states.reverse_each.find { |i| requirement == i.requirement && i.is_a?(DependencyState) } + end + + # @return [Boolean] whether or not the given state has any possibilities + # left. + def state_any?(state) + state && state.possibilities.any? + end + + # @return [Conflict] a {Conflict} that reflects the failure to activate + # the {#possibility} in conjunction with the current {#state} + def create_conflict + vertex = activated.vertex_named(name) + requirements = { + name_for_explicit_dependency_source => vertex.explicit_requirements, + name_for_locking_dependency_source => Array(locked_requirement_named(name)), + } + vertex.incoming_edges.each { |edge| (requirements[edge.origin.payload] ||= []).unshift(*edge.requirements) } + conflicts[name] = Conflict.new( + requirement, + Hash[requirements.select { |_, r| !r.empty? }], + vertex.payload, + possibility, + locked_requirement_named(name), + requirement_trees + ) + end + + # @return [Array>] The different requirement + # trees that led to every requirement for the current spec. + def requirement_trees + activated.vertex_named(name).requirements.map { |r| requirement_tree_for(r) } + end + + # @return [Array] the list of requirements that led to + # `requirement` being required. + def requirement_tree_for(requirement) + tree = [] + while requirement + tree.unshift(requirement) + requirement = parent_of(requirement) + end + tree + end + + # Indicates progress roughly once every second + # @return [void] + def indicate_progress + @iteration_counter += 1 + @progress_rate ||= resolver_ui.progress_rate + if iteration_rate.nil? + if Time.now - started_at >= @progress_rate + self.iteration_rate = @iteration_counter + end + end + + if iteration_rate && (@iteration_counter % iteration_rate) == 0 + resolver_ui.indicate_progress + end + end + + # Calls the {#resolver_ui}'s {UI#debug} method + # @param [Integer] depth the depth of the {#states} stack + # @param [Proc] block a block that yields a {#to_s} + # @return [void] + def debug(depth = 0, &block) + resolver_ui.debug(depth, &block) + end + + # Attempts to activate the current {#possibility} + # @return [void] + def attempt_to_activate + debug(depth) { 'Attempting to activate ' + possibility.to_s } + existing_node = activated.vertex_named(name) + if existing_node.payload + debug(depth) { "Found existing spec (#{existing_node.payload})" } + attempt_to_activate_existing_spec(existing_node) + else + attempt_to_activate_new_spec + end + end + + # Attempts to activate the current {#possibility} (given that it has + # already been activated) + # @return [void] + def attempt_to_activate_existing_spec(existing_node) + existing_spec = existing_node.payload + if requirement_satisfied_by?(requirement, activated, existing_spec) + new_requirements = requirements.dup + push_state_for_requirements(new_requirements) + else + return if attempt_to_swap_possibility + create_conflict + debug(depth) { "Unsatisfied by existing spec (#{existing_node.payload})" } + unwind_for_conflict + end + end + + # Attempts to swp the current {#possibility} with the already-activated + # spec with the given name + # @return [Boolean] Whether the possibility was swapped into {#activated} + def attempt_to_swap_possibility + swapped = activated.dup + swapped.vertex_named(name).payload = possibility + return unless swapped.vertex_named(name).requirements. + all? { |r| requirement_satisfied_by?(r, swapped, possibility) } + attempt_to_activate_new_spec + end + + # Attempts to activate the current {#possibility} (given that it hasn't + # already been activated) + # @return [void] + def attempt_to_activate_new_spec + satisfied = begin + locked_requirement = locked_requirement_named(name) + requested_spec_satisfied = requirement_satisfied_by?(requirement, activated, possibility) + locked_spec_satisfied = !locked_requirement || + requirement_satisfied_by?(locked_requirement, activated, possibility) + debug(depth) { 'Unsatisfied by requested spec' } unless requested_spec_satisfied + debug(depth) { 'Unsatisfied by locked spec' } unless locked_spec_satisfied + requested_spec_satisfied && locked_spec_satisfied + end + if satisfied + activate_spec + else + create_conflict + unwind_for_conflict + end + end + + # @param [String] requirement_name the spec name to search for + # @return [Object] the locked spec named `requirement_name`, if one + # is found on {#base} + def locked_requirement_named(requirement_name) + vertex = base.vertex_named(requirement_name) + vertex && vertex.payload + end + + # Add the current {#possibility} to the dependency graph of the current + # {#state} + # @return [void] + def activate_spec + conflicts.delete(name) + debug(depth) { 'Activated ' + name + ' at ' + possibility.to_s } + vertex = activated.vertex_named(name) + vertex.payload = possibility + require_nested_dependencies_for(possibility) + end + + # Requires the dependencies that the recently activated spec has + # @param [Object] activated_spec the specification that has just been + # activated + # @return [void] + def require_nested_dependencies_for(activated_spec) + nested_dependencies = dependencies_for(activated_spec) + debug(depth) { "Requiring nested dependencies (#{nested_dependencies.map(&:to_s).join(', ')})" } + nested_dependencies.each { |d| activated.add_child_vertex name_for(d), nil, [name_for(activated_spec)], d } + + push_state_for_requirements(requirements + nested_dependencies) + end + + # Pushes a new {DependencyState} that encapsulates both existing and new + # requirements + # @param [Array] new_requirements + # @return [void] + def push_state_for_requirements(new_requirements, new_activated = activated.dup) + new_requirements = sort_dependencies(new_requirements.uniq, new_activated, conflicts) + new_requirement = new_requirements.shift + new_name = new_requirement ? name_for(new_requirement) : '' + possibilities = new_requirement ? search_for(new_requirement) : [] + handle_missing_or_push_dependency_state DependencyState.new( + new_name, new_requirements, new_activated, + new_requirement, possibilities, depth, conflicts.dup + ) + end + + # Pushes a new {DependencyState}. + # If the {#specification_provider} says to + # {SpecificationProvider#allow_missing?} that particular requirement, and + # there are no possibilities for that requirement, then `state` is not + # pushed, and the node in {#activated} is removed, and we continue + # resolving the remaining requirements. + # @param [DependencyState] state + # @return [void] + def handle_missing_or_push_dependency_state(state) + if state.requirement && state.possibilities.empty? && allow_missing?(state.requirement) + state.activated.detach_vertex_named(state.name) + push_state_for_requirements(state.requirements, state.activated) + else + states.push state + end + end + end + end +end diff --git a/lib/rubygems/resolver/molinillo/lib/molinillo/resolver.rb b/lib/rubygems/resolver/molinillo/lib/molinillo/resolver.rb new file mode 100644 index 0000000000..b22caf44da --- /dev/null +++ b/lib/rubygems/resolver/molinillo/lib/molinillo/resolver.rb @@ -0,0 +1,43 @@ +require 'rubygems/resolver/molinillo/lib/molinillo/dependency_graph' + +module Gem::Resolver::Molinillo + # This class encapsulates a dependency resolver. + # The resolver is responsible for determining which set of dependencies to + # activate, with feedback from the the {#specification_provider} + # + # + class Resolver + require 'rubygems/resolver/molinillo/lib/molinillo/resolution' + + # @return [SpecificationProvider] the specification provider used + # in the resolution process + attr_reader :specification_provider + + # @return [UI] the UI module used to communicate back to the user + # during the resolution process + attr_reader :resolver_ui + + # @param [SpecificationProvider] specification_provider + # see {#specification_provider} + # @param [UI] resolver_ui + # see {#resolver_ui} + def initialize(specification_provider, resolver_ui) + @specification_provider = specification_provider + @resolver_ui = resolver_ui + end + + # Resolves the requested dependencies into a {DependencyGraph}, + # locking to the base dependency graph (if specified) + # @param [Array] requested an array of 'requested' dependencies that the + # {#specification_provider} can understand + # @param [DependencyGraph,nil] base the base dependency graph to which + # dependencies should be 'locked' + def resolve(requested, base = DependencyGraph.new) + Resolution.new(specification_provider, + resolver_ui, + requested, + base). + resolve + end + end +end diff --git a/lib/rubygems/resolver/molinillo/lib/molinillo/state.rb b/lib/rubygems/resolver/molinillo/lib/molinillo/state.rb new file mode 100644 index 0000000000..f0317185ab --- /dev/null +++ b/lib/rubygems/resolver/molinillo/lib/molinillo/state.rb @@ -0,0 +1,51 @@ +module Gem::Resolver::Molinillo + # A state that a {Resolution} can be in + # @attr [String] name + # @attr [Array] requirements + # @attr [DependencyGraph] activated + # @attr [Object] requirement + # @attr [Object] possibility + # @attr [Integer] depth + # @attr [Set] conflicts + ResolutionState = Struct.new( + :name, + :requirements, + :activated, + :requirement, + :possibilities, + :depth, + :conflicts + ) + + class ResolutionState + # Returns an empty resolution state + # @return [ResolutionState] an empty state + def self.empty + new(nil, [], DependencyGraph.new, nil, nil, 0, Set.new) + end + end + + # A state that encapsulates a set of {#requirements} with an {Array} of + # possibilities + class DependencyState < ResolutionState + # Removes a possibility from `self` + # @return [PossibilityState] a state with a single possibility, + # the possibility that was removed from `self` + def pop_possibility_state + PossibilityState.new( + name, + requirements.dup, + activated.dup, + requirement, + [possibilities.pop], + depth + 1, + conflicts.dup + ) + end + end + + # A state that encapsulates a single possibility to fulfill the given + # {#requirement} + class PossibilityState < ResolutionState + end +end diff --git a/lib/rubygems/resolver/specification.rb b/lib/rubygems/resolver/specification.rb index 4d77293262..9b597f1916 100644 --- a/lib/rubygems/resolver/specification.rb +++ b/lib/rubygems/resolver/specification.rb @@ -89,7 +89,7 @@ class Gem::Resolver::Specification gem = source.download spec, destination - installer = Gem::Installer.new gem, options + installer = Gem::Installer.at gem, options yield installer if block_given? diff --git a/lib/rubygems/specification.rb b/lib/rubygems/specification.rb index b73c563e2e..5ddf5fd931 100644 --- a/lib/rubygems/specification.rb +++ b/lib/rubygems/specification.rb @@ -12,7 +12,8 @@ require 'rubygems/platform' require 'rubygems/deprecate' require 'rubygems/basic_specification' require 'rubygems/stub_specification' -require 'rubygems/util/stringio' +require 'rubygems/util/list' +require 'stringio' ## # The Specification class contains the information for a Gem. Typically @@ -172,6 +173,8 @@ class Gem::Specification < Gem::BasicSpecification @@default_value[k].nil? } + @@stubs_by_name = {} + ###################################################################### # :section: Required gemspec attributes @@ -345,7 +348,7 @@ class Gem::Specification < Gem::BasicSpecification add_bindir(@executables), @extra_rdoc_files, @extensions, - ].flatten.uniq.compact.sort + ].flatten.compact.uniq.sort end ###################################################################### @@ -729,12 +732,57 @@ class Gem::Specification < Gem::BasicSpecification end end - def self.each_stub(dirs) # :nodoc: - each_gemspec(dirs) do |path| - stub = Gem::StubSpecification.new(path) - yield stub if stub.valid? + def self.gemspec_stubs_in dir, pattern + Dir[File.join(dir, pattern)].map { |path| + if dir == default_specifications_dir + Gem::StubSpecification.default_gemspec_stub(path) + else + Gem::StubSpecification.gemspec_stub(path) + end + }.select(&:valid?) + end + private_class_method :gemspec_stubs_in + + if [].respond_to? :flat_map + def self.map_stubs(dirs, pattern) # :nodoc: + dirs.flat_map { |dir| gemspec_stubs_in(dir, pattern) } + end + else # FIXME: remove when 1.8 is dropped + def self.map_stubs(dirs, pattern) # :nodoc: + dirs.map { |dir| gemspec_stubs_in(dir, pattern) }.flatten 1 end end + private_class_method :map_stubs + + uniq_takes_a_block = false + [1,2].uniq { uniq_takes_a_block = true } + + if uniq_takes_a_block + def self.uniq_by(list, &block) # :nodoc: + list.uniq(&block) + end + else # FIXME: remove when 1.8 is dropped + def self.uniq_by(list) # :nodoc: + values = {} + list.each { |item| + value = yield item + values[value] ||= item + } + values.values + end + end + private_class_method :uniq_by + + if [].respond_to? :sort_by! + def self.sort_by! list, &block + list.sort_by!(&block) + end + else # FIXME: remove when 1.8 is dropped + def self.sort_by! list, &block + list.replace list.sort_by(&block) + end + end + private_class_method :sort_by! def self.each_spec(dirs) # :nodoc: each_gemspec(dirs) do |path| @@ -748,17 +796,33 @@ class Gem::Specification < Gem::BasicSpecification def self.stubs @@stubs ||= begin - stubs = {} - each_stub([default_specifications_dir] + dirs) do |stub| - stubs[stub.full_name] ||= stub - end + stubs = map_stubs([default_specifications_dir] + dirs, "*.gemspec") + stubs = uniq_by(stubs) { |stub| stub.full_name } - stubs = stubs.values _resort!(stubs) + @@stubs_by_name = stubs.group_by(&:name) stubs end end + EMPTY = [].freeze # :nodoc: + + ## + # Returns a Gem::StubSpecification for installed gem named +name+ + + def self.stubs_for name + if @@stubs || @@stubs_by_name[name] + @@stubs_by_name[name] || [] + else + stubs = map_stubs([default_specifications_dir] + dirs, "#{name}-*.gemspec") + stubs = uniq_by(stubs) { |stub| stub.full_name }.group_by(&:name) + stubs.each_value { |v| sort_by!(v) { |i| i.version } } + + @@stubs_by_name.merge! stubs + @@stubs_by_name[name] ||= EMPTY + end + end + def self._resort!(specs) # :nodoc: specs.sort! { |a, b| names = a.name <=> b.name @@ -783,6 +847,7 @@ class Gem::Specification < Gem::BasicSpecification # properly sorted. def self.add_spec spec + warn "Gem::Specification.add_spec is deprecated and will be removed in Rubygems 3.0" unless Gem::Deprecate.skip # TODO: find all extraneous adds # puts # p :add_spec => [spec.full_name, caller.reject { |s| s =~ /minitest/ }] @@ -797,6 +862,8 @@ class Gem::Specification < Gem::BasicSpecification _all << spec stubs << spec + (@@stubs_by_name[spec.name] ||= []) << spec + sort_by!(@@stubs_by_name[spec.name]) { |s| s.version } _resort!(_all) _resort!(stubs) end @@ -805,14 +872,18 @@ class Gem::Specification < Gem::BasicSpecification # Adds multiple specs to the known specifications. def self.add_specs *specs + warn "Gem::Specification.add_specs is deprecated and will be removed in Rubygems 3.0" unless Gem::Deprecate.skip + raise "nil spec!" if specs.any?(&:nil?) # TODO: remove once we're happy # TODO: this is much more efficient, but we need the extra checks for now # _all.concat specs # _resort! - specs.each do |spec| # TODO: slow - add_spec spec + Gem::Deprecate.skip_during do + specs.each do |spec| # TODO: slow + add_spec spec + end end end @@ -839,6 +910,7 @@ class Gem::Specification < Gem::BasicSpecification # -- wilsonb def self.all= specs + @@stubs_by_name = specs.group_by(&:name) @@all = @@stubs = specs end @@ -927,9 +999,10 @@ class Gem::Specification < Gem::BasicSpecification # Return the best specification that contains the file matching +path+. def self.find_by_path path - self.find { |spec| + stub = stubs.find { |spec| spec.contains_requirable_file? path } + stub && stub.to_spec end ## @@ -961,15 +1034,13 @@ class Gem::Specification < Gem::BasicSpecification specs = unresolved_deps.values.map { |dep| dep.to_specs }.flatten specs.reverse_each do |spec| - trails = [] spec.traverse do |from_spec, dep, to_spec, trail| - next unless to_spec.conflicts.empty? - trails << trail if to_spec.contains_requirable_file? path + if to_spec.has_conflicts? || to_spec.conficts_when_loaded_with?(trail) + :next + else + return trail.reverse if to_spec.contains_requirable_file? path + end end - - next if trails.empty? - - return trails.map(&:reverse).sort.first.reverse end [] @@ -1008,10 +1079,14 @@ class Gem::Specification < Gem::BasicSpecification # +prerelease+ is true. def self.latest_specs prerelease = false + _latest_specs Gem::Specification._all, prerelease + end + + def self._latest_specs specs, prerelease = false # :nodoc: result = Hash.new { |h,k| h[k] = {} } native = {} - Gem::Specification.reverse_each do |spec| + specs.reverse_each do |spec| next if spec.version.prerelease? unless prerelease native[spec.name] = spec.version if spec.platform == Gem::Platform::RUBY @@ -1029,12 +1104,13 @@ class Gem::Specification < Gem::BasicSpecification def self.load file return unless file - file = file.dup.untaint - return unless File.file?(file) _spec = LOAD_CACHE[file] return _spec if _spec + file = file.dup.untaint + return unless File.file?(file) + code = if defined? Encoding File.read file, :mode => 'r:UTF-8:-' else @@ -1126,8 +1202,11 @@ class Gem::Specification < Gem::BasicSpecification # Removes +spec+ from the known specs. def self.remove_spec spec + warn "Gem::Specification.remove_spec is deprecated and will be removed in Rubygems 3.0" unless Gem::Deprecate.skip _all.delete spec stubs.delete_if { |s| s.full_name == spec.full_name } + (@@stubs_by_name[spec.name] || []).delete_if { |s| s.full_name == spec.full_name } + reset end ## @@ -1153,6 +1232,7 @@ class Gem::Specification < Gem::BasicSpecification Gem.pre_reset_hooks.each { |hook| hook.call } @@all = nil @@stubs = nil + @@stubs_by_name = {} _clear_load_cache unresolved = unresolved_deps unless unresolved.empty? then @@ -1563,6 +1643,30 @@ class Gem::Specification < Gem::BasicSpecification conflicts end + ## + # return true if there will be conflict when spec if loaded together with the list of specs. + + def conficts_when_loaded_with?(list_of_specs) # :nodoc: + result = list_of_specs.any? { |spec| + spec.dependencies.any? { |dep| dep.runtime? && (dep.name == name) && !satisfies_requirement?(dep) } + } + result + end + + ## + # Return true if there are possible conflicts against the currently loaded specs. + + def has_conflicts? + self.dependencies.any? { |dep| + if dep.runtime? then + spec = Gem.loaded_specs[dep.name] + spec and not spec.satisfies_requirement? dep + else + false + end + } + end + ## # The date this gem was created. Lazily defaults to the current UTC date. # @@ -1883,9 +1987,10 @@ class Gem::Specification < Gem::BasicSpecification # +version+. def initialize name = nil, version = nil + super() @loaded = false @activated = false - self.loaded_from = nil + @loaded_from = nil @original_platform = nil @installed_by_version = nil @@ -1951,20 +2056,6 @@ class Gem::Specification < Gem::BasicSpecification end end - ## - # Returns a string usable in Dir.glob to match all requirable paths - # for this spec. - - def lib_dirs_glob - dirs = if self.require_paths.size > 1 then - "{#{self.require_paths.join(',')}}" - else - self.require_paths.first - end - - "#{self.full_gem_path}/#{dirs}" - end - ## # Files in the Gem under one of the require_paths @@ -1992,9 +2083,8 @@ class Gem::Specification < Gem::BasicSpecification @licenses ||= [] end - def loaded_from= path # :nodoc: + def internal_init # :nodoc: super - @bin_dir = nil @cache_dir = nil @cache_file = nil @@ -2011,16 +2101,6 @@ class Gem::Specification < Gem::BasicSpecification @rubygems_version = Gem::VERSION end - ## - # Return all files in this gem that match for +glob+. - - def matches_for_glob glob # TODO: rename? - # TODO: do we need these?? Kill it - glob = File.join(self.lib_dirs_glob, glob) - - Dir[glob].map { |f| f.untaint } # FIX our tests are broken, run w/ SAFE=1 - end - ## # Warn about unknown attributes while loading a spec. @@ -2154,10 +2234,8 @@ class Gem::Specification < Gem::BasicSpecification # Check the spec for possible conflicts and freak out if there are any. def raise_if_conflicts # :nodoc: - conf = self.conflicts - - unless conf.empty? then - raise Gem::ConflictError.new self, conf + if has_conflicts? then + raise Gem::ConflictError.new self, conflicts end end @@ -2234,7 +2312,7 @@ class Gem::Specification < Gem::BasicSpecification # List of dependencies that will automatically be activated at runtime. def runtime_dependencies - dependencies.select { |d| d.type == :runtime } + dependencies.select(&:runtime?) end ## @@ -2461,7 +2539,7 @@ class Gem::Specification < Gem::BasicSpecification builder << self ast = builder.tree - io = Gem::StringSink.new + io = StringIO.new io.set_encoding Encoding::UTF_8 if Object.const_defined? :Encoding Psych::Visitors::Emitter.new(io).accept(ast) @@ -2480,14 +2558,28 @@ class Gem::Specification < Gem::BasicSpecification # Recursively walk dependencies of this spec, executing the +block+ for each # hop. - def traverse trail = [], &block - trail = trail + [self] - runtime_dependencies.each do |dep| - dep.to_specs.each do |dep_spec| - block[self, dep, dep_spec, trail + [dep_spec]] - dep_spec.traverse(trail, &block) unless - trail.map(&:name).include? dep_spec.name + def traverse trail = [], visited = {}, &block + trail.push(self) + begin + dependencies.each do |dep| + dep.to_specs.reverse_each do |dep_spec| + next if visited.has_key?(dep_spec) + visited[dep_spec] = true + trail.push(dep_spec) + begin + result = block[self, dep, dep_spec, trail] + ensure + trail.pop + end + unless result == :next + spec_name = dep_spec.name + dep_spec.traverse(trail, visited, &block) unless + trail.any? { |s| s.name == spec_name } + end + end end + ensure + trail.pop end end @@ -2535,13 +2627,13 @@ class Gem::Specification < Gem::BasicSpecification 'specification must have at least one require_path' end - @files.delete_if { |x| File.directory?(x) } - @test_files.delete_if { |x| File.directory?(x) } + @files.delete_if { |x| File.directory?(x) && !File.symlink?(x) } + @test_files.delete_if { |x| File.directory?(x) && !File.symlink?(x) } @executables.delete_if { |x| File.directory?(File.join(@bindir, x)) } - @extra_rdoc_files.delete_if { |x| File.directory?(x) } - @extensions.delete_if { |x| File.directory?(x) } + @extra_rdoc_files.delete_if { |x| File.directory?(x) && !File.symlink?(x) } + @extensions.delete_if { |x| File.directory?(x) && !File.symlink?(x) } - non_files = files.reject { |x| File.file?(x) } + non_files = files.reject { |x| File.file?(x) || File.symlink?(x) } unless not packaging or non_files.empty? then raise Gem::InvalidSpecificationException, @@ -2676,6 +2768,11 @@ http://opensource.org/licenses/alphabetical warning "#{executable_path} is missing #! line" unless shebang end + files.each do |file| + next unless File.symlink?(file) + warning "#{file} is a symlink, which is not supported on all platforms" + end + validate_dependencies true @@ -2761,12 +2858,14 @@ open-ended dependency on #{dep} is not recommended return if Gem.win_platform? files.each do |file| + next unless File.file?(file) next if File.stat(file).mode & 0444 == 0444 warning "#{file} is not world-readable" end executables.each do |name| exec = File.join @bindir, name + next unless File.file?(exec) next if File.stat(exec).executable? warning "#{exec} is not executable" end diff --git a/lib/rubygems/stub_specification.rb b/lib/rubygems/stub_specification.rb index b184d29d5e..ed07e8011f 100644 --- a/lib/rubygems/stub_specification.rb +++ b/lib/rubygems/stub_specification.rb @@ -15,35 +15,32 @@ class Gem::StubSpecification < Gem::BasicSpecification end class StubLine # :nodoc: all - attr_reader :parts + attr_reader :name, :version, :platform, :require_paths def initialize(data) - @parts = data[PREFIX.length..-1].split(" ") - end - - def name - @parts[0] - end - - def version - Gem::Version.new @parts[1] + parts = data[PREFIX.length..-1].split(" ") + @name = parts[0] + @version = Gem::Version.new parts[1] + @platform = Gem::Platform.new parts[2] + @require_paths = parts.drop(3).join(" ").split("\0") end + end - def platform - Gem::Platform.new @parts[2] - end + def self.default_gemspec_stub filename + new filename, true + end - def require_paths - @parts[3..-1].join(" ").split("\0") - end + def self.gemspec_stub filename + new filename, false end - def initialize(filename) + def initialize filename, default_gem self.loaded_from = filename @data = nil @extensions = nil @name = nil @spec = nil + @default_gem = default_gem end ## @@ -57,6 +54,10 @@ class Gem::StubSpecification < Gem::BasicSpecification end end + def default_gem? + @default_gem + end + def build_extensions # :nodoc: return if default_gem? return if extensions.empty? @@ -135,14 +136,14 @@ class Gem::StubSpecification < Gem::BasicSpecification # Name of the gem def name - @name ||= data.name + data.name end ## # Platform of the gem def platform - @platform ||= data.platform + data.platform end ## diff --git a/lib/rubygems/test_case.rb b/lib/rubygems/test_case.rb index 306edcc3f6..aee4b75773 100644 --- a/lib/rubygems/test_case.rb +++ b/lib/rubygems/test_case.rb @@ -36,6 +36,7 @@ require 'shellwords' require 'tmpdir' require 'uri' require 'zlib' +require 'benchmark' # stdlib Gem.load_yaml @@ -275,6 +276,7 @@ class Gem::TestCase < MiniTest::Unit::TestCase @orig_ENV_HOME = ENV['HOME'] ENV['HOME'] = @userhome Gem.instance_variable_set :@user_home, nil + Gem.instance_variable_set :@gemdeps, nil Gem.send :remove_instance_variable, :@ruby_version if Gem.instance_variables.include? :@ruby_version @@ -329,6 +331,7 @@ class Gem::TestCase < MiniTest::Unit::TestCase end @marshal_version = "#{Marshal::MAJOR_VERSION}.#{Marshal::MINOR_VERSION}" + @orig_LOADED_FEATURES = $LOADED_FEATURES.dup end ## @@ -337,6 +340,7 @@ class Gem::TestCase < MiniTest::Unit::TestCase def teardown $LOAD_PATH.replace @orig_LOAD_PATH if @orig_LOAD_PATH + $LOADED_FEATURES.replace @orig_LOADED_FEATURES if @orig_LOADED_FEATURES if @orig_BASERUBY RbConfig::CONFIG['BASERUBY'] = @orig_BASERUBY @@ -373,6 +377,7 @@ class Gem::TestCase < MiniTest::Unit::TestCase ENV['GEM_PRIVATE_KEY_PASSPHRASE'] = @orig_gem_private_key_passphrase Gem::Specification._clear_load_cache + Gem::Specification.unresolved_deps.clear end def common_installer_setup @@ -488,7 +493,7 @@ class Gem::TestCase < MiniTest::Unit::TestCase gem = File.join(@tempdir, File.basename(spec.cache_file)).untaint end - Gem::Installer.new(gem, options.merge({:wrappers => true})).install + Gem::Installer.at(gem, options.merge({:wrappers => true})).install end ## @@ -503,8 +508,11 @@ class Gem::TestCase < MiniTest::Unit::TestCase def uninstall_gem spec require 'rubygems/uninstaller' - Gem::Uninstaller.new(spec.name, - :executables => true, :user_install => true).uninstall + Class.new(Gem::Uninstaller) { + def ask_if_ok spec + true + end + }.new(spec.name, :executables => true, :user_install => true).uninstall end ## @@ -598,7 +606,7 @@ class Gem::TestCase < MiniTest::Unit::TestCase spec.loaded_from = spec.loaded_from = written_path - Gem::Specification.add_spec spec.for_cache + Gem::Specification.reset return spec end @@ -654,7 +662,10 @@ class Gem::TestCase < MiniTest::Unit::TestCase # Install the provided specs def install_specs(*specs) - Gem::Specification.add_specs(*specs) + specs.each do |spec| + Gem::Installer.for_spec(spec).install + end + Gem.searcher = nil end @@ -675,8 +686,9 @@ class Gem::TestCase < MiniTest::Unit::TestCase # Install the provided default specs def install_default_specs(*specs) - install_specs(*specs) specs.each do |spec| + installer = Gem::Installer.for_spec(spec, :install_as_default => true) + installer.install Gem.register_default_spec(spec) end end @@ -788,9 +800,7 @@ class Gem::TestCase < MiniTest::Unit::TestCase end end - spec.loaded_from = spec.spec_file - - Gem::Specification.add_spec spec + Gem::Specification.reset return spec end @@ -977,14 +987,13 @@ Also, a list: # Best used with +@all_gems+ from #util_setup_fake_fetcher. def util_setup_spec_fetcher(*specs) - specs -= Gem::Specification._all - Gem::Specification.add_specs(*specs) + all_specs = Gem::Specification.to_a + specs + Gem::Specification._resort! all_specs spec_fetcher = Gem::SpecFetcher.fetcher - prerelease, all = Gem::Specification.partition { |spec| - spec.version.prerelease? - } + prerelease, all = all_specs.partition { |spec| spec.version.prerelease? } + latest = Gem::Specification._latest_specs all_specs spec_fetcher.specs[@uri] = [] all.each do |spec| @@ -992,7 +1001,7 @@ Also, a list: end spec_fetcher.latest_specs[@uri] = [] - Gem::Specification.latest_specs.each do |spec| + latest.each do |spec| spec_fetcher.latest_specs[@uri] << spec.name_tuple end @@ -1008,7 +1017,7 @@ Also, a list: specs = all.map { |spec| spec.name_tuple } s_zip = util_gzip Marshal.dump Gem::NameTuple.to_basic specs - latest_specs = Gem::Specification.latest_specs.map do |spec| + latest_specs = latest.map do |spec| spec.name_tuple end @@ -1023,7 +1032,7 @@ Also, a list: v = Gem.marshal_version - Gem::Specification.each do |spec| + all_specs.each do |spec| path = "#{@gem_repo}quick/Marshal.#{v}/#{spec.original_name}.gemspec.rz" data = Marshal.dump spec data_deflate = Zlib::Deflate.deflate data @@ -1450,6 +1459,12 @@ begin rescue LoadError, Gem::LoadError end +begin + gem 'builder' + require 'builder/xchar' +rescue LoadError, Gem::LoadError +end + require 'rubygems/test_utilities' tmpdirs = [] tmpdirs << (ENV['GEM_HOME'] = Dir.mktmpdir("home")) diff --git a/lib/rubygems/test_utilities.rb b/lib/rubygems/test_utilities.rb index 25786e6a21..6789f6efc5 100644 --- a/lib/rubygems/test_utilities.rb +++ b/lib/rubygems/test_utilities.rb @@ -186,7 +186,6 @@ end # f.gem 'a', 1 # f.spec 'a', 2 # f.gem 'b', 1' 'a' => '~> 1.0' -# f.clear # end # # The above declaration creates two gems, a-1 and b-1, with a dependency from @@ -214,18 +213,11 @@ class Gem::TestCase::SpecFetcherSetup @repository = repository @gems = {} + @downloaded = [] @installed = [] @operations = [] end - ## - # Removes any created gems or specifications from Gem.dir (the default - # install location). - - def clear - @operations << [:clear] - end - ## # Returns a Hash of created Specification full names and the corresponding # Specification. @@ -254,9 +246,6 @@ class Gem::TestCase::SpecFetcherSetup def execute_operations # :nodoc: @operations.each do |operation, *arguments| case operation - when :clear then - @test.util_clear_gems - @installed.clear when :gem then spec, gem = @test.util_gem(*arguments, &arguments.pop) @@ -264,6 +253,11 @@ class Gem::TestCase::SpecFetcherSetup @gems[spec] = gem @installed << spec + when :download then + spec, gem = @test.util_gem(*arguments, &arguments.pop) + + @gems[spec] = gem + @downloaded << spec when :spec then spec = @test.util_spec(*arguments, &arguments.pop) @@ -286,6 +280,17 @@ class Gem::TestCase::SpecFetcherSetup @operations << [:gem, name, version, dependencies, block] end + ## + # Creates a gem with +name+, +version+ and +deps+. The created gem is + # downloaded in to the cache directory but is not installed + # + # The specification will be yielded before gem creation for customization, + # but only the block or the dependencies may be set, not both. + + def download name, version, dependencies = nil, &block + @operations << [:download, name, version, dependencies, block] + end + ## # Creates a legacy platform spec with the name 'pl' and version 1 @@ -312,17 +317,12 @@ class Gem::TestCase::SpecFetcherSetup gem_repo, @test.gem_repo = @test.gem_repo, @repository @test.uri = URI @repository - @test.util_setup_spec_fetcher(*@gems.keys) + @test.util_setup_spec_fetcher(*@downloaded) ensure @test.gem_repo = gem_repo @test.uri = URI gem_repo end - # This works around util_setup_spec_fetcher adding all created gems to the - # installed set. - Gem::Specification.reset - Gem::Specification.add_specs(*@installed) - @gems.each do |spec, gem| next unless gem diff --git a/lib/rubygems/uninstaller.rb b/lib/rubygems/uninstaller.rb index 2a6edc6131..bb3e204c8d 100644 --- a/lib/rubygems/uninstaller.rb +++ b/lib/rubygems/uninstaller.rb @@ -271,7 +271,7 @@ class Gem::Uninstaller say "Successfully uninstalled #{spec.full_name}" - Gem::Specification.remove_spec spec + Gem::Specification.reset end ## diff --git a/lib/rubygems/util.rb b/lib/rubygems/util.rb index cd0af4d8fe..9bfe13f815 100644 --- a/lib/rubygems/util.rb +++ b/lib/rubygems/util.rb @@ -10,8 +10,8 @@ module Gem::Util def self.gunzip(data) require 'zlib' - require 'rubygems/util/stringio' - data = Gem::StringSource.new data + require 'stringio' + data = StringIO.new(data, 'r') unzipped = Zlib::GzipReader.new(data).read unzipped.force_encoding Encoding::BINARY if Object.const_defined? :Encoding @@ -23,8 +23,8 @@ module Gem::Util def self.gzip(data) require 'zlib' - require 'rubygems/util/stringio' - zipped = Gem::StringSink.new + require 'stringio' + zipped = StringIO.new('','w') zipped.set_encoding Encoding::BINARY if Object.const_defined? :Encoding Zlib::GzipWriter.wrap zipped do |io| io.write data end diff --git a/lib/rubygems/util/list.rb b/lib/rubygems/util/list.rb index 9bc11fe334..6fa767646c 100644 --- a/lib/rubygems/util/list.rb +++ b/lib/rubygems/util/list.rb @@ -1,7 +1,13 @@ module Gem - List = Struct.new(:value, :tail) - class List + include Enumerable + attr_accessor :value, :tail + + def initialize(value = nil, tail = nil) + @value = value + @tail = tail + end + def each n = self while n @@ -11,25 +17,7 @@ module Gem end def to_a - ary = [] - n = self - while n - ary.unshift n.value - n = n.tail - end - - ary - end - - def find - n = self - while n - v = n.value - return v if yield(v) - n = n.tail - end - - nil + super.reverse end def prepend(value) diff --git a/lib/rubygems/util/stringio.rb b/lib/rubygems/util/stringio.rb deleted file mode 100644 index 2ea69617bc..0000000000 --- a/lib/rubygems/util/stringio.rb +++ /dev/null @@ -1,34 +0,0 @@ -class Gem::StringSink - def initialize - @string = "" - end - - attr_reader :string - - def write(s) - @string += s - s.size - end - - def set_encoding(enc) - @string.force_encoding enc - end -end - -class Gem::StringSource - def initialize(str) - @string = str.dup - end - - def read(count=nil) - if count - @string.slice!(0,count) - else - s = @string - @string = "" - s - end - end - - alias_method :readpartial, :read -end diff --git a/lib/rubygems/version.rb b/lib/rubygems/version.rb index c581aa367f..e2ee59a0c2 100644 --- a/lib/rubygems/version.rb +++ b/lib/rubygems/version.rb @@ -232,11 +232,11 @@ class Gem::Version # same precision. Version "1.0" is not the same as version "1". def eql? other - self.class === other and @version == other.version + self.class === other and @version == other._version end def hash # :nodoc: - @hash ||= segments.hash + @version.hash end def init_with coder # :nodoc: @@ -333,7 +333,7 @@ class Gem::Version def <=> other return unless Gem::Version === other - return 0 if @version == other.version + return 0 if @version == other._version lhsegments = segments rhsegments = other.segments @@ -357,4 +357,10 @@ class Gem::Version return 0 end + + protected + + def _version + @version + end end diff --git a/test/rubygems/simple_gem.rb b/test/rubygems/simple_gem.rb index d7c035675c..c40002e230 100644 --- a/test/rubygems/simple_gem.rb +++ b/test/rubygems/simple_gem.rb @@ -16,7 +16,7 @@ SIMPLE_GEM = <<-GEMDATA @directory = options[:directory] || Gem.dir @force = options[:force] - gem = Gem::Installer.new(__FILE__).install(@force, @directory) + gem = Gem::Installer.at(__FILE__).install(@force, @directory) if options[:gen_rdoc] Gem::DocManager.new(gem).generate_rdoc end diff --git a/test/rubygems/test_gem.rb b/test/rubygems/test_gem.rb index 0428bea213..0c5ac51f29 100644 --- a/test/rubygems/test_gem.rb +++ b/test/rubygems/test_gem.rb @@ -37,7 +37,7 @@ class TestGem < Gem::TestCase c1 = new_spec "c", "1" c2 = new_spec "c", "2" - install_specs a1, b1, b2, c1, c2 + install_specs c1, c2, b1, b2, a1 a1.activate @@ -61,7 +61,7 @@ class TestGem < Gem::TestCase d1 = new_spec "d", "1", { "c" => "< 2" }, "lib/d.rb" d2 = new_spec "d", "2", { "c" => "< 2" }, "lib/d.rb" # this - install_specs a1, b1, b2, c1, c2, d1, d2 + install_specs c1, c2, b1, b2, d1, d2, a1 a1.activate @@ -90,6 +90,23 @@ class TestGem < Gem::TestCase assert_path_exists File.join(gemhome2, 'gems', 'a-1') end + def test_self_install_in_rescue + spec_fetcher do |f| + f.gem 'a', 1 + f.spec 'a', 2 + end + + gemhome2 = "#{@gemhome}2" + + installed = + begin + raise 'Error' + rescue StandardError + Gem.install 'a', '= 1', :install_dir => gemhome2 + end + assert_equal %w[a-1], installed.map { |spec| spec.full_name } + end + def test_require_missing save_loaded_features do assert_raises ::LoadError do @@ -135,12 +152,12 @@ class TestGem < Gem::TestCase end def test_self_bin_path_bin_name - util_exec_gem + install_specs util_exec_gem assert_equal @abin_path, Gem.bin_path('a', 'abin') end def test_self_bin_path_bin_name_version - util_exec_gem + install_specs util_exec_gem assert_equal @abin_path, Gem.bin_path('a', 'abin', '4') end @@ -167,10 +184,11 @@ class TestGem < Gem::TestCase end def test_self_bin_path_bin_file_gone_in_latest - util_exec_gem - util_spec 'a', '10' do |s| + install_specs util_exec_gem + spec = util_spec 'a', '10' do |s| s.executables = [] end + install_specs spec # Should not find a-10's non-abin (bug) assert_equal @abin_path, Gem.bin_path('a', 'abin') end @@ -881,9 +899,23 @@ class TestGem < Gem::TestCase assert_equal %w[http://gems.example.com/], Gem.sources end + def test_try_activate_returns_true_for_activated_specs + b = util_spec 'b', '1.0' do |spec| + spec.files << 'lib/b.rb' + end + install_specs b + + assert Gem.try_activate('b'), 'try_activate should return true' + assert Gem.try_activate('b'), 'try_activate should still return true' + end + def test_self_try_activate_missing_dep + b = util_spec 'b', '1.0' a = util_spec 'a', '1.0', 'b' => '>= 1.0' + install_specs b, a + uninstall_gem b + a_file = File.join a.gem_dir, 'lib', 'a_file.rb' write_file a_file do |io| @@ -898,12 +930,17 @@ class TestGem < Gem::TestCase end def test_self_try_activate_missing_extensions - util_spec 'ext', '1' do |s| + spec = util_spec 'ext', '1' do |s| s.extensions = %w[ext/extconf.rb] s.mark_version s.installed_by_version = v('2.2') end + # write the spec without install to simulate a failed install + write_file spec.spec_file do |io| + io.write spec.to_ruby_for_cache + end + _, err = capture_io do refute Gem.try_activate 'nonexistent' end @@ -944,7 +981,7 @@ class TestGem < Gem::TestCase b = util_spec "b", "1", "c" => nil c = util_spec "c", "2" - install_specs a, b, c + install_specs a, c, b Gem.needs do |r| r.gem "a" @@ -966,7 +1003,7 @@ class TestGem < Gem::TestCase d = new_spec "d", "1", {'e' => '= 1'}, "lib/d.rb" e = util_spec "e", "1" - install_specs a, b, c, d, e + install_specs a, c, b, e, d Gem.needs do |r| r.gem "a" @@ -1372,16 +1409,21 @@ class TestGem < Gem::TestCase def test_use_gemdeps gem_deps_file = 'gem.deps.rb'.untaint spec = util_spec 'a', 1 + install_specs spec + spec = Gem::Specification.find { |s| s == spec } refute spec.activated? open gem_deps_file, 'w' do |io| io.write 'gem "a"' end + assert_nil Gem.gemdeps + Gem.use_gemdeps gem_deps_file assert spec.activated? + refute_nil Gem.gemdeps end def test_use_gemdeps_ENV @@ -1430,6 +1472,8 @@ class TestGem < Gem::TestCase rubygems_gemdeps, ENV['RUBYGEMS_GEMDEPS'] = ENV['RUBYGEMS_GEMDEPS'], '-' spec = util_spec 'a', 1 + install_specs spec + spec = Gem::Specification.find { |s| s == spec } refute spec.activated? @@ -1499,7 +1543,9 @@ You may need to `gem install -g` to install missing gems rubygems_gemdeps, ENV['RUBYGEMS_GEMDEPS'] = ENV['RUBYGEMS_GEMDEPS'], 'x' spec = util_spec 'a', 1 + install_specs spec + spec = Gem::Specification.find { |s| s == spec } refute spec.activated? open 'x', 'w' do |io| @@ -1560,6 +1606,7 @@ You may need to `gem install -g` to install missing gems @exec_path = File.join spec.full_gem_path, spec.bindir, 'exec' @abin_path = File.join spec.full_gem_path, spec.bindir, 'abin' + spec end def util_remove_interrupt_command diff --git a/test/rubygems/test_gem_available_set.rb b/test/rubygems/test_gem_available_set.rb index 2b515447a3..8b01112c11 100644 --- a/test/rubygems/test_gem_available_set.rb +++ b/test/rubygems/test_gem_available_set.rb @@ -79,7 +79,8 @@ class TestGemAvailableSet < Gem::TestCase end def test_remove_installed_bang - a1, _ = util_gem 'a', '1' + a1, _ = util_spec 'a', '1' + install_specs a1 a1.activate diff --git a/test/rubygems/test_gem_commands_cleanup_command.rb b/test/rubygems/test_gem_commands_cleanup_command.rb index fdaac545af..e8ad35fe8e 100644 --- a/test/rubygems/test_gem_commands_cleanup_command.rb +++ b/test/rubygems/test_gem_commands_cleanup_command.rb @@ -1,5 +1,6 @@ require 'rubygems/test_case' require 'rubygems/commands/cleanup_command' +require 'rubygems/installer' class TestGemCommandsCleanupCommand < Gem::TestCase @@ -78,8 +79,8 @@ class TestGemCommandsCleanupCommand < Gem::TestCase end def test_execute_all_user - @a_1_1 = util_spec 'a', '1.1' - @a_1_1 = install_gem_user @a_1_1 # pick up user install path + @a_1_1, = util_gem 'a', '1.1' + @a_1_1 = install_gem @a_1_1 # pick up user install path Gem::Specification.dirs = [Gem.dir, Gem.user_dir] @@ -97,8 +98,8 @@ class TestGemCommandsCleanupCommand < Gem::TestCase def test_execute_all_user_no_sudo FileUtils.chmod 0555, @gemhome - @a_1_1 = util_spec 'a', '1.1' - @a_1_1 = install_gem_user @a_1_1 # pick up user install path + @a_1_1, = util_gem 'a', '1.1' + @a_1_1 = install_gem @a_1_1, :user_install => true # pick up user install path Gem::Specification.dirs = [Gem.dir, Gem.user_dir] @@ -132,9 +133,9 @@ class TestGemCommandsCleanupCommand < Gem::TestCase s.add_dependency 'b', '1' end - install_gem @c install_gem @b_1 install_gem @b_2 + install_gem @c @cmd.options[:args] = [] diff --git a/test/rubygems/test_gem_commands_dependency_command.rb b/test/rubygems/test_gem_commands_dependency_command.rb index e22b240afe..1409162cec 100644 --- a/test/rubygems/test_gem_commands_dependency_command.rb +++ b/test/rubygems/test_gem_commands_dependency_command.rb @@ -28,6 +28,8 @@ class TestGemCommandsDependencyCommand < Gem::TestCase end def test_execute_no_args + install_specs new_spec 'x', '2' + spec_fetcher do |fetcher| fetcher.spec 'a', 1 fetcher.spec 'a', '2.a' @@ -51,6 +53,8 @@ Gem dep_x-1 Gem pl-1-x86-linux +Gem x-2 + EOF assert_equal expected, @ui.output @@ -71,9 +75,11 @@ Gem pl-1-x86-linux end def test_execute_pipe_format - util_spec 'foo' do |gem| + spec = util_spec 'foo' do |gem| gem.add_dependency 'bar', '> 1' end + install_specs util_spec 'bar', 2 + install_specs spec @cmd.options[:args] = %w[foo] @cmd.options[:pipe_format] = true @@ -164,6 +170,8 @@ ERROR: Only reverse dependencies for local gems are supported. end def test_execute_remote + install_specs new_spec 'bar', '2' + spec_fetcher do |fetcher| fetcher.spec 'foo', 2, 'bar' => '> 1' end diff --git a/test/rubygems/test_gem_commands_install_command.rb b/test/rubygems/test_gem_commands_install_command.rb index f03285ae85..b3062b4b0d 100644 --- a/test/rubygems/test_gem_commands_install_command.rb +++ b/test/rubygems/test_gem_commands_install_command.rb @@ -251,8 +251,7 @@ ERROR: Possible alternatives: non_existent_with_hint correctly_spelled = "nonexistent-with_hint" spec_fetcher do |fetcher| - fetcher.spec correctly_spelled, 2 - fetcher.clear + fetcher.download correctly_spelled, 2 end @cmd.options[:args] = [misspelled] @@ -310,9 +309,8 @@ ERROR: Possible alternatives: non_existent_with_hint def test_execute_prerelease_wins_over_previous_ver spec_fetcher do |fetcher| - fetcher.gem 'a', 1 - fetcher.gem 'a', '2.a' - fetcher.clear + fetcher.download 'a', 1 + fetcher.download 'a', '2.a' end @cmd.options[:prerelease] = true @@ -535,9 +533,7 @@ ERROR: Possible alternatives: non_existent_with_hint def test_execute_conservative spec_fetcher do |fetcher| - fetcher.gem 'b', 2 - - fetcher.clear + fetcher.download 'b', 2 fetcher.gem 'a', 2 end @@ -665,8 +661,7 @@ ERROR: Possible alternatives: non_existent_with_hint def test_show_source_problems_even_on_success spec_fetcher do |fetcher| - fetcher.gem 'a', 2 - fetcher.clear + fetcher.download 'a', 2 end Gem.sources << "http://nonexistent.example" @@ -738,8 +733,7 @@ ERROR: Possible alternatives: non_existent_with_hint def test_execute_installs_from_a_gemdeps_with_conservative spec_fetcher do |fetcher| - fetcher.gem 'a', 2 - fetcher.clear + fetcher.download 'a', 2 fetcher.gem 'a', 1 end @@ -763,8 +757,7 @@ ERROR: Possible alternatives: non_existent_with_hint def test_execute_installs_from_a_gemdeps spec_fetcher do |fetcher| - fetcher.gem 'a', 2 - fetcher.clear + fetcher.download 'a', 2 end File.open @gemdeps, "w" do |f| @@ -786,9 +779,8 @@ ERROR: Possible alternatives: non_existent_with_hint def test_execute_installs_deps_a_gemdeps spec_fetcher do |fetcher| - fetcher.gem 'q', '1.0' - fetcher.gem 'r', '2.0', 'q' => nil - fetcher.clear + fetcher.download 'q', '1.0' + fetcher.download 'r', '2.0', 'q' => nil end File.open @gemdeps, "w" do |f| @@ -813,9 +805,7 @@ ERROR: Possible alternatives: non_existent_with_hint def test_execute_uses_deps_a_gemdeps spec_fetcher do |fetcher| - fetcher.gem 'r', '2.0', 'q' => nil - - fetcher.clear + fetcher.download 'r', '2.0', 'q' => nil fetcher.spec 'q', '1.0' end @@ -842,9 +832,8 @@ ERROR: Possible alternatives: non_existent_with_hint def test_execute_installs_deps_a_gemdeps_into_a_path spec_fetcher do |fetcher| - fetcher.gem 'q', '1.0' - fetcher.gem 'r', '2.0', 'q' => nil - fetcher.clear + fetcher.download 'q', '1.0' + fetcher.download 'r', '2.0', 'q' => nil end File.open @gemdeps, "w" do |f| @@ -873,12 +862,11 @@ ERROR: Possible alternatives: non_existent_with_hint def test_execute_with_gemdeps_path_ignores_system specs = spec_fetcher do |fetcher| - fetcher.gem 'q', '1.0' - fetcher.gem 'r', '2.0', 'q' => nil - fetcher.clear + fetcher.download 'q', '1.0' + fetcher.download 'r', '2.0', 'q' => nil end - Gem::Specification.add_specs specs['q-1.0'] + install_specs specs['q-1.0'] File.open @gemdeps, "w" do |f| f << "gem 'r'" @@ -910,7 +898,7 @@ ERROR: Possible alternatives: non_existent_with_hint fetcher.gem 'r', '2.0', 'q' => nil end - i = Gem::Installer.new specs['q-1.0'].cache_file, :install_dir => "gf-path" + i = Gem::Installer.at specs['q-1.0'].cache_file, :install_dir => "gf-path" i.install assert File.file?("gf-path/specifications/q-1.0.gemspec"), "not installed" diff --git a/test/rubygems/test_gem_commands_mirror.rb b/test/rubygems/test_gem_commands_mirror.rb index 2f6fe52401..3b01ab3645 100644 --- a/test/rubygems/test_gem_commands_mirror.rb +++ b/test/rubygems/test_gem_commands_mirror.rb @@ -2,23 +2,10 @@ require 'rubygems/test_case' require 'rubygems/commands/mirror_command' class TestGemCommandsMirrorCommand < Gem::TestCase - def setup super @cmd = Gem::Commands::MirrorCommand.new - - @mirror_specs = Gem::Specification.find_all_by_name('rubygems-mirror').each do |spec| - Gem::Specification.remove_spec spec - end - end - - def teardown - @mirror_specs.each do |spec| - Gem::Specification.add_spec spec - end - - super end def test_execute diff --git a/test/rubygems/test_gem_commands_outdated_command.rb b/test/rubygems/test_gem_commands_outdated_command.rb index d369c6b14b..1bc5acc1f4 100644 --- a/test/rubygems/test_gem_commands_outdated_command.rb +++ b/test/rubygems/test_gem_commands_outdated_command.rb @@ -15,9 +15,8 @@ class TestGemCommandsOutdatedCommand < Gem::TestCase def test_execute spec_fetcher do |fetcher| - fetcher.spec 'foo', '1.0' - fetcher.spec 'foo', '2.0' - fetcher.clear + fetcher.download 'foo', '1.0' + fetcher.download 'foo', '2.0' fetcher.gem 'foo', '0.1' fetcher.gem 'foo', '0.2' end diff --git a/test/rubygems/test_gem_commands_pristine_command.rb b/test/rubygems/test_gem_commands_pristine_command.rb index ec5f3ad186..7c0109be2c 100644 --- a/test/rubygems/test_gem_commands_pristine_command.rb +++ b/test/rubygems/test_gem_commands_pristine_command.rb @@ -103,10 +103,12 @@ class TestGemCommandsPristineCommand < Gem::TestCase assert_path_exists gem_exec + ruby_exec = sprintf Gem.default_exec_format, 'ruby' + if win_platform? - assert_match %r%\A#!\s*ruby%, File.read(gem_exec) + assert_match %r%\A#!\s*#{ruby_exec}%, File.read(gem_exec) else - assert_match %r%\A#!\s*/usr/bin/env ruby%, File.read(gem_exec) + assert_match %r%\A#!\s*/usr/bin/env #{ruby_exec}%, File.read(gem_exec) end end @@ -151,10 +153,11 @@ class TestGemCommandsPristineCommand < Gem::TestCase ext_path = File.join @tempdir, 'ext', 'a', 'extconf.rb' write_file ext_path do |io| - io.write '# extconf.rb' + io.write "# extconf.rb\nrequire 'mkmf'; create_makefile 'a'" end util_build_gem a + install_gem a @cmd.options[:args] = %w[a] @cmd.options[:extensions] = false @@ -403,7 +406,7 @@ class TestGemCommandsPristineCommand < Gem::TestCase end def test_execute_unknown_gem_at_remote_source - util_spec 'a' + install_specs util_spec 'a' @cmd.options[:args] = %w[a] @@ -442,9 +445,10 @@ class TestGemCommandsPristineCommand < Gem::TestCase def test_execute_bundled_gem_on_old_rubies util_set_RUBY_VERSION '1.9.3', 551 - util_spec 'bigdecimal', '1.1.0' do |s| + spec = util_spec 'bigdecimal', '1.1.0' do |s| s.summary = "This bigdecimal is bundled with Ruby" end + install_specs spec @cmd.options[:args] = %w[bigdecimal] diff --git a/test/rubygems/test_gem_commands_query_command.rb b/test/rubygems/test_gem_commands_query_command.rb index 43fa82571d..50cedbb50e 100644 --- a/test/rubygems/test_gem_commands_query_command.rb +++ b/test/rubygems/test_gem_commands_query_command.rb @@ -1,23 +1,22 @@ require 'rubygems/test_case' require 'rubygems/commands/query_command' -class TestGemCommandsQueryCommand < Gem::TestCase - +module TestGemCommandsQueryCommandSetup def setup super @cmd = Gem::Commands::QueryCommand.new - @specs = spec_fetcher do |fetcher| - fetcher.spec 'a', 1 - fetcher.spec 'a', 2 - fetcher.spec 'a', '3.a' - end + @specs = add_gems_to_fetcher @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 do |fetcher| @@ -42,37 +41,6 @@ pl (1 i386-linux) assert_equal '', @ui.error end - def test_execute_platform - spec_fetcher do |fetcher| - fetcher.clear - - 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 @ui do - @cmd.execute - end - - expected = <<-EOF - -*** REMOTE GEMS *** - -a (2 universal-darwin, 1 ruby x86-linux) - EOF - - assert_equal expected, @ui.output - assert_equal '', @ui.error - end - def test_execute_all spec_fetcher do |fetcher| fetcher.legacy_platform @@ -147,56 +115,6 @@ a (2) 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, @ui.output - assert_equal '', @ui.error - end - - def test_execute_details_platform - spec_fetcher do |fetcher| - fetcher.clear - - 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[-r -d] - - use_ui @ui do - @cmd.execute - end - - expected = <<-EOF - -*** REMOTE GEMS *** - -a (2, 1) - Platforms: - 1: x86-linux - 2: universal-darwin - 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 @@ -526,67 +444,100 @@ pl (1 i386-linux) assert_equal '', @ui.error end - def test_execute_local_details + def test_make_entry + a_2_name = @specs['a-2'].original_name + + @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 do |fetcher| - fetcher.clear + fetcher.legacy_platform + end + + @cmd.handle_options %w[a pl] + + use_ui @ui do + @cmd.execute + end + assert_match %r%^a %, @ui.output + assert_match %r%^pl %, @ui.output + assert_equal '', @ui.error + end + + def test_show_gems + @cmd.options[:name] = // + @cmd.options[:domain] = :remote + + use_ui @ui do + @cmd.send :show_gems, /a/i, false + end + + assert_match %r%^a %, @ui.output + refute_match %r%^pl %, @ui.output + assert_empty @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.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] + @cmd.handle_options %w[-r -a] use_ui @ui do @cmd.execute end - str = @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: - +*** REMOTE GEMS *** - this is a summary +a (2 universal-darwin, 1 ruby x86-linux) EOF assert_equal expected, @ui.output + assert_equal '', @ui.error end def test_execute_default_details spec_fetcher do |fetcher| - fetcher.clear - fetcher.spec 'a', 2 end @@ -615,54 +566,68 @@ a (2, 1) assert_equal expected, @ui.output end - def test_make_entry - a_2_name = @specs['a-2'].original_name - - @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 + def test_execute_local_details + spec_fetcher do |fetcher| + fetcher.spec 'a', 1 do |s| + s.platform = 'x86-linux' + end - assert_equal 'a (2)', entry - 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 - # Test for multiple args handling! - def test_execute_multiple_args - spec_fetcher do |fetcher| fetcher.legacy_platform end - @cmd.handle_options %w[a pl] + @cmd.handle_options %w[-l -d] use_ui @ui do @cmd.execute end - assert_match %r%^a %, @ui.output - assert_match %r%^pl %, @ui.output - assert_equal '', @ui.error - end + str = @ui.output - def test_show_gems - @cmd.options[:name] = // - @cmd.options[:domain] = :remote + str.gsub!(/\(\d\): [^\n]*/, "-") + str.gsub!(/at: [^\n]*/, "at: -") - use_ui @ui do - @cmd.send :show_gems, /a/i, false - end + expected = <<-EOF - assert_match %r%^a %, @ui.output - refute_match %r%^pl %, @ui.output - assert_empty @ui.error +*** 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, @ui.output end -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_specification_command.rb b/test/rubygems/test_gem_commands_specification_command.rb index 80564f9dce..ccc2dbbae9 100644 --- a/test/rubygems/test_gem_commands_specification_command.rb +++ b/test/rubygems/test_gem_commands_specification_command.rb @@ -26,8 +26,8 @@ class TestGemCommandsSpecificationCommand < Gem::TestCase end def test_execute_all - util_spec 'foo', '0.0.1' - util_spec 'foo', '0.0.2' + install_specs util_spec 'foo', '0.0.1' + install_specs util_spec 'foo', '0.0.2' @cmd.options[:args] = %w[foo] @cmd.options[:all] = true @@ -89,8 +89,8 @@ class TestGemCommandsSpecificationCommand < Gem::TestCase end def test_execute_exact_match - util_spec 'foo' - util_spec 'foo_bar' + install_specs util_spec 'foo' + install_specs util_spec 'foo_bar' @cmd.options[:args] = %w[foo] diff --git a/test/rubygems/test_gem_commands_stale_command.rb b/test/rubygems/test_gem_commands_stale_command.rb index ca80784749..69b1e29e2d 100644 --- a/test/rubygems/test_gem_commands_stale_command.rb +++ b/test/rubygems/test_gem_commands_stale_command.rb @@ -13,10 +13,12 @@ class TestGemCommandsStaleCommand < Gem::TestCase foo_bar = util_spec 'foo_bar' do |gem| gem.files = files end + install_specs foo_bar bar_baz = util_spec 'bar_baz' do |gem| gem.files = files end + install_specs bar_baz files.each do |file| filename = File.join(bar_baz.full_gem_path, file) diff --git a/test/rubygems/test_gem_commands_uninstall_command.rb b/test/rubygems/test_gem_commands_uninstall_command.rb index 4f045c5e3d..2e32e397a6 100644 --- a/test/rubygems/test_gem_commands_uninstall_command.rb +++ b/test/rubygems/test_gem_commands_uninstall_command.rb @@ -24,7 +24,7 @@ class TestGemCommandsUninstallCommand < Gem::InstallerTestCase gemhome2 = "#{@gemhome}2" - a_4 = util_spec 'a', 4 + a_4, = util_gem 'a', 4 install_gem a_4, :install_dir => gemhome2 Gem::Specification.dirs = [@gemhome, gemhome2] @@ -41,7 +41,7 @@ class TestGemCommandsUninstallCommand < Gem::InstallerTestCase @cmd.execute end - assert_equal %w[a-4 a_evil-9 b-2 c-1.2 default-1 dep_x-1 pl-1-x86-linux x-1], + assert_equal %w[a_evil-9 b-2 c-1.2 default-1 dep_x-1 pl-1-x86-linux x-1], Gem::Specification.all_names.sort end @@ -213,7 +213,7 @@ class TestGemCommandsUninstallCommand < Gem::InstallerTestCase gemhome2 = "#{@gemhome}2" - a_4 = util_spec 'a', 4 + a_4, = util_gem 'a', 4 install_gem a_4, :install_dir => gemhome2 Gem::Specification.dirs = [@gemhome, gemhome2] @@ -229,7 +229,7 @@ class TestGemCommandsUninstallCommand < Gem::InstallerTestCase @cmd.execute end - assert_equal %w[a-4 default-1], Gem::Specification.all_names.sort + assert_equal %w[default-1], Gem::Specification.all_names.sort end def test_handle_options diff --git a/test/rubygems/test_gem_commands_unpack_command.rb b/test/rubygems/test_gem_commands_unpack_command.rb index 9264b3fc13..05002f8832 100644 --- a/test/rubygems/test_gem_commands_unpack_command.rb +++ b/test/rubygems/test_gem_commands_unpack_command.rb @@ -101,10 +101,8 @@ class TestGemCommandsUnpackCommand < Gem::TestCase def test_execute_remote spec_fetcher do |fetcher| - fetcher.spec 'a', 1 - fetcher.gem 'a', 2 - - fetcher.clear + fetcher.download 'a', 1 + fetcher.download 'a', 2 end Gem.configuration.verbose = :really @@ -184,8 +182,8 @@ class TestGemCommandsUnpackCommand < Gem::TestCase foo_path = File.join(@tempdir, "#{foo_spec.full_name}.gem") foo_bar_path = File.join(@tempdir, "#{foo_bar_spec.full_name}.gem") - Gem::Installer.new(foo_path).install - Gem::Installer.new(foo_bar_path).install + Gem::Installer.at(foo_path).install + Gem::Installer.at(foo_bar_path).install @cmd.options[:args] = %w[foo] diff --git a/test/rubygems/test_gem_commands_update_command.rb b/test/rubygems/test_gem_commands_update_command.rb index 6a327068e2..03894d7590 100644 --- a/test/rubygems/test_gem_commands_update_command.rb +++ b/test/rubygems/test_gem_commands_update_command.rb @@ -18,11 +18,9 @@ class TestGemCommandsUpdateCommand < Gem::TestCase @cmd.options[:document] = [] @specs = spec_fetcher do |fetcher| - fetcher.gem 'a', 1 - fetcher.gem 'a', 2 - fetcher.gem 'a', '3.a' - - fetcher.clear + fetcher.download 'a', 1 + fetcher.download 'a', 2 + fetcher.download 'a', '3.a' end @a1_path = @specs['a-1'].cache_file @@ -32,10 +30,7 @@ class TestGemCommandsUpdateCommand < Gem::TestCase def test_execute spec_fetcher do |fetcher| - fetcher.gem 'a', 2 - - fetcher.clear - + fetcher.download 'a', 2 fetcher.spec 'a', 1 end @@ -54,10 +49,8 @@ class TestGemCommandsUpdateCommand < Gem::TestCase def test_execute_multiple spec_fetcher do |fetcher| - fetcher.gem 'a', 2 - fetcher.gem 'ab', 2 - - fetcher.clear + fetcher.download 'a', 2 + fetcher.download 'ab', 2 fetcher.spec 'a', 1 fetcher.spec 'ab', 1 @@ -78,9 +71,7 @@ class TestGemCommandsUpdateCommand < Gem::TestCase def test_execute_system spec_fetcher do |fetcher| - fetcher.gem 'rubygems-update', 9 do |s| s.files = %w[setup.rb] end - - fetcher.clear + fetcher.download 'rubygems-update', 9 do |s| s.files = %w[setup.rb] end end @cmd.options[:args] = [] @@ -100,11 +91,9 @@ class TestGemCommandsUpdateCommand < Gem::TestCase def test_execute_system_at_latest spec_fetcher do |fetcher| - fetcher.gem 'rubygems-update', Gem::VERSION do |s| + fetcher.download 'rubygems-update', Gem::VERSION do |s| s.files = %w[setup.rb] end - - fetcher.clear end @cmd.options[:args] = [] @@ -123,10 +112,8 @@ class TestGemCommandsUpdateCommand < Gem::TestCase def test_execute_system_multiple spec_fetcher do |fetcher| - fetcher.gem 'rubygems-update', 8 do |s| s.files = %w[setup.rb] end - fetcher.gem 'rubygems-update', 9 do |s| s.files = %w[setup.rb] end - - fetcher.clear + fetcher.download 'rubygems-update', 8 do |s| s.files = %w[setup.rb] end + fetcher.download 'rubygems-update', 9 do |s| s.files = %w[setup.rb] end end @cmd.options[:args] = [] @@ -146,10 +133,8 @@ class TestGemCommandsUpdateCommand < Gem::TestCase def test_execute_system_specific spec_fetcher do |fetcher| - fetcher.gem 'rubygems-update', 8 do |s| s.files = %w[setup.rb] end - fetcher.gem 'rubygems-update', 9 do |s| s.files = %w[setup.rb] end - - fetcher.clear + fetcher.download 'rubygems-update', 8 do |s| s.files = %w[setup.rb] end + fetcher.download 'rubygems-update', 9 do |s| s.files = %w[setup.rb] end end @cmd.options[:args] = [] @@ -169,10 +154,8 @@ class TestGemCommandsUpdateCommand < Gem::TestCase def test_execute_system_specifically_to_latest_version spec_fetcher do |fetcher| - fetcher.gem 'rubygems-update', 8 do |s| s.files = %w[setup.rb] end - fetcher.gem 'rubygems-update', 9 do |s| s.files = %w[setup.rb] end - - fetcher.clear + fetcher.download 'rubygems-update', 8 do |s| s.files = %w[setup.rb] end + fetcher.download 'rubygems-update', 9 do |s| s.files = %w[setup.rb] end end @cmd.options[:args] = [] @@ -213,11 +196,9 @@ class TestGemCommandsUpdateCommand < Gem::TestCase def test_execute_dependencies spec_fetcher do |fetcher| - fetcher.gem 'a', 2, 'b' => 2, 'c' => 2 - fetcher.gem 'b', 2 - fetcher.gem 'c', 2 - - fetcher.clear + fetcher.download 'a', 2, 'b' => 2, 'c' => 2 + fetcher.download 'b', 2 + fetcher.download 'c', 2 fetcher.spec 'a', 1, 'c' => '1.2' fetcher.spec 'c', '1.2' @@ -243,10 +224,7 @@ class TestGemCommandsUpdateCommand < Gem::TestCase def test_execute_rdoc skip if RUBY_VERSION <= "1.8.7" spec_fetcher do |fetcher| - fetcher.gem 'a', 2 - - fetcher.clear - + fetcher.download 'a', 2 fetcher.spec 'a', 1 end @@ -269,9 +247,7 @@ class TestGemCommandsUpdateCommand < Gem::TestCase def test_execute_named spec_fetcher do |fetcher| - fetcher.gem 'a', 2 - - fetcher.clear + fetcher.download 'a', 2 fetcher.spec 'a', 1 end @@ -292,8 +268,7 @@ class TestGemCommandsUpdateCommand < Gem::TestCase def test_execute_named_some_up_to_date spec_fetcher do |fetcher| - fetcher.gem 'a', 2 - fetcher.clear + fetcher.download 'a', 2 fetcher.spec 'a', 1 fetcher.spec 'b', 2 @@ -334,9 +309,7 @@ class TestGemCommandsUpdateCommand < Gem::TestCase def test_execute_named_up_to_date_prerelease spec_fetcher do |fetcher| - fetcher.gem 'a', '3.a' - - fetcher.clear + fetcher.download 'a', '3.a' fetcher.gem 'a', 2 end @@ -376,10 +349,7 @@ class TestGemCommandsUpdateCommand < Gem::TestCase def test_execute_user_install spec_fetcher do |fetcher| - fetcher.gem 'a', 2 - - fetcher.clear - + fetcher.download 'a', 2 fetcher.spec 'a', 1 end diff --git a/test/rubygems/test_gem_commands_which_command.rb b/test/rubygems/test_gem_commands_which_command.rb index 7ce26c861a..5c6afe93d1 100644 --- a/test/rubygems/test_gem_commands_which_command.rb +++ b/test/rubygems/test_gem_commands_which_command.rb @@ -73,6 +73,7 @@ class TestGemCommandsWhichCommand < Gem::TestCase @foo_bar = util_spec 'foo_bar' do |gem| gem.files = files end + install_specs @foo_bar files.each do |file| filename = File.join(@foo_bar.full_gem_path, file) diff --git a/test/rubygems/test_gem_dependency.rb b/test/rubygems/test_gem_dependency.rb index c43ff0e03d..4c27ce7300 100644 --- a/test/rubygems/test_gem_dependency.rb +++ b/test/rubygems/test_gem_dependency.rb @@ -294,10 +294,11 @@ class TestGemDependency < Gem::TestCase end def test_to_spec - util_spec 'a', '1' + a_1 = util_spec 'a', '1' a_2 = util_spec 'a', '2' a_dep = dep 'a', '>= 0' + install_specs a_1, a_2 assert_equal a_2, a_dep.to_spec end @@ -307,6 +308,7 @@ class TestGemDependency < Gem::TestCase a_1_1_a = util_spec 'a', '1.1.a' a_dep = dep 'a', '>= 0' + install_specs a_1, a_1_1_a assert_equal a_1, a_dep.to_spec @@ -317,7 +319,8 @@ class TestGemDependency < Gem::TestCase end def test_to_specs_suggests_other_versions - a = util_spec 'a', '1.0', 'b' => '>= 1.0' + a = util_spec 'a', '1.0' + install_specs a a_file = File.join a.gem_dir, 'lib', 'a_file.rb' @@ -335,7 +338,8 @@ class TestGemDependency < Gem::TestCase end def test_to_specs_indicates_total_gem_set_size - a = util_spec 'a', '1.0', 'b' => '>= 1.0' + a = util_spec 'a', '1.0' + install_specs a a_file = File.join a.gem_dir, 'lib', 'a_file.rb' diff --git a/test/rubygems/test_gem_dependency_installer.rb b/test/rubygems/test_gem_dependency_installer.rb index 82640068e4..03bcd04bc0 100644 --- a/test/rubygems/test_gem_dependency_installer.rb +++ b/test/rubygems/test_gem_dependency_installer.rb @@ -406,7 +406,7 @@ class TestGemDependencyInstaller < Gem::TestCase def test_install_dependency_existing util_setup_gems - Gem::Installer.new(@a1_gem).install + Gem::Installer.at(@a1_gem).install FileUtils.mv @a1_gem, @tempdir FileUtils.mv @b1_gem, @tempdir inst = nil @@ -437,7 +437,7 @@ class TestGemDependencyInstaller < Gem::TestCase _, f1_gem = util_gem 'f', '1', 'e' => nil - Gem::Installer.new(e1_gem).install + Gem::Installer.at(e1_gem).install FileUtils.rm_r e1.extension_dir FileUtils.mv e1_gem, @tempdir @@ -525,7 +525,7 @@ class TestGemDependencyInstaller < Gem::TestCase inst = nil Dir.chdir @tempdir do - Gem::Installer.new('a-1.gem').install + Gem::Installer.at('a-1.gem').install inst = Gem::DependencyInstaller.new :domain => :local inst.install 'b-1.gem' @@ -669,7 +669,7 @@ class TestGemDependencyInstaller < Gem::TestCase FileUtils.mv @a1_gem, @tempdir FileUtils.mv @b1_gem, @tempdir - inst = Gem::Installer.new @a1.file_name + inst = Gem::Installer.at @a1.file_name inst.install gemhome2 = File.join @tempdir, 'gemhome2' @@ -799,7 +799,7 @@ class TestGemDependencyInstaller < Gem::TestCase def test_install_reinstall util_setup_gems - Gem::Installer.new(@a1_gem).install + Gem::Installer.at(@a1_gem).install FileUtils.mv @a1_gem, @tempdir inst = nil diff --git a/test/rubygems/test_gem_doctor.rb b/test/rubygems/test_gem_doctor.rb index f4d4659c02..1097528e18 100644 --- a/test/rubygems/test_gem_doctor.rb +++ b/test/rubygems/test_gem_doctor.rb @@ -157,7 +157,7 @@ This directory does not appear to be a RubyGems repository, skipping refute doctor.gem_repository?, 'no gems installed' - util_spec 'a' + install_specs util_spec 'a' doctor = Gem::Doctor.new @gemhome diff --git a/test/rubygems/test_gem_ext_configure_builder.rb b/test/rubygems/test_gem_ext_configure_builder.rb index 34e9fccd46..610d60a920 100644 --- a/test/rubygems/test_gem_ext_configure_builder.rb +++ b/test/rubygems/test_gem_ext_configure_builder.rb @@ -29,12 +29,16 @@ class TestGemExtConfigureBuilder < Gem::TestCase Gem::Ext::ConfigureBuilder.build nil, nil, @dest_path, output end + assert_match(/^current directory:/, output.shift) assert_equal "sh ./configure --prefix=#{@dest_path}", output.shift assert_equal "", output.shift + assert_match(/^current directory:/, output.shift) assert_contains_make_command 'clean', output.shift assert_match(/^ok$/m, output.shift) + assert_match(/^current directory:/, output.shift) assert_contains_make_command '', output.shift assert_match(/^ok$/m, output.shift) + assert_match(/^current directory:/, output.shift) assert_contains_make_command 'install', output.shift assert_match(/^ok$/m, output.shift) end @@ -54,6 +58,7 @@ class TestGemExtConfigureBuilder < Gem::TestCase assert_match 'configure failed', error.message + assert_match(/^current directory:/, output.shift) assert_equal "#{sh_prefix_configure}#{@dest_path}", output.shift assert_match %r(#{shell_error_msg}), output.shift assert_equal true, output.empty? @@ -73,10 +78,9 @@ class TestGemExtConfigureBuilder < Gem::TestCase Gem::Ext::ConfigureBuilder.build nil, nil, @dest_path, output end - assert_contains_make_command 'clean', output[0] - assert_contains_make_command '', output[2] - assert_contains_make_command 'install', output[4] + assert_contains_make_command 'clean', output[1] + assert_contains_make_command '', output[4] + assert_contains_make_command 'install', output[7] 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 f2f467e871..908dbdeb8c 100644 --- a/test/rubygems/test_gem_ext_ext_conf_builder.rb +++ b/test/rubygems/test_gem_ext_ext_conf_builder.rb @@ -33,11 +33,13 @@ class TestGemExtExtConfBuilder < Gem::TestCase assert_same result, output end - assert_match(/^#{Gem.ruby}.* extconf.rb/, output[0]) - assert_equal "creating Makefile\n", output[1] - assert_contains_make_command 'clean', output[2] - assert_contains_make_command '', output[4] - assert_contains_make_command 'install', output[6] + assert_match(/^current directory:/, output[0]) + assert_match(/^#{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] assert_empty Dir.glob(File.join(@ext, 'siteconf*.rb')) end @@ -54,10 +56,10 @@ class TestGemExtExtConfBuilder < Gem::TestCase Gem::Ext::ExtConfBuilder.build 'extconf.rb', nil, @dest_path, output end - assert_equal "creating Makefile\n", output[1] - assert_contains_make_command 'clean', output[2] - assert_contains_make_command '', output[4] - assert_contains_make_command 'install', output[6] + assert_equal "creating Makefile\n", output[2] + assert_contains_make_command 'clean', output[4] + assert_contains_make_command '', output[7] + assert_contains_make_command 'install', output[10] end end @@ -78,8 +80,8 @@ class TestGemExtExtConfBuilder < Gem::TestCase end end - assert_equal "creating Makefile\n", output[1] - assert_contains_make_command 'clean', output[2] + assert_equal "creating Makefile\n", output[2] + assert_contains_make_command 'clean', output[4] end ensure ENV['make'] = env_make @@ -106,7 +108,9 @@ class TestGemExtExtConfBuilder < Gem::TestCase assert_equal 'extconf failed, exit code 1', error.message - assert_match(/^#{Gem.ruby}.* extconf.rb/, output[0]) + assert_match(/^#{Gem.ruby}.* extconf.rb/, output[1]) + assert_match(File.join(@dest_path, 'mkmf.log'), output[4]) + assert_path_exists File.join @dest_path, 'mkmf.log' end @@ -147,9 +151,9 @@ end Gem::Ext::ExtConfBuilder.build 'extconf.rb', nil, @dest_path, output end - assert_contains_make_command 'clean', output[2] - assert_contains_make_command '', output[4] - assert_contains_make_command 'install', output[6] + assert_contains_make_command 'clean', output[4] + assert_contains_make_command '', output[7] + assert_contains_make_command 'install', output[10] assert_empty Dir.glob(File.join(@ext, 'siteconf*.rb')) end @@ -173,9 +177,9 @@ end Gem::Ext::ExtConfBuilder.make @ext, output end - assert_contains_make_command 'clean', output[0] - assert_contains_make_command '', output[2] - assert_contains_make_command 'install', output[4] + assert_contains_make_command 'clean', output[1] + assert_contains_make_command '', output[4] + assert_contains_make_command 'install', output[7] end def test_class_make_no_Makefile diff --git a/test/rubygems/test_gem_indexer.rb b/test/rubygems/test_gem_indexer.rb index 8b505efd98..9622cd1abb 100644 --- a/test/rubygems/test_gem_indexer.rb +++ b/test/rubygems/test_gem_indexer.rb @@ -294,7 +294,7 @@ class TestGemIndexer < Gem::TestCase sys_gem = util_spec 'systemgem', '1.0' util_build_gem sys_gem - Gem::Specification.add_spec sys_gem + install_default_gems sys_gem yield util_remove_gem sys_gem end diff --git a/test/rubygems/test_gem_install_update_options.rb b/test/rubygems/test_gem_install_update_options.rb index de09d7a171..e7dc52a63c 100644 --- a/test/rubygems/test_gem_install_update_options.rb +++ b/test/rubygems/test_gem_install_update_options.rb @@ -129,7 +129,7 @@ class TestGemInstallUpdateOptions < Gem::InstallerTestCase assert @cmd.options[:user_install] - @installer = Gem::Installer.new @gem, @cmd.options + @installer = Gem::Installer.at @gem, @cmd.options @installer.install assert_path_exists File.join(Gem.user_dir, 'gems') assert_path_exists File.join(Gem.user_dir, 'gems', @spec.full_name) @@ -149,7 +149,7 @@ class TestGemInstallUpdateOptions < Gem::InstallerTestCase Gem.use_paths @gemhome, @userhome assert_raises(Gem::FilePermissionError) do - Gem::Installer.new(@gem, @cmd.options).install + Gem::Installer.at(@gem, @cmd.options).install end end ensure diff --git a/test/rubygems/test_gem_installer.rb b/test/rubygems/test_gem_installer.rb index 701fda15d4..2e5ac6ab76 100644 --- a/test/rubygems/test_gem_installer.rb +++ b/test/rubygems/test_gem_installer.rb @@ -36,7 +36,7 @@ class TestGemInstaller < Gem::InstallerTestCase require 'rubygems' -version = \">= 0\" +version = \">= 0.a\" if ARGV.first str = ARGV.first @@ -257,7 +257,7 @@ gem 'other', version s.add_dependency 'garbage ~> 5' end - installer = Gem::Installer.new a_gem + installer = Gem::Installer.at a_gem e = assert_raises Gem::InstallError do installer.ensure_loadable_spec @@ -275,7 +275,7 @@ gem 'other', version end policy = Gem::Security::HighSecurity - installer = Gem::Installer.new a_gem, :security_policy => policy + installer = Gem::Installer.at a_gem, :security_policy => policy assert_raises Gem::Security::Exception do installer.ensure_loadable_spec @@ -322,7 +322,7 @@ gem 'other', version :install_dir => "/non/existent" } - inst = Gem::Installer.new '', options + inst = Gem::Installer.at '', options Gem::Installer.path_warning = false @@ -575,8 +575,8 @@ gem 'other', version util_make_exec one = @spec.dup one.version = 1 + @installer = Gem::Installer.for_spec spec @installer.gem_dir = util_gem_dir one - @installer.spec = spec @installer.generate_bin @@ -672,14 +672,14 @@ gem 'other', version util_build_gem spec FileUtils.mv spec.cache_file, @tempdir - installer = Gem::Installer.new gem + installer = Gem::Installer.at gem assert_equal File.join(@gemhome, 'gems', spec.full_name), installer.gem_dir assert_equal File.join(@gemhome, 'bin'), installer.bin_dir end def test_initialize_user_install - installer = Gem::Installer.new @gem, :user_install => true + installer = Gem::Installer.at @gem, :user_install => true assert_equal File.join(Gem.user_dir, 'gems', @spec.full_name), installer.gem_dir @@ -688,7 +688,7 @@ gem 'other', version def test_initialize_user_install_bin_dir installer = - Gem::Installer.new @gem, :user_install => true, :bin_dir => @tempdir + Gem::Installer.at @gem, :user_install => true, :bin_dir => @tempdir assert_equal File.join(Gem.user_dir, 'gems', @spec.full_name), installer.gem_dir @@ -870,7 +870,7 @@ gem 'other', version Gem::Package.build @spec end end - @installer = Gem::Installer.new @gem + @installer = Gem::Installer.at @gem build_rake_in do use_ui @ui do assert_equal @spec, @installer.install @@ -884,7 +884,7 @@ gem 'other', version def test_install_force use_ui @ui do - installer = Gem::Installer.new old_ruby_required, :force => true + installer = Gem::Installer.at old_ruby_required, :force => true installer.install end @@ -993,7 +993,7 @@ gem 'other', version use_ui @ui do path = Gem::Package.build @spec - @installer = Gem::Installer.new path + @installer = Gem::Installer.at path @installer.install end @@ -1016,7 +1016,7 @@ gem 'other', version use_ui @ui do path = Gem::Package.build @spec - installer = Gem::Installer.new path, :install_dir => gemhome2 + installer = Gem::Installer.at path, :install_dir => gemhome2 installer.install end @@ -1056,7 +1056,7 @@ gem 'other', version use_ui @ui do path = Gem::Package.build @spec - @installer = Gem::Installer.new path + @installer = Gem::Installer.at path @installer.install end assert_path_exists File.join @spec.gem_dir, rb @@ -1097,7 +1097,7 @@ gem 'other', version use_ui @ui do path = Gem::Package.build @spec - @installer = Gem::Installer.new path + @installer = Gem::Installer.at path @installer.install end assert_path_exists so @@ -1175,7 +1175,7 @@ gem 'other', version # that it work everything out on it's own. Gem::Specification.reset - installer = Gem::Installer.new gem, :install_dir => gemhome2 + installer = Gem::Installer.at gem, :install_dir => gemhome2 build_rake_in do use_ui @ui do @@ -1186,7 +1186,7 @@ gem 'other', version def test_pre_install_checks_ruby_version use_ui @ui do - installer = Gem::Installer.new old_ruby_required + installer = Gem::Installer.at old_ruby_required e = assert_raises Gem::InstallError do installer.pre_install_checks end @@ -1205,7 +1205,7 @@ gem 'other', version gem = File.join(@gemhome, 'cache', spec.file_name) use_ui @ui do - @installer = Gem::Installer.new gem + @installer = Gem::Installer.at gem e = assert_raises Gem::InstallError do @installer.pre_install_checks end @@ -1231,7 +1231,7 @@ gem 'other', version def test_process_options_build_root build_root = File.join @tempdir, 'build_root' - @installer = Gem::Installer.new @gem, :build_root => build_root + @installer = Gem::Installer.at @gem, :build_root => build_root assert_equal Pathname(build_root), @installer.build_root assert_equal File.join(build_root, @gemhome, 'bin'), @installer.bin_dir @@ -1406,7 +1406,7 @@ gem 'other', version end def test_write_build_info_file_install_dir - installer = Gem::Installer.new @gem, :install_dir => "#{@gemhome}2" + installer = Gem::Installer.at @gem, :install_dir => "#{@gemhome}2" installer.build_args = %w[ --with-libyaml-dir /usr/local/Cellar/libyaml/0.1.4 @@ -1426,8 +1426,7 @@ gem 'other', version FileUtils.mv cache_file, gem refute_path_exists cache_file - installer = Gem::Installer.new gem - installer.spec = @spec + installer = Gem::Installer.at gem installer.gem_home = @gemhome installer.write_cache_file @@ -1439,7 +1438,7 @@ gem 'other', version FileUtils.rm @spec.spec_file refute_path_exists @spec.spec_file - @installer.spec = @spec + @installer = Gem::Installer.for_spec @spec @installer.gem_home = @gemhome @installer.write_spec @@ -1459,7 +1458,7 @@ gem 'other', version @spec.files = %w[a.rb b.rb c.rb] - @installer.spec = @spec + @installer = Gem::Installer.for_spec @spec @installer.gem_home = @gemhome @installer.write_spec @@ -1474,6 +1473,13 @@ gem 'other', version 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? + end + def test_default_gem FileUtils.rm_f File.join(Gem.dir, 'specifications') @@ -1490,10 +1496,10 @@ gem 'other', version installed_exec = File.join util_inst_bindir, 'executable' assert_path_exists installed_exec - assert File.directory? File.join(Gem.dir, 'specifications') - assert File.directory? File.join(Gem.dir, 'specifications', 'default') + assert File.directory? File.join(Gem.default_dir, 'specifications') + assert File.directory? File.join(Gem.default_dir, 'specifications', 'default') - default_spec = eval File.read File.join(Gem.dir, 'specifications', 'default', 'a-2.gemspec') + 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 end diff --git a/test/rubygems/test_gem_package.rb b/test/rubygems/test_gem_package.rb index 9d9c224349..bbb036d849 100644 --- a/test/rubygems/test_gem_package.rb +++ b/test/rubygems/test_gem_package.rb @@ -132,6 +132,37 @@ class TestGemPackage < Gem::Package::TarTestCase assert_equal %w[lib/code.rb], files end + def test_add_files_symlink + skip 'symlink not supported' if Gem.win_platform? + + spec = Gem::Specification.new + spec.files = %w[lib/code.rb lib/code_sym.rb] + + FileUtils.mkdir_p 'lib' + open 'lib/code.rb', 'w' do |io| io.write '# lib/code.rb' end + File.symlink('lib/code.rb', 'lib/code_sym.rb') + + package = Gem::Package.new 'bogus.gem' + package.spec = spec + + tar = util_tar do |tar_io| + package.add_files tar_io + end + + tar.rewind + + files, symlinks = [], [] + + Gem::Package::TarReader.new tar do |tar_io| + tar_io.each_entry do |entry| + (entry.symlink? ? symlinks : files) << entry.full_name + end + end + + assert_equal %w[lib/code.rb], files + assert_equal %w[lib/code_sym.rb], symlinks + end + def test_build spec = Gem::Specification.new 'build', '1' spec.summary = 'build' @@ -396,6 +427,21 @@ class TestGemPackage < Gem::Package::TarTestCase "#{@destination} is not allowed", e.message) end + def test_extract_tar_gz_symlink_absolute + package = Gem::Package.new @gem + + tgz_io = util_tar_gz do |tar| + tar.add_symlink 'code.rb', '/absolute.rb', 0644 + end + + e = assert_raises Gem::Package::PathError do + package.extract_tar_gz tgz_io, @destination + end + + assert_equal("installing into parent path /absolute.rb of " + + "#{@destination} is not allowed", e.message) + end + def test_extract_tar_gz_directory package = Gem::Package.new @gem @@ -821,4 +867,3 @@ class TestGemPackage < Gem::Package::TarTestCase end end - diff --git a/test/rubygems/test_gem_package_tar_reader_entry.rb b/test/rubygems/test_gem_package_tar_reader_entry.rb index 11431870e4..335601b5ca 100644 --- a/test/rubygems/test_gem_package_tar_reader_entry.rb +++ b/test/rubygems/test_gem_package_tar_reader_entry.rb @@ -92,6 +92,14 @@ class TestGemPackageTarReaderEntry < Gem::Package::TarTestCase close_util_entry(dir_ent) if dir_ent end + def test_symlink_eh + assert_equal false, @entry.symlink? + symlink_ent = util_symlink_entry + assert_equal true, symlink_ent.symlink? + ensure + close_util_entry(symlink_ent) if symlink_ent + end + def test_file_eh assert_equal true, @entry.file? dir_ent = util_dir_entry @@ -131,4 +139,3 @@ class TestGemPackageTarReaderEntry < Gem::Package::TarTestCase end end - diff --git a/test/rubygems/test_gem_package_tar_writer.rb b/test/rubygems/test_gem_package_tar_writer.rb index f087df3fcf..dac9a54b46 100644 --- a/test/rubygems/test_gem_package_tar_writer.rb +++ b/test/rubygems/test_gem_package_tar_writer.rb @@ -30,6 +30,16 @@ class TestGemPackageTarWriter < Gem::Package::TarTestCase assert_equal 1024, @io.pos end + def test_add_symlink + Time.stub :now, Time.at(1458518157) do + @tar_writer.add_symlink 'x', 'y', 0644 + + assert_headers_equal(tar_symlink_header('x', '', 0644, Time.now, 'y'), + @io.string[0, 512]) + end + assert_equal 512, @io.pos + end + def test_add_file_digest digest_algorithms = Digest::SHA1, Digest::SHA512 @@ -251,4 +261,3 @@ class TestGemPackageTarWriter < Gem::Package::TarTestCase end end - diff --git a/test/rubygems/test_gem_request_connection_pools.rb b/test/rubygems/test_gem_request_connection_pools.rb index 1cf6b27979..403f7679f8 100644 --- a/test/rubygems/test_gem_request_connection_pools.rb +++ b/test/rubygems/test_gem_request_connection_pools.rb @@ -80,6 +80,15 @@ class TestGemRequestConnectionPool < Gem::TestCase assert_equal ['example', 80], net_http_args end + def test_net_http_args_ipv6 + pools = Gem::Request::ConnectionPools.new nil, [] + + net_http_args = pools.send :net_http_args, URI('http://[::1]'), nil + + expected_host = RUBY_VERSION >= "1.9.3" ? "::1" : "[::1]" + assert_equal [expected_host, 80], net_http_args + end + def test_net_http_args_proxy pools = Gem::Request::ConnectionPools.new nil, [] diff --git a/test/rubygems/test_gem_request_set.rb b/test/rubygems/test_gem_request_set.rb index 3c1d5ac1d1..8afab65356 100644 --- a/test/rubygems/test_gem_request_set.rb +++ b/test/rubygems/test_gem_request_set.rb @@ -141,10 +141,9 @@ Gems to install: def test_install_from_gemdeps_lockfile spec_fetcher do |fetcher| - fetcher.gem 'a', 1 - fetcher.gem 'a', 2 - fetcher.gem 'b', 1, 'a' => '>= 0' - fetcher.clear + fetcher.download 'a', 1 + fetcher.download 'a', 2 + fetcher.download 'b', 1, 'a' => '>= 0' end rs = Gem::RequestSet.new @@ -445,10 +444,8 @@ ruby "0" end spec_fetcher do |fetcher| - fetcher.gem "a", "1", "b" => "= 1" - fetcher.gem "b", "1" - - fetcher.clear + fetcher.download "a", "1", "b" => "= 1" + fetcher.download "b", "1" end rs = Gem::RequestSet.new diff --git a/test/rubygems/test_gem_request_set_lockfile.rb b/test/rubygems/test_gem_request_set_lockfile.rb index 6d5b4de581..3b85e256f2 100644 --- a/test/rubygems/test_gem_request_set_lockfile.rb +++ b/test/rubygems/test_gem_request_set_lockfile.rb @@ -338,13 +338,11 @@ DEPENDENCIES def test_to_s_gem_source spec_fetcher do |fetcher| - fetcher.spec 'a', 2 - fetcher.clear + fetcher.download 'a', 2 end spec_fetcher 'http://other.example/' do |fetcher| - fetcher.spec 'b', 2 - fetcher.clear + fetcher.download 'b', 2 end Gem.sources << 'http://other.example/' diff --git a/test/rubygems/test_gem_resolver.rb b/test/rubygems/test_gem_resolver.rb index 2b9e9fe137..c4b5251c82 100644 --- a/test/rubygems/test_gem_resolver.rb +++ b/test/rubygems/test_gem_resolver.rb @@ -95,24 +95,6 @@ class TestGemResolver < Gem::TestCase assert_same index_set, composed end - def test_handle_conflict - a1 = util_spec 'a', 1 - - r1 = Gem::Resolver::DependencyRequest.new dep('a', '= 1'), nil - r2 = Gem::Resolver::DependencyRequest.new dep('a', '= 2'), nil - r3 = Gem::Resolver::DependencyRequest.new dep('a', '= 3'), nil - - existing = Gem::Resolver::ActivationRequest.new a1, r1, false - - res = Gem::Resolver.new [a1] - - res.handle_conflict r2, existing - res.handle_conflict r2, existing - res.handle_conflict r3, existing - - assert_equal 2, res.conflicts.length - end - def test_requests a1 = util_spec 'a', 1, 'b' => 2 @@ -122,11 +104,11 @@ class TestGemResolver < Gem::TestCase res = Gem::Resolver.new [a1] - reqs = Gem::Resolver::RequirementList.new + reqs = [] res.requests a1, act, reqs - assert_equal ['b (= 2)'], reqs.to_a.map { |req| req.to_s } + assert_equal ['b (= 2)'], reqs.map { |req| req.to_s } end def test_requests_development @@ -144,11 +126,11 @@ class TestGemResolver < Gem::TestCase res = Gem::Resolver.new [act] res.development = true - reqs = Gem::Resolver::RequirementList.new + reqs = [] res.requests spec, act, reqs - assert_equal ['b (= 2)'], reqs.to_a.map { |req| req.to_s } + assert_equal ['b (= 2)'], reqs.map { |req| req.to_s } assert spec.instance_variable_defined? :@called end @@ -163,7 +145,7 @@ class TestGemResolver < Gem::TestCase res = Gem::Resolver.new [a1] res.ignore_dependencies = true - reqs = Gem::Resolver::RequirementList.new + reqs = [] res.requests a1, act, reqs @@ -438,19 +420,19 @@ class TestGemResolver < Gem::TestCase r.resolve end - deps = [make_dep("c", "= 1"), make_dep("c", "= 2")] + deps = [make_dep("c", "= 2"), make_dep("c", "= 1")] assert_equal deps, e.conflicting_dependencies con = e.conflict act = con.activated - assert_equal "c-2", act.spec.full_name + assert_equal "c-1", act.spec.full_name parent = act.parent - assert_equal "b-1", parent.spec.full_name + assert_equal "a-1", parent.spec.full_name act = con.requester - assert_equal "a-1", act.spec.full_name + assert_equal "b-1", act.spec.full_name end def test_raises_when_a_gem_is_missing @@ -538,11 +520,11 @@ class TestGemResolver < Gem::TestCase assert_equal req('>= 0'), dependency.requirement activated = e.conflict.activated - assert_equal 'c-1', activated.full_name + assert_equal 'c-2', activated.full_name - assert_equal dep('c', '= 1'), activated.request.dependency + assert_equal dep('c', '>= 2'), activated.request.dependency - assert_equal [dep('c', '>= 2'), dep('c', '= 1')], + assert_equal [dep('c', '= 1'), dep('c', '>= 2')], e.conflict.conflicting_dependencies end @@ -730,4 +712,3 @@ class TestGemResolver < Gem::TestCase end end - diff --git a/test/rubygems/test_gem_resolver_installer_set.rb b/test/rubygems/test_gem_resolver_installer_set.rb index d7b917b331..039582e690 100644 --- a/test/rubygems/test_gem_resolver_installer_set.rb +++ b/test/rubygems/test_gem_resolver_installer_set.rb @@ -4,9 +4,8 @@ class TestGemResolverInstallerSet < Gem::TestCase def test_add_always_install spec_fetcher do |fetcher| - fetcher.spec 'a', 1 - fetcher.spec 'a', 2 - fetcher.clear + fetcher.download 'a', 1 + fetcher.download 'a', 2 end util_gem 'a', 1 @@ -39,11 +38,10 @@ class TestGemResolverInstallerSet < Gem::TestCase def test_add_always_install_platform spec_fetcher do |fetcher| - fetcher.spec 'a', 1 - fetcher.spec 'a', 2 do |s| + fetcher.download 'a', 1 + fetcher.download 'a', 2 do |s| s.platform = Gem::Platform.new 'x86-freebsd-9' end - fetcher.clear end set = Gem::Resolver::InstallerSet.new :both @@ -139,8 +137,7 @@ class TestGemResolverInstallerSet < Gem::TestCase def test_find_all_always_install spec_fetcher do |fetcher| - fetcher.spec 'a', 2 - fetcher.clear + fetcher.download 'a', 2 end util_gem 'a', 1 @@ -156,9 +153,8 @@ class TestGemResolverInstallerSet < Gem::TestCase def test_find_all_prerelease spec_fetcher do |fetcher| - fetcher.spec 'a', '1' - fetcher.spec 'a', '1.a' - fetcher.clear + fetcher.download 'a', '1' + fetcher.download 'a', '1.a' end set = Gem::Resolver::InstallerSet.new :both diff --git a/test/rubygems/test_gem_resolver_lock_specification.rb b/test/rubygems/test_gem_resolver_lock_specification.rb index f8a336e658..93240356ef 100644 --- a/test/rubygems/test_gem_resolver_lock_specification.rb +++ b/test/rubygems/test_gem_resolver_lock_specification.rb @@ -1,4 +1,5 @@ require 'rubygems/test_case' +require 'rubygems/installer' require 'rubygems/resolver' class TestGemResolverLockSpecification < Gem::TestCase @@ -34,8 +35,7 @@ class TestGemResolverLockSpecification < Gem::TestCase def test_install spec_fetcher do |fetcher| - fetcher.gem 'a', 2 - fetcher.clear + fetcher.download 'a', 2 end spec = @LS.new @set, 'a', v(2), @source, Gem::Platform::RUBY @@ -85,6 +85,7 @@ class TestGemResolverLockSpecification < Gem::TestCase def test_spec_loaded real_spec = util_spec 'a', 2 + install_specs real_spec real_spec.activate version = v(2) diff --git a/test/rubygems/test_gem_specification.rb b/test/rubygems/test_gem_specification.rb index 27650837be..5784fa58af 100644 --- a/test/rubygems/test_gem_specification.rb +++ b/test/rubygems/test_gem_specification.rb @@ -1,8 +1,10 @@ +require 'benchmark' require 'rubygems/test_case' require 'pathname' require 'stringio' require 'rubygems/ext' require 'rubygems/specification' +require 'rubygems/installer' class TestGemSpecification < Gem::TestCase @@ -112,7 +114,7 @@ end c2 = new_spec "c", "2" Gem::Specification.reset - install_specs a1, b1, b2, c1, c2 + install_specs c1, c2, b1, b2, a1 a1.activate assert_equal %w(a-1), loaded_spec_names @@ -125,6 +127,32 @@ end end end + def test_find_in_unresolved_tree_is_not_exponentiental + save_loaded_features do + num_of_pkg = 7 + num_of_version_per_pkg = 3 + packages = (0..num_of_pkg).map do |pkgi| + (0..num_of_version_per_pkg).map do |pkg_version| + deps = Hash[((pkgi + 1)..num_of_pkg).map { |deppkgi| + ["pkg#{deppkgi}", ">= 0"] + }] + new_spec "pkg#{pkgi}", pkg_version.to_s, deps + end + end + base = new_spec "pkg_base", "1", {"pkg0" => ">= 0"} + + Gem::Specification.reset + install_specs(*packages.flatten.reverse) + install_specs base + base.activate + + tms = Benchmark.measure { + assert_raises(LoadError) { require 'no_such_file_foo' } + } + assert_operator tms.total, :<=, 10 + end + end + def test_self_activate_ambiguous_indirect save_loaded_features do a1 = new_spec "a", "1", "b" => "> 0" @@ -133,7 +161,7 @@ end c1 = new_spec "c", "1", nil, "lib/d.rb" c2 = new_spec "c", "2", nil, "lib/d.rb" - install_specs a1, b1, b2, c1, c2 + install_specs c1, c2, b1, b2, a1 a1.activate assert_equal %w(a-1), loaded_spec_names @@ -155,7 +183,7 @@ end c1 = new_spec "c", "1", nil, "lib/d.rb" c2 = new_spec("c", "2", { "a" => "1" }, "lib/d.rb") # conflicts with a-2 - install_specs a1, a2, b1, b2, c1, c2 + install_specs c1, b1, a1, a2, c2, b2 a2.activate assert_equal %w(a-2), loaded_spec_names @@ -177,7 +205,7 @@ end c2 = new_spec "c", "2" d1 = new_spec "d", "1", nil, "lib/d.rb" - install_specs a1, b1, b2, c1, c2, d1 + install_specs d1, c1, c2, b1, b2, a1 a1.activate assert_equal %w(a-1), loaded_spec_names @@ -190,6 +218,155 @@ end end end + def test_require_should_prefer_latest_gem_level1 + save_loaded_features do + a1 = new_spec "a", "1", "b" => "> 0" + b1 = new_spec "b", "1", "c" => ">= 0" # unresolved + b2 = new_spec "b", "2", "c" => ">= 0" + c1 = new_spec "c", "1", nil, "lib/c.rb" # 1st level + c2 = new_spec "c", "2", nil, "lib/c.rb" + + install_specs c1, c2, b1, b2, a1 + + a1.activate + + require "c" + + assert_equal %w(a-1 b-2 c-2), loaded_spec_names + end + end + + def test_require_should_prefer_latest_gem_level2 + save_loaded_features do + a1 = new_spec "a", "1", "b" => "> 0" + b1 = new_spec "b", "1", "c" => ">= 0" # unresolved + b2 = new_spec "b", "2", "c" => ">= 0" + c1 = new_spec "c", "1", "d" => ">= 0" # 1st level + c2 = new_spec "c", "2", "d" => ">= 0" + d1 = new_spec "d", "1", nil, "lib/d.rb" # 2nd level + d2 = new_spec "d", "2", nil, "lib/d.rb" + + install_specs d1, d2, c1, c2, b1, b2, a1 + + a1.activate + + require "d" + + assert_equal %w(a-1 b-2 c-2 d-2), loaded_spec_names + end + end + + def test_require_finds_in_2nd_level_indirect + save_loaded_features do + a1 = new_spec "a", "1", "b" => "> 0" + b1 = new_spec "b", "1", "c" => ">= 0" # unresolved + b2 = new_spec "b", "2", "c" => ">= 0" + c1 = new_spec "c", "1", "d" => "<= 2" # 1st level + c2 = new_spec "c", "2", "d" => "<= 2" + d1 = new_spec "d", "1", nil, "lib/d.rb" # 2nd level + d2 = new_spec "d", "2", nil, "lib/d.rb" + d3 = new_spec "d", "3", nil, "lib/d.rb" + + install_specs d1, d2, d3, c1, c2, b1, b2, a1 + + a1.activate + + require "d" + + assert_equal %w(a-1 b-2 c-2 d-2), loaded_spec_names + end + end + + def test_require_should_prefer_reachable_gems + save_loaded_features do + a1 = new_spec "a", "1", "b" => "> 0" + b1 = new_spec "b", "1", "c" => ">= 0" # unresolved + b2 = new_spec "b", "2", "c" => ">= 0" + c1 = new_spec "c", "1", "d" => "<= 2" # 1st level + c2 = new_spec "c", "2", "d" => "<= 2" + d1 = new_spec "d", "1", nil, "lib/d.rb" # 2nd level + d2 = new_spec "d", "2", nil, "lib/d.rb" + d3 = new_spec "d", "3", nil, "lib/d.rb" + e = new_spec "anti_d", "1", nil, "lib/d.rb" + + install_specs d1, d2, d3, e, c1, c2, b1, b2, a1 + + a1.activate + + require "d" + + assert_equal %w(a-1 b-2 c-2 d-2), loaded_spec_names + end + end + + def test_require_should_not_conflict + save_loaded_features do + base = new_spec "0", "1", "A" => ">= 1" + a1 = new_spec "A", "1", {"c" => ">= 2", "b" => "> 0"}, "lib/a.rb" + a2 = new_spec "A", "2", {"c" => ">= 2", "b" => "> 0"}, "lib/a.rb" + b1 = new_spec "b", "1", {"c" => "= 1"}, "lib/d.rb" + b2 = new_spec "b", "2", {"c" => "= 2"}, "lib/d.rb" + c1 = new_spec "c", "1", {}, "lib/c.rb" + c2 = new_spec "c", "2", {}, "lib/c.rb" + c3 = new_spec "c", "3", {}, "lib/c.rb" + + install_specs c1, c2, c3, b1, b2, a1, a2, base + + base.activate + assert_equal %w(0-1), loaded_spec_names + assert_equal ["A (>= 1)"], unresolved_names + + require "d" + + assert_equal %w(0-1 A-2 b-2 c-2), loaded_spec_names + assert_equal [], unresolved_names + end + end + + def test_inner_clonflict_in_indirect_gems + save_loaded_features do + a1 = new_spec "a", "1", "b" => "> 0" + b1 = new_spec "b", "1", "c" => ">= 1" # unresolved + b2 = new_spec "b", "2", "c" => ">= 1", "d" => "< 3" + c1 = new_spec "c", "1", "d" => "<= 2" # 1st level + c2 = new_spec "c", "2", "d" => "<= 2" + c3 = new_spec "c", "3", "d" => "<= 3" + d1 = new_spec "d", "1", nil, "lib/d.rb" # 2nd level + d2 = new_spec "d", "2", nil, "lib/d.rb" + d3 = new_spec "d", "3", nil, "lib/d.rb" + + install_specs d1, d2, d3, c1, c2, c3, b1, b2, a1 + + a1.activate + + require "d" + + assert_includes [%w(a-1 b-2 c-3 d-2),%w(a-1 b-2 d-2)], loaded_spec_names + end + end + + def test_inner_clonflict_in_indirect_gems_reversed + save_loaded_features do + a1 = new_spec "a", "1", "b" => "> 0" + b1 = new_spec "b", "1", "xc" => ">= 1" # unresolved + b2 = new_spec "b", "2", "xc" => ">= 1", "d" => "< 3" + c1 = new_spec "xc", "1", "d" => "<= 3" # 1st level + c2 = new_spec "xc", "2", "d" => "<= 2" + c3 = new_spec "xc", "3", "d" => "<= 3" + d1 = new_spec "d", "1", nil, "lib/d.rb" # 2nd level + d2 = new_spec "d", "2", nil, "lib/d.rb" + d3 = new_spec "d", "3", nil, "lib/d.rb" + + install_specs d1, d2, d3, c1, c2, c3, b1, b2, a1 + + a1.activate + + require "d" + + assert_includes [%w(a-1 b-2 d-2 xc-3), %w(a-1 b-2 d-2)], loaded_spec_names + end + end + ## # [A] depends on # [C] = 1.0 depends on @@ -197,13 +374,14 @@ end # [B] ~> 1.0 (satisfied by 1.0) def test_self_activate_checks_dependencies - a, _ = util_spec 'a', '1.0' + a = util_spec 'a', '1.0' a.add_dependency 'c', '= 1.0' a.add_dependency 'b', '~> 1.0' - util_spec 'b', '1.0' - util_spec 'b', '2.0' - c, _ = util_spec 'c', '1.0', 'b' => '= 2.0' + b1 = util_spec 'b', '1.0' + b2 = util_spec 'b', '2.0' + c = util_spec 'c', '1.0', 'b' => '= 2.0' + install_specs b1, b2, c, a e = assert_raises Gem::LoadError do assert_activate nil, a, c, "b" @@ -220,10 +398,12 @@ end # [B] = 2.0 def test_self_activate_divergent - a, _ = util_spec 'a', '1.0', 'b' => '~> 1.0', 'c' => '= 1.0' - util_spec 'b', '1.0' - util_spec 'b', '2.0' - c, _ = util_spec 'c', '1.0', 'b' => '= 2.0' + a = util_spec 'a', '1.0', 'b' => '~> 1.0', 'c' => '= 1.0' + b1 = util_spec 'b', '1.0' + b2 = util_spec 'b', '2.0' + c = util_spec 'c', '1.0', 'b' => '= 2.0' + + install_specs b1, b2, c, a e = assert_raises Gem::ConflictError do assert_activate nil, a, c, "b" @@ -241,6 +421,8 @@ end @d1 = util_spec 'd', '1' @d2 = util_spec 'd', '2' + install_specs @d1, @d2, e1 + assert_activate %w[d-1 e-1], e1, "d" end @@ -259,6 +441,7 @@ end @w1 = util_spec 'w', '1', 'x' => nil util_set_arch 'cpu-my_platform1' + install_specs @x1_m, @x1_o, @w1 assert_activate %w[x-1-cpu-my_platform-1 w-1], @w1, @x1_m end @@ -274,6 +457,7 @@ end end @z1 = util_spec 'z', '1', 'y' => nil + install_specs @y1, @y1_1_p, @z1 assert_activate %w[y-1 z-1], @z1, @y1 end @@ -291,6 +475,7 @@ end s.add_dependency 'a' s.add_development_dependency 'aa' end + install_specs @a1_pre, @b1, @c1_pre assert_activate %w[a-1.a b-1 c-1.a], @c1_pre, @a1_pre, @b1 end @@ -300,7 +485,7 @@ end b1 = new_spec "b", "1", nil, "lib/b/c.rb" b2 = new_spec "b", "2", nil, "lib/b/c.rb" - install_specs a1, b1, b2 + install_specs b1, b2, a1 a1.activate save_loaded_features do @@ -320,7 +505,7 @@ end d1 = new_spec "d", "1", { "c" => "< 2" }, "lib/d.rb" d2 = new_spec "d", "2", { "c" => "< 2" }, "lib/d.rb" # this - install_specs a1, b1, b2, c1, c2, d1, d2 + install_specs c1, c2, b1, b2, d1, d2, a1 a1.activate @@ -347,7 +532,7 @@ end c1 = new_spec "c", "1" c2 = new_spec "c", "2" - install_specs a1, b1, b2, c1, c2 + install_specs c1, c2, b1, b2, a1 a1.activate assert_equal %w(a-1 b-1 c-1), loaded_spec_names @@ -367,8 +552,9 @@ end def test_self_activate_unrelated a = util_spec 'a', '1.0', 'b' => '>= 1.0' - util_spec 'b', '1.0' + b = util_spec 'b', '1.0' c = util_spec 'c', '1.0' + install_specs b, c, a assert_activate %w[b-1.0 c-1.0 a-1.0], a, c, "b" end @@ -384,10 +570,11 @@ end def test_self_activate_over a = util_spec 'a', '1.0', 'b' => '>= 1.0', 'c' => '= 1.0' - util_spec 'b', '1.0' - util_spec 'b', '1.1' - util_spec 'b', '2.0' - util_spec 'c', '1.0', 'b' => '~> 1.0' + install_specs util_spec 'b', '1.0' + install_specs util_spec 'b', '1.1' + install_specs util_spec 'b', '2.0' + install_specs util_spec 'c', '1.0', 'b' => '~> 1.0' + install_specs a a.activate @@ -407,10 +594,12 @@ end # first resolve through a dependency that is later pruned. def test_self_activate_under - a, _ = util_spec 'a', '1.0', 'b' => '~> 1.0', 'c' => '= 1.0' - util_spec 'b', '1.0' - util_spec 'b', '1.1' - c, _ = util_spec 'c', '1.0', 'b' => '= 1.0' + a = util_spec 'a', '1.0', 'b' => '~> 1.0', 'c' => '= 1.0' + b1 = util_spec 'b', '1.0' + b1_1 = util_spec 'b', '1.1' + c = util_spec 'c', '1.0', 'b' => '= 1.0' + + install_specs b1, b1_1, c, a assert_activate %w[b-1.0 c-1.0 a-1.0], a, c, "b" end @@ -424,10 +613,11 @@ end # [C1] depends on nothing def test_self_activate_dropped - a1, = util_spec 'a', '1', 'b' => nil - util_spec 'b', '1', 'c' => nil - util_spec 'b', '2' - util_spec 'c', '1' + a1 = util_spec 'a', '1', 'b' => nil + b1 = util_spec 'b', '1', 'c' => nil + b2 = util_spec 'b', '2' + c1 = util_spec 'c', '1' + install_specs c1, b1, b2, a1 assert_activate %w[b-2 a-1], a1, "b" end @@ -443,17 +633,20 @@ end # resolve. def test_self_activate_raggi_the_edgecase_generator - a, _ = util_spec 'a', '1.0', 'b' => '>= 1.0', 'c' => '>= 1.0' - util_spec 'b', '1.0' - util_spec 'b', '1.1', 'z' => '>= 1.0' - c, _ = util_spec 'c', '1.0', 'b' => '= 1.0' + a = util_spec 'a', '1.0', 'b' => '>= 1.0', 'c' => '>= 1.0' + b1 = util_spec 'b', '1.0' + b1_0 = util_spec 'b', '1.1', 'z' => '>= 1.0' + c = util_spec 'c', '1.0', 'b' => '= 1.0' + z = util_spec 'z', '1' + + install_specs z, b1, b1_0, c, z assert_activate %w[b-1.0 c-1.0 a-1.0], a, c, "b" end def test_self_activate_conflict - util_spec 'b', '1.0' - util_spec 'b', '2.0' + install_specs util_spec 'b', '1.0' + install_specs util_spec 'b', '2.0' gem "b", "= 1.0" @@ -465,6 +658,7 @@ end def test_self_all_equals a = new_spec "foo", "1", nil, "lib/foo.rb" + install_specs a Gem::Specification.all = [a] assert_equal a, Gem::Specification.find_inactive_by_path('foo') @@ -854,9 +1048,7 @@ dependencies: [] def test_self_outdated spec_fetcher do |fetcher| - fetcher.spec 'a', 4 - - fetcher.clear + fetcher.download 'a', 4 fetcher.spec 'a', 3 end @@ -866,10 +1058,8 @@ dependencies: [] def test_self_outdated_and_latest_remotes specs = spec_fetcher do |fetcher| - fetcher.spec 'a', 4 - fetcher.spec 'b', 3 - - fetcher.clear + fetcher.download 'a', 4 + fetcher.download 'b', 3 fetcher.spec 'a', '3.a' fetcher.spec 'b', 2 @@ -884,10 +1074,13 @@ dependencies: [] end def test_self_remove_spec + install_specs @a1 + assert_includes Gem::Specification.all_names, 'a-1' assert_includes Gem::Specification.stubs.map { |s| s.full_name }, 'a-1' - Gem::Specification.remove_spec @a1 + uninstall_gem @a1 + Gem::Specification.reset refute_includes Gem::Specification.all_names, 'a-1' refute_includes Gem::Specification.stubs.map { |s| s.full_name }, 'a-1' @@ -902,7 +1095,7 @@ dependencies: [] FileUtils.rm @a1.spec_file # bug #698 - Gem::Specification.remove_spec @a1 + Gem::Specification.reset refute_includes Gem::Specification.all_names, 'a-1' refute_includes Gem::Specification.stubs.map { |s| s.full_name }, 'a-1' @@ -1183,6 +1376,17 @@ dependencies: [] assert_path_exists @ext.extension_dir end + def test_default_spec_stub_is_marked_default + default = new_default_spec 'default', 2 + install_default_gems default + + stub = Gem::Specification.stubs.find { |s| s.name == 'default' } + assert_predicate stub, :default_gem? + + stub = Gem::Specification.find_all_by_name('default').first + assert_predicate stub, :default_gem? + end + def test_build_extensions_built ext_spec @@ -1419,7 +1623,9 @@ dependencies: [] assert_empty @gem.dependent_gems - bonobo = util_spec 'bonobo' + bonobo = util_spec 'bonobo', 1 + install_gem bonobo + install_gem @gem expected = [ [@gem, @bonobo, [bonobo]], @@ -1932,7 +2138,7 @@ dependencies: [] c1 = new_spec "c", "1", nil, "lib/d.rb" c2 = new_spec("c", "2", { "a" => "1" }, "lib/d.rb") # conflicts with a-2 - install_specs a1, a2, b1, b2, c1, c2 + install_specs c1, b1, a1, a2, c2, b2 a1.activate c1.activate @@ -2014,6 +2220,19 @@ dependencies: [] assert_equal ['test/suite.rb'], @a1.test_files end + def test_runtime_predicate_true + @a2.add_runtime_dependency 'b', '1' + assert_predicate @a2.dependencies.first, :runtime? + + @a2.dependencies.first.instance_variable_set :@type, nil + assert_predicate @a2.dependencies.first, :runtime? + end + + def test_runtime_predicate_false + @a2.add_development_dependency 'b', '1' + refute_predicate @a2.dependencies.first, :runtime? + end + def test_to_ruby @a2.add_runtime_dependency 'b', '1' @a2.dependencies.first.instance_variable_set :@type, nil @@ -2533,13 +2752,13 @@ duplicate dependency on b (>= 1.2.3), (~> 1.2) use: @a1.extensions << 'ext/a/extconf.rb' Dir.chdir @tempdir do - FileUtils.ln_s '/root/path', 'lib2' unless vc_windows? + FileUtils.ln_s 'lib/code.rb', 'lib2' unless vc_windows? - e = assert_raises Gem::InvalidSpecificationException do + use_ui @ui do @a1.validate end - assert_equal '["lib2"] are not files', e.message + assert_match 'WARNING: lib2 is a symlink, which is not supported on all platforms', @ui.error end assert_equal %w[bin/exec ext/a/extconf.rb lib/code.rb lib2 test/suite.rb].sort, @@ -2656,6 +2875,20 @@ http://opensource.org/licenses/alphabetical end end + def test_validate_permissions_of_missing_file_non_packaging + skip 'chmod not supported' if Gem.win_platform? + + util_setup_validate + + Dir.chdir @tempdir do + File.delete File.join('lib', 'code.rb') + + use_ui @ui do + assert @a1.validate(false) + end + end + end + def test_validate_platform_legacy util_setup_validate @@ -2992,7 +3225,8 @@ end end def test_find_by_name - util_spec "a" + install_specs util_spec "a" + install_specs util_spec "a", 1 assert Gem::Specification.find_by_name "a" assert Gem::Specification.find_by_name "a", "1" @@ -3008,6 +3242,8 @@ end b.activate + install_specs b + assert Gem::Specification.find_by_name "b" assert_raises Gem::LoadError do diff --git a/test/rubygems/test_gem_stub_specification.rb b/test/rubygems/test_gem_stub_specification.rb index 9d273125a5..e1ab8342dd 100644 --- a/test/rubygems/test_gem_stub_specification.rb +++ b/test/rubygems/test_gem_stub_specification.rb @@ -9,7 +9,7 @@ class TestStubSpecification < Gem::TestCase def setup super - @foo = Gem::StubSpecification.new FOO + @foo = Gem::StubSpecification.gemspec_stub FOO end def test_initialize @@ -31,7 +31,7 @@ class TestStubSpecification < Gem::TestCase end def test_initialize_missing_stubline - stub = Gem::StubSpecification.new(BAR) + stub = Gem::StubSpecification.gemspec_stub(BAR) assert_equal "bar", stub.name assert_equal Gem::Version.new("0.0.2"), stub.version assert_equal Gem::Platform.new("ruby"), stub.platform @@ -72,6 +72,21 @@ class TestStubSpecification < Gem::TestCase assert_equal expected, stub.full_require_paths end + def test_lib_dirs_glob + stub = stub_without_extension + + assert_equal File.join(stub.full_gem_path, 'lib'), stub.lib_dirs_glob + end + + def test_matches_for_glob + stub = stub_without_extension + code_rb = File.join stub.gem_dir, 'lib', 'code.rb' + FileUtils.mkdir_p File.dirname code_rb + FileUtils.touch code_rb + + assert_equal code_rb, stub.matches_for_glob('code*').first + end + def test_missing_extensions_eh stub = stub_with_extension do |s| extconf_rb = File.join s.gem_dir, s.extensions.first @@ -103,7 +118,7 @@ class TestStubSpecification < Gem::TestCase io.write spec.to_ruby_for_cache end - default_spec = Gem::StubSpecification.new spec.loaded_from + default_spec = Gem::StubSpecification.gemspec_stub spec.loaded_from refute default_spec.missing_extensions? end @@ -125,7 +140,7 @@ class TestStubSpecification < Gem::TestCase def test_to_spec_with_other_specs_loaded_does_not_warn real_foo = util_spec @foo.name, @foo.version real_foo.activate - bar = Gem::StubSpecification.new BAR + bar = Gem::StubSpecification.gemspec_stub BAR refute_predicate Gem.loaded_specs, :empty? assert bar.to_spec end @@ -164,7 +179,7 @@ end io.flush - stub = Gem::StubSpecification.new io.path + stub = Gem::StubSpecification.gemspec_stub io.path yield stub if block_given? @@ -187,7 +202,7 @@ end io.flush - stub = Gem::StubSpecification.new io.path + stub = Gem::StubSpecification.gemspec_stub io.path yield stub if block_given? diff --git a/test/rubygems/test_gem_uninstaller.rb b/test/rubygems/test_gem_uninstaller.rb index 774fbe5ae8..c682f72c38 100644 --- a/test/rubygems/test_gem_uninstaller.rb +++ b/test/rubygems/test_gem_uninstaller.rb @@ -158,6 +158,7 @@ class TestGemUninstaller < Gem::InstallerTestCase uninstaller = Gem::Uninstaller.new nil @spec.loaded_from = @spec.loaded_from.gsub @spec.full_name, '\&-legacy' + @spec.internal_init # blow out cache. but why did ^^ depend on cache? @spec.platform = 'legacy' assert_equal true, uninstaller.path_ok?(@gemhome, @spec) @@ -236,7 +237,7 @@ create_makefile '#{@spec.name}' use_ui @ui do path = Gem::Package.build @spec - installer = Gem::Installer.new path + installer = Gem::Installer.at path installer.install end diff --git a/test/rubygems/test_gem_util.rb b/test/rubygems/test_gem_util.rb index 414487acc0..2546262d7f 100644 --- a/test/rubygems/test_gem_util.rb +++ b/test/rubygems/test_gem_util.rb @@ -27,5 +27,13 @@ class TestGemUtil < Gem::TestCase assert_equal File.join(@tempdir, 'a'), enum.next end + def test_linked_list_find + list = [1,2,3,4,5].inject(Gem::List.new(0)) { |m,o| + Gem::List.new o, m + } + assert_equal 5, list.find { |x| x == 5 } + assert_equal 4, list.find { |x| x == 4 } + end + end diff --git a/test/rubygems/test_require.rb b/test/rubygems/test_require.rb index dec5285d21..96906595d7 100644 --- a/test/rubygems/test_require.rb +++ b/test/rubygems/test_require.rb @@ -26,11 +26,11 @@ class TestGemRequire < Gem::TestCase def setup super + @old_loaded_features = $LOADED_FEATURES.dup assert_raises LoadError do - save_loaded_features do - require 'test_gem_require_a' - end + require 'test_gem_require_a' end + $LOADED_FEATURES.replace @old_loaded_features end def assert_require(path) @@ -49,6 +49,36 @@ class TestGemRequire < Gem::TestCase end end + # Providing -I on the commandline should always beat gems + def test_dash_i_beats_gems + a1 = new_spec "a", "1", {"b" => "= 1"}, "lib/test_gem_require_a.rb" + b1 = new_spec "b", "1", {"c" => "> 0"}, "lib/b/c.rb" + c1 = new_spec "c", "1", nil, "lib/c/c.rb" + c2 = new_spec "c", "2", nil, "lib/c/c.rb" + + install_specs c1, c2, b1, a1 + + dir = Dir.mktmpdir + dash_i_arg = File.join dir, 'lib' + + c_rb = File.join dash_i_arg, 'b', 'c.rb' + + FileUtils.mkdir_p File.dirname c_rb + File.open(c_rb, 'w') { |f| f.write "class Object; HELLO = 'world' end" } + + lp = $LOAD_PATH.dup + + # Pretend to provide a commandline argument that overrides a file in gem b + $LOAD_PATH.unshift dash_i_arg + + assert_require 'test_gem_require_a' + assert_require 'b/c' # this should be required from -I + assert_equal "world", ::Object::HELLO + ensure + $LOAD_PATH.replace lp + Object.send :remove_const, :HELLO if Object.const_defined? :HELLO + end + def test_concurrent_require Object.const_set :FILE_ENTERED_LATCH, Latch.new(2) Object.const_set :FILE_EXIT_LATCH, Latch.new(1) @@ -82,16 +112,14 @@ class TestGemRequire < Gem::TestCase b1 = new_spec "b", "1", nil, "lib/b/c.rb" b2 = new_spec "b", "2", nil, "lib/b/c.rb" - install_specs a1, b1, b2 + install_specs b1, b2, a1 - save_loaded_features do - assert_require 'test_gem_require_a' - assert_equal %w(a-1 b-1), loaded_spec_names - assert_equal unresolved_names, [] + assert_require 'test_gem_require_a' + assert_equal %w(a-1 b-1), loaded_spec_names + assert_equal unresolved_names, [] - assert_require "b/c" - assert_equal %w(a-1 b-1), loaded_spec_names - end + assert_require "b/c" + assert_equal %w(a-1 b-1), loaded_spec_names end def test_require_is_lazy_with_inexact_req @@ -99,32 +127,28 @@ class TestGemRequire < Gem::TestCase b1 = new_spec "b", "1", nil, "lib/b/c.rb" b2 = new_spec "b", "2", nil, "lib/b/c.rb" - install_specs a1, b1, b2 + install_specs b1, b2, a1 - save_loaded_features do - assert_require 'test_gem_require_a' - assert_equal %w(a-1), loaded_spec_names - assert_equal unresolved_names, ["b (>= 1)"] + assert_require 'test_gem_require_a' + assert_equal %w(a-1), loaded_spec_names + assert_equal unresolved_names, ["b (>= 1)"] - assert_require "b/c" - assert_equal %w(a-1 b-2), loaded_spec_names - end + assert_require "b/c" + assert_equal %w(a-1 b-2), loaded_spec_names end def test_require_is_not_lazy_with_one_possible a1 = new_spec "a", "1", {"b" => ">= 1"}, "lib/test_gem_require_a.rb" b1 = new_spec "b", "1", nil, "lib/b/c.rb" - install_specs a1, b1 + install_specs b1, a1 - save_loaded_features do - assert_require 'test_gem_require_a' - assert_equal %w(a-1 b-1), loaded_spec_names - assert_equal unresolved_names, [] + assert_require 'test_gem_require_a' + assert_equal %w(a-1 b-1), loaded_spec_names + assert_equal unresolved_names, [] - assert_require "b/c" - assert_equal %w(a-1 b-1), loaded_spec_names - end + assert_require "b/c" + assert_equal %w(a-1 b-1), loaded_spec_names end def test_require_can_use_a_pathname_object @@ -132,129 +156,114 @@ class TestGemRequire < Gem::TestCase install_specs a1 - save_loaded_features do - assert_require Pathname.new 'test_gem_require_a' - assert_equal %w(a-1), loaded_spec_names - assert_equal unresolved_names, [] - end + assert_require Pathname.new 'test_gem_require_a' + assert_equal %w(a-1), loaded_spec_names + assert_equal unresolved_names, [] end def test_activate_via_require_respects_loaded_files - require 'benchmark' # stdlib - save_loaded_features do - a1 = new_spec "a", "1", {"b" => ">= 1"}, "lib/test_gem_require_a.rb" - b1 = new_spec "b", "1", nil, "lib/benchmark.rb" - b2 = new_spec "b", "2", nil, "lib/benchmark.rb" + a1 = new_spec "a", "1", {"b" => ">= 1"}, "lib/test_gem_require_a.rb" + b1 = new_spec "b", "1", nil, "lib/benchmark.rb" + b2 = new_spec "b", "2", nil, "lib/benchmark.rb" - install_specs a1, b1, b2 + install_specs b1, b2, a1 - require 'test_gem_require_a' - assert_equal unresolved_names, ["b (>= 1)"] + require 'test_gem_require_a' + assert_equal unresolved_names, ["b (>= 1)"] - refute require('benchmark'), "benchmark should have already been loaded" + refute require('benchmark'), "benchmark should have already been loaded" - # We detected that we should activate b-2, so we did so, but - # then original_require decided "I've already got benchmark.rb" loaded. - # This case is fine because our lazy loading is provided exactly - # the same behavior as eager loading would have. + # We detected that we should activate b-2, so we did so, but + # then original_require decided "I've already got benchmark.rb" loaded. + # This case is fine because our lazy loading is provided exactly + # the same behavior as eager loading would have. - assert_equal %w(a-1 b-2), loaded_spec_names - end + assert_equal %w(a-1 b-2), loaded_spec_names end def test_already_activated_direct_conflict - save_loaded_features do - a1 = new_spec "a", "1", { "b" => "> 0" } - b1 = new_spec "b", "1", { "c" => ">= 1" }, "lib/ib.rb" - b2 = new_spec "b", "2", { "c" => ">= 2" }, "lib/ib.rb" - c1 = new_spec "c", "1", nil, "lib/d.rb" - c2 = new_spec("c", "2", nil, "lib/d.rb") + a1 = new_spec "a", "1", { "b" => "> 0" } + b1 = new_spec "b", "1", { "c" => ">= 1" }, "lib/ib.rb" + b2 = new_spec "b", "2", { "c" => ">= 2" }, "lib/ib.rb" + c1 = new_spec "c", "1", nil, "lib/d.rb" + c2 = new_spec("c", "2", nil, "lib/d.rb") - install_specs a1, b1, b2, c1, c2 + install_specs c1, c2, b1, b2, a1 - a1.activate - c1.activate - assert_equal %w(a-1 c-1), loaded_spec_names - assert_equal ["b (> 0)"], unresolved_names + a1.activate + c1.activate + assert_equal %w(a-1 c-1), loaded_spec_names + assert_equal ["b (> 0)"], unresolved_names - assert require("ib") + assert require("ib") - assert_equal %w(a-1 b-1 c-1), loaded_spec_names - assert_equal [], unresolved_names - end + assert_equal %w(a-1 b-1 c-1), loaded_spec_names + assert_equal [], unresolved_names end def test_multiple_gems_with_the_same_path - save_loaded_features do - a1 = new_spec "a", "1", { "b" => "> 0", "x" => "> 0" } - b1 = new_spec "b", "1", { "c" => ">= 1" }, "lib/ib.rb" - b2 = new_spec "b", "2", { "c" => ">= 2" }, "lib/ib.rb" - x1 = new_spec "x", "1", nil, "lib/ib.rb" - x2 = new_spec "x", "2", nil, "lib/ib.rb" - c1 = new_spec "c", "1", nil, "lib/d.rb" - c2 = new_spec("c", "2", nil, "lib/d.rb") - - install_specs a1, b1, b2, c1, c2, x1, x2 - - a1.activate - c1.activate - assert_equal %w(a-1 c-1), loaded_spec_names - assert_equal ["b (> 0)", "x (> 0)"], unresolved_names - - e = assert_raises(Gem::LoadError) do - require("ib") - end - - assert_equal "ib found in multiple gems: b, x", e.message + a1 = new_spec "a", "1", { "b" => "> 0", "x" => "> 0" } + b1 = new_spec "b", "1", { "c" => ">= 1" }, "lib/ib.rb" + b2 = new_spec "b", "2", { "c" => ">= 2" }, "lib/ib.rb" + x1 = new_spec "x", "1", nil, "lib/ib.rb" + x2 = new_spec "x", "2", nil, "lib/ib.rb" + c1 = new_spec "c", "1", nil, "lib/d.rb" + c2 = new_spec("c", "2", nil, "lib/d.rb") + + install_specs c1, c2, x1, x2, b1, b2, a1 + + a1.activate + c1.activate + assert_equal %w(a-1 c-1), loaded_spec_names + assert_equal ["b (> 0)", "x (> 0)"], unresolved_names + + e = assert_raises(Gem::LoadError) do + require("ib") end + + assert_equal "ib found in multiple gems: b, x", e.message end def test_unable_to_find_good_unresolved_version - save_loaded_features do - a1 = new_spec "a", "1", { "b" => "> 0" } - b1 = new_spec "b", "1", { "c" => ">= 2" }, "lib/ib.rb" - b2 = new_spec "b", "2", { "c" => ">= 3" }, "lib/ib.rb" + a1 = new_spec "a", "1", { "b" => "> 0" } + b1 = new_spec "b", "1", { "c" => ">= 2" }, "lib/ib.rb" + b2 = new_spec "b", "2", { "c" => ">= 3" }, "lib/ib.rb" - c1 = new_spec "c", "1", nil, "lib/d.rb" - c2 = new_spec "c", "2", nil, "lib/d.rb" - c3 = new_spec "c", "3", nil, "lib/d.rb" + c1 = new_spec "c", "1", nil, "lib/d.rb" + c2 = new_spec "c", "2", nil, "lib/d.rb" + c3 = new_spec "c", "3", nil, "lib/d.rb" - install_specs a1, b1, b2, c1, c2, c3 + install_specs c1, c2, c3, b1, b2, a1 - a1.activate - c1.activate - assert_equal %w(a-1 c-1), loaded_spec_names - assert_equal ["b (> 0)"], unresolved_names + a1.activate + c1.activate + assert_equal %w(a-1 c-1), loaded_spec_names + assert_equal ["b (> 0)"], unresolved_names - e = assert_raises(Gem::LoadError) do - require("ib") - end - - assert_equal "unable to find a version of 'b' to activate", e.message + e = assert_raises(Gem::LoadError) do + require("ib") end + + assert_equal "unable to find a version of 'b' to activate", e.message end def test_default_gem_only - save_loaded_features do - default_gem_spec = new_default_spec("default", "2.0.0.0", - nil, "default/gem.rb") - install_default_specs(default_gem_spec) - assert_require "default/gem" - assert_equal %w(default-2.0.0.0), loaded_spec_names - end + default_gem_spec = new_default_spec("default", "2.0.0.0", + nil, "default/gem.rb") + install_default_specs(default_gem_spec) + assert_require "default/gem" + assert_equal %w(default-2.0.0.0), loaded_spec_names end def test_default_gem_and_normal_gem - save_loaded_features do - default_gem_spec = new_default_spec("default", "2.0.0.0", - nil, "default/gem.rb") - install_default_specs(default_gem_spec) - normal_gem_spec = new_spec("default", "3.0", nil, - "lib/default/gem.rb") - install_specs(normal_gem_spec) - assert_require "default/gem" - assert_equal %w(default-3.0), loaded_spec_names - end + default_gem_spec = new_default_spec("default", "2.0.0.0", + nil, "default/gem.rb") + install_default_specs(default_gem_spec) + normal_gem_spec = new_spec("default", "3.0", nil, + "lib/default/gem.rb") + install_specs(normal_gem_spec) + assert_require "default/gem" + assert_equal %w(default-3.0), loaded_spec_names end def loaded_spec_names @@ -264,12 +273,4 @@ class TestGemRequire < Gem::TestCase def unresolved_names Gem::Specification.unresolved_deps.values.map(&:to_s).sort end - - def save_loaded_features - old_loaded_features = $LOADED_FEATURES.dup - yield - ensure - $LOADED_FEATURES.replace old_loaded_features - end - end -- cgit v1.2.3