diff options
75 files changed, 3139 insertions, 612 deletions
@@ -1,3 +1,18 @@ +Mon Nov 11 02:51:17 2013 Eric Hodel <[email protected]> + + * lib/rubygems: Update to RubyGems master 4bdc4f2. Important changes + in this commit: + + RubyGems now chooses the test server port reliably. Patch by akr. + + Partial implementation of bundler's Gemfile format. + + Refactorings to improve the new resolver. + + Fixes bugs in the resolver. + + * test/rubygems: Tests for the above. + Mon Nov 11 01:02:06 2013 Zachary Scott <[email protected]> * lib/timeout.rb: [DOC] Add note about change from #8730 [Fixes GH-440] diff --git a/lib/rubygems.rb b/lib/rubygems.rb index 1c84356bd7..1c8acda74d 100644 --- a/lib/rubygems.rb +++ b/lib/rubygems.rb @@ -8,7 +8,7 @@ require 'rbconfig' module Gem - VERSION = '2.2.0' + VERSION = '2.2.0.preview.2' end # Must be first since it unloads the prelude from 1.9.2 diff --git a/lib/rubygems/basic_specification.rb b/lib/rubygems/basic_specification.rb index 7d0469000b..a29ed0aa6d 100644 --- a/lib/rubygems/basic_specification.rb +++ b/lib/rubygems/basic_specification.rb @@ -196,5 +196,13 @@ class Gem::BasicSpecification raise NotImplementedError end + ## + # Whether this specification is stubbed - i.e. we have information + # about the gem from a stub line, without having to evaluate the + # entire gemspec file. + def stubbed? + raise NotImplementedError + end + end diff --git a/lib/rubygems/commands/install_command.rb b/lib/rubygems/commands/install_command.rb index 1f3210ff5d..ad90b37fdc 100644 --- a/lib/rubygems/commands/install_command.rb +++ b/lib/rubygems/commands/install_command.rb @@ -22,6 +22,7 @@ class Gem::Commands::InstallCommand < Gem::Command defaults = Gem::DependencyInstaller::DEFAULT_OPTIONS.merge({ :format_executable => false, :version => Gem::Requirement.default, + :without_groups => [], }) super 'install', 'Install a gem into the local repository', defaults @@ -42,6 +43,13 @@ class Gem::Commands::InstallCommand < Gem::Command o[:gemdeps] = v end + add_option(:"Install/Update", '--without GROUPS', Array, + 'Omit the named groups (comma separated)', + 'when installing from a gem dependencies', + 'file') do |v,o| + o[:without_groups].concat v.map { |without| without.intern } + end + add_option(:"Install/Update", '--default', 'Add the gem\'s full specification to', 'specifications/default and extract only its bin') do |v,o| @@ -133,8 +141,8 @@ to write the specification by hand. For example: end def execute - if gf = options[:gemdeps] then - install_from_gemdeps gf + if options.include? :gemdeps then + install_from_gemdeps return # not reached end @@ -154,14 +162,11 @@ to write the specification by hand. For example: terminate_interaction exit_code end - def install_from_gemdeps gf # :nodoc: + def install_from_gemdeps # :nodoc: require 'rubygems/request_set' rs = Gem::RequestSet.new - rs.load_gemdeps gf - - rs.resolve - specs = rs.install options do |req, inst| + specs = rs.install_from_gemdeps options do |req, inst| s = req.full_spec if inst diff --git a/lib/rubygems/commands/update_command.rb b/lib/rubygems/commands/update_command.rb index c3532841c3..b4ee59b3bb 100644 --- a/lib/rubygems/commands/update_command.rb +++ b/lib/rubygems/commands/update_command.rb @@ -112,7 +112,7 @@ command to remove old versions. spec_tuples, errors = fetcher.search_for_dependency dependency - error = errors.find { |errors| errors.respond_to? :exception } + error = errors.find { |e| e.respond_to? :exception } raise error if error diff --git a/lib/rubygems/dependency_installer.rb b/lib/rubygems/dependency_installer.rb index c6985b27c0..22ff6f5cb7 100644 --- a/lib/rubygems/dependency_installer.rb +++ b/lib/rubygems/dependency_installer.rb @@ -250,6 +250,14 @@ class Gem::DependencyInstaller if gem_name =~ /\.gem$/ and File.file? gem_name then src = Gem::Source::SpecificFile.new(gem_name) set.add src.spec, src + elsif gem_name =~ /\.gem$/ then + Dir[gem_name].each do |name| + begin + src = Gem::Source::SpecificFile.new name + set.add src.spec, src + rescue Gem::Package::FormatError + end + end else local = Gem::Source::Local.new diff --git a/lib/rubygems/dependency_resolver.rb b/lib/rubygems/dependency_resolver.rb index 10cecf7972..35fbe925ad 100644 --- a/lib/rubygems/dependency_resolver.rb +++ b/lib/rubygems/dependency_resolver.rb @@ -30,7 +30,16 @@ class Gem::DependencyResolver attr_accessor :soft_missing def self.compose_sets *sets - Gem::DependencyResolver::ComposedSet.new(*sets) + sets.compact! + + case sets.length + when 0 then + raise ArgumentError, 'one set in the composition must be non-nil' + when 1 then + sets.first + else + Gem::DependencyResolver::ComposedSet.new(*sets) + end end ## @@ -53,12 +62,27 @@ class Gem::DependencyResolver @set = set || Gem::DependencyResolver::IndexSet.new @needed = needed - @conflicts = nil + @conflicts = [] @development = false @missing = [] @soft_missing = false end + ## + # Creates an ActivationRequest for the given +dep+ and the last +possible+ + # specification. + # + # Returns the Specification and the ActivationRequest + + def activation_request dep, possible # :nodoc: + spec = possible.pop + + activation_request = + Gem::DependencyResolver::ActivationRequest.new spec, dep, possible + + return spec, activation_request + end + def requests s, act, reqs=nil s.dependencies.reverse_each do |d| next if d.type == :development and not @development @@ -95,27 +119,38 @@ class Gem::DependencyResolver ## # Finds the State in +states+ that matches the +conflict+ so that we can try # other possible sets. + # + # If no good candidate is found, the first state is tried. def find_conflict_state conflict, states # :nodoc: + rejected = [] + until states.empty? do - if conflict.for_spec? states.last.spec - state = states.last + state = states.pop + + if conflict.for_spec? state.spec state.conflicts << [state.spec, conflict] return state - else - states.pop end + + rejected << state end - nil + return rejected.shift + ensure + rejected = rejected.concat states + states.replace rejected end ## - # Extracts the specifications that may be able to fulfill +dependency+ + # Extracts the specifications that may be able to fulfill +dependency+ and + # returns those that match the local platform and all those that match. def find_possible dependency # :nodoc: - possible = @set.find_all dependency - select_local_platforms possible + all = @set.find_all dependency + matching_platform = select_local_platforms all + + return matching_platform, all end def handle_conflict(dep, existing) @@ -134,7 +169,7 @@ class Gem::DependencyResolver Gem::DependencyResolver::DependencyConflict.new depreq, existing, dep end - @conflicts << conflict + @conflicts << conflict unless @conflicts.include? conflict return conflict end @@ -150,7 +185,29 @@ class Gem::DependencyResolver # +conflicts+ is a [DependencyRequest, DependencyConflict] hit tried to # activate the state. # - State = Struct.new(:needed, :specs, :dep, :spec, :possibles, :conflicts) + 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 @@ -178,20 +235,22 @@ class Gem::DependencyResolver needed, specs = resolve_for_conflict needed, specs, state + states << state unless state.possibles.empty? + next end - possible = find_possible dep + matching, all = find_possible dep - case possible.size + case matching.size when 0 - resolve_for_zero dep + resolve_for_zero dep, all when 1 needed, specs = - resolve_for_single needed, specs, dep, possible + resolve_for_single needed, specs, dep, matching else needed, specs = - resolve_for_multiple needed, specs, states, dep, possible + resolve_for_multiple needed, specs, states, dep, matching end end @@ -208,10 +267,8 @@ class Gem::DependencyResolver raise Gem::ImpossibleDependenciesError.new state.dep, state.conflicts if state.possibles.empty? - spec = state.possibles.pop - # Retry resolution with this spec and add it's dependencies - act = Gem::DependencyResolver::ActivationRequest.new spec, state.dep + spec, act = activation_request state.dep, state.possibles needed = requests spec, act, state.needed specs = Gem::List.prepend state.specs, act @@ -230,19 +287,11 @@ class Gem::DependencyResolver [s.source, s.version, s.platform == Gem::Platform::RUBY ? -1 : 1] end - # To figure out which to pick, we keep resolving given each one being - # activated and if there isn't a conflict, we know we've found a full set. - # - # We use an until loop rather than reverse_each to keep the stack short - # since we're using a recursive algorithm. - spec = possible.pop + 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. - - act = Gem::DependencyResolver::ActivationRequest.new spec, dep - states << State.new(needed, specs, dep, spec, possible, []) needed = requests spec, act, needed @@ -256,8 +305,7 @@ class Gem::DependencyResolver # dependencies by adding them to +needed+. def resolve_for_single needed, specs, dep, possible # :nodoc: - spec = possible.first - act = Gem::DependencyResolver::ActivationRequest.new spec, dep, false + spec, act = activation_request dep, possible specs = Gem::List.prepend specs, act @@ -274,11 +322,11 @@ class Gem::DependencyResolver ## # When there are no possible specifications for +dep+ our work is done. - def resolve_for_zero dep # :nodoc: + def resolve_for_zero dep, platform_mismatch # :nodoc: @missing << dep unless @soft_missing - raise Gem::UnsatisfiableDependencyError, dep + raise Gem::UnsatisfiableDependencyError.new(dep, platform_mismatch) end end @@ -287,23 +335,30 @@ class Gem::DependencyResolver def select_local_platforms specs # :nodoc: specs.select do |spec| - Gem::Platform.match spec.platform + Gem::Platform.installable? spec end end end -require 'rubygems/dependency_resolver/api_set' -require 'rubygems/dependency_resolver/api_specification' require 'rubygems/dependency_resolver/activation_request' -require 'rubygems/dependency_resolver/composed_set' -require 'rubygems/dependency_resolver/current_set' require 'rubygems/dependency_resolver/dependency_conflict' require 'rubygems/dependency_resolver/dependency_request' + +require 'rubygems/dependency_resolver/set' +require 'rubygems/dependency_resolver/api_set' +require 'rubygems/dependency_resolver/composed_set' +require 'rubygems/dependency_resolver/best_set' +require 'rubygems/dependency_resolver/current_set' require 'rubygems/dependency_resolver/index_set' -require 'rubygems/dependency_resolver/index_specification' -require 'rubygems/dependency_resolver/installed_specification' require 'rubygems/dependency_resolver/installer_set' +require 'rubygems/dependency_resolver/lock_set' require 'rubygems/dependency_resolver/vendor_set' + +require 'rubygems/dependency_resolver/specification' +require 'rubygems/dependency_resolver/spec_specification' +require 'rubygems/dependency_resolver/api_specification' +require 'rubygems/dependency_resolver/index_specification' +require 'rubygems/dependency_resolver/installed_specification' require 'rubygems/dependency_resolver/vendor_specification' diff --git a/lib/rubygems/dependency_resolver/activation_request.rb b/lib/rubygems/dependency_resolver/activation_request.rb index 25af6378ac..c5d1e24d85 100644 --- a/lib/rubygems/dependency_resolver/activation_request.rb +++ b/lib/rubygems/dependency_resolver/activation_request.rb @@ -47,11 +47,21 @@ class Gem::DependencyResolver::ActivationRequest end def inspect # :nodoc: - others_possible = nil - others_possible = ' (others possible)' if @others_possible + others = + case @others_possible + when true then # TODO remove at RubyGems 3 + ' (others possible)' + when false then # TODO remove at RubyGems 3 + nil + else + unless @others_possible.empty? then + others = @others_possible.map { |s| s.full_name } + " (others possible: #{others.join ', '})" + end + end '#<%s for %p from %s%s>' % [ - self.class, @spec, @request, others_possible + self.class, @spec, @request, others ] end @@ -59,10 +69,15 @@ class Gem::DependencyResolver::ActivationRequest # Indicates if the requested gem has already been installed. def installed? - this_spec = full_spec + case @spec + when Gem::DependencyResolver::VendorSpecification then + true + else + this_spec = full_spec - Gem::Specification.any? do |s| - s == this_spec + Gem::Specification.any? do |s| + s == this_spec + end end end @@ -75,7 +90,12 @@ class Gem::DependencyResolver::ActivationRequest # requests for the same Dependency request. def others_possible? - @others_possible + case @others_possible + when true, false then + @others_possible + else + not @others_possible.empty? + end end ## @@ -95,9 +115,18 @@ class Gem::DependencyResolver::ActivationRequest q.text ' for ' q.pp @request - - q.breakable - q.text ' (other possible)' if @others_possible + case @others_possible + when false then + when true then + q.breakable + q.text 'others possible' + else + unless @others_possible.empty? then + q.breakable + q.text 'others ' + q.pp @others_possible.map { |s| s.full_name } + end + end end end diff --git a/lib/rubygems/dependency_resolver/api_set.rb b/lib/rubygems/dependency_resolver/api_set.rb index 469c005a09..9dd34562b1 100644 --- a/lib/rubygems/dependency_resolver/api_set.rb +++ b/lib/rubygems/dependency_resolver/api_set.rb @@ -2,11 +2,21 @@ # The global rubygems pool, available via the rubygems.org API. # Returns instances of APISpecification. -class Gem::DependencyResolver::APISet +class Gem::DependencyResolver::APISet < Gem::DependencyResolver::Set - def initialize + ## + # The URI for the dependency API this APISet uses. + + attr_reader :dep_uri # :nodoc: + + ## + # Creates a new APISet that will retrieve gems from +uri+ using the RubyGems + # API described at http://guides.rubygems.org/rubygems-org-api + + def initialize uri = 'https://rubygems.org/api/v1/dependencies' + uri = URI uri unless URI === uri # for ruby 1.8 @data = Hash.new { |h,k| h[k] = [] } - @dep_uri = URI 'https://rubygems.org/api/v1/dependencies' + @dep_uri = uri end ## @@ -46,7 +56,7 @@ class Gem::DependencyResolver::APISet ## # Return data for all versions of the gem +name+. - def versions name + def versions name # :nodoc: if @data.key?(name) return @data[name] end diff --git a/lib/rubygems/dependency_resolver/api_specification.rb b/lib/rubygems/dependency_resolver/api_specification.rb index ae688780dd..5178d7c28e 100644 --- a/lib/rubygems/dependency_resolver/api_specification.rb +++ b/lib/rubygems/dependency_resolver/api_specification.rb @@ -1,18 +1,21 @@ ## -# Represents a specification retrieved via the rubygems.org -# API. This is used to avoid having to load the full -# Specification object when all we need is the name, version, -# and dependencies. +# Represents a specification retrieved via the rubygems.org API. +# +# This is used to avoid loading the full Specification object when all we need +# is the name, version, and dependencies. -class Gem::DependencyResolver::APISpecification +class Gem::DependencyResolver::APISpecification < Gem::DependencyResolver::Specification - attr_reader :dependencies - attr_reader :name - attr_reader :platform - attr_reader :set # :nodoc: - attr_reader :version + ## + # Creates an APISpecification for the given +set+ from the rubygems.org + # +api_data+. + # + # See http://guides.rubygems.org/rubygems-org-api/#misc_methods for the + # format of the +api_data+. def initialize(set, api_data) + super() + @set = set @name = api_data[:name] @version = Gem::Version.new api_data[:number] @@ -31,9 +34,5 @@ class Gem::DependencyResolver::APISpecification @dependencies == other.dependencies end - def full_name - "#{@name}-#{@version}" - end - end diff --git a/lib/rubygems/dependency_resolver/best_set.rb b/lib/rubygems/dependency_resolver/best_set.rb new file mode 100644 index 0000000000..987eea552e --- /dev/null +++ b/lib/rubygems/dependency_resolver/best_set.rb @@ -0,0 +1,21 @@ +## +# The BestSet chooses the best available method to query a remote index. +# +# It combines IndexSet and APISet + +class Gem::DependencyResolver::BestSet < Gem::DependencyResolver::ComposedSet + + ## + # Creates a BestSet for the given +sources+ or Gem::sources if none are + # specified. +sources+ must be a Gem::SourceList. + + def initialize sources = Gem.sources + super() + + sources.each_source do |source| + @sets << source.dependency_resolver_set + end + end + +end + diff --git a/lib/rubygems/dependency_resolver/composed_set.rb b/lib/rubygems/dependency_resolver/composed_set.rb index fb38128bb0..aeecf047b8 100644 --- a/lib/rubygems/dependency_resolver/composed_set.rb +++ b/lib/rubygems/dependency_resolver/composed_set.rb @@ -1,4 +1,6 @@ -class Gem::DependencyResolver::ComposedSet +class Gem::DependencyResolver::ComposedSet < Gem::DependencyResolver::Set + + attr_reader :sets # :nodoc: def initialize *sets @sets = sets diff --git a/lib/rubygems/dependency_resolver/current_set.rb b/lib/rubygems/dependency_resolver/current_set.rb index 13bc490e9e..ef15c9d7f3 100644 --- a/lib/rubygems/dependency_resolver/current_set.rb +++ b/lib/rubygems/dependency_resolver/current_set.rb @@ -3,14 +3,11 @@ # all the normal settings that control where to look # for installed gems. -class Gem::DependencyResolver::CurrentSet +class Gem::DependencyResolver::CurrentSet < Gem::DependencyResolver::Set def find_all req req.dependency.matching_specs end - def prefetch gems - end - end diff --git a/lib/rubygems/dependency_resolver/dependency_conflict.rb b/lib/rubygems/dependency_resolver/dependency_conflict.rb index 1755d910c3..092f000cdb 100644 --- a/lib/rubygems/dependency_resolver/dependency_conflict.rb +++ b/lib/rubygems/dependency_resolver/dependency_conflict.rb @@ -8,12 +8,21 @@ class Gem::DependencyResolver::DependencyConflict attr_reader :dependency + attr_reader :failed_dep # :nodoc: + def initialize(dependency, activated, failed_dep=dependency) @dependency = dependency @activated = activated @failed_dep = failed_dep end + def == other + self.class === other and + @dependency == other.dependency and + @activated == other.activated and + @failed_dep == other.failed_dep + end + ## # Return the 2 dependency objects that conflicted @@ -71,6 +80,8 @@ class Gem::DependencyResolver::DependencyConflict current = current.request.requester end + path = ['user request (gem command or Gemfile)'] if path.empty? + path end diff --git a/lib/rubygems/dependency_resolver/dependency_request.rb b/lib/rubygems/dependency_resolver/dependency_request.rb index 05e447c3be..36b77ab558 100644 --- a/lib/rubygems/dependency_resolver/dependency_request.rb +++ b/lib/rubygems/dependency_resolver/dependency_request.rb @@ -32,6 +32,22 @@ class Gem::DependencyResolver::DependencyRequest @dependency.name end + # Indicate that the request is for a gem explicitly requested by the user + def explicit? + @requester.nil? + end + + # Indicate that the requset is for a gem requested as a dependency of another gem + def implicit? + !explicit? + end + + # Return a String indicating who caused this request to be added (only + # valid for implicit requests) + def request_context + @requester ? @requester.request : "(unknown)" + end + def pretty_print q # :nodoc: q.group 2, '[Dependency request ', ']' do q.breakable @@ -43,6 +59,10 @@ class Gem::DependencyResolver::DependencyRequest end end + def requirement + @dependency.requirement + end + def to_s # :nodoc: @dependency.to_s end diff --git a/lib/rubygems/dependency_resolver/index_set.rb b/lib/rubygems/dependency_resolver/index_set.rb index 8c8bc4319d..04d6ec816f 100644 --- a/lib/rubygems/dependency_resolver/index_set.rb +++ b/lib/rubygems/dependency_resolver/index_set.rb @@ -2,10 +2,17 @@ # The global rubygems pool represented via the traditional # source index. -class Gem::DependencyResolver::IndexSet +class Gem::DependencyResolver::IndexSet < Gem::DependencyResolver::Set - def initialize - @f = Gem::SpecFetcher.fetcher + def initialize source = nil # :nodoc: + @f = + if source then + sources = Gem::SourceList.from [source] + + Gem::SpecFetcher.new sources + else + Gem::SpecFetcher.fetcher + end @all = Hash.new { |h,k| h[k] = [] } @@ -39,26 +46,5 @@ class Gem::DependencyResolver::IndexSet res end - ## - # Called from IndexSpecification to get a true Specification - # object. - - def load_spec name, ver, platform, source - key = "#{name}-#{ver}-#{platform}" - - @specs.fetch key do - tuple = Gem::NameTuple.new name, ver, platform - - @specs[key] = source.fetch_spec tuple - end - end - - ## - # No prefetching needed since we load the whole index in - # initially. - - def prefetch gems - end - end diff --git a/lib/rubygems/dependency_resolver/index_specification.rb b/lib/rubygems/dependency_resolver/index_specification.rb index 6cf267dac1..9b4057f0c8 100644 --- a/lib/rubygems/dependency_resolver/index_specification.rb +++ b/lib/rubygems/dependency_resolver/index_specification.rb @@ -3,17 +3,20 @@ # delay needed to download full Specification objects when only the +name+ # and +version+ are needed. -class Gem::DependencyResolver::IndexSpecification +class Gem::DependencyResolver::IndexSpecification < Gem::DependencyResolver::Specification - attr_reader :name - - attr_reader :platform - - attr_reader :source - - attr_reader :version + ## + # An IndexSpecification is created from the index format described in `gem + # help generate_index`. + # + # The +set+ contains other specifications for this (URL) +source+. + # + # The +name+, +version+ and +platform+ are the name, version and platform of + # the gem. def initialize set, name, version, source, platform + super() + @set = set @name = name @version = version @@ -23,14 +26,13 @@ class Gem::DependencyResolver::IndexSpecification @spec = nil end + ## + # The dependencies of the gem for this specification + def dependencies spec.dependencies end - def full_name - "#{@name}-#{@version}" - end - def inspect # :nodoc: '#<%s %s source %s>' % [self.class, full_name, @source] end @@ -51,8 +53,16 @@ class Gem::DependencyResolver::IndexSpecification end end - def spec - @spec ||= @set.load_spec(@name, @version, @platform, @source) + ## + # Fetches a Gem::Specification for this IndexSpecification from the #source. + + def spec # :nodoc: + @spec ||= + begin + tuple = Gem::NameTuple.new @name, @version, @platform + + @source.fetch_spec tuple + end end end diff --git a/lib/rubygems/dependency_resolver/installed_specification.rb b/lib/rubygems/dependency_resolver/installed_specification.rb index ca20ace61e..4b591661a8 100644 --- a/lib/rubygems/dependency_resolver/installed_specification.rb +++ b/lib/rubygems/dependency_resolver/installed_specification.rb @@ -1,12 +1,8 @@ -class Gem::DependencyResolver::InstalledSpecification +## +# An InstalledSpecification represents a gem that is already installed +# locally. - attr_reader :spec - - def initialize set, spec, source=nil - @set = set - @source = source - @spec = spec - end +class Gem::DependencyResolver::InstalledSpecification < Gem::DependencyResolver::SpecSpecification def == other # :nodoc: self.class === other and @@ -14,29 +10,25 @@ class Gem::DependencyResolver::InstalledSpecification @spec == other.spec end - def dependencies - @spec.dependencies - end - - def full_name - "#{@spec.name}-#{@spec.version}" - end + ## + # Returns +true+ if this gem is installable for the current platform. - def name - @spec.name + def installable_platform? + # BACKCOMPAT If the file is coming out of a specified file, then we + # ignore the platform. This code can be removed in RG 3.0. + if @source.kind_of? Gem::Source::SpecificFile + return true + else + Gem::Platform.match @spec.platform + end end - def platform - @spec.platform - end + ## + # The source for this specification def source @source ||= Gem::Source::Installed.new end - def version - @spec.version - end - end diff --git a/lib/rubygems/dependency_resolver/installer_set.rb b/lib/rubygems/dependency_resolver/installer_set.rb index 2993766d3a..801b60a8bb 100644 --- a/lib/rubygems/dependency_resolver/installer_set.rb +++ b/lib/rubygems/dependency_resolver/installer_set.rb @@ -2,23 +2,23 @@ # A set of gems for installation sourced from remote sources and local .gem # files -class Gem::DependencyResolver::InstallerSet +class Gem::DependencyResolver::InstallerSet < Gem::DependencyResolver::Set ## # List of Gem::Specification objects that must always be installed. - attr_reader :always_install + attr_reader :always_install # :nodoc: ## # Only install gems in the always_install list - attr_accessor :ignore_dependencies + attr_accessor :ignore_dependencies # :nodoc: ## # Do not look in the installed set when finding specifications. This is # used by the --install-dir option to `gem install` - attr_accessor :ignore_installed + attr_accessor :ignore_installed # :nodoc: def initialize domain @domain = domain @@ -36,14 +36,14 @@ class Gem::DependencyResolver::InstallerSet ## # Should local gems should be considered? - def consider_local? + def consider_local? # :nodoc: @domain == :both or @domain == :local end ## # Should remote gems should be considered? - def consider_remote? + def consider_remote? # :nodoc: @domain == :both or @domain == :remote end @@ -101,7 +101,7 @@ class Gem::DependencyResolver::InstallerSet ## # Loads remote prerelease specs if +dep+ is a prerelease dependency - def load_remote_specs dep + def load_remote_specs dep # :nodoc: types = [:released] types << :prerelease if dep.prerelease? @@ -123,7 +123,7 @@ class Gem::DependencyResolver::InstallerSet # Called from IndexSpecification to get a true Specification # object. - def load_spec name, ver, platform, source + def load_spec name, ver, platform, source # :nodoc: key = "#{name}-#{ver}-#{platform}" @specs.fetch key do @@ -133,12 +133,6 @@ class Gem::DependencyResolver::InstallerSet end end - ## - # No prefetching needed since we load the whole index in initially. - - def prefetch(reqs) - end - def pretty_print q # :nodoc: q.group 2, '[InstallerSet', ']' do q.breakable diff --git a/lib/rubygems/dependency_resolver/lock_set.rb b/lib/rubygems/dependency_resolver/lock_set.rb new file mode 100644 index 0000000000..f95c7f0fd6 --- /dev/null +++ b/lib/rubygems/dependency_resolver/lock_set.rb @@ -0,0 +1,60 @@ +## +# A set of gems from a gem dependencies lockfile. + +class Gem::DependencyResolver::LockSet < Gem::DependencyResolver::Set + + attr_reader :specs # :nodoc: + + ## + # Creates a new LockSet from the given +source+ + + def initialize source + @source = source + @specs = [] + end + + ## + # Creates a new IndexSpecification in this set using the given +name+, + # +version+ and +platform+. + # + # The specification's set will be the current set, and the source will be + # the current set's source. + + def add name, version, platform # :nodoc: + version = Gem::Version.new version + + spec = + Gem::DependencyResolver::IndexSpecification.new self, name, version, + @source, platform + + @specs << spec + end + + ## + # Returns an Array of IndexSpecification objects matching the + # DependencyRequest +req+. + + def find_all req + @specs.select do |spec| + req.matches_spec? spec + end + end + + ## + # Loads a Gem::Specification with the given +name+, +version+ and + # +platform+. +source+ is ignored. + + def load_spec name, version, platform, source # :nodoc: + dep = Gem::Dependency.new name, version + + found = @specs.find do |spec| + dep.matches_spec? spec and spec.platform == platform + end + + tuple = Gem::NameTuple.new found.name, found.version, found.platform + + found.source.fetch_spec tuple + end + +end + diff --git a/lib/rubygems/dependency_resolver/set.rb b/lib/rubygems/dependency_resolver/set.rb new file mode 100644 index 0000000000..65801871ac --- /dev/null +++ b/lib/rubygems/dependency_resolver/set.rb @@ -0,0 +1,28 @@ +## +# DependencyResolver sets are used to look up specifications (and their +# dependencies) used in resolution. This set is abstract. + +class Gem::DependencyResolver::Set + + ## + # The find_all method must be implemented. It returns all + # DependencyResolver Specification objects matching the given + # DependencyRequest +req+. + + def find_all req + raise NotImplementedError + end + + ## + # The #prefetch method may be overridden, but this is not necessary. This + # default implementation does nothing, which is suitable for sets where + # looking up a specification is cheap (such as installed gems). + # + # When overridden, the #prefetch method should look up specifications + # matching +reqs+. + + def prefetch reqs + end + +end + diff --git a/lib/rubygems/dependency_resolver/spec_specification.rb b/lib/rubygems/dependency_resolver/spec_specification.rb new file mode 100644 index 0000000000..cca1d58b9f --- /dev/null +++ b/lib/rubygems/dependency_resolver/spec_specification.rb @@ -0,0 +1,58 @@ +## +# The DependencyResolver::SpecSpecification contains common functionality for +# DependencyResolver specifications that are backed by a Gem::Specification. + +class Gem::DependencyResolver::SpecSpecification < Gem::DependencyResolver::Specification + + attr_reader :spec # :nodoc: + + ## + # A SpecSpecification is created for a +set+ for a Gem::Specification in + # +spec+. The +source+ is either where the +spec+ came from, or should be + # loaded from. + + def initialize set, spec, source = nil + @set = set + @source = source + @spec = spec + end + + ## + # The dependencies of the gem for this specification + + def dependencies + spec.dependencies + end + + ## + # The name and version of the specification. + # + # Unlike Gem::Specification#full_name, the platform is not included. + + def full_name + "#{spec.name}-#{spec.version}" + end + + ## + # The name of the gem for this specification + + def name + spec.name + end + + ## + # The platform this gem works on. + + def platform + spec.platform + end + + ## + # The version of the gem for this specification. + + def version + spec.version + end + +end + diff --git a/lib/rubygems/dependency_resolver/specification.rb b/lib/rubygems/dependency_resolver/specification.rb new file mode 100644 index 0000000000..6fbd241316 --- /dev/null +++ b/lib/rubygems/dependency_resolver/specification.rb @@ -0,0 +1,60 @@ +## +# A DependencyResolver::Specification contains a subset of the information +# contained in a Gem::Specification. Only the information necessary for +# dependency resolution in the resolver is included. + +class Gem::DependencyResolver::Specification + + ## + # The dependencies of the gem for this specification + + attr_reader :dependencies + + ## + # The name of the gem for this specification + + attr_reader :name + + ## + # The platform this gem works on. + + attr_reader :platform + + ## + # The set this specification came from. + + attr_reader :set + + ## + # The source for this specification + + attr_reader :source + + ## + # The version of the gem for this specification. + + attr_reader :version + + ## + # Sets default instance variables for the specification. + + def initialize + @dependencies = nil + @name = nil + @platform = nil + @set = nil + @source = nil + @version = nil + end + + ## + # The name and version of the specification. + # + # Unlike Gem::Specification#full_name, the platform is not included. + + def full_name + "#{@name}-#{@version}" + end + +end + diff --git a/lib/rubygems/dependency_resolver/vendor_set.rb b/lib/rubygems/dependency_resolver/vendor_set.rb index 716c2a8e26..87eb6fd818 100644 --- a/lib/rubygems/dependency_resolver/vendor_set.rb +++ b/lib/rubygems/dependency_resolver/vendor_set.rb @@ -13,17 +13,18 @@ # The directory vendor/rake must contain an unpacked rake gem along with a # rake.gemspec (watching the given name). -class Gem::DependencyResolver::VendorSet +class Gem::DependencyResolver::VendorSet < Gem::DependencyResolver::Set - def initialize - @specs = {} + def initialize # :nodoc: + @directories = {} + @specs = {} end ## # Adds a specification to the set with the given +name+ which has been # unpacked into the given +directory+. - def add_vendor_gem name, directory + def add_vendor_gem name, directory # :nodoc: gemspec = File.join directory, "#{name}.gemspec" spec = Gem::Specification.load gemspec @@ -33,7 +34,8 @@ class Gem::DependencyResolver::VendorSet key = "#{spec.name}-#{spec.version}-#{spec.platform}" - @specs[key] = spec + @specs[key] = spec + @directories[spec] = directory end ## @@ -44,7 +46,8 @@ class Gem::DependencyResolver::VendorSet @specs.values.select do |spec| req.matches_spec? spec end.map do |spec| - Gem::DependencyResolver::VendorSpecification.new self, spec, nil + source = Gem::Source::Vendor.new @directories[spec] + Gem::DependencyResolver::VendorSpecification.new self, spec, source end end @@ -53,17 +56,11 @@ class Gem::DependencyResolver::VendorSet # +source+ is defined when the specification was added to index it is not # used. - def load_spec name, version, platform, source + def load_spec name, version, platform, source # :nodoc: key = "#{name}-#{version}-#{platform}" @specs.fetch key end - ## - # No prefetch is needed as the index is loaded at creation time. - - def prefetch gems - end - end diff --git a/lib/rubygems/dependency_resolver/vendor_specification.rb b/lib/rubygems/dependency_resolver/vendor_specification.rb index 2f18fab4ce..27b2fd6df2 100644 --- a/lib/rubygems/dependency_resolver/vendor_specification.rb +++ b/lib/rubygems/dependency_resolver/vendor_specification.rb @@ -1,43 +1,15 @@ -class Gem::DependencyResolver::VendorSpecification +## +# A VendorSpecification represents a gem that has been unpacked into a project +# and is being loaded through a gem dependencies file through the +path:+ +# option. - attr_reader :spec - - attr_reader :set - - def initialize set, spec, source=nil - @set = set - @source = source - @spec = spec - end +class Gem::DependencyResolver::VendorSpecification < Gem::DependencyResolver::SpecSpecification def == other # :nodoc: self.class === other and @set == other.set and - @spec == other.spec - end - - def dependencies - @spec.dependencies - end - - def full_name - "#{@spec.name}-#{@spec.version}" - end - - def name - @spec.name - end - - def platform - @spec.platform - end - - def source - @source ||= Gem::Source::Vendor.new - end - - def version - @spec.version + @spec == other.spec and + @source == other.source end end diff --git a/lib/rubygems/errors.rb b/lib/rubygems/errors.rb index 4a92011e8b..3c5486a800 100644 --- a/lib/rubygems/errors.rb +++ b/lib/rubygems/errors.rb @@ -19,8 +19,6 @@ module Gem attr_accessor :requirement end - # FIX: does this need to exist? The subclass is the only other reference - # I can find. class ErrorReason; end # Generated when trying to lookup a gem to indicate that the gem diff --git a/lib/rubygems/exceptions.rb b/lib/rubygems/exceptions.rb index 30d9880d8a..6d92b144b6 100644 --- a/lib/rubygems/exceptions.rb +++ b/lib/rubygems/exceptions.rb @@ -35,7 +35,7 @@ class Gem::DependencyResolutionError < Gem::Exception @conflict = conflict a, b = conflicting_dependencies - super "unable to resolve conflicting dependencies '#{a}' and '#{b}'" + super "conflicting dependencies #{a} and #{b}\n#{@conflict.explanation}" end def conflicting_dependencies @@ -226,10 +226,17 @@ class Gem::UnsatisfiableDependencyError < Gem::Exception # Creates a new UnsatisfiableDepedencyError for the unsatisfiable # Gem::DependencyResolver::DependencyRequest +dep+ - def initialize dep - requester = dep.requester ? dep.requester.request : '(unknown)' - - super "Unable to resolve dependency: #{requester} requires #{dep}" + def initialize dep, platform_mismatch=nil + if platform_mismatch and !platform_mismatch.empty? + plats = platform_mismatch.map { |x| x.platform.to_s }.sort.uniq + super "Unable to resolve dependency: No match for '#{dep}' on this platform. Found: #{plats.join(', ')}" + else + if dep.explicit? + super "Unable to resolve dependency: user requested '#{dep}'" + else + super "Unable to resolve dependency: '#{dep.request_context}' requires '#{dep}'" + end + end @dependency = dep end diff --git a/lib/rubygems/platform.rb b/lib/rubygems/platform.rb index 247ee6ed3e..e050959dc6 100644 --- a/lib/rubygems/platform.rb +++ b/lib/rubygems/platform.rb @@ -29,6 +29,14 @@ class Gem::Platform end end + def self.installable?(spec) + if spec.respond_to? :installable_platform? + spec.installable_platform? + else + match spec.platform + end + end + def self.new(arch) # :nodoc: case arch when Gem::Platform::CURRENT then diff --git a/lib/rubygems/remote_fetcher.rb b/lib/rubygems/remote_fetcher.rb index bab96cba1a..b96c77033a 100644 --- a/lib/rubygems/remote_fetcher.rb +++ b/lib/rubygems/remote_fetcher.rb @@ -78,7 +78,6 @@ class Gem::RemoteFetcher end ## - # # Given a source at +uri+, calculate what hostname to actually # connect to query the data for it. diff --git a/lib/rubygems/request_set.rb b/lib/rubygems/request_set.rb index 9a7ac83e91..3a997f32ee 100644 --- a/lib/rubygems/request_set.rb +++ b/lib/rubygems/request_set.rb @@ -32,6 +32,11 @@ class Gem::RequestSet attr_accessor :development ## + # Sets used for resolution + + attr_reader :sets # :nodoc: + + ## # Treat missing dependencies as silent errors attr_accessor :soft_missing @@ -53,13 +58,15 @@ class Gem::RequestSet def initialize *deps @dependencies = deps - @always_install = [] - @development = false - @requests = [] - @soft_missing = false - @sorted = nil - @specs = nil - @vendor_set = nil + @always_install = [] + @dependency_names = {} + @development = false + @requests = [] + @sets = [] + @soft_missing = false + @sorted = nil + @specs = nil + @vendor_set = nil yield self if block_given? end @@ -68,7 +75,13 @@ class Gem::RequestSet # Declare that a gem of name +name+ with +reqs+ requirements is needed. def gem name, *reqs - @dependencies << Gem::Dependency.new(name, reqs) + if dep = @dependency_names[name] then + dep.requirement.concat reqs + else + dep = Gem::Dependency.new name, reqs + @dependency_names[name] = dep + @dependencies << dep + end end ## @@ -78,7 +91,14 @@ class Gem::RequestSet @dependencies.concat deps end - def install options, &block + ## + # Installs gems for this RequestSet using the Gem::Installer +options+. + # + # If a +block+ is given an activation +request+ and +installer+ are yielded. + # The +installer+ will be +nil+ if a gem matching the request was already + # installed. + + def install options, &block # :yields: request, installer if dir = options[:install_dir] return install_into dir, false, options, &block end @@ -109,6 +129,21 @@ class Gem::RequestSet specs end + ## + # Installs from the gem dependencies files in the +:gemdeps+ option in + # +options+, yielding to the +block+ as in #install. + # + # If +:without_groups+ is given in the +options+, those groups in the gem + # dependencies file are not used. See Gem::Installer for other +options+. + + def install_from_gemdeps options, &block + load_gemdeps options[:gemdeps], options[:without_groups] + + resolve + + install options, &block + end + def install_into dir, force = true, options = {} existing = force ? [] : specs_in(dir) existing.delete_if { |s| @always_install.include? s } @@ -148,10 +183,11 @@ class Gem::RequestSet ## # Load a dependency management file. - def load_gemdeps path + def load_gemdeps path, without_groups = [] @vendor_set = Gem::DependencyResolver::VendorSet.new gf = Gem::RequestSet::GemDependencyAPI.new self, path + gf.without_groups = without_groups if without_groups gf.load end @@ -160,13 +196,10 @@ class Gem::RequestSet # objects to be activated. def resolve set = Gem::DependencyResolver::IndexSet.new - sets = [set, @vendor_set].compact + @sets << set + @sets << @vendor_set - set = if sets.size == 1 then - sets.first - else - Gem::DependencyResolver.compose_sets(*sets) - end + set = Gem::DependencyResolver.compose_sets(*@sets) resolver = Gem::DependencyResolver.new @dependencies, set resolver.development = @development diff --git a/lib/rubygems/request_set/gem_dependency_api.rb b/lib/rubygems/request_set/gem_dependency_api.rb index f11ffb12c3..e8f3138990 100644 --- a/lib/rubygems/request_set/gem_dependency_api.rb +++ b/lib/rubygems/request_set/gem_dependency_api.rb @@ -3,10 +3,114 @@ class Gem::RequestSet::GemDependencyAPI + ENGINE_MAP = { # :nodoc: + :jruby => %w[jruby], + :jruby_18 => %w[jruby], + :jruby_19 => %w[jruby], + :maglev => %w[maglev], + :mri => %w[ruby], + :mri_18 => %w[ruby], + :mri_19 => %w[ruby], + :mri_20 => %w[ruby], + :mri_21 => %w[ruby], + :rbx => %w[rbx], + :ruby => %w[ruby rbx maglev], + :ruby_18 => %w[ruby rbx maglev], + :ruby_19 => %w[ruby rbx maglev], + :ruby_20 => %w[ruby rbx maglev], + :ruby_21 => %w[ruby rbx maglev], + } + + x86_mingw = Gem::Platform.new 'x86-mingw32' + x64_mingw = Gem::Platform.new 'x64-mingw32' + + PLATFORM_MAP = { # :nodoc: + :jruby => Gem::Platform::RUBY, + :jruby_18 => Gem::Platform::RUBY, + :jruby_19 => Gem::Platform::RUBY, + :maglev => Gem::Platform::RUBY, + :mingw => x86_mingw, + :mingw_18 => x86_mingw, + :mingw_19 => x86_mingw, + :mingw_20 => x86_mingw, + :mingw_21 => x86_mingw, + :mri => Gem::Platform::RUBY, + :mri_18 => Gem::Platform::RUBY, + :mri_19 => Gem::Platform::RUBY, + :mri_20 => Gem::Platform::RUBY, + :mri_21 => Gem::Platform::RUBY, + :mswin => Gem::Platform::RUBY, + :rbx => Gem::Platform::RUBY, + :ruby => Gem::Platform::RUBY, + :ruby_18 => Gem::Platform::RUBY, + :ruby_19 => Gem::Platform::RUBY, + :ruby_20 => Gem::Platform::RUBY, + :ruby_21 => Gem::Platform::RUBY, + :x64_mingw => x64_mingw, + :x64_mingw_20 => x64_mingw, + :x64_mingw_21 => x64_mingw + } + + gt_eq_0 = Gem::Requirement.new '>= 0' + tilde_gt_1_8_0 = Gem::Requirement.new '~> 1.8.0' + tilde_gt_1_9_0 = Gem::Requirement.new '~> 1.9.0' + tilde_gt_2_0_0 = Gem::Requirement.new '~> 2.0.0' + tilde_gt_2_1_0 = Gem::Requirement.new '~> 2.1.0' + + VERSION_MAP = { # :nodoc: + :jruby => gt_eq_0, + :jruby_18 => tilde_gt_1_8_0, + :jruby_19 => tilde_gt_1_9_0, + :maglev => gt_eq_0, + :mingw => gt_eq_0, + :mingw_18 => tilde_gt_1_8_0, + :mingw_19 => tilde_gt_1_9_0, + :mingw_20 => tilde_gt_2_0_0, + :mingw_21 => tilde_gt_2_1_0, + :mri => gt_eq_0, + :mri_18 => tilde_gt_1_8_0, + :mri_19 => tilde_gt_1_9_0, + :mri_20 => tilde_gt_2_0_0, + :mri_21 => tilde_gt_2_1_0, + :mswin => gt_eq_0, + :rbx => gt_eq_0, + :ruby => gt_eq_0, + :ruby_18 => tilde_gt_1_8_0, + :ruby_19 => tilde_gt_1_9_0, + :ruby_20 => tilde_gt_2_0_0, + :ruby_21 => tilde_gt_2_1_0, + :x64_mingw => gt_eq_0, + :x64_mingw_20 => tilde_gt_2_0_0, + :x64_mingw_21 => tilde_gt_2_1_0, + } + + WINDOWS = { # :nodoc: + :mingw => :only, + :mingw_18 => :only, + :mingw_19 => :only, + :mingw_20 => :only, + :mingw_21 => :only, + :mri => :never, + :mri_18 => :never, + :mri_19 => :never, + :mri_20 => :never, + :mri_21 => :never, + :mswin => :only, + :rbx => :never, + :ruby => :never, + :ruby_18 => :never, + :ruby_19 => :never, + :ruby_20 => :never, + :ruby_21 => :never, + :x64_mingw => :only, + :x64_mingw_20 => :only, + :x64_mingw_21 => :only, + } + ## - # The dependency groups created by #group in the dependency API file. + # A Hash containing gem names and files to require from those gems. - attr_reader :dependency_groups + attr_reader :requires ## # A set of gems that are loaded via the +:path+ option to #gem @@ -14,6 +118,11 @@ class Gem::RequestSet::GemDependencyAPI attr_reader :vendor_set # :nodoc: ## + # The groups of gems to exclude from installation + + attr_accessor :without_groups + + ## # Creates a new GemDependencyAPI that will add dependencies to the # Gem::RequestSet +set+ based on the dependency API description in +path+. @@ -21,9 +130,13 @@ class Gem::RequestSet::GemDependencyAPI @set = set @path = path - @current_groups = nil - @dependency_groups = Hash.new { |h, group| h[group] = [] } - @vendor_set = @set.vendor_set + @current_groups = nil + @current_platform = nil + @default_sources = true + @requires = Hash.new { |h, name| h[name] = [] } + @vendor_set = @set.vendor_set + @gem_sources = {} + @without_groups = [] end ## @@ -47,10 +160,32 @@ class Gem::RequestSet::GemDependencyAPI options = requirements.pop if requirements.last.kind_of?(Hash) options ||= {} - if directory = options.delete(:path) then - @vendor_set.add_vendor_gem name, directory + source_set = gem_path name, options + + return unless gem_platforms options + + groups = gem_group name, options + + return unless (groups & @without_groups).empty? + + unless source_set then + raise ArgumentError, + "duplicate source (default) for gem #{name}" if + @gem_sources.include? name + + @gem_sources[name] = :default end + gem_requires name, options + + @set.gem name, *requirements + end + + ## + # Handles the :group and :groups +options+ for the gem with the given + # +name+. + + def gem_group name, options # :nodoc: g = options.delete :group all_groups = g ? Array(g) : [] @@ -59,19 +194,81 @@ class Gem::RequestSet::GemDependencyAPI all_groups |= @current_groups if @current_groups - unless all_groups.empty? then - all_groups.each do |group| - gem_arguments = [name, *requirements] - gem_arguments << options unless options.empty? - @dependency_groups[group] << gem_arguments + all_groups + end + + private :gem_group + + ## + # Handles the path: option from +options+ for gem +name+. + # + # Returns +true+ if the path option was handled. + + def gem_path name, options # :nodoc: + return unless directory = options.delete(:path) + + raise ArgumentError, + "duplicate source path: #{directory} for gem #{name}" if + @gem_sources.include? name + + @vendor_set.add_vendor_gem name, directory + + @gem_sources[name] = directory + + true + end + + private :gem_path + + ## + # Handles the platforms: option from +options+. Returns true if the + # platform matches the current platform. + + def gem_platforms options # :nodoc: + platform_names = Array(options.delete :platforms) + platform_names << @current_platform if @current_platform + + return true if platform_names.empty? + + platform_names.any? do |platform_name| + raise ArgumentError, "unknown platform #{platform_name.inspect}" unless + platform = PLATFORM_MAP[platform_name] + + next false unless Gem::Platform.match platform + + if engines = ENGINE_MAP[platform_name] then + next false unless engines.include? Gem.ruby_engine + end + + case WINDOWS[platform_name] + when :only then + next false unless Gem.win_platform? + when :never then + next false if Gem.win_platform? end - return + VERSION_MAP[platform_name].satisfied_by? Gem.ruby_version end + end - @set.gem name, *requirements + private :gem_platforms + + ## + # Handles the require: option from +options+ and adds those files, or the + # default file to the require list for +name+. + + def gem_requires name, options # :nodoc: + if options.include? :require then + if requires = options.delete(:require) then + @requires[name].concat requires + end + else + @requires[name] << name + end end + private :gem_requires + ## # Returns the basename of the file the dependencies were loaded from @@ -96,9 +293,12 @@ class Gem::RequestSet::GemDependencyAPI # :category: Gem Dependencies DSL def platform what - if what == :ruby - yield - end + @current_platform = what + + yield + + ensure + @current_platform = nil end ## @@ -112,23 +312,58 @@ class Gem::RequestSet::GemDependencyAPI # +:engine+ options from Bundler are currently ignored. def ruby version, options = {} - return true if version == RUBY_VERSION + engine = options[:engine] + engine_version = options[:engine_version] + + raise ArgumentError, + 'you must specify engine_version along with the ruby engine' if + engine and not engine_version + + unless RUBY_VERSION == version then + message = "Your Ruby version is #{RUBY_VERSION}, " + + "but your #{gem_deps_file} requires #{version}" + + raise Gem::RubyVersionMismatch, message + end + + if engine and engine != Gem.ruby_engine then + message = "Your ruby engine is #{Gem.ruby_engine}, " + + "but your #{gem_deps_file} requires #{engine}" + + raise Gem::RubyVersionMismatch, message + end - message = "Your Ruby version is #{RUBY_VERSION}, " + - "but your #{gem_deps_file} specified #{version}" + if engine_version then + my_engine_version = Object.const_get "#{Gem.ruby_engine.upcase}_VERSION" - raise Gem::RubyVersionMismatch, message + if engine_version != my_engine_version then + message = + "Your ruby engine version is #{Gem.ruby_engine} #{my_engine_version}, " + + "but your #{gem_deps_file} requires #{engine} #{engine_version}" + + raise Gem::RubyVersionMismatch, message + end + end + + return true end ## # :category: Gem Dependencies DSL + # + # Sets +url+ as a source for gems for this dependency API. def source url + Gem.sources.clear if @default_sources + + @default_sources = false + + Gem.sources << url end # TODO: remove this typo name at RubyGems 3.0 - Gem::RequestSet::DepedencyAPI = self # :nodoc: + Gem::RequestSet::GemDepedencyAPI = self # :nodoc: end diff --git a/lib/rubygems/request_set/lockfile.rb b/lib/rubygems/request_set/lockfile.rb new file mode 100644 index 0000000000..a9c419549d --- /dev/null +++ b/lib/rubygems/request_set/lockfile.rb @@ -0,0 +1,347 @@ +require 'pathname' + +class Gem::RequestSet::Lockfile + + ## + # Raised when a lockfile cannot be parsed + + class ParseError < Gem::Exception + + ## + # The column where the error was encountered + + attr_reader :column + + ## + # The line where the error was encountered + + attr_reader :line + + ## + # The location of the lock file + + attr_reader :path + + ## + # Raises a ParseError with the given +message+ which was encountered at a + # +line+ and +column+ while parsing. + + def initialize message, line, column, path + @line = line + @column = column + @path = path + super "#{message} (at #{line}:#{column})" + end + + end + + ## + # The platforms for this Lockfile + + attr_reader :platforms + + ## + # Creates a new Lockfile for the given +request_set+ and +gem_deps_file+ + # location. + + def initialize request_set, gem_deps_file + @set = request_set + @gem_deps_file = Pathname(gem_deps_file).expand_path + @gem_deps_dir = @gem_deps_file.dirname + + @current_token = nil + @line = 0 + @line_pos = 0 + @platforms = [] + @tokens = [] + end + + def add_DEPENDENCIES out # :nodoc: + out << "DEPENDENCIES" + + @set.dependencies.sort.map do |dependency| + source = @requests.find do |req| + req.name == dependency.name and + req.spec.class == Gem::DependencyResolver::VendorSpecification + end + + source_dep = '!' if source + + requirement = dependency.requirement + + out << " #{dependency.name}#{source_dep}#{requirement.for_lockfile}" + end + + out << nil + end + + def add_GEM out # :nodoc: + out << "GEM" + + source_groups = @spec_groups.values.flatten.group_by do |request| + request.spec.source.uri + end + + source_groups.map do |group, requests| + out << " remote: #{group}" + out << " specs:" + + requests.sort_by { |request| request.name }.each do |request| + platform = "-#{request.spec.platform}" unless + Gem::Platform::RUBY == request.spec.platform + + out << " #{request.name} (#{request.version}#{platform})" + + request.full_spec.dependencies.sort.each do |dependency| + requirement = dependency.requirement + out << " #{dependency.name}#{requirement.for_lockfile}" + end + end + end + + out << nil + end + + def add_PATH out # :nodoc: + return unless path_requests = + @spec_groups.delete(Gem::DependencyResolver::VendorSpecification) + + out << "PATH" + path_requests.each do |request| + directory = Pathname(request.spec.source.uri).expand_path + + out << " remote: #{directory.relative_path_from @gem_deps_dir}" + out << " specs:" + out << " #{request.name} (#{request.version})" + end + + out << nil + end + + def add_PLATFORMS out # :nodoc: + out << "PLATFORMS" + + platforms = @requests.map { |request| request.spec.platform }.uniq + platforms.delete Gem::Platform::RUBY if platforms.length > 1 + + platforms.each do |platform| + out << " #{platform}" + end + + out << nil + end + + ## + # Gets the next token for a Lockfile + + def get expected_type = nil, expected_value = nil # :nodoc: + @current_token = @tokens.shift + + type, value, line, column = @current_token + + if expected_type and expected_type != type then + unget + + message = "unexpected token [#{type.inspect}, #{value.inspect}], " + + "expected #{expected_type.inspect}" + + raise ParseError.new message, line, column, "#{@gem_deps_file}.lock" + end + + if expected_value and expected_value != value then + unget + + message = "unexpected token [#{type.inspect}, #{value.inspect}], " + + "expected [#{expected_type.inspect}, #{expected_value.inspect}]" + + raise ParseError.new message, line, column, "#{@gem_deps_file}.lock" + end + + @current_token + end + + def parse # :nodoc: + tokenize + + until @tokens.empty? do + type, data, column, line = get + + case type + when :section then + skip :newline + + case data + when 'DEPENDENCIES' then + parse_DEPENDENCIES + when 'GEM' then + parse_GEM + when 'PLATFORMS' then + parse_PLATFORMS + else + type, = get until @tokens.empty? or peek.first == :section + end + else + raise "BUG: unhandled token #{type} (#{data.inspect}) at #{line}:#{column}" + end + end + end + + def parse_DEPENDENCIES # :nodoc: + while not @tokens.empty? and :text == peek.first do + _, name, = get :text + + @set.gem name + + skip :newline + end + end + + def parse_GEM # :nodoc: + get :entry, 'remote' + _, data, = get :text + + source = Gem::Source.new data + + skip :newline + + get :entry, 'specs' + + skip :newline + + set = Gem::DependencyResolver::LockSet.new source + + while not @tokens.empty? and :text == peek.first do + _, name, = get :text + + case peek[0] + when :newline then # ignore + when :l_paren then + get :l_paren + + _, version, = get :text + + get :r_paren + + set.add name, version, Gem::Platform::RUBY + else + raise "BUG: unknown token #{peek}" + end + + skip :newline + end + + @set.sets << set + end + + def parse_PLATFORMS # :nodoc: + while not @tokens.empty? and :text == peek.first do + _, name, = get :text + + @platforms << name + + skip :newline + end + end + + ## + # Peeks at the next token for Lockfile + + def peek # :nodoc: + @tokens.first + end + + def skip type # :nodoc: + get while not @tokens.empty? and peek.first == type + end + + def to_s + @set.resolve + + out = [] + + @requests = @set.sorted_requests + + @spec_groups = @requests.group_by do |request| + request.spec.class + end + + add_PATH out + + add_GEM out + + add_PLATFORMS out + + add_DEPENDENCIES out + + out.join "\n" + end + + ## + # Calculates the column (by byte) and the line of the current token based on + # +byte_offset+. + + def token_pos byte_offset # :nodoc: + [byte_offset - @line_pos, @line] + end + + def tokenize # :nodoc: + @line = 0 + @line_pos = 0 + + @platforms = [] + @tokens = [] + @current_token = nil + + lock_file = "#{@gem_deps_file}.lock" + + @input = File.read lock_file + s = StringScanner.new @input + + until s.eos? do + pos = s.pos + + # leading whitespace is for the user's convenience + next if s.scan(/ +/) + + if s.scan(/[<|=>]{7}/) then + message = "your #{lock_file} contains merge conflict markers" + line, column = token_pos pos + + raise ParseError.new message, line, column, lock_file + end + + @tokens << + case + when s.scan(/\r?\n/) then + token = [:newline, nil, *token_pos(pos)] + @line_pos = s.pos + @line += 1 + token + when s.scan(/[A-Z]+/) then + [:section, s.matched, *token_pos(pos)] + when s.scan(/([a-z]+):\s/) then + s.pos -= 1 # rewind for possible newline + [:entry, s[1], *token_pos(pos)] + when s.scan(/\(/) then + [:l_paren, nil, *token_pos(pos)] + when s.scan(/\)/) then + [:r_paren, nil, *token_pos(pos)] + when s.scan(/[^\s)]*/) then + [:text, s.matched, *token_pos(pos)] + else + raise "BUG: can't create token for: #{s.string[s.pos..-1].inspect}" + end + end + + @tokens + end + + ## + # Ungets the last token retrieved by #get + + def unget # :nodoc: + @tokens.unshift @current_token + end + +end + diff --git a/lib/rubygems/requirement.rb b/lib/rubygems/requirement.rb index 4bf1ed914f..2b112a8022 100644 --- a/lib/rubygems/requirement.rb +++ b/lib/rubygems/requirement.rb @@ -21,11 +21,21 @@ class Gem::Requirement } quoted = OPS.keys.map { |k| Regexp.quote k }.join "|" - PATTERN_RAW = "\\s*(#{quoted})?\\s*(#{Gem::Version::VERSION_PATTERN})\\s*" + PATTERN_RAW = "\\s*(#{quoted})?\\s*(#{Gem::Version::VERSION_PATTERN})\\s*" # :nodoc: + + ## + # A regular expression that matches a requirement + PATTERN = /\A#{PATTERN_RAW}\z/ + ## + # The default requirement matches any version + DefaultRequirement = [">=", Gem::Version.new(0)] + ## + # Raised when a bad requirement is encountered + class BadRequirementError < ArgumentError; end ## @@ -108,9 +118,27 @@ class Gem::Requirement end ## + # Concatenates the +new+ requirements onto this requirement. + + def concat new + new = new.flatten + new.compact! + new.uniq! + new = new.map { |r| self.class.parse r } + + @requirements.concat new + end + + ## + # Formats this requirement for use in a Gem::RequestSet::Lockfile. + + def for_lockfile # :nodoc: + " (#{to_s})" unless [DefaultRequirement] == @requirements + end + + ## # true if this gem has no requirements. - # FIX: maybe this should be using #default ? def none? if @requirements.size == 1 @requirements[0] == DefaultRequirement @@ -152,11 +180,11 @@ class Gem::Requirement yaml_initialize coder.tag, coder.map end - def to_yaml_properties + def to_yaml_properties # :nodoc: ["@requirements"] end - def encode_with(coder) + def encode_with coder # :nodoc: coder.add 'requirements', @requirements end @@ -200,15 +228,13 @@ class Gem::Requirement as_list.join ", " end - # DOC: this should probably be :nodoc'd - def == other + def == other # :nodoc: Gem::Requirement === other and to_s == other.to_s end private - # DOC: this should probably be :nodoc'd - def fix_syck_default_key_in_requirements + def fix_syck_default_key_in_requirements # :nodoc: Gem.load_yaml # Fixup the Syck DefaultKey bug @@ -220,9 +246,9 @@ class Gem::Requirement end end -# This is needed for compatibility with older yaml -# gemspecs. - class Gem::Version - Requirement = Gem::Requirement + # This is needed for compatibility with older yaml + # gemspecs. + + Requirement = Gem::Requirement # :nodoc: end diff --git a/lib/rubygems/server.rb b/lib/rubygems/server.rb index 3ca588ae92..ca6dc683f5 100644 --- a/lib/rubygems/server.rb +++ b/lib/rubygems/server.rb @@ -445,7 +445,7 @@ div.method-source-code pre { color: #ffdead; overflow: hidden; } @spec_dirs = @gem_dirs.map { |gem_dir| File.join gem_dir, 'specifications' } @spec_dirs.reject! { |spec_dir| !File.directory? spec_dir } - Gem::Specification.dirs = @gem_dirs + reset_gems @have_rdoc_4_plus = nil end @@ -470,7 +470,7 @@ div.method-source-code pre { color: #ffdead; overflow: hidden; } end def latest_specs(req, res) - Gem::Specification.reset + reset_gems res['content-type'] = 'application/x-gzip' @@ -531,7 +531,7 @@ div.method-source-code pre { color: #ffdead; overflow: hidden; } end def quick(req, res) - Gem::Specification.reset + reset_gems res['content-type'] = 'text/plain' add_date res @@ -567,7 +567,8 @@ div.method-source-code pre { color: #ffdead; overflow: hidden; } end def root(req, res) - Gem::Specification.reset + reset_gems + add_date res raise WEBrick::HTTPStatus::NotFound, "`#{req.path}' not found." unless @@ -698,6 +699,13 @@ div.method-source-code pre { color: #ffdead; overflow: hidden; } end ## + # Updates the server to use the latest installed gems. + + def reset_gems # :nodoc: + Gem::Specification.dirs = @gem_dirs + end + + ## # Returns true and prepares http response, if rdoc for the requested gem # name pattern was found. # @@ -787,7 +795,7 @@ div.method-source-code pre { color: #ffdead; overflow: hidden; } end def specs(req, res) - Gem::Specification.reset + reset_gems add_date res diff --git a/lib/rubygems/source.rb b/lib/rubygems/source.rb index 394ea6cf22..bcf814b010 100644 --- a/lib/rubygems/source.rb +++ b/lib/rubygems/source.rb @@ -52,6 +52,24 @@ class Gem::Source alias_method :eql?, :== + ## + # Returns a Set that can fetch specifications from this source. + + def dependency_resolver_set # :nodoc: + uri = api_uri + + bundler_api_uri = api_uri + './api/v1/dependencies' + + begin + fetcher = Gem::RemoteFetcher.fetcher + fetcher.fetch_path bundler_api_uri, nil, true + rescue Gem::RemoteFetcher::FetchError + Gem::DependencyResolver::IndexSet.new self + else + Gem::DependencyResolver::APISet.new bundler_api_uri + end + end + def hash @uri.hash end diff --git a/lib/rubygems/source/vendor.rb b/lib/rubygems/source/vendor.rb index 701bc7fe84..f2cf540c8d 100644 --- a/lib/rubygems/source/vendor.rb +++ b/lib/rubygems/source/vendor.rb @@ -2,5 +2,10 @@ # This represents a vendored source that is similar to an installed gem. class Gem::Source::Vendor < Gem::Source::Installed + + def initialize uri + @uri = uri + end + end diff --git a/lib/rubygems/source_list.rb b/lib/rubygems/source_list.rb index 7bd8ef0b78..e6da50c2e5 100644 --- a/lib/rubygems/source_list.rb +++ b/lib/rubygems/source_list.rb @@ -1,28 +1,40 @@ require 'rubygems/source' class Gem::SourceList + + include Enumerable + + ## + # Creates a new SourceList + def initialize @sources = [] end + ## + # The sources in this list + attr_reader :sources + ## + # Creates a new SourceList from an array of sources. + def self.from(ary) list = new - if ary - ary.each do |x| - list << x - end - end + list.replace ary return list end - def initialize_copy(other) + def initialize_copy(other) # :nodoc: @sources = @sources.dup end + ## + # Appends +obj+ to the source list which may be a Gem::Source, URI or URI + # String. + def <<(obj) src = case obj when URI @@ -37,8 +49,12 @@ class Gem::SourceList src end + ## + # Replaces this SourceList with the sources in +other+ See #<< for + # acceptable items in +other+. + def replace(other) - @sources.clear + clear other.each do |x| self << x @@ -47,28 +63,58 @@ class Gem::SourceList self end + ## + # Removes all sources from the SourceList. + + def clear + @sources.clear + end + + ## + # Yields each source URI in the list. + def each @sources.each { |s| yield s.uri.to_s } end + ## + # Yields each source in the list. + def each_source(&b) @sources.each(&b) end + ## + # Returns true if there are no sources in this SourceList. + + def empty? + @sources.empty? + end + def ==(other) to_a == other end + ## + # Returns an Array of source URI Strings. + def to_a @sources.map { |x| x.uri.to_s } end alias_method :to_ary, :to_a + ## + # Returns the first source in the list. + def first @sources.first end + ## + # Returns true if this source list includes +other+ which may be a + # Gem::Source or a source URI. + def include?(other) if other.kind_of? Gem::Source @sources.include? other @@ -77,11 +123,14 @@ class Gem::SourceList end end - def delete(uri) - if uri.kind_of? Gem::Source - @sources.delete uri + ## + # Deletes +source+ from the source list which may be a Gem::Source or a URI. + + def delete source + if source.kind_of? Gem::Source + @sources.delete source else - @sources.delete_if { |x| x.uri.to_s == uri.to_s } + @sources.delete_if { |x| x.uri.to_s == source.to_s } end end end diff --git a/lib/rubygems/spec_fetcher.rb b/lib/rubygems/spec_fetcher.rb index 4bef93351f..22fa68db69 100644 --- a/lib/rubygems/spec_fetcher.rb +++ b/lib/rubygems/spec_fetcher.rb @@ -18,6 +18,11 @@ class Gem::SpecFetcher attr_reader :latest_specs # :nodoc: ## + # Sources for this SpecFetcher + + attr_reader :sources # :nodoc: + + ## # Cache of all released specs attr_reader :specs # :nodoc: @@ -37,7 +42,16 @@ class Gem::SpecFetcher @fetcher = fetcher end - def initialize + ## + # Creates a new SpecFetcher. Ordinarily you want to use + # Gem::SpecFetcher::fetcher which uses the Gem.sources. + # + # If you need to retrieve specifications from a different +source+, you can + # send it as an argument. + + def initialize sources = nil + @sources = sources || Gem.sources + @update_cache = begin File.stat(Gem.user_home).uid == Process.uid @@ -197,7 +211,7 @@ class Gem::SpecFetcher errors = [] list = {} - Gem.sources.each_source do |source| + @sources.each_source do |source| begin names = case type when :latest diff --git a/lib/rubygems/specification.rb b/lib/rubygems/specification.rb index 6b8528f238..5330c108ba 100644 --- a/lib/rubygems/specification.rb +++ b/lib/rubygems/specification.rb @@ -856,12 +856,8 @@ class Gem::Specification < Gem::BasicSpecification # this resets the list of known specs. def self.dirs= dirs - # TODO: find extra calls to dir= - # warn "NOTE: dirs= called from #{caller.first} for #{dirs.inspect}" - self.reset - # ugh @@dirs = Array(dirs).map { |dir| File.join dir, "specifications" } end @@ -1105,9 +1101,6 @@ class Gem::Specification < Gem::BasicSpecification # Removes +spec+ from the known specs. def self.remove_spec spec - # TODO: beat on the tests - raise "wtf: #{spec.full_name} not in #{all_names.inspect}" unless - _all.include? spec _all.delete spec stubs.delete_if { |s| s.full_name == spec.full_name } end @@ -1400,7 +1393,7 @@ class Gem::Specification < Gem::BasicSpecification # Returns the build_args used to install the gem def build_args - if File.exist? build_info_file + if File.exists? build_info_file File.readlines(build_info_file).map { |x| x.strip } else [] @@ -1788,6 +1781,7 @@ class Gem::Specification < Gem::BasicSpecification end def init_with coder # :nodoc: + @installed_by_version ||= nil yaml_initialize coder.tag, coder.map end @@ -2293,9 +2287,9 @@ class Gem::Specification < Gem::BasicSpecification end end - if defined?(@installed_by_version) && @installed_by_version then + if @installed_by_version then result << nil - result << " s.installed_by_version = \"#{Gem::VERSION}\"" + result << " s.installed_by_version = \"#{Gem::VERSION}\" if s.respond_to? :installed_by_version" end unless dependencies.empty? then @@ -2488,7 +2482,6 @@ class Gem::Specification < Gem::BasicSpecification end end - # FIX: uhhhh single element array.each? [:authors].each do |field| val = self.send field raise Gem::InvalidSpecificationException, "#{field} may not be empty" if @@ -2540,7 +2533,6 @@ licenses is empty. Use a license abbreviation from: # reject lazy developers: - # FIX: Doesn't this just evaluate to "FIXME" or "TODO"? lazy = '"FIxxxXME" or "TOxxxDO"'.gsub(/xxx/, '') unless authors.grep(/FI XME|TO DO/x).empty? then @@ -2586,19 +2578,80 @@ licenses is empty. Use a license abbreviation from: warning "#{executable_path} is missing #! line" unless shebang end + validate_dependencies + + true + ensure + if $! or @warnings > 0 then + alert_warning "See http://guides.rubygems.org/specification-reference/ for help" + end + end + + ## + # Checks that dependencies use requirements as we recommend. Warnings are + # issued when dependencies are open-ended or overly strict for semantic + # versioning. + + def validate_dependencies # :nodoc: + seen = {} + dependencies.each do |dep| + if prev = seen[dep.name] then + raise Gem::InvalidSpecificationException, <<-MESSAGE +duplicate dependency on #{dep}, (#{prev.requirement}) use: + add_runtime_dependency '#{dep.name}', '#{dep.requirement}', '#{prev.requirement}' + MESSAGE + end + + seen[dep.name] = dep + prerelease_dep = dep.requirements_list.any? do |req| Gem::Requirement.new(req).prerelease? end warning "prerelease dependency on #{dep} is not recommended" if prerelease_dep - end - true - ensure - if $! or @warnings > 0 then - alert_warning "See http://guides.rubygems.org/specification-reference/ for help" + overly_strict = dep.requirement.requirements.length == 1 && + dep.requirement.requirements.any? do |op, version| + op == '~>' and + not version.prerelease? and + version.segments.length > 2 + end + + if overly_strict then + _, dep_version = dep.requirement.requirements.first + + base = dep_version.segments.first 2 + + warning <<-WARNING +pessimistic dependency on #{dep} may be overly strict + if #{dep.name} is semantically versioned, use: + add_#{dep.type}_dependency '#{dep.name}', '~> #{base.join '.'}', '>= #{dep_version}' + WARNING + end + + open_ended = dep.requirement.requirements.all? do |op, version| + not version.prerelease? and (op == '>' or op == '>=') + end + + if open_ended then + op, dep_version = dep.requirement.requirements.first + + base = dep_version.segments.first 2 + + bugfix = if op == '>' then + ", '> #{dep_version}'" + elsif op == '>=' and base != dep_version.segments then + ", '>= #{dep_version}'" + end + + warning <<-WARNING +open-ended dependency on #{dep} is not recommended + if #{dep.name} is semantically versioned, use: + add_#{dep.type}_dependency '#{dep.name}', '~> #{base.join '.'}'#{bugfix} + WARNING + end end end @@ -2633,7 +2686,10 @@ licenses is empty. Use a license abbreviation from: return @version end - # FIX: have this handle the platform/new_platform/original_platform bullshit + def stubbed? + false + end + def yaml_initialize(tag, vals) # :nodoc: vals.each do |ivar, val| case ivar @@ -2667,6 +2723,8 @@ licenses is empty. Use a license abbreviation from: instance_variable_set "@#{attribute}", value end + + @installed_by_version ||= nil end def warning statement # :nodoc: diff --git a/lib/rubygems/ssl_certs/DigiCertHighAssuranceEVRootCA.pem b/lib/rubygems/ssl_certs/DigiCertHighAssuranceEVRootCA.pem new file mode 100644 index 0000000000..9e6810ab70 --- /dev/null +++ b/lib/rubygems/ssl_certs/DigiCertHighAssuranceEVRootCA.pem @@ -0,0 +1,23 @@ +-----BEGIN CERTIFICATE----- +MIIDxTCCAq2gAwIBAgIQAqxcJmoLQJuPC3nyrkYldzANBgkqhkiG9w0BAQUFADBs +MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMSswKQYDVQQDEyJEaWdpQ2VydCBIaWdoIEFzc3VyYW5j +ZSBFViBSb290IENBMB4XDTA2MTExMDAwMDAwMFoXDTMxMTExMDAwMDAwMFowbDEL +MAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3 +LmRpZ2ljZXJ0LmNvbTErMCkGA1UEAxMiRGlnaUNlcnQgSGlnaCBBc3N1cmFuY2Ug +RVYgUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMbM5XPm ++9S75S0tMqbf5YE/yc0lSbZxKsPVlDRnogocsF9ppkCxxLeyj9CYpKlBWTrT3JTW +PNt0OKRKzE0lgvdKpVMSOO7zSW1xkX5jtqumX8OkhPhPYlG++MXs2ziS4wblCJEM +xChBVfvLWokVfnHoNb9Ncgk9vjo4UFt3MRuNs8ckRZqnrG0AFFoEt7oT61EKmEFB +Ik5lYYeBQVCmeVyJ3hlKV9Uu5l0cUyx+mM0aBhakaHPQNAQTXKFx01p8VdteZOE3 +hzBWBOURtCmAEvF5OYiiAhF8J2a3iLd48soKqDirCmTCv2ZdlYTBoSUeh10aUAsg +EsxBu24LUTi4S8sCAwEAAaNjMGEwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQF +MAMBAf8wHQYDVR0OBBYEFLE+w2kD+L9HAdSYJhoIAu9jZCvDMB8GA1UdIwQYMBaA +FLE+w2kD+L9HAdSYJhoIAu9jZCvDMA0GCSqGSIb3DQEBBQUAA4IBAQAcGgaX3Nec +nzyIZgYIVyHbIUf4KmeqvxgydkAQV8GK83rZEWWONfqe/EW1ntlMMUu4kehDLI6z +eM7b41N5cdblIZQB2lWHmiRk9opmzN6cN82oNLFpmyPInngiK3BD41VHMWEZ71jF +hS9OMPagMRYjyOfiZRYzy78aG6A9+MpeizGLYAiJLQwGXFK3xPkKmNEVX58Svnw2 +Yzi9RKR/5CYrCsSXaQ3pjOLAEFe4yHYSkVXySGnYvCoCWw9E1CAx2/S6cCZdkGCe +vEsXCS+0yx5DaMkHJ8HSXPfqIbloEpw8nL+e/IBcm2PN7EeqJSdnoDfzAIJ9VNep ++OkuE6N36B9K +-----END CERTIFICATE----- diff --git a/lib/rubygems/test_case.rb b/lib/rubygems/test_case.rb index de85c0fceb..08109c6405 100644 --- a/lib/rubygems/test_case.rb +++ b/lib/rubygems/test_case.rb @@ -241,6 +241,8 @@ class Gem::TestCase < MiniTest::Unit::TestCase @orig_ENV_HOME = ENV['HOME'] ENV['HOME'] = @userhome Gem.instance_variable_set :@user_home, nil + Gem.send :remove_instance_variable, :@ruby_version if + Gem.instance_variables.include? :@ruby_version FileUtils.mkdir_p @gemhome FileUtils.mkdir_p @userhome @@ -376,7 +378,7 @@ class Gem::TestCase < MiniTest::Unit::TestCase gem = File.join @tempdir, "gems", "#{spec.full_name}.gem" - unless File.exist? gem + unless File.exists? gem use_ui Gem::MockGemUi.new do Dir.chdir @tempdir do Gem::Package.build spec @@ -898,14 +900,35 @@ Also, a list: spec_fetcher.prerelease_specs[@uri] << spec.name_tuple end - v = Gem.marshal_version + # HACK for test_download_to_cache + unless Gem::RemoteFetcher === @fetcher then + v = Gem.marshal_version - Gem::Specification.each do |spec| - path = "#{@gem_repo}quick/Marshal.#{v}/#{spec.original_name}.gemspec.rz" - data = Marshal.dump spec - data_deflate = Zlib::Deflate.deflate data - @fetcher.data[path] = data_deflate - end unless Gem::RemoteFetcher === @fetcher # HACK for test_download_to_cache + 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| + spec.name_tuple + end + + l_zip = util_gzip Marshal.dump Gem::NameTuple.to_basic latest_specs + + prerelease_specs = prerelease.map { |spec| spec.name_tuple } + p_zip = util_gzip Marshal.dump Gem::NameTuple.to_basic prerelease_specs + + @fetcher.data["#{@gem_repo}specs.#{v}.gz"] = s_zip + @fetcher.data["#{@gem_repo}latest_specs.#{v}.gz"] = l_zip + @fetcher.data["#{@gem_repo}prerelease_specs.#{v}.gz"] = p_zip + + v = Gem.marshal_version + + Gem::Specification.each do |spec| + path = "#{@gem_repo}quick/Marshal.#{v}/#{spec.original_name}.gemspec.rz" + data = Marshal.dump spec + data_deflate = Zlib::Deflate.deflate data + @fetcher.data[path] = data_deflate + end + end nil # force errors end @@ -1089,6 +1112,62 @@ Also, a list: end ## + # Creates a SpecFetcher pre-filled with the gems or specs defined in the + # block. + # + # Yields a +fetcher+ object that responds to +spec+ and +gem+. +spec+ adds + # a specification to the SpecFetcher while +gem+ adds both a specification + # and the gem data to the RemoteFetcher so the built gem can be downloaded. + # + # If only the a-3 gem is supposed to be downloaded you can save setup + # time by creating only specs for the other versions: + # + # spec_fetcher do |fetcher| + # fetcher.spec 'a', 1 + # fetcher.spec 'a', 2, 'b' => 3 # dependency on b = 3 + # fetcher.gem 'a', 3 do |spec| + # # spec is a Gem::Specification + # # ... + # end + # end + + def spec_fetcher + gems = {} + + fetcher = Object.new + fetcher.instance_variable_set :@test, self + fetcher.instance_variable_set :@gems, gems + + def fetcher.gem name, version, dependencies = nil, &block + spec, gem = @test.util_gem name, version, dependencies, &block + + @gems[spec] = gem + + spec + end + + def fetcher.spec name, version, dependencies = nil, &block + spec = @test.util_spec name, version, dependencies, &block + + @gems[spec] = nil + + spec + end + + yield fetcher + + util_setup_fake_fetcher unless @fetcher + util_setup_spec_fetcher(*gems.keys) + + gems.each do |spec, gem| + next unless gem + + @fetcher.data["http://gems.example.com/gems/#{spec.file_name}"] = + Gem.read_binary(gem) + end + end + + ## # Construct a new Gem::Version. def v string diff --git a/lib/rubygems/version.rb b/lib/rubygems/version.rb index 15e72292b1..fda8b0b5d4 100644 --- a/lib/rubygems/version.rb +++ b/lib/rubygems/version.rb @@ -145,8 +145,6 @@ class Gem::Version include Comparable - # FIX: These are only used once, in .correct?. Do they deserve to be - # constants? VERSION_PATTERN = '[0-9]+(?>\.[0-9a-zA-Z]+)*(-[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*)?' # :nodoc: ANCHORED_VERSION_PATTERN = /\A\s*(#{VERSION_PATTERN})?\s*\z/ # :nodoc: diff --git a/test/rubygems/test_bundled_ca.rb b/test/rubygems/test_bundled_ca.rb index d2ccdaf484..711cd1b8f2 100644 --- a/test/rubygems/test_bundled_ca.rb +++ b/test/rubygems/test_bundled_ca.rb @@ -34,7 +34,7 @@ class TestBundledCA < Gem::TestCase http.verify_mode = OpenSSL::SSL::VERIFY_PEER http.cert_store = bundled_certificate_store http.get('/') - rescue Errno::ENOENT + rescue Errno::ENOENT, Errno::ETIMEDOUT skip "#{host} seems offline, I can't tell whether ssl would work." rescue OpenSSL::SSL::SSLError => e # Only fail for certificate verification errors diff --git a/test/rubygems/test_gem.rb b/test/rubygems/test_gem.rb index 88d0b1c432..f42225635e 100644 --- a/test/rubygems/test_gem.rb +++ b/test/rubygems/test_gem.rb @@ -420,12 +420,13 @@ class TestGem < Gem::TestCase end def test_self_latest_spec_for - a1 = quick_spec 'a', 1 - a2 = quick_spec 'a', 2 - a3a = quick_spec 'a', '3.a' + a2 = nil - util_setup_fake_fetcher - util_setup_spec_fetcher a1, a2, a3a + spec_fetcher do |fetcher| + fetcher.spec 'a', 1 + fetcher.spec 'a', '3.a' + a2 = fetcher.spec 'a', 2 + end spec = Gem.latest_spec_for 'a' @@ -433,12 +434,11 @@ class TestGem < Gem::TestCase end def test_self_latest_rubygems_version - r1 = quick_spec 'rubygems-update', '1.8.23' - r2 = quick_spec 'rubygems-update', '1.8.24' - r3 = quick_spec 'rubygems-update', '2.0.0.preview3' - - util_setup_fake_fetcher - util_setup_spec_fetcher r1, r2, r3 + spec_fetcher do |fetcher| + fetcher.spec 'rubygems-update', '1.8.23' + fetcher.spec 'rubygems-update', '1.8.24' + fetcher.spec 'rubygems-update', '2.0.0.preview3' + end version = Gem.latest_rubygems_version @@ -446,12 +446,11 @@ class TestGem < Gem::TestCase end def test_self_latest_version_for - a1 = quick_spec 'a', 1 - a2 = quick_spec 'a', 2 - a3a = quick_spec 'a', '3.a' - - util_setup_fake_fetcher - util_setup_spec_fetcher a1, a2, a3a + spec_fetcher do |fetcher| + fetcher.spec 'a', 1 + fetcher.spec 'a', 2 + fetcher.spec 'a', '3.a' + end version = Gem.latest_version_for 'a' diff --git a/test/rubygems/test_gem_commands_dependency_command.rb b/test/rubygems/test_gem_commands_dependency_command.rb index a5670e7575..99b04bcd19 100644 --- a/test/rubygems/test_gem_commands_dependency_command.rb +++ b/test/rubygems/test_gem_commands_dependency_command.rb @@ -168,17 +168,10 @@ ERROR: Only reverse dependencies for local gems are supported. end def test_execute_remote - foo = quick_gem 'foo' do |gem| - gem.add_dependency 'bar', '> 1' + spec_fetcher do |fetcher| + fetcher.spec 'foo', 2, 'bar' => '> 1' end - @fetcher = Gem::FakeFetcher.new - Gem::RemoteFetcher.fetcher = @fetcher - - util_setup_spec_fetcher foo - - FileUtils.rm File.join(@gemhome, 'specifications', foo.spec_name) - @cmd.options[:args] = %w[foo] @cmd.options[:domain] = :remote diff --git a/test/rubygems/test_gem_commands_install_command.rb b/test/rubygems/test_gem_commands_install_command.rb index 625d85e372..55246502a5 100644 --- a/test/rubygems/test_gem_commands_install_command.rb +++ b/test/rubygems/test_gem_commands_install_command.rb @@ -855,4 +855,14 @@ ERROR: Possible alternatives: non_existent_with_hint assert_equal 'gem.deps.rb', @cmd.options[:gemdeps] end + def test_handle_options_without + @cmd.handle_options %w[--without test] + + assert_equal [:test], @cmd.options[:without_groups] + + @cmd.handle_options %w[--without test,development] + + assert_equal [:test, :development], @cmd.options[:without_groups] + end + end diff --git a/test/rubygems/test_gem_commands_outdated_command.rb b/test/rubygems/test_gem_commands_outdated_command.rb index 72696d6549..eee273af5c 100644 --- a/test/rubygems/test_gem_commands_outdated_command.rb +++ b/test/rubygems/test_gem_commands_outdated_command.rb @@ -14,13 +14,10 @@ class TestGemCommandsOutdatedCommand < Gem::TestCase end def test_execute - remote_10 = quick_spec 'foo', '1.0' - remote_20 = quick_spec 'foo', '2.0' - - Gem::RemoteFetcher.fetcher = @fetcher = Gem::FakeFetcher.new - - util_clear_gems - util_setup_spec_fetcher remote_10, remote_20 + spec_fetcher do |fetcher| + fetcher.spec 'foo', '1.0' + fetcher.spec 'foo', '2.0' + end quick_gem 'foo', '0.1' quick_gem 'foo', '0.2' diff --git a/test/rubygems/test_gem_commands_specification_command.rb b/test/rubygems/test_gem_commands_specification_command.rb index 252d0bdd55..fb28302cc3 100644 --- a/test/rubygems/test_gem_commands_specification_command.rb +++ b/test/rubygems/test_gem_commands_specification_command.rb @@ -152,14 +152,9 @@ class TestGemCommandsSpecificationCommand < Gem::TestCase end def test_execute_remote - foo = quick_gem 'foo' - - @fetcher = Gem::FakeFetcher.new - Gem::RemoteFetcher.fetcher = @fetcher - - util_setup_spec_fetcher foo - - FileUtils.rm File.join(@gemhome, 'specifications', foo.spec_name) + spec_fetcher do |fetcher| + fetcher.spec 'foo', 1 + end @cmd.options[:args] = %w[foo] @cmd.options[:domain] = :remote @@ -173,16 +168,10 @@ class TestGemCommandsSpecificationCommand < Gem::TestCase end def test_execute_remote_with_version - foo1 = quick_gem 'foo', "1" - foo2 = quick_gem 'foo', "2" - - @fetcher = Gem::FakeFetcher.new - Gem::RemoteFetcher.fetcher = @fetcher - - util_setup_spec_fetcher foo1, foo2 - - FileUtils.rm File.join(@gemhome, 'specifications', foo1.spec_name) - FileUtils.rm File.join(@gemhome, 'specifications', foo2.spec_name) + spec_fetcher do |fetcher| + fetcher.spec 'foo', "1" + fetcher.spec 'foo', "2" + end @cmd.options[:args] = %w[foo] @cmd.options[:version] = "1" @@ -198,16 +187,12 @@ class TestGemCommandsSpecificationCommand < Gem::TestCase end def test_execute_remote_without_prerelease - foo = new_spec 'foo', '2.0.0' - foo_pre = new_spec 'foo', '2.0.1.pre' - - install_specs foo, foo_pre + spec_fetcher do |fetcher| + foo = fetcher.spec 'foo', '2.0.0' + foo_pre = fetcher.spec 'foo', '2.0.1.pre' - @fetcher = Gem::FakeFetcher.new - Gem::RemoteFetcher.fetcher = @fetcher - - util_setup_spec_fetcher foo - util_setup_spec_fetcher foo_pre + install_specs foo, foo_pre + end @cmd.options[:args] = %w[foo] @cmd.options[:domain] = :remote @@ -225,16 +210,12 @@ class TestGemCommandsSpecificationCommand < Gem::TestCase end def test_execute_remote_with_prerelease - foo = new_spec 'foo', '2.0.0' - foo_pre = new_spec 'foo', '2.0.1.pre' + spec_fetcher do |fetcher| + foo = fetcher.spec 'foo', '2.0.0' + foo_pre = fetcher.spec 'foo', '2.0.1.pre' - install_specs foo, foo_pre - - @fetcher = Gem::FakeFetcher.new - Gem::RemoteFetcher.fetcher = @fetcher - - util_setup_spec_fetcher foo - util_setup_spec_fetcher foo_pre + install_specs foo, foo_pre + end @cmd.options[:args] = %w[foo] @cmd.options[:domain] = :remote diff --git a/test/rubygems/test_gem_dependency_installer.rb b/test/rubygems/test_gem_dependency_installer.rb index 8433be6bd0..f8c3dd493b 100644 --- a/test/rubygems/test_gem_dependency_installer.rb +++ b/test/rubygems/test_gem_dependency_installer.rb @@ -682,7 +682,7 @@ class TestGemDependencyInstaller < Gem::TestCase inst.install 'b' end - expected = "Unable to resolve dependency: b (= 1) requires a (>= 0)" + expected = "Unable to resolve dependency: 'b (= 1)' requires 'a (>= 0)'" assert_equal expected, e.message end @@ -816,6 +816,17 @@ class TestGemDependencyInstaller < Gem::TestCase assert_equal %w[a-1], inst.installed_gems.map { |s| s.full_name } end + def test_install_platform_is_ignored_when_a_file_is_specified + _, a_gem = util_gem 'a', '1' do |s| + s.platform = Gem::Platform.new %w[cpu other_platform 1] + end + + inst = Gem::DependencyInstaller.new :domain => :local + inst.install a_gem + + assert_equal %w[a-1-cpu-other_platform-1], inst.installed_gems.map { |s| s.full_name } + end + if defined? OpenSSL then def test_install_security_policy util_setup_gems @@ -904,6 +915,29 @@ class TestGemDependencyInstaller < Gem::TestCase assert_equal Gem::Source.new(@gem_repo), s.source end + def test_find_spec_by_name_and_version_wildcard + util_gem 'a', 1 + FileUtils.mv 'gems/a-1.gem', @tempdir + + FileUtils.touch 'rdoc.gem' + + inst = Gem::DependencyInstaller.new + + available = inst.find_spec_by_name_and_version('*.gem') + + assert_equal %w[a-1], available.each_spec.map { |spec| spec.full_name } + end + + def test_find_spec_by_name_and_version_wildcard_bad_gem + FileUtils.touch 'rdoc.gem' + + inst = Gem::DependencyInstaller.new + + assert_raises Gem::Package::FormatError do + inst.find_spec_by_name_and_version '*.gem' + end + end + def test_find_spec_by_name_and_version_bad_gem FileUtils.touch 'rdoc.gem' diff --git a/test/rubygems/test_gem_dependency_resolution_error.rb b/test/rubygems/test_gem_dependency_resolution_error.rb new file mode 100644 index 0000000000..7f8cf0c5cc --- /dev/null +++ b/test/rubygems/test_gem_dependency_resolution_error.rb @@ -0,0 +1,28 @@ +require 'rubygems/test_case' + +class TestGemDependencyResolutionError < Gem::TestCase + + def setup + super + + @DR = Gem::DependencyResolver + + @spec = quick_spec 'a', 2 + + @a1_req = @DR::DependencyRequest.new dep('a', '= 1'), nil + @a2_req = @DR::DependencyRequest.new dep('a', '= 2'), nil + + @activated = @DR::ActivationRequest.new @spec, @a2_req + + @conflict = @DR::DependencyConflict.new @a1_req, @activated + + @error = Gem::DependencyResolutionError.new @conflict + end + + def test_message + assert_match %r%^conflicting dependencies a \(= 1\) and a \(= 2\)$%, + @error.message + end + +end + diff --git a/test/rubygems/test_gem_dependency_resolver.rb b/test/rubygems/test_gem_dependency_resolver.rb index bd349996c1..7d968494b8 100644 --- a/test/rubygems/test_gem_dependency_resolver.rb +++ b/test/rubygems/test_gem_dependency_resolver.rb @@ -3,6 +3,12 @@ require 'rubygems/dependency_resolver' class TestGemDependencyResolver < Gem::TestCase + def setup + super + + @DR = Gem::DependencyResolver + end + def make_dep(name, *req) Gem::Dependency.new(name, *req) end @@ -21,7 +27,58 @@ class TestGemDependencyResolver < Gem::TestCase assert_equal exp, act, msg rescue Gem::DependencyResolutionError => e - flunk "#{e.message}\n#{e.conflict.explanation}" + flunk e.message + end + + def test_self_compose_sets_multiple + index_set = @DR::IndexSet.new + vendor_set = @DR::VendorSet.new + + composed = @DR.compose_sets index_set, vendor_set + + assert_kind_of Gem::DependencyResolver::ComposedSet, composed + + assert_equal [index_set, vendor_set], composed.sets + end + + def test_self_compose_sets_nil + index_set = @DR::IndexSet.new + + composed = @DR.compose_sets index_set, nil + + assert_same index_set, composed + + e = assert_raises ArgumentError do + @DR.compose_sets nil + end + + assert_equal 'one set in the composition must be non-nil', e.message + end + + def test_self_compose_sets_single + index_set = @DR::IndexSet.new + + composed = @DR.compose_sets index_set + + assert_same index_set, composed + end + + def test_handle_conflict + a1 = util_spec 'a', 1 + + r1 = Gem::DependencyResolver::DependencyRequest.new dep('a', '= 1'), nil + r2 = Gem::DependencyResolver::DependencyRequest.new dep('a', '= 2'), nil + r3 = Gem::DependencyResolver::DependencyRequest.new dep('a', '= 3'), nil + + existing = Gem::DependencyResolver::ActivationRequest.new a1, r1, false + + res = Gem::DependencyResolver.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_no_overlap_specificly @@ -71,10 +128,15 @@ class TestGemDependencyResolver < Gem::TestCase end def test_picks_best_platform - is = Gem::DependencyResolver::IndexSpecification + is = Gem::DependencyResolver::IndexSpecification unknown = Gem::Platform.new 'unknown' - a2_p1 = quick_spec 'a', 2 do |s| s.platform = Gem::Platform.local end - a3_p2 = quick_spec 'a', 3 do |s| s.platform = unknown end + a2_p1 = a3_p2 = nil + + spec_fetcher do |fetcher| + a2_p1 = fetcher.spec 'a', 2 do |s| s.platform = Gem::Platform.local end + a3_p2 = fetcher.spec 'a', 3 do |s| s.platform = unknown end + end + v2 = v(2) v3 = v(3) source = Gem::Source.new @gem_repo @@ -183,8 +245,6 @@ class TestGemDependencyResolver < Gem::TestCase r.resolve end - assert_equal "unable to resolve conflicting dependencies 'c (= 2)' and 'c (= 1)'", e.message - deps = [make_dep("c", "= 2"), make_dep("c", "= 1")] assert_equal deps, e.conflicting_dependencies @@ -209,7 +269,7 @@ class TestGemDependencyResolver < Gem::TestCase r.resolve end - assert_equal "Unable to resolve dependency: (unknown) requires a (>= 0)", + assert_equal "Unable to resolve dependency: user requested 'a (>= 0)'", e.message assert_equal "a (>= 0)", e.dependency.to_s @@ -229,6 +289,37 @@ class TestGemDependencyResolver < Gem::TestCase assert_equal "a (= 3)", e.dependency.to_s end + def test_raises_and_reports_a_toplevel_request_properly + a1 = util_spec "a", "1" + ad = make_dep "a", "= 3" + + r = Gem::DependencyResolver.new([ad], set(a1)) + + e = assert_raises Gem::UnsatisfiableDepedencyError do + r.resolve + end + + assert_equal "Unable to resolve dependency: user requested 'a (= 3)'", + e.message + end + + def test_raises_and_reports_an_implicit_request_properly + a1 = util_spec "a", "1" do |s| + s.add_runtime_dependency 'b', '= 2' + end + + ad = make_dep "a", "= 1" + + r = Gem::DependencyResolver.new([ad], set(a1)) + + e = assert_raises Gem::UnsatisfiableDepedencyError do + r.resolve + end + + assert_equal "Unable to resolve dependency: 'a (= 1)' requires 'b (= 2)'", + e.message + end + def test_raises_when_possibles_are_exhausted a1 = util_spec "a", "1", "c" => ">= 2" b1 = util_spec "b", "1", "c" => "= 1" @@ -244,18 +335,22 @@ class TestGemDependencyResolver < Gem::TestCase r = Gem::DependencyResolver.new([ad, bd], s) - e = assert_raises Gem::ImpossibleDependenciesError do + e = assert_raises Gem::DependencyResolutionError do r.resolve end - assert_match "a-1 requires c (>= 2) but it conflicted", e.message + dependency = e.conflict.dependency - assert_equal "c (>= 2)", e.dependency.to_s + assert_equal 'a', dependency.name + assert_equal req('>= 0'), dependency.requirement - s, con = e.conflicts[0] - assert_equal "c-3", s.full_name - assert_equal "c (= 1)", con.dependency.to_s - assert_equal "b-1", con.requester.full_name + activated = e.conflict.activated + assert_equal 'c-2', activated.full_name + + assert_equal dep('c', '>= 2'), activated.request.dependency + + assert_equal [dep('c', '= 1'), dep('c', '>= 2')], + e.conflict.conflicting_dependencies end def test_keeps_resolving_after_seeing_satisfied_dep @@ -312,7 +407,7 @@ class TestGemDependencyResolver < Gem::TestCase r = Gem::DependencyResolver.new([d1, d2, d3], s) - assert_raises Gem::ImpossibleDependenciesError do + assert_raises Gem::DependencyResolutionError do r.resolve end end @@ -335,6 +430,42 @@ class TestGemDependencyResolver < Gem::TestCase end end + def test_resolve_bug_699 + a1 = util_spec 'a', '1', 'b' => '= 2', + 'c' => '~> 1.0.3' + + b1 = util_spec 'b', '2', 'c' => '~> 1.0' + + c1 = util_spec 'c', '1.0.9' + c2 = util_spec 'c', '1.1.0' + c3 = util_spec 'c', '1.2.0' + + s = set a1, b1, c1, c2, c3 + + a_dep = dep 'a', '= 1' + + r = Gem::DependencyResolver.new [a_dep], s + + assert_resolves_to [a1, b1, c1], r + end + + def test_resolve_rollback + a1 = util_spec 'a', 1 + a2 = util_spec 'a', 2 + + b1 = util_spec 'b', 1, 'a' => '~> 1.0' + b2 = util_spec 'b', 2, 'a' => '~> 2.0' + + s = set a1, a2, b1, b2 + + a_dep = dep 'a', '~> 1.0' + b_dep = dep 'b' + + r = Gem::DependencyResolver.new [a_dep, b_dep], s + + assert_resolves_to [a1, b1], r + end + # actionmailer 2.3.4 # activemerchant 1.5.0 # activesupport 2.3.5, 2.3.4 @@ -388,5 +519,22 @@ class TestGemDependencyResolver < Gem::TestCase assert_equal [a1, a1_p1], selected end + def test_raises_and_explains_when_platform_prevents_install + a1 = util_spec "a", "1" do |s| + s.platform = Gem::Platform.new %w[c p 1] + end + + ad = make_dep "a", "= 1" + + r = Gem::DependencyResolver.new([ad], set(a1)) + + e = assert_raises Gem::UnsatisfiableDepedencyError do + r.resolve + end + + assert_match "No match for 'a (= 1)' on this platform. Found: c-p-1", + e.message + end + end diff --git a/test/rubygems/test_gem_dependency_resolver_activation_request.rb b/test/rubygems/test_gem_dependency_resolver_activation_request.rb new file mode 100644 index 0000000000..ac554d128c --- /dev/null +++ b/test/rubygems/test_gem_dependency_resolver_activation_request.rb @@ -0,0 +1,63 @@ +require 'rubygems/test_case' + +class TestGemDependencyResolverActivationRequest < Gem::TestCase + + def setup + super + + @DR = Gem::DependencyResolver + + @dep = @DR::DependencyRequest.new dep('a', '>= 0'), nil + + source = Gem::Source::Local.new + platform = Gem::Platform::RUBY + + @a1 = @DR::IndexSpecification.new nil, 'a', v(1), source, platform + @a2 = @DR::IndexSpecification.new nil, 'a', v(2), source, platform + @a3 = @DR::IndexSpecification.new nil, 'a', v(3), source, platform + + @req = @DR::ActivationRequest.new @a3, @dep, [@a1, @a2] + end + + def test_inspect + assert_match 'a-3', @req.inspect + assert_match 'from a (>= 0)', @req.inspect + assert_match '(others possible: a-1, a-2)', @req.inspect + end + + def test_inspect_legacy + req = @DR::ActivationRequest.new @a3, @dep, true + + assert_match '(others possible)', req.inspect + + req = @DR::ActivationRequest.new @a3, @dep, false + + refute_match '(others possible)', req.inspect + end + + def test_installed_eh + v_spec = Gem::DependencyResolver::VendorSpecification.new nil, @a3 + + @req = @DR::ActivationRequest.new v_spec, @dep, [@a1, @a2] + + assert @req.installed? + end + + def test_others_possible_eh + assert @req.others_possible? + + req = @DR::ActivationRequest.new @a3, @dep, [] + + refute req.others_possible? + + req = @DR::ActivationRequest.new @a3, @dep, true + + assert req.others_possible? + + req = @DR::ActivationRequest.new @a3, @dep, false + + refute req.others_possible? + end + +end + diff --git a/test/rubygems/test_gem_dependency_resolver_api_set.rb b/test/rubygems/test_gem_dependency_resolver_api_set.rb index c1e7eda191..ef99b6ca7f 100644 --- a/test/rubygems/test_gem_dependency_resolver_api_set.rb +++ b/test/rubygems/test_gem_dependency_resolver_api_set.rb @@ -7,73 +7,20 @@ class TestGemDependencyResolverAPISet < Gem::TestCase super @DR = Gem::DependencyResolver - - @api_set = @DR::APISet.new - @uri = 'https://rubygems.org/api/v1/dependencies' - @fetcher = Gem::FakeFetcher.new - Gem::RemoteFetcher.fetcher = @fetcher - end - - def test_find_all - b_entry = { - :name => 'b', - :number => '2', - :platform => 'ruby', - :dependencies => [['a', '>= 0']], - } - - @fetcher.data["#{@uri}?gems=b"] = Marshal.dump [b_entry] - - b_req = @DR::DependencyRequest.new dep('b', '>= 0'), nil - - expected = [ - @DR::APISpecification.new(@api_set, b_entry) - ] - - assert_equal expected, @api_set.find_all(b_req) end - def test_prefetch - b_entry = { - :name => 'b', - :number => '2', - :platform => 'ruby', - :dependencies => [['a', '>= 0']], - } + def test_initialize + set = @DR::APISet.new - a_entry = { - :name => 'a', - :number => '2', - :platform => 'ruby', - :dependencies => [], - } - - @fetcher.data["#{@uri}?gems=a,b"] = Marshal.dump [a_entry, b_entry] - - a_req = @DR::DependencyRequest.new dep('a', '>= 0'), nil - b_req = @DR::DependencyRequest.new dep('b', '>= 0'), nil - - @api_set.prefetch([b_req, a_req]) - - assert_equal [a_entry], @api_set.versions('a') - assert_equal [b_entry], @api_set.versions('b') + assert_equal URI('https://rubygems.org/api/v1/dependencies'), + set.dep_uri end - def test_versions_cache - entry = { - :name => 'b', - :number => '2', - :platform => 'ruby', - :dependencies => [['a', '>= 0']], - } - - @fetcher.data["#{@uri}?gems=b"] = Marshal.dump [entry] - - assert_equal [entry], @api_set.versions('b') - - @fetcher.data["#{@uri}?gems=b"] = 'garbage' + def test_initialize_uri + set = @DR::APISet.new @gem_repo - assert_equal [entry], @api_set.versions('b'), 'version data must be cached' + assert_equal URI('http://gems.example.com/'), + set.dep_uri end end diff --git a/test/rubygems/test_gem_dependency_resolver_best_set.rb b/test/rubygems/test_gem_dependency_resolver_best_set.rb new file mode 100644 index 0000000000..20fbf4514f --- /dev/null +++ b/test/rubygems/test_gem_dependency_resolver_best_set.rb @@ -0,0 +1,31 @@ +require 'rubygems/test_case' +require 'rubygems/dependency_resolver' + +class TestGemDependencyResolverBestSet < Gem::TestCase + + def setup + super + + @DR = Gem::DependencyResolver + end + + def test_find_all_index + spec_fetcher do |fetcher| + fetcher.spec 'a', 1 + fetcher.spec 'a', 2 + fetcher.spec 'b', 1 + end + + set = @DR::BestSet.new + + dependency = dep 'a', '~> 1' + + req = @DR::DependencyRequest.new dependency, nil + + found = set.find_all req + + assert_equal %w[a-1], found.map { |s| s.full_name } + end + +end + diff --git a/test/rubygems/test_gem_dependency_resolver_dependency_conflict.rb b/test/rubygems/test_gem_dependency_resolver_dependency_conflict.rb index 5043865e5c..964997568a 100644 --- a/test/rubygems/test_gem_dependency_resolver_dependency_conflict.rb +++ b/test/rubygems/test_gem_dependency_resolver_dependency_conflict.rb @@ -20,6 +20,26 @@ class TestGemDependencyResolverDependencyConflict < Gem::TestCase assert_equal expected, conflict.explanation end + def test_explanation_user_request + @DR = Gem::DependencyResolver + + spec = quick_spec 'a', 2 + + a1_req = @DR::DependencyRequest.new dep('a', '= 1'), nil + a2_req = @DR::DependencyRequest.new dep('a', '= 2'), nil + + activated = @DR::ActivationRequest.new spec, a2_req + + conflict = @DR::DependencyConflict.new a1_req, activated + + expected = <<-EXPECTED + Activated a-2 instead of (= 1) via: + user request (gem command or Gemfile) + EXPECTED + + assert_equal expected, conflict.explanation + end + def test_request_path root = dependency_request dep('net-ssh', '>= 2.0.13'), 'rye', '0.9.8' diff --git a/test/rubygems/test_gem_dependency_resolver_dependency_request.rb b/test/rubygems/test_gem_dependency_resolver_dependency_request.rb new file mode 100644 index 0000000000..f5b3a6960e --- /dev/null +++ b/test/rubygems/test_gem_dependency_resolver_dependency_request.rb @@ -0,0 +1,20 @@ +require 'rubygems/test_case' + +class TestGemDependencyResolverDependencyRequest < Gem::TestCase + + def setup + super + + @DR = Gem::DependencyResolver::DependencyRequest + end + + def test_requirement + dependency = dep 'a', '>= 1' + + dr = @DR.new dependency, nil + + assert_equal dependency, dr.dependency + end + +end + diff --git a/test/rubygems/test_gem_dependency_resolver_index_set.rb b/test/rubygems/test_gem_dependency_resolver_index_set.rb index 82ea486792..83b7cce501 100644 --- a/test/rubygems/test_gem_dependency_resolver_index_set.rb +++ b/test/rubygems/test_gem_dependency_resolver_index_set.rb @@ -3,50 +3,26 @@ require 'rubygems/dependency_resolver' class TestGemDependencyResolverIndexSet < Gem::TestCase - def test_load_spec - @fetcher = Gem::FakeFetcher.new - Gem::RemoteFetcher.fetcher = @fetcher + def setup + super - a_2 = quick_spec 'a', 2 - a_2_p = quick_spec 'a', 2 do |s| s.platform = Gem::Platform.local end - - Gem::Specification.add_specs a_2, a_2_p - - util_setup_spec_fetcher a_2, a_2_p - - source = Gem::Source.new @gem_repo - version = v 2 - - set = Gem::DependencyResolver::IndexSet.new - - spec = set.load_spec 'a', version, Gem::Platform.local, source - - assert_equal a_2_p.full_name, spec.full_name + @DR = Gem::DependencyResolver end - def test_load_spec_cached - @fetcher = Gem::FakeFetcher.new - Gem::RemoteFetcher.fetcher = @fetcher + def test_initialize + set = @DR::IndexSet.new - a_2 = quick_spec 'a', 2 - a_2_p = quick_spec 'a', 2 do |s| s.platform = Gem::Platform.local end + fetcher = set.instance_variable_get :@f - Gem::Specification.add_specs a_2, a_2_p - - util_setup_spec_fetcher a_2, a_2_p - - source = Gem::Source.new @gem_repo - version = v 2 - - set = Gem::DependencyResolver::IndexSet.new - - first = set.load_spec 'a', version, Gem::Platform.local, source + assert_same Gem::SpecFetcher.fetcher, fetcher + end - util_setup_spec_fetcher # clear + def test_initialize_source + set = @DR::IndexSet.new 'http://alternate.example' - second = set.load_spec 'a', version, Gem::Platform.local, source + fetcher = set.instance_variable_get :@f - assert_same first, second + refute_same Gem::SpecFetcher.fetcher, fetcher end end diff --git a/test/rubygems/test_gem_dependency_resolver_index_specification.rb b/test/rubygems/test_gem_dependency_resolver_index_specification.rb index b1e74a3cd1..c8e02ff62d 100644 --- a/test/rubygems/test_gem_dependency_resolver_index_specification.rb +++ b/test/rubygems/test_gem_dependency_resolver_index_specification.rb @@ -31,15 +31,12 @@ class TestGemDependencyResolverIndexSpecification < Gem::TestCase end def test_spec - @fetcher = Gem::FakeFetcher.new - Gem::RemoteFetcher.fetcher = @fetcher + a_2_p = nil - a_2 = quick_spec 'a', 2 - a_2_p = quick_spec 'a', 2 do |s| s.platform = Gem::Platform.local end - - Gem::Specification.add_specs a_2, a_2_p - - util_setup_spec_fetcher a_2, a_2_p + spec_fetcher do |fetcher| + fetcher.spec 'a', 2 + a_2_p = fetcher.spec 'a', 2 do |s| s.platform = Gem::Platform.local end + end source = Gem::Source.new @gem_repo version = v 2 diff --git a/test/rubygems/test_gem_dependency_resolver_installer_set.rb b/test/rubygems/test_gem_dependency_resolver_installer_set.rb index b6b50a12fe..516a4d03fe 100644 --- a/test/rubygems/test_gem_dependency_resolver_installer_set.rb +++ b/test/rubygems/test_gem_dependency_resolver_installer_set.rb @@ -4,15 +4,12 @@ require 'rubygems/dependency_resolver' class TestGemDependencyResolverInstallerSet < Gem::TestCase def test_load_spec - @fetcher = Gem::FakeFetcher.new - Gem::RemoteFetcher.fetcher = @fetcher + a_2_p = nil - a_2 = quick_spec 'a', 2 - a_2_p = quick_spec 'a', 2 do |s| s.platform = Gem::Platform.local end - - Gem::Specification.add_specs a_2, a_2_p - - util_setup_spec_fetcher a_2, a_2_p + spec_fetcher do |fetcher| + fetcher.spec 'a', 2 + a_2_p = fetcher.spec 'a', 2 do |s| s.platform = Gem::Platform.local end + end source = Gem::Source.new @gem_repo version = v 2 diff --git a/test/rubygems/test_gem_dependency_resolver_lock_set.rb b/test/rubygems/test_gem_dependency_resolver_lock_set.rb new file mode 100644 index 0000000000..6142f2b8d0 --- /dev/null +++ b/test/rubygems/test_gem_dependency_resolver_lock_set.rb @@ -0,0 +1,57 @@ +require 'rubygems/test_case' +require 'rubygems/dependency_resolver' + +class TestGemDependencyResolverLockSet < Gem::TestCase + + def setup + super + + @source = Gem::Source.new @gem_repo + + @set = Gem::DependencyResolver::LockSet.new @source + end + + def test_add + @set.add 'a', '2', Gem::Platform::RUBY + + assert_equal %w[a-2], @set.specs.map { |t| t.full_name } + + spec = @set.specs.first + + assert_equal @set, spec.set + assert_equal 'a', spec.name + assert_equal v(2), spec.version + assert_equal Gem::Platform::RUBY, spec.platform + assert_equal @source, spec.source + end + + def test_find_all + @set.add 'a', '2', Gem::Platform::RUBY + @set.add 'b', '2', Gem::Platform::RUBY + + found = @set.find_all dep 'a' + + assert_equal %w[a-2], found.map { |s| s.full_name } + end + + def test_load_spec + spec_fetcher do |fetcher| + fetcher.spec 'a', 2 + end + + version = v(2) + @set.add 'a', version, Gem::Platform::RUBY + + loaded = @set.load_spec 'a', version, Gem::Platform::RUBY, @source + + assert_kind_of Gem::Specification, loaded + + assert_equal 'a-2', loaded.full_name + end + + def test_prefetch + assert_respond_to @set, :prefetch + end + +end + diff --git a/test/rubygems/test_gem_dependency_resolver_vendor_set.rb b/test/rubygems/test_gem_dependency_resolver_vendor_set.rb index b925ff3f47..227cf369c8 100644 --- a/test/rubygems/test_gem_dependency_resolver_vendor_set.rb +++ b/test/rubygems/test_gem_dependency_resolver_vendor_set.rb @@ -20,7 +20,7 @@ class TestGemDependencyResolverVendorSet < Gem::TestCase end def test_add_vendor_gem_missing - name, version, directory = vendor_gem + name, _, directory = vendor_gem FileUtils.rm_r directory @@ -45,15 +45,19 @@ class TestGemDependencyResolverVendorSet < Gem::TestCase spec = @set.load_spec name, version, Gem::Platform::RUBY, nil + source = Gem::Source::Vendor.new directory + expected = [ - Gem::DependencyResolver::VendorSpecification.new(@set, spec, nil) + Gem::DependencyResolver::VendorSpecification.new(@set, spec, source) ] assert_equal expected, found end def test_load_spec - assert_raises KeyError do + error = Object.const_defined?(:KeyError) ? KeyError : IndexError + + assert_raises error do @set.load_spec 'a', v(1), Gem::Platform::RUBY, nil end end diff --git a/test/rubygems/test_gem_dependency_resolver_vendor_specification.rb b/test/rubygems/test_gem_dependency_resolver_vendor_specification.rb index c1d668c777..77d78d1dc7 100644 --- a/test/rubygems/test_gem_dependency_resolver_vendor_specification.rb +++ b/test/rubygems/test_gem_dependency_resolver_vendor_specification.rb @@ -60,12 +60,6 @@ class TestGemDependencyResolverVendorSpecification < Gem::TestCase assert_equal Gem::Platform::RUBY, v_spec.platform end - def test_source - v_spec = Gem::DependencyResolver::VendorSpecification.new @set, @spec - - assert_equal Gem::Source::Vendor.new, v_spec.source - end - def test_version spec = Gem::Specification.new 'a', 1 diff --git a/test/rubygems/test_gem_remote_fetcher.rb b/test/rubygems/test_gem_remote_fetcher.rb index fe6da708b6..5f36e71807 100644 --- a/test/rubygems/test_gem_remote_fetcher.rb +++ b/test/rubygems/test_gem_remote_fetcher.rb @@ -75,12 +75,6 @@ gems: PROXY_DATA = SERVER_DATA.gsub(/0.4.11/, '0.4.2') - # don't let 1.8 and 1.9 autotest collide - RUBY_VERSION =~ /(\d+)\.(\d+)\.(\d+)/ - # don't let parallel runners collide - PROXY_PORT = process_based_port + 100 + $1.to_i * 100 + $2.to_i * 10 + $3.to_i - SERVER_PORT = process_based_port + 200 + $1.to_i * 100 + $2.to_i * 10 + $3.to_i - DIR = File.expand_path(File.dirname(__FILE__)) def setup @@ -93,8 +87,8 @@ gems: self.class.enable_yaml = true self.class.enable_zip = false - base_server_uri = "http://localhost:#{SERVER_PORT}" - @proxy_uri = "http://localhost:#{PROXY_PORT}" + base_server_uri = "http://localhost:#{self.class.normal_server_port}" + @proxy_uri = "http://localhost:#{self.class.proxy_server_port}" @server_uri = base_server_uri + "/yaml" @server_z_uri = base_server_uri + "/yaml.Z" @@ -712,12 +706,20 @@ gems: attr_accessor :enable_zip, :enable_yaml def start_servers - @normal_server ||= start_server(SERVER_PORT, SERVER_DATA) - @proxy_server ||= start_server(PROXY_PORT, PROXY_DATA) + @normal_server ||= start_server(SERVER_DATA) + @proxy_server ||= start_server(PROXY_DATA) @enable_yaml = true @enable_zip = false end + def normal_server_port + @normal_server[:server].config[:Port] + end + + def proxy_server_port + @proxy_server[:server].config[:Port] + end + DIR = File.expand_path(File.dirname(__FILE__)) def start_ssl_server(config = {}) @@ -763,45 +765,45 @@ gems: private - def start_server(port, data) - Thread.new do + def start_server(data) + null_logger = NilLog.new + s = WEBrick::HTTPServer.new( + :Port => 0, + :DocumentRoot => nil, + :Logger => null_logger, + :AccessLog => null_logger + ) + s.mount_proc("/kill") { |req, res| s.shutdown } + s.mount_proc("/yaml") { |req, res| + if @enable_yaml + res.body = data + res['Content-Type'] = 'text/plain' + res['content-length'] = data.size + else + res.status = "404" + res.body = "<h1>NOT FOUND</h1>" + res['Content-Type'] = 'text/html' + end + } + s.mount_proc("/yaml.Z") { |req, res| + if @enable_zip + res.body = Zlib::Deflate.deflate(data) + res['Content-Type'] = 'text/plain' + else + res.status = "404" + res.body = "<h1>NOT FOUND</h1>" + res['Content-Type'] = 'text/html' + end + } + th = Thread.new do begin - null_logger = NilLog.new - s = WEBrick::HTTPServer.new( - :Port => port, - :DocumentRoot => nil, - :Logger => null_logger, - :AccessLog => null_logger - ) - s.mount_proc("/kill") { |req, res| s.shutdown } - s.mount_proc("/yaml") { |req, res| - if @enable_yaml - res.body = data - res['Content-Type'] = 'text/plain' - res['content-length'] = data.size - else - res.status = "404" - res.body = "<h1>NOT FOUND</h1>" - res['Content-Type'] = 'text/html' - end - } - s.mount_proc("/yaml.Z") { |req, res| - if @enable_zip - res.body = Zlib::Deflate.deflate(data) - res['Content-Type'] = 'text/plain' - else - res.status = "404" - res.body = "<h1>NOT FOUND</h1>" - res['Content-Type'] = 'text/html' - end - } s.start rescue Exception => ex - abort ex.message - puts "ERROR during server thread: #{ex.message}" + abort "ERROR during server thread: #{ex.message}" end end - sleep 0.2 # Give the servers time to startup + th[:server] = s + th end def cert(filename) diff --git a/test/rubygems/test_gem_request_set.rb b/test/rubygems/test_gem_request_set.rb index 12a1942d54..4aaa15ac68 100644 --- a/test/rubygems/test_gem_request_set.rb +++ b/test/rubygems/test_gem_request_set.rb @@ -6,6 +6,8 @@ class TestGemRequestSet < Gem::TestCase super Gem::RemoteFetcher.fetcher = @fetcher = Gem::FakeFetcher.new + + @DR = Gem::DependencyResolver end def test_gem @@ -17,6 +19,15 @@ class TestGemRequestSet < Gem::TestCase assert_equal [Gem::Dependency.new("a", "=2")], rs.dependencies end + def test_gem_duplicate + rs = Gem::RequestSet.new + + rs.gem 'a', '1' + rs.gem 'a', '2' + + assert_equal [dep('a', '= 1', '= 2')], rs.dependencies + end + def test_import rs = Gem::RequestSet.new rs.gem 'a' @@ -26,6 +37,26 @@ class TestGemRequestSet < Gem::TestCase assert_equal [dep('a'), dep('b')], rs.dependencies end + def test_install_from_gemdeps + spec_fetcher do |fetcher| + fetcher.gem 'a', 2 + end + + rs = Gem::RequestSet.new + installed = [] + + Tempfile.open 'gem.deps.rb' do |io| + io.puts 'gem "a"' + io.flush + + rs.install_from_gemdeps :gemdeps => io.path do |req, installer| + installed << req.full_name + end + end + + assert_includes installed, 'a-2' + end + def test_load_gemdeps rs = Gem::RequestSet.new @@ -41,6 +72,19 @@ class TestGemRequestSet < Gem::TestCase assert rs.vendor_set end + def test_load_gemdeps_without_groups + rs = Gem::RequestSet.new + + Tempfile.open 'gem.deps.rb' do |io| + io.puts 'gem "a", :group => :test' + io.flush + + rs.load_gemdeps io.path, [:test] + end + + assert_empty rs.dependencies + end + def test_resolve a = util_spec "a", "2", "b" => ">= 2" b = util_spec "b", "2" @@ -56,6 +100,21 @@ class TestGemRequestSet < Gem::TestCase assert_equal ["a-2", "b-2"], names end + def test_resolve_incompatible + a1 = util_spec 'a', 1 + a2 = util_spec 'a', 2 + + rs = Gem::RequestSet.new + rs.gem 'a', '= 1' + rs.gem 'a', '= 2' + + set = StaticSet.new [a1, a2] + + assert_raises Gem::UnsatisfiableDependencyError do + rs.resolve set + end + end + def test_resolve_vendor a_name, _, a_directory = vendor_gem 'a', 1 do |s| s.add_dependency 'b', '~> 2.0' @@ -82,6 +141,9 @@ class TestGemRequestSet < Gem::TestCase names = res.map { |s| s.full_name }.sort assert_equal ["a-1", "b-2"], names + + assert_equal [@DR::IndexSet, @DR::VendorSet], + rs.sets.map { |set| set.class } end def test_sorted_requests @@ -99,13 +161,10 @@ class TestGemRequestSet < Gem::TestCase end def test_install_into - a, ad = util_gem "a", "1", "b" => "= 1" - b, bd = util_gem "b", "1" - - util_setup_spec_fetcher a, b - - @fetcher.data["http://gems.example.com/gems/#{a.file_name}"] = Gem.read_binary(ad) - @fetcher.data["http://gems.example.com/gems/#{b.file_name}"] = Gem.read_binary(bd) + spec_fetcher do |fetcher| + fetcher.gem "a", "1", "b" => "= 1" + fetcher.gem "b", "1" + end rs = Gem::RequestSet.new rs.gem "a" diff --git a/test/rubygems/test_gem_request_set_gem_dependency_api.rb b/test/rubygems/test_gem_request_set_gem_dependency_api.rb index 5c35484421..367f3ba20a 100644 --- a/test/rubygems/test_gem_request_set_gem_dependency_api.rb +++ b/test/rubygems/test_gem_request_set_gem_dependency_api.rb @@ -16,20 +16,56 @@ class TestGemRequestSetGemDependencyAPI < Gem::TestCase @gda.instance_variable_set :@vendor_set, @vendor_set end + def with_engine_version name, version + engine = RUBY_ENGINE if Object.const_defined? :RUBY_ENGINE + engine_version_const = "#{Gem.ruby_engine.upcase}_VERSION" + engine_version = Object.const_get engine_version_const + + Object.send :remove_const, :RUBY_ENGINE if engine + Object.send :remove_const, engine_version_const if name == 'ruby' and + Object.const_defined? engine_version_const + + new_engine_version_const = "#{name.upcase}_VERSION" + Object.const_set :RUBY_ENGINE, name if name + Object.const_set new_engine_version_const, version if version + + Gem.instance_variable_set :@ruby_version, Gem::Version.new(version) + + yield + + ensure + Object.send :remove_const, :RUBY_ENGINE if name + Object.send :remove_const, new_engine_version_const if version + + Object.send :remove_const, engine_version_const if name == 'ruby' and + Object.const_defined? engine_version_const + + Object.const_set :RUBY_ENGINE, engine if engine + Object.const_set engine_version_const, engine_version unless + Object.const_defined? engine_version_const + + Gem.send :remove_instance_variable, :@ruby_version if + Gem.instance_variables.include? :@ruby_version + end + def test_gem @gda.gem 'a' assert_equal [dep('a')], @set.dependencies + + assert_equal %w[a], @gda.requires['a'] end def test_gem_group @gda.gem 'a', :group => :test - expected = { - :test => [['a']], - } + assert_equal [dep('a')], @set.dependencies + end - assert_equal expected, @gda.dependency_groups + def test_gem_group_without + @gda.without_groups << :test + + @gda.gem 'a', :group => :test assert_empty @set.dependencies end @@ -37,14 +73,7 @@ class TestGemRequestSetGemDependencyAPI < Gem::TestCase def test_gem_groups @gda.gem 'a', :groups => [:test, :development] - expected = { - :development => [['a']], - :test => [['a']], - } - - assert_equal expected, @gda.dependency_groups - - assert_empty @set.dependencies + assert_equal [dep('a')], @set.dependencies end def test_gem_path @@ -59,6 +88,133 @@ class TestGemRequestSetGemDependencyAPI < Gem::TestCase assert_equal "#{name}-#{version}", loaded.full_name end + def test_gem_platforms + with_engine_version 'ruby', '2.0.0' do + @gda.gem 'a', :platforms => :ruby + + refute_empty @set.dependencies + end + end + + def test_gem_platforms_bundler_ruby + win_platform, Gem.win_platform = Gem.win_platform?, false + + with_engine_version 'ruby', '2.0.0' do + set = Gem::RequestSet.new + gda = @GDA.new set, 'gem.deps.rb' + gda.gem 'a', :platforms => :ruby + + refute_empty set.dependencies + end + + with_engine_version 'rbx', '2.0.0' do + set = Gem::RequestSet.new + gda = @GDA.new set, 'gem.deps.rb' + gda.gem 'a', :platforms => :ruby + + refute_empty set.dependencies + end + + with_engine_version 'jruby', '1.7.6' do + set = Gem::RequestSet.new + gda = @GDA.new set, 'gem.deps.rb' + gda.gem 'a', :platforms => :ruby + + assert_empty set.dependencies + end + + Gem.win_platform = true + + with_engine_version 'ruby', '2.0.0' do + set = Gem::RequestSet.new + gda = @GDA.new set, 'gem.deps.rb' + gda.gem 'a', :platforms => :ruby + + assert_empty set.dependencies + end + + Gem.win_platform = win_platform + end + + def test_gem_platforms_engine + with_engine_version 'jruby', '1.7.6' do + @gda.gem 'a', :platforms => :mri + + assert_empty @set.dependencies + end + end + + def test_gem_platforms_maglev + with_engine_version 'maglev', '1.0.0' do + set = Gem::RequestSet.new + gda = @GDA.new set, 'gem.deps.rb' + gda.gem 'a', :platforms => :ruby + + refute_empty set.dependencies + + set = Gem::RequestSet.new + gda = @GDA.new set, 'gem.deps.rb' + gda.gem 'a', :platforms => :maglev + + refute_empty set.dependencies + end + end + + def test_gem_platforms_multiple + win_platform, Gem.win_platform = Gem.win_platform?, false + + with_engine_version 'ruby', '2.0.0' do + @gda.gem 'a', :platforms => [:mswin, :jruby] + + assert_empty @set.dependencies + end + + ensure + Gem.win_platform = win_platform + end + + def test_gem_platforms_version + with_engine_version 'ruby', '2.0.0' do + @gda.gem 'a', :platforms => :ruby_18 + + assert_empty @set.dependencies + end + end + + def test_gem_platforms_unknown + e = assert_raises ArgumentError do + @gda.gem 'a', :platforms => :unknown + end + + assert_equal 'unknown platform :unknown', e.message + end + + def test_gem_require + @gda.gem 'a', :require => %w[b c] + + assert_equal [dep('a')], @set.dependencies + + assert_equal %w[b c], @gda.requires['a'] + end + + def test_gem_require_false + @gda.gem 'a', :require => false + + assert_equal [dep('a')], @set.dependencies + + assert_empty @gda.requires + end + + def test_gem_require_without_group + @gda.without_groups << :test + + @gda.gem 'a', :group => :test + + assert_empty @set.dependencies + + assert_empty @gda.requires['a'] + end + def test_gem_requirement @gda.gem 'a', '~> 1.0' @@ -77,6 +233,31 @@ class TestGemRequestSetGemDependencyAPI < Gem::TestCase assert_equal [dep('c')], @set.dependencies end + def test_gem_source_mismatch + name, _, directory = vendor_gem + + gda = @GDA.new @set, nil + gda.gem name + + e = assert_raises ArgumentError do + gda.gem name, :path => directory + end + + assert_equal "duplicate source path: #{directory} for gem #{name}", + e.message + + gda = @GDA.new @set, nil + gda.instance_variable_set :@vendor_set, @vendor_set + gda.gem name, :path => directory + + e = assert_raises ArgumentError do + gda.gem name + end + + assert_equal "duplicate source (default) for gem #{name}", + e.message + end + def test_gem_deps_file assert_equal 'gem.deps.rb', @gda.gem_deps_file @@ -85,25 +266,22 @@ class TestGemRequestSetGemDependencyAPI < Gem::TestCase assert_equal 'Gemfile', gda.gem_deps_file end - def test_group - @gda.group :test do - @gda.gem 'a' - end + def test_gem_group_method + groups = [] - assert_equal [['a']], @gda.dependency_groups[:test] + @gda.group :a do + groups = @gda.send :gem_group, 'a', :group => :b, :groups => [:c, :d] + end - assert_empty @set.dependencies + assert_equal [:a, :b, :c, :d], groups.sort_by { |group| group.to_s } end - def test_group_multiple - @gda.group :a do - @gda.gem 'a', :group => :b, :groups => [:c, :d] + def test_group + @gda.group :test do + @gda.gem 'a' end - assert_equal [['a']], @gda.dependency_groups[:a] - assert_equal [['a']], @gda.dependency_groups[:b] - assert_equal [['a']], @gda.dependency_groups[:c] - assert_equal [['a']], @gda.dependency_groups[:d] + assert_equal [dep('a')], @set.dependencies end def test_load @@ -121,18 +299,12 @@ end gda.load - expected = { - :test => [['b']], - } - - assert_equal expected, gda.dependency_groups - - assert_equal [dep('a')], @set.dependencies + assert_equal [dep('a'), dep('b')], @set.dependencies end end def test_name_typo - assert_same @GDA, Gem::RequestSet::DepedencyAPI + assert_same @GDA, Gem::RequestSet::GemDepedencyAPI end def test_platform_mswin @@ -152,11 +324,30 @@ end end def test_platforms + win_platform, Gem.win_platform = Gem.win_platform?, false + @gda.platforms :ruby do @gda.gem 'a' end assert_equal [dep('a')], @set.dependencies + + @gda.platforms :mswin do + @gda.gem 'b' + end + + assert_equal [dep('a')], @set.dependencies + + Gem.win_platform = true + + @gda.platforms :mswin do + @gda.gem 'c' + end + + assert_equal [dep('a'), dep('c')], @set.dependencies + + ensure + Gem.win_platform = win_platform end def test_ruby @@ -164,8 +355,42 @@ end end def test_ruby_engine - assert @gda.ruby RUBY_VERSION, - :engine => 'jruby', :engine_version => '1.7.4' + with_engine_version 'jruby', '1.7.6' do + assert @gda.ruby RUBY_VERSION, + :engine => 'jruby', :engine_version => '1.7.6' + + end + end + + def test_ruby_engine_mismatch_engine + with_engine_version 'ruby', '2.0.0' do + e = assert_raises Gem::RubyVersionMismatch do + @gda.ruby RUBY_VERSION, :engine => 'jruby', :engine_version => '1.7.4' + end + + assert_equal 'Your ruby engine is ruby, but your gem.deps.rb requires jruby', + e.message + end + end + + def test_ruby_engine_mismatch_version + with_engine_version 'jruby', '1.7.6' do + e = assert_raises Gem::RubyVersionMismatch do + @gda.ruby RUBY_VERSION, :engine => 'jruby', :engine_version => '1.7.4' + end + + assert_equal 'Your ruby engine version is jruby 1.7.6, but your gem.deps.rb requires jruby 1.7.4', + e.message + end + end + + def test_ruby_engine_no_engine_version + e = assert_raises ArgumentError do + @gda.ruby RUBY_VERSION, :engine => 'jruby' + end + + assert_equal 'you must specify engine_version along with the ruby engine', + e.message end def test_ruby_mismatch @@ -173,7 +398,42 @@ end @gda.ruby '1.8.0' end - assert_equal "Your Ruby version is #{RUBY_VERSION}, but your gem.deps.rb specified 1.8.0", e.message + assert_equal "Your Ruby version is #{RUBY_VERSION}, but your gem.deps.rb requires 1.8.0", e.message + end + + def test_source + sources = Gem.sources + + @gda.source 'http://first.example' + + assert_equal %w[http://first.example], Gem.sources + + assert_same sources, Gem.sources + + @gda.source 'http://second.example' + + assert_equal %w[http://first.example http://second.example], Gem.sources + end + + def test_with_engine_version + version = RUBY_VERSION + engine = Gem.ruby_engine + + engine_version_const = "#{Gem.ruby_engine.upcase}_VERSION" + engine_version = Object.const_get engine_version_const + + with_engine_version 'other', '1.2.3' do + assert_equal 'other', Gem.ruby_engine + assert_equal '1.2.3', OTHER_VERSION + + assert_equal version, RUBY_VERSION if engine + end + + assert_equal version, RUBY_VERSION + assert_equal engine, Gem.ruby_engine + + assert_equal engine_version, Object.const_get(engine_version_const) if + engine end end diff --git a/test/rubygems/test_gem_request_set_lockfile.rb b/test/rubygems/test_gem_request_set_lockfile.rb new file mode 100644 index 0000000000..9e947f54ec --- /dev/null +++ b/test/rubygems/test_gem_request_set_lockfile.rb @@ -0,0 +1,404 @@ +require 'rubygems/test_case' +require 'rubygems/request_set' +require 'rubygems/request_set/lockfile' + +class TestGemRequestSetLockfile < Gem::TestCase + + def setup + super + + Gem::RemoteFetcher.fetcher = @fetcher = Gem::FakeFetcher.new + + util_set_arch 'i686-darwin8.10.1' + + @set = Gem::RequestSet.new + + @vendor_set = Gem::DependencyResolver::VendorSet.new + + @set.instance_variable_set :@vendor_set, @vendor_set + + @gem_deps_file = 'gem.deps.rb' + + @lockfile = Gem::RequestSet::Lockfile.new @set, @gem_deps_file + end + + def write_gem_deps gem_deps + open @gem_deps_file, 'w' do |io| + io.write gem_deps + end + end + + def write_lockfile lockfile + @lock_file = File.expand_path "#{@gem_deps_file}.lock" + + open @lock_file, 'w' do |io| + io.write lockfile + end + end + + def test_get + @lockfile.instance_variable_set :@tokens, [:token] + + assert_equal :token, @lockfile.get + end + + def test_get_type_mismatch + @lockfile.instance_variable_set :@tokens, [[:section, 'x', 5, 1]] + + e = assert_raises Gem::RequestSet::Lockfile::ParseError do + @lockfile.get :text + end + + expected = 'unexpected token [:section, "x"], expected :text (at 5:1)' + + assert_equal expected, e.message + + assert_equal 5, e.line + assert_equal 1, e.column + assert_equal File.expand_path("#{@gem_deps_file}.lock"), e.path + end + + def test_get_type_value_mismatch + @lockfile.instance_variable_set :@tokens, [[:section, 'x', 5, 1]] + + e = assert_raises Gem::RequestSet::Lockfile::ParseError do + @lockfile.get :section, 'y' + end + + expected = + 'unexpected token [:section, "x"], expected [:section, "y"] (at 5:1)' + + assert_equal expected, e.message + + assert_equal 5, e.line + assert_equal 1, e.column + assert_equal File.expand_path("#{@gem_deps_file}.lock"), e.path + end + + def test_parse + write_lockfile <<-LOCKFILE +GEM + remote: #{@gem_repo} + specs: + a (2) + +PLATFORMS + #{Gem::Platform::RUBY} + +DEPENDENCIES + a + LOCKFILE + + @lockfile.parse + + assert_equal [dep('a')], @set.dependencies + + assert_equal [Gem::Platform::RUBY], @lockfile.platforms + + lockfile_set = @set.sets.find do |set| + Gem::DependencyResolver::LockSet === set + end + + assert lockfile_set, 'could not find a LockSet' + + assert_equal %w[a-2], lockfile_set.specs.map { |tuple| tuple.full_name } + end + + def test_peek + @lockfile.instance_variable_set :@tokens, [:token] + + assert_equal :token, @lockfile.peek + + assert_equal :token, @lockfile.get + end + + def test_skip + tokens = [[:token]] + + @lockfile.instance_variable_set :@tokens, tokens + + @lockfile.skip :token + + assert_empty tokens + end + + def test_token_pos + assert_equal [5, 0], @lockfile.token_pos(5) + + @lockfile.instance_variable_set :@line_pos, 2 + @lockfile.instance_variable_set :@line, 1 + + assert_equal [3, 1], @lockfile.token_pos(5) + end + + def test_tokenize + write_lockfile <<-LOCKFILE +GEM + remote: #{@gem_repo} + specs: + a (2) + +PLATFORMS + #{Gem::Platform::RUBY} + +DEPENDENCIES + a + LOCKFILE + + expected = [ + [:section, 'GEM', 0, 0], + [:newline, nil, 3, 0], + [:entry, 'remote', 2, 1], + [:text, @gem_repo, 10, 1], + [:newline, nil, 34, 1], + [:entry, 'specs', 2, 2], + [:newline, nil, 8, 2], + [:text, 'a', 4, 3], + [:l_paren, nil, 6, 3], + [:text, '2', 7, 3], + [:r_paren, nil, 8, 3], + [:newline, nil, 9, 3], + [:newline, nil, 0, 4], + [:section, 'PLATFORMS', 0, 5], + [:newline, nil, 9, 5], + [:text, Gem::Platform::RUBY, 2, 6], + [:newline, nil, 6, 6], + [:newline, nil, 0, 7], + [:section, 'DEPENDENCIES', 0, 8], + [:newline, nil, 12, 8], + [:text, 'a', 2, 9], + [:newline, nil, 3, 9], + ] + + assert_equal expected, @lockfile.tokenize + end + + def test_tokenize_conflict_markers + write_lockfile '<<<<<<<' + + e = assert_raises Gem::RequestSet::Lockfile::ParseError do + @lockfile.tokenize + end + + assert_equal "your #{@lock_file} contains merge conflict markers (at 0:0)", + e.message + + write_lockfile '|||||||' + + e = assert_raises Gem::RequestSet::Lockfile::ParseError do + @lockfile.tokenize + end + + assert_equal "your #{@lock_file} contains merge conflict markers (at 0:0)", + e.message + + write_lockfile '=======' + + e = assert_raises Gem::RequestSet::Lockfile::ParseError do + @lockfile.tokenize + end + + assert_equal "your #{@lock_file} contains merge conflict markers (at 0:0)", + e.message + + write_lockfile '>>>>>>>' + + e = assert_raises Gem::RequestSet::Lockfile::ParseError do + @lockfile.tokenize + end + + assert_equal "your #{@lock_file} contains merge conflict markers (at 0:0)", + e.message + end + + def test_to_s_gem + spec_fetcher do |fetcher| + fetcher.spec 'a', 2 + end + + @set.gem 'a' + + expected = <<-LOCKFILE +GEM + remote: #{@gem_repo} + specs: + a (2) + +PLATFORMS + #{Gem::Platform::RUBY} + +DEPENDENCIES + a + LOCKFILE + + assert_equal expected, @lockfile.to_s + end + + def test_to_s_gem_dependency + spec_fetcher do |fetcher| + fetcher.spec 'a', 2, 'c' => '>= 0', 'b' => '>= 0' + fetcher.spec 'b', 2 + fetcher.spec 'c', 2 + end + + @set.gem 'a' + + expected = <<-LOCKFILE +GEM + remote: #{@gem_repo} + specs: + a (2) + b + c + b (2) + c (2) + +PLATFORMS + #{Gem::Platform::RUBY} + +DEPENDENCIES + a + LOCKFILE + + assert_equal expected, @lockfile.to_s + end + + def test_to_s_gem_dependency_non_default + spec_fetcher do |fetcher| + fetcher.spec 'a', 2, 'b' => '>= 1' + fetcher.spec 'b', 2 + end + + @set.gem 'b' + @set.gem 'a' + + expected = <<-LOCKFILE +GEM + remote: #{@gem_repo} + specs: + a (2) + b (>= 1) + b (2) + +PLATFORMS + #{Gem::Platform::RUBY} + +DEPENDENCIES + a + b + LOCKFILE + + assert_equal expected, @lockfile.to_s + end + + def test_to_s_gem_dependency_requirement + spec_fetcher do |fetcher| + fetcher.spec 'a', 2, 'b' => '>= 0' + fetcher.spec 'b', 2 + end + + @set.gem 'a', '>= 1' + + expected = <<-LOCKFILE +GEM + remote: #{@gem_repo} + specs: + a (2) + b + b (2) + +PLATFORMS + #{Gem::Platform::RUBY} + +DEPENDENCIES + a (>= 1) + LOCKFILE + + assert_equal expected, @lockfile.to_s + end + + def test_to_s_gem_path + name, version, directory = vendor_gem + + @vendor_set.add_vendor_gem name, directory + + @set.gem 'a' + + expected = <<-LOCKFILE +PATH + remote: #{directory} + specs: + #{name} (#{version}) + +GEM + +PLATFORMS + #{Gem::Platform::RUBY} + +DEPENDENCIES + a! + LOCKFILE + + assert_equal expected, @lockfile.to_s + end + + def test_to_s_gem_path_absolute + name, version, directory = vendor_gem + + @vendor_set.add_vendor_gem name, File.expand_path(directory) + + @set.gem 'a' + + expected = <<-LOCKFILE +PATH + remote: #{directory} + specs: + #{name} (#{version}) + +GEM + +PLATFORMS + #{Gem::Platform::RUBY} + +DEPENDENCIES + a! + LOCKFILE + + assert_equal expected, @lockfile.to_s + end + + def test_to_s_gem_platform + spec_fetcher do |fetcher| + fetcher.spec 'a', 2 do |spec| + spec.platform = Gem::Platform.local + end + end + + @set.gem 'a' + + expected = <<-LOCKFILE +GEM + remote: #{@gem_repo} + specs: + a (2-#{Gem::Platform.local}) + +PLATFORMS + #{Gem::Platform.local} + +DEPENDENCIES + a + LOCKFILE + + assert_equal expected, @lockfile.to_s + end + + def test_unget + @lockfile.instance_variable_set :@current_token, :token + + @lockfile.unget + + assert_equal :token, @lockfile.get + end + +end + diff --git a/test/rubygems/test_gem_requirement.rb b/test/rubygems/test_gem_requirement.rb index 01db08e84f..29a4675bc9 100644 --- a/test/rubygems/test_gem_requirement.rb +++ b/test/rubygems/test_gem_requirement.rb @@ -3,6 +3,14 @@ require "rubygems/requirement" class TestGemRequirement < Gem::TestCase + def test_concat + r = req '>= 1' + + r.concat ['< 2'] + + assert_equal [['>=', v(1)], ['<', v(2)]], r.requirements + end + def test_equals2 r = req "= 1.2" assert_equal r, r.dup @@ -36,6 +44,12 @@ class TestGemRequirement < Gem::TestCase assert_equal false, r.none? end + def test_for_lockfile + assert_equal ' (~> 1.0)', req('~> 1.0').for_lockfile + + assert_nil Gem::Requirement.default.for_lockfile + end + def test_parse assert_equal ['=', Gem::Version.new(1)], Gem::Requirement.parse(' 1') assert_equal ['=', Gem::Version.new(1)], Gem::Requirement.parse('= 1') diff --git a/test/rubygems/test_gem_server.rb b/test/rubygems/test_gem_server.rb index d50d3525ff..bf30399db2 100644 --- a/test/rubygems/test_gem_server.rb +++ b/test/rubygems/test_gem_server.rb @@ -85,6 +85,30 @@ class TestGemServer < Gem::TestCase Marshal.load(@res.body) end + def test_latest_specs_gemdirs + data = StringIO.new "GET /latest_specs.#{Gem.marshal_version} HTTP/1.0\r\n\r\n" + dir = "#{@gemhome}2" + + spec = quick_spec 'z', 9 + + specs_dir = File.join dir, 'specifications' + FileUtils.mkdir_p specs_dir + + open File.join(specs_dir, spec.spec_name), 'w' do |io| + io.write spec.to_ruby + end + + server = Gem::Server.new dir, process_based_port, false + + @req.parse data + + server.latest_specs @req, @res + + assert_equal 200, @res.status + + assert_equal [['z', v(9), Gem::Platform::RUBY]], Marshal.load(@res.body) + end + def test_latest_specs_gz data = StringIO.new "GET /latest_specs.#{Gem.marshal_version}.gz HTTP/1.0\r\n\r\n" @req.parse data @@ -120,8 +144,41 @@ class TestGemServer < Gem::TestCase assert_equal 2, @server.server.listeners.length end + def test_quick_gemdirs + data = StringIO.new "GET /quick/Marshal.4.8/z-9.gemspec.rz HTTP/1.0\r\n\r\n" + dir = "#{@gemhome}2" + + server = Gem::Server.new dir, process_based_port, false + + @req.parse data + + server.quick @req, @res + + assert_equal 404, @res.status + + spec = quick_spec 'z', 9 + + specs_dir = File.join dir, 'specifications' + + FileUtils.mkdir_p specs_dir + + open File.join(specs_dir, spec.spec_name), 'w' do |io| + io.write spec.to_ruby + end + + data.rewind + + req = WEBrick::HTTPRequest.new :Logger => nil + res = WEBrick::HTTPResponse.new :HTTPVersion => '1.0' + req.parse data + + server.quick req, res + + assert_equal 200, res.status + end + def test_quick_missing - data = StringIO.new "GET /quick/z-9.gemspec.rz HTTP/1.0\r\n\r\n" + data = StringIO.new "GET /quick/Marshal.4.8/z-9.gemspec.rz HTTP/1.0\r\n\r\n" @req.parse data @server.quick @req, @res @@ -188,6 +245,29 @@ class TestGemServer < Gem::TestCase assert_equal 'text/html', @res['content-type'] end + def test_root_gemdirs + data = StringIO.new "GET / HTTP/1.0\r\n\r\n" + dir = "#{@gemhome}2" + + spec = quick_spec 'z', 9 + + specs_dir = File.join dir, 'specifications' + FileUtils.mkdir_p specs_dir + + open File.join(specs_dir, spec.spec_name), 'w' do |io| + io.write spec.to_ruby + end + + server = Gem::Server.new dir, process_based_port, false + + @req.parse data + + server.root @req, @res + + assert_equal 200, @res.status + assert_match 'z 9', @res.body + end + def test_specs data = StringIO.new "GET /specs.#{Gem.marshal_version} HTTP/1.0\r\n\r\n" @req.parse data @@ -203,6 +283,30 @@ class TestGemServer < Gem::TestCase Marshal.load(@res.body) end + def test_specs_gemdirs + data = StringIO.new "GET /specs.#{Gem.marshal_version} HTTP/1.0\r\n\r\n" + dir = "#{@gemhome}2" + + spec = quick_spec 'z', 9 + + specs_dir = File.join dir, 'specifications' + FileUtils.mkdir_p specs_dir + + open File.join(specs_dir, spec.spec_name), 'w' do |io| + io.write spec.to_ruby + end + + server = Gem::Server.new dir, process_based_port, false + + @req.parse data + + server.specs @req, @res + + assert_equal 200, @res.status + + assert_equal [['z', v(9), Gem::Platform::RUBY]], Marshal.load(@res.body) + end + def test_specs_gz data = StringIO.new "GET /specs.#{Gem.marshal_version}.gz HTTP/1.0\r\n\r\n" @req.parse data diff --git a/test/rubygems/test_gem_source.rb b/test/rubygems/test_gem_source.rb index 61fb682001..ed981cbc09 100644 --- a/test/rubygems/test_gem_source.rb +++ b/test/rubygems/test_gem_source.rb @@ -68,6 +68,20 @@ class TestGemSource < Gem::TestCase assert cache_dir !~ /:/, "#{cache_dir} should not contain a :" end + def test_dependency_resolver_set_bundler_api + @fetcher.data["#{@gem_repo}api/v1/dependencies"] = 'data' + + set = @source.dependency_resolver_set + + assert_kind_of Gem::DependencyResolver::APISet, set + end + + def test_dependency_resolver_set_marshal_api + set = @source.dependency_resolver_set + + assert_kind_of Gem::DependencyResolver::IndexSet, set + end + def test_fetch_spec spec_uri = "#{@gem_repo}#{Gem::MARSHAL_SPEC_DIR}#{@a1.spec_name}" @fetcher.data["#{spec_uri}.rz"] = util_zip(Marshal.dump(@a1)) diff --git a/test/rubygems/test_gem_source_list.rb b/test/rubygems/test_gem_source_list.rb index e2d6da62cd..43db204a50 100644 --- a/test/rubygems/test_gem_source_list.rb +++ b/test/rubygems/test_gem_source_list.rb @@ -18,6 +18,10 @@ class TestGemSourceList < Gem::TestCase assert_equal [Gem::Source.new(@uri)], sl.sources end + def test_Enumerable + assert_includes Gem::SourceList.ancestors, Enumerable + end + def test_append sl = Gem::SourceList.new source = (sl << @uri) @@ -30,6 +34,16 @@ class TestGemSourceList < Gem::TestCase assert_equal [source], sl.sources end + def test_clear + sl = Gem::SourceList.new + + sl << 'http://source.example' + + sl.clear + + assert_empty sl + end + def test_replace sl = Gem::SourceList.new sl.replace [@uri] @@ -49,6 +63,16 @@ class TestGemSourceList < Gem::TestCase end end + def test_empty? + sl = Gem::SourceList.new + + assert_empty sl + + sl << 'http://source.example' + + refute_empty sl + end + def test_equal_to_another_list sl2 = Gem::SourceList.new sl2 << Gem::Source.new(@uri) diff --git a/test/rubygems/test_gem_source_local.rb b/test/rubygems/test_gem_source_local.rb index b3b444ccde..19e1c4b759 100644 --- a/test/rubygems/test_gem_source_local.rb +++ b/test/rubygems/test_gem_source_local.rb @@ -1,5 +1,5 @@ require 'rubygems/test_case' -require 'rubygems/source/local' +require 'rubygems/source' require 'fileutils' diff --git a/test/rubygems/test_gem_source_vendor.rb b/test/rubygems/test_gem_source_vendor.rb new file mode 100644 index 0000000000..17403a1fc8 --- /dev/null +++ b/test/rubygems/test_gem_source_vendor.rb @@ -0,0 +1,13 @@ +require 'rubygems/test_case' +require 'rubygems/source' + +class TestGemSourceVendor < Gem::TestCase + + def test_initialize + source = Gem::Source::Vendor.new 'vendor/foo' + + assert_equal 'vendor/foo', source.uri + end + +end + diff --git a/test/rubygems/test_gem_spec_fetcher.rb b/test/rubygems/test_gem_spec_fetcher.rb index 9e9a8e4d69..54e6e4176f 100644 --- a/test/rubygems/test_gem_spec_fetcher.rb +++ b/test/rubygems/test_gem_spec_fetcher.rb @@ -52,7 +52,22 @@ class TestGemSpecFetcher < Gem::TestCase ['x', Gem::Version.new(1), 'ruby']] end - def test_initialize_unwritable_home_dir + def test_initialize + fetcher = Gem::SpecFetcher.new + + assert_same Gem.sources, fetcher.sources + end + + def test_initialize_source + alternate = 'http://alternate.example' + fetcher = Gem::SpecFetcher.new alternate + + refute_same Gem.sources, fetcher.sources + + assert_equal alternate, fetcher.sources + end + + def test_initialize_nonexistent_home_dir FileUtils.rmdir Gem.user_home assert Gem::SpecFetcher.new diff --git a/test/rubygems/test_gem_specification.rb b/test/rubygems/test_gem_specification.rb index 0623e8d51d..3936bc0681 100644 --- a/test/rubygems/test_gem_specification.rb +++ b/test/rubygems/test_gem_specification.rb @@ -873,6 +873,31 @@ dependencies: [] Gem::Specification.outdated_and_latest_version.to_a end + def test_self_remove_spec + 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 + + refute_includes Gem::Specification.all_names, 'a-1' + refute_includes Gem::Specification.stubs.map { |s| s.full_name }, 'a-1' + end + + def test_self_remove_spec_removed + open @a1.spec_file, 'w' do |io| + io.write @a1.to_ruby + end + + Gem::Specification.reset + + FileUtils.rm @a1.spec_file # bug #698 + + Gem::Specification.remove_spec @a1 + + refute_includes Gem::Specification.all_names, 'a-1' + refute_includes Gem::Specification.stubs.map { |s| s.full_name }, 'a-1' + end + DATA_PATH = File.expand_path "../data", __FILE__ def test_handles_private_null_type @@ -1886,7 +1911,7 @@ Gem::Specification.new do |s| s.rubygems_version = "#{Gem::VERSION}" s.summary = "this is a summary" - s.installed_by_version = "#{Gem::VERSION}" + s.installed_by_version = "#{Gem::VERSION}" if s.respond_to? :installed_by_version if s.respond_to? :specification_version then s.specification_version = #{Gem::Specification::CURRENT_SPECIFICATION_VERSION} @@ -2128,6 +2153,15 @@ end Dir.chdir @tempdir do @a1.add_runtime_dependency 'b', '>= 1.0.rc1' @a1.add_development_dependency 'c', '>= 2.0.rc2' + @a1.add_runtime_dependency 'd', '~> 1.2.3' + @a1.add_runtime_dependency 'e', '~> 1.2.3.4' + @a1.add_runtime_dependency 'g', '~> 1.2.3', '>= 1.2.3.4' + @a1.add_runtime_dependency 'h', '>= 1.2.3', '<= 2' + @a1.add_runtime_dependency 'i', '>= 1.2' + @a1.add_runtime_dependency 'j', '>= 1.2.3' + @a1.add_runtime_dependency 'k', '> 1.2' + @a1.add_runtime_dependency 'l', '> 1.2.3' + @a1.add_runtime_dependency 'm', '~> 2.1.0' use_ui @ui do @a1.validate @@ -2136,9 +2170,57 @@ end expected = <<-EXPECTED #{w}: prerelease dependency on b (>= 1.0.rc1) is not recommended #{w}: prerelease dependency on c (>= 2.0.rc2, development) is not recommended +#{w}: pessimistic dependency on d (~> 1.2.3) may be overly strict + if d is semantically versioned, use: + add_runtime_dependency 'd', '~> 1.2', '>= 1.2.3' +#{w}: pessimistic dependency on e (~> 1.2.3.4) may be overly strict + if e is semantically versioned, use: + add_runtime_dependency 'e', '~> 1.2', '>= 1.2.3.4' +#{w}: open-ended dependency on i (>= 1.2) is not recommended + if i is semantically versioned, use: + add_runtime_dependency 'i', '~> 1.2' +#{w}: open-ended dependency on j (>= 1.2.3) is not recommended + if j is semantically versioned, use: + add_runtime_dependency 'j', '~> 1.2', '>= 1.2.3' +#{w}: open-ended dependency on k (> 1.2) is not recommended + if k is semantically versioned, use: + add_runtime_dependency 'k', '~> 1.2', '> 1.2' +#{w}: open-ended dependency on l (> 1.2.3) is not recommended + if l is semantically versioned, use: + add_runtime_dependency 'l', '~> 1.2', '> 1.2.3' +#{w}: pessimistic dependency on m (~> 2.1.0) may be overly strict + if m is semantically versioned, use: + add_runtime_dependency 'm', '~> 2.1', '>= 2.1.0' +#{w}: See http://guides.rubygems.org/specification-reference/ for help EXPECTED - assert_match expected, @ui.error, 'warning' + assert_equal expected, @ui.error, 'warning' + end + end + + def test_validate_dependencies_open_ended + util_setup_validate + + Dir.chdir @tempdir do + @a1.add_runtime_dependency 'b', '~> 1.2' + @a1.add_runtime_dependency 'b', '>= 1.2.3' + + use_ui @ui do + e = assert_raises Gem::InvalidSpecificationException do + @a1.validate + end + + expected = <<-EXPECTED +duplicate dependency on b (>= 1.2.3), (~> 1.2) use: + add_runtime_dependency 'b', '>= 1.2.3', '~> 1.2' + EXPECTED + + assert_equal expected, e.message + end + + assert_equal <<-EXPECTED, @ui.error +#{w}: See http://guides.rubygems.org/specification-reference/ for help + EXPECTED end end diff --git a/test/rubygems/test_gem_stub_specification.rb b/test/rubygems/test_gem_stub_specification.rb index 1c200ec1ed..bb04fb4dcc 100644 --- a/test/rubygems/test_gem_stub_specification.rb +++ b/test/rubygems/test_gem_stub_specification.rb @@ -23,8 +23,6 @@ class TestStubSpecification < Gem::TestCase def test_initialize_extension stub = stub_with_extension - gem_dir = File.join stub.gems_dir, stub.full_name - ext_install_dir = Pathname(stub.extension_install_dir) full_gem_path = Pathname(stub.full_gem_path) relative_install_dir = ext_install_dir.relative_path_from full_gem_path |