From 9d4f37f51fb2ffdef5e318afb3cb81516dcba4f7 Mon Sep 17 00:00:00 2001 From: drbrain Date: Tue, 17 Jun 2008 22:04:18 +0000 Subject: Update RubyGems to 1.1.1 r1778 (almost 1.2) git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@17392 b2dd03c8-39d4-4d8f-98ff-823fe69b080e --- ChangeLog | 4 + lib/rubygems.rb | 95 +++++- lib/rubygems/command_manager.rb | 1 + lib/rubygems/commands/dependency_command.rb | 67 ++-- lib/rubygems/commands/environment_command.rb | 2 + lib/rubygems/commands/fetch_command.rb | 4 +- lib/rubygems/commands/install_command.rb | 4 +- lib/rubygems/commands/list_command.rb | 58 ++-- lib/rubygems/commands/lock_command.rb | 2 +- lib/rubygems/commands/outdated_command.rb | 7 +- lib/rubygems/commands/pristine_command.rb | 47 +-- lib/rubygems/commands/query_command.rb | 101 ++++-- lib/rubygems/commands/sources_command.rb | 84 +++-- lib/rubygems/commands/specification_command.rb | 7 +- lib/rubygems/commands/stale_command.rb | 27 ++ lib/rubygems/commands/update_command.rb | 57 ++-- lib/rubygems/config_file.rb | 40 ++- lib/rubygems/custom_require.rb | 2 +- lib/rubygems/defaults.rb | 2 +- lib/rubygems/dependency.rb | 72 ++++- lib/rubygems/dependency_installer.rb | 80 +++-- lib/rubygems/dependency_list.rb | 2 +- lib/rubygems/doc_manager.rb | 10 +- lib/rubygems/indexer.rb | 352 ++++++++++++++++----- lib/rubygems/install_update_options.rb | 6 + lib/rubygems/installer.rb | 47 ++- lib/rubygems/local_remote_options.rb | 31 +- lib/rubygems/platform.rb | 16 +- lib/rubygems/remote_fetcher.rb | 230 ++++++++------ lib/rubygems/requirement.rb | 28 +- lib/rubygems/rubygems_version.rb | 2 +- lib/rubygems/server.rb | 347 +++++++++++++------- lib/rubygems/source_index.rb | 62 +++- lib/rubygems/spec_fetcher.rb | 251 +++++++++++++++ lib/rubygems/specification.rb | 143 +++++++-- lib/rubygems/test_utilities.rb | 120 +++++++ lib/rubygems/uninstaller.rb | 5 +- lib/rubygems/user_interaction.rb | 223 ++++++++----- lib/rubygems/version.rb | 44 +-- test/rubygems/gemutilities.rb | 149 ++++----- test/rubygems/test_config.rb | 5 - test/rubygems/test_gem.rb | 24 +- test/rubygems/test_gem_command_manager.rb | 2 +- .../test_gem_commands_dependency_command.rb | 79 ++++- .../test_gem_commands_environment_command.rb | 1 + test/rubygems/test_gem_commands_fetch_command.rb | 28 +- test/rubygems/test_gem_commands_install_command.rb | 14 +- .../rubygems/test_gem_commands_outdated_command.rb | 43 +++ .../rubygems/test_gem_commands_pristine_command.rb | 17 +- test/rubygems/test_gem_commands_query_command.rb | 150 ++++++--- test/rubygems/test_gem_commands_sources_command.rb | 132 ++++++-- .../test_gem_commands_specification_command.rb | 5 +- test/rubygems/test_gem_commands_stale_command.rb | 39 +++ test/rubygems/test_gem_commands_update_command.rb | 44 +-- test/rubygems/test_gem_config_file.rb | 33 +- test/rubygems/test_gem_dependency.rb | 51 +++ test/rubygems/test_gem_dependency_installer.rb | 133 ++++++-- test/rubygems/test_gem_gem_path_searcher.rb | 5 +- test/rubygems/test_gem_indexer.rb | 143 +++++++-- test/rubygems/test_gem_installer.rb | 32 +- test/rubygems/test_gem_local_remote_options.rb | 11 +- test/rubygems/test_gem_remote_fetcher.rb | 23 +- test/rubygems/test_gem_server.rb | 228 +++++++++++-- test/rubygems/test_gem_source_index.rb | 271 ++++++++++++---- test/rubygems/test_gem_source_info_cache.rb | 12 +- test/rubygems/test_gem_source_info_cache_entry.rb | 20 +- test/rubygems/test_gem_spec_fetcher.rb | 303 ++++++++++++++++++ test/rubygems/test_gem_specification.rb | 83 ++++- test/rubygems/test_gem_uninstaller.rb | 21 ++ test/rubygems/test_gem_version.rb | 19 +- test/rubygems/test_kernel.rb | 2 +- 71 files changed, 3721 insertions(+), 1083 deletions(-) create mode 100644 lib/rubygems/commands/stale_command.rb create mode 100644 lib/rubygems/spec_fetcher.rb create mode 100644 lib/rubygems/test_utilities.rb create mode 100644 test/rubygems/test_gem_commands_outdated_command.rb create mode 100644 test/rubygems/test_gem_commands_stale_command.rb create mode 100644 test/rubygems/test_gem_spec_fetcher.rb diff --git a/ChangeLog b/ChangeLog index 87c81cc835..1809b79552 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,7 @@ +Wed Jun 18 07:03:30 2008 Eric Hodel + + * lib/rubygems/*: Update to RubyGems r1778 (pre 1.2). + Wed Jun 18 04:27:58 2008 Koichi Sasada * KNOWNBUGS.rb, bootstraptest/pending.rb: move pending bug. diff --git a/lib/rubygems.rb b/lib/rubygems.rb index 1cb205cbc9..4f2cc94ae0 100644 --- a/lib/rubygems.rb +++ b/lib/rubygems.rb @@ -67,11 +67,14 @@ module Gem :RUBY_SO_NAME => RbConfig::CONFIG["RUBY_SO_NAME"], :arch => RbConfig::CONFIG["arch"], :bindir => RbConfig::CONFIG["bindir"], + :datadir => RbConfig::CONFIG["datadir"], :libdir => RbConfig::CONFIG["libdir"], :ruby_install_name => RbConfig::CONFIG["ruby_install_name"], :ruby_version => RbConfig::CONFIG["ruby_version"], :sitedir => RbConfig::CONFIG["sitedir"], - :sitelibdir => RbConfig::CONFIG["sitelibdir"] + :sitelibdir => RbConfig::CONFIG["sitelibdir"], + :vendordir => RbConfig::CONFIG["vendordir"] , + :vendorlibdir => RbConfig::CONFIG["vendorlibdir"] ) DIRECTORIES = %w[cache doc gems specifications] unless defined?(DIRECTORIES) @@ -137,7 +140,7 @@ module Gem unless matches.any? { |spec| spec.version == existing_spec.version } then raise Gem::Exception, - "can't activate #{gem}, already activated #{existing_spec.full_name}]" + "can't activate #{gem}, already activated #{existing_spec.full_name}" end return false @@ -151,7 +154,7 @@ module Gem @loaded_specs[spec.name] = spec # Load dependent gems first - spec.dependencies.each do |dep_gem| + spec.runtime_dependencies.each do |dep_gem| activate dep_gem end @@ -203,6 +206,19 @@ module Gem private_class_method :all_partials + ## + # See if a given gem is available. + + def self.available?(gem, *requirements) + requirements = Gem::Requirement.default if requirements.empty? + + unless gem.respond_to?(:name) && gem.respond_to?(:version_requirements) + gem = Gem::Dependency.new(gem, requirements) + end + + !Gem.source_index.search(gem).empty? + end + ## # The mode needed to read a file as straight binary. @@ -267,6 +283,13 @@ module Gem File.join(spec.full_gem_path, 'data', gem_name) end + ## + # A Zlib::Deflate.deflate wrapper + + def self.deflate(data) + Zlib::Deflate.deflate data + end + ## # The path where gems are to be installed. @@ -345,6 +368,33 @@ module Gem private_class_method :find_home + ## + # Zlib::GzipReader wrapper that unzips +data+. + + def self.gunzip(data) + data = StringIO.new data + + Zlib::GzipReader.new(data).read + end + + ## + # Zlib::GzipWriter wrapper that zips +data+. + + def self.gzip(data) + zipped = StringIO.new + + Zlib::GzipWriter.wrap zipped do |io| io.write data end + + zipped.string + end + + ## + # A Zlib::Inflate#inflate wrapper + + def self.inflate(data) + Zlib::Inflate.inflate data + end + ## # Return a list of all possible load paths for the latest version for all # gems in the Gem installation. @@ -438,7 +488,11 @@ module Gem @gem_path ||= nil unless @gem_path then - paths = [ENV['GEM_PATH']] || [default_path] + paths = if ENV['GEM_PATH'] then + [ENV['GEM_PATH']] + else + [default_path] + end if defined?(APPLE_GEM_HOME) and not ENV['GEM_PATH'] then paths << APPLE_GEM_HOME @@ -459,7 +513,7 @@ module Gem ## # Array of platforms this RubyGems supports. - + def self.platforms @platforms ||= [] if @platforms.empty? @@ -586,13 +640,13 @@ module Gem def self.set_paths(gpaths) if gpaths @gem_path = gpaths.split(File::PATH_SEPARATOR) - + if File::ALT_SEPARATOR then @gem_path.map! do |path| path.gsub File::ALT_SEPARATOR, File::SEPARATOR end end - + @gem_path << Gem.dir else @gem_path = [Gem.dir] @@ -683,24 +737,25 @@ module Gem end -end + MARSHAL_SPEC_DIR = "quick/Marshal.#{Gem.marshal_version}/" -# Modify the non-gem version of datadir to handle gem package names. + YAML_SPEC_DIR = 'quick/' -require 'rbconfig/datadir' +end -module Config # :nodoc: +module Config + # :stopdoc: class << self - alias gem_original_datadir datadir - # Return the path to the data directory associated with the named # package. If the package is loaded as a gem, return the gem # specific data directory. Otherwise return a path to the share # area as define by "#{ConfigMap[:datadir]}/#{package_name}". def datadir(package_name) - Gem.datadir(package_name) || Config.gem_original_datadir(package_name) + Gem.datadir(package_name) || + File.join(Gem::ConfigMap[:datadir], package_name) end end + # :startdoc: end require 'rubygems/exceptions' @@ -712,6 +767,18 @@ require 'rubygems/source_index' # Needed for Kernel#gem require 'rubygems/platform' require 'rubygems/builder' # HACK: Needed for rake's package task. +begin + require 'rubygems/defaults/operating_system' +rescue LoadError +end + +if defined?(RUBY_ENGINE) then + begin + require "rubygems/defaults/#{RUBY_ENGINE}" + rescue LoadError + end +end + if RUBY_VERSION < '1.9' then require 'rubygems/custom_require' end diff --git a/lib/rubygems/command_manager.rb b/lib/rubygems/command_manager.rb index ee55d5a200..dd9a1aee15 100644 --- a/lib/rubygems/command_manager.rb +++ b/lib/rubygems/command_manager.rb @@ -46,6 +46,7 @@ module Gem register_command :server register_command :sources register_command :specification + register_command :stale register_command :uninstall register_command :unpack register_command :update diff --git a/lib/rubygems/commands/dependency_command.rb b/lib/rubygems/commands/dependency_command.rb index 1a43505d7c..8fae87c90f 100644 --- a/lib/rubygems/commands/dependency_command.rb +++ b/lib/rubygems/commands/dependency_command.rb @@ -46,37 +46,67 @@ class Gem::Commands::DependencyCommand < Gem::Command options[:args] << '.' if options[:args].empty? specs = {} - source_indexes = [] + source_indexes = Hash.new do |h, source_uri| + h[source_uri] = Gem::SourceIndex.new + end - if local? then - source_indexes << Gem::SourceIndex.from_installed_gems + pattern = /\A#{Regexp.union(*options[:args])}/ + dependency = Gem::Dependency.new pattern, options[:version] + + if options[:reverse_dependencies] and remote? and not local? then + alert_error 'Only reverse dependencies for local gems are supported.' + terminate_interaction 1 end - if remote? then - Gem::SourceInfoCache.cache_data.map do |_, sice| - source_indexes << sice.source_index + if local? then + Gem.source_index.search(dependency).each do |spec| + source_indexes[:local].add_spec spec end end - options[:args].each do |name| - new_specs = nil - source_indexes.each do |source_index| - new_specs = find_gems(name, source_index) + if remote? and not options[:reverse_dependencies] then + fetcher = Gem::SpecFetcher.fetcher + + begin + fetcher.find_matching(dependency).each do |spec_tuple, source_uri| + spec = fetcher.fetch_spec spec_tuple, URI.parse(source_uri) + + source_indexes[source_uri].add_spec spec + end + rescue Gem::RemoteFetcher::FetchError => e + raise unless fetcher.warn_legacy e do + require 'rubygems/source_info_cache' + + specs = Gem::SourceInfoCache.search_with_source dependency, false + + specs.each do |spec, source_uri| + source_indexes[source_uri].add_spec spec + end + end end + end - say "No match found for #{name} (#{options[:version]})" if - new_specs.empty? + if source_indexes.empty? then + patterns = options[:args].join ',' + say "No gems found matching #{patterns} (#{options[:version]})" if + Gem.configuration.verbose - specs = specs.merge new_specs + terminate_interaction 1 end - terminate_interaction 1 if specs.empty? + specs = {} + + source_indexes.values.each do |source_index| + source_index.gems.each do |name, spec| + specs[spec.full_name] = [source_index, spec] + end + end reverse = Hash.new { |h, k| h[k] = [] } if options[:reverse_dependencies] then - specs.values.each do |source_index, spec| - reverse[spec.full_name] = find_reverse_dependencies spec, source_index + specs.values.each do |_, spec| + reverse[spec.full_name] = find_reverse_dependencies spec end end @@ -118,10 +148,10 @@ class Gem::Commands::DependencyCommand < Gem::Command end # Retuns list of [specification, dep] that are satisfied by spec. - def find_reverse_dependencies(spec, source_index) + def find_reverse_dependencies(spec) result = [] - source_index.each do |name, sp| + Gem.source_index.each do |name, sp| sp.dependencies.each do |dep| dep = Gem::Dependency.new(*dep) unless Gem::Dependency === dep @@ -146,5 +176,6 @@ class Gem::Commands::DependencyCommand < Gem::Command specs end + end diff --git a/lib/rubygems/commands/environment_command.rb b/lib/rubygems/commands/environment_command.rb index 342f93ca54..a67c00bfd6 100644 --- a/lib/rubygems/commands/environment_command.rb +++ b/lib/rubygems/commands/environment_command.rb @@ -51,6 +51,8 @@ class Gem::Commands::EnvironmentCommand < Gem::Command out << " - RUBY EXECUTABLE: #{Gem.ruby}\n" + out << " - EXECUTABLE DIRECTORY: #{Gem.bindir}\n" + out << " - RUBYGEMS PLATFORMS:\n" Gem.platforms.each do |platform| out << " - #{platform}\n" diff --git a/lib/rubygems/commands/fetch_command.rb b/lib/rubygems/commands/fetch_command.rb index ccedc45401..76c9924e6b 100644 --- a/lib/rubygems/commands/fetch_command.rb +++ b/lib/rubygems/commands/fetch_command.rb @@ -33,12 +33,14 @@ class Gem::Commands::FetchCommand < Gem::Command def execute version = options[:version] || Gem::Requirement.default + all = Gem::Requirement.default gem_names = get_all_gem_names gem_names.each do |gem_name| dep = Gem::Dependency.new gem_name, version - specs_and_sources = Gem::SourceInfoCache.search_with_source dep, true + + specs_and_sources = Gem::SpecFetcher.fetcher.fetch dep, all specs_and_sources.sort_by { |spec,| spec.version } diff --git a/lib/rubygems/commands/install_command.rb b/lib/rubygems/commands/install_command.rb index ce0bc6ba04..48cd3869f9 100644 --- a/lib/rubygems/commands/install_command.rb +++ b/lib/rubygems/commands/install_command.rb @@ -16,7 +16,6 @@ class Gem::Commands::InstallCommand < Gem::Command defaults = Gem::DependencyInstaller::DEFAULT_OPTIONS.merge({ :generate_rdoc => true, :generate_ri => true, - :install_dir => Gem.dir, :format_executable => false, :test => false, :version => Gem::Requirement.default, @@ -62,7 +61,8 @@ class Gem::Commands::InstallCommand < Gem::Command :install_dir => options[:install_dir], :security_policy => options[:security_policy], :wrappers => options[:wrappers], - :bin_dir => options[:bin_dir] + :bin_dir => options[:bin_dir], + :development => options[:development], } exit_code = 0 diff --git a/lib/rubygems/commands/list_command.rb b/lib/rubygems/commands/list_command.rb index f8b377fcde..f3e5da9551 100644 --- a/lib/rubygems/commands/list_command.rb +++ b/lib/rubygems/commands/list_command.rb @@ -1,33 +1,35 @@ require 'rubygems/command' require 'rubygems/commands/query_command' -module Gem - module Commands - class ListCommand < QueryCommand - - def initialize - super 'list', 'Display gems whose name starts with STRING' - - remove_option('--name-matches') - end - - def arguments # :nodoc: - "STRING start of gem name to look for" - end - - def defaults_str # :nodoc: - "--local --no-details" - end - - def usage # :nodoc: - "#{program_name} [STRING]" - end - - def execute - string = get_one_optional_argument || '' - options[:name] = /^#{string}/i - super - end - end +## +# An alternate to Gem::Commands::QueryCommand that searches for gems starting +# with the the supplied argument. + +class Gem::Commands::ListCommand < Gem::Commands::QueryCommand + + def initialize + super 'list', 'Display gems whose name starts with STRING' + + remove_option('--name-matches') + end + + def arguments # :nodoc: + "STRING start of gem name to look for" + end + + def defaults_str # :nodoc: + "--local --no-details" end + + def usage # :nodoc: + "#{program_name} [STRING]" + end + + def execute + string = get_one_optional_argument || '' + options[:name] = /^#{string}/i + super + end + end + diff --git a/lib/rubygems/commands/lock_command.rb b/lib/rubygems/commands/lock_command.rb index 3a3dcc0c6b..6be2774e92 100644 --- a/lib/rubygems/commands/lock_command.rb +++ b/lib/rubygems/commands/lock_command.rb @@ -80,7 +80,7 @@ lock it down to the exact version. say "gem '#{spec.name}', '= #{spec.version}'" unless locked[spec.name] locked[spec.name] = true - spec.dependencies.each do |dep| + spec.runtime_dependencies.each do |dep| next if locked[dep.name] candidates = Gem.source_index.search dep.name, dep.requirement_list diff --git a/lib/rubygems/commands/outdated_command.rb b/lib/rubygems/commands/outdated_command.rb index 9c0062019b..1cd1087dd1 100644 --- a/lib/rubygems/commands/outdated_command.rb +++ b/lib/rubygems/commands/outdated_command.rb @@ -1,6 +1,6 @@ require 'rubygems/command' require 'rubygems/local_remote_options' -require 'rubygems/source_info_cache' +require 'rubygems/spec_fetcher' require 'rubygems/version_option' class Gem::Commands::OutdatedCommand < Gem::Command @@ -20,8 +20,11 @@ class Gem::Commands::OutdatedCommand < Gem::Command locals.outdated.sort.each do |name| local = locals.search(/^#{name}$/).last - remotes = Gem::SourceInfoCache.search_with_source(/^#{name}$/, true) + + dep = Gem::Dependency.new local.name, ">= #{local.version}" + remotes = Gem::SpecFetcher.fetcher.fetch dep remote = remotes.last.first + say "#{local.name} (#{local.version} < #{remote.version})" end end diff --git a/lib/rubygems/commands/pristine_command.rb b/lib/rubygems/commands/pristine_command.rb index bbea835133..3e55a1bb30 100644 --- a/lib/rubygems/commands/pristine_command.rb +++ b/lib/rubygems/commands/pristine_command.rb @@ -82,51 +82,10 @@ revert the gem. end # TODO use installer options - installer = Gem::Installer.new gem, :wrappers => true + installer = Gem::Installer.new gem, :wrappers => true, :force => true + installer.install - gem_file = File.join install_dir, "cache", "#{spec.full_name}.gem" - - security_policy = nil # TODO use installer option - - format = Gem::Format.from_file_by_path gem_file, security_policy - - target_directory = File.join(install_dir, "gems", format.spec.full_name) - target_directory.untaint - - pristine_files = format.file_entries.collect { |data| data[0]["path"] } - file_map = {} - - format.file_entries.each do |entry, file_data| - file_map[entry["path"]] = file_data - end - - Dir.chdir target_directory do - deployed_files = Dir.glob(File.join("**", "*")) + - Dir.glob(File.join("**", ".*")) - - pristine_files = pristine_files.map { |f| File.expand_path f } - deployed_files = deployed_files.map { |f| File.expand_path f } - - to_redeploy = (pristine_files - deployed_files) - to_redeploy = to_redeploy.map { |path| path.untaint} - - if to_redeploy.length > 0 then - say "Restoring #{to_redeploy.length} file#{to_redeploy.length == 1 ? "" : "s"} to #{spec.full_name}..." - - to_redeploy.each do |path| - say " #{path}" - FileUtils.mkdir_p File.dirname(path) - File.open(path, "wb") do |out| - out.write file_map[path] - end - end - else - say "#{spec.full_name} is in pristine condition" - end - end - - installer.generate_bin - installer.build_extensions + say "Restored #{spec.full_name}" end end diff --git a/lib/rubygems/commands/query_command.rb b/lib/rubygems/commands/query_command.rb index ea83b93bbb..cc81f3f07e 100644 --- a/lib/rubygems/commands/query_command.rb +++ b/lib/rubygems/commands/query_command.rb @@ -1,6 +1,6 @@ require 'rubygems/command' require 'rubygems/local_remote_options' -require 'rubygems/source_info_cache' +require 'rubygems/spec_fetcher' require 'rubygems/version_option' class Gem::Commands::QueryCommand < Gem::Command @@ -74,7 +74,13 @@ class Gem::Commands::QueryCommand < Gem::Command say "*** LOCAL GEMS ***" say - output_query_results Gem.source_index.search(name) + specs = Gem.source_index.search name + + spec_tuples = specs.map do |spec| + [[spec.name, spec.version, spec.original_platform, spec], :local] + end + + output_query_results spec_tuples end if remote? then @@ -84,13 +90,26 @@ class Gem::Commands::QueryCommand < Gem::Command all = options[:all] + dep = Gem::Dependency.new name, Gem::Requirement.default begin - Gem::SourceInfoCache.cache all - rescue Gem::RemoteFetcher::FetchError - # no network + fetcher = Gem::SpecFetcher.fetcher + spec_tuples = fetcher.find_matching dep, all, false + rescue Gem::RemoteFetcher::FetchError => e + raise unless fetcher.warn_legacy e do + require 'rubygems/source_info_cache' + + dep.name = '' if dep.name == // + + specs = Gem::SourceInfoCache.search_with_source dep, false, all + + spec_tuples = specs.map do |spec, source_uri| + [[spec.name, spec.version, spec.original_platform, spec], + source_uri] + end + end end - output_query_results Gem::SourceInfoCache.search(name, false, all) + output_query_results spec_tuples end end @@ -104,28 +123,30 @@ class Gem::Commands::QueryCommand < Gem::Command !Gem.source_index.search(dep).empty? end - def output_query_results(gemspecs) + def output_query_results(spec_tuples) output = [] - gem_list_with_version = {} + versions = Hash.new { |h,name| h[name] = [] } - gemspecs.flatten.each do |gemspec| - gem_list_with_version[gemspec.name] ||= [] - gem_list_with_version[gemspec.name] << gemspec + spec_tuples.each do |spec_tuple, source_uri| + versions[spec_tuple.first] << [spec_tuple, source_uri] end - gem_list_with_version = gem_list_with_version.sort_by do |name, spec| + versions = versions.sort_by do |(name,),| name.downcase end - gem_list_with_version.each do |gem_name, list_of_matching| - list_of_matching = list_of_matching.sort_by { |x| x.version.to_ints }.reverse - seen_versions = {} + versions.each do |gem_name, matching_tuples| + matching_tuples = matching_tuples.sort_by do |(name, version,),| + version + end.reverse - list_of_matching.delete_if do |item| - if seen_versions[item.version] then + seen = {} + + matching_tuples.delete_if do |(name, version,),| + if seen[version] then true else - seen_versions[item.version] = true + seen[version] = true false end end @@ -133,12 +154,50 @@ class Gem::Commands::QueryCommand < Gem::Command entry = gem_name.dup if options[:versions] then - versions = list_of_matching.map { |s| s.version }.uniq + versions = matching_tuples.map { |(name, version,),| version }.uniq entry << " (#{versions.join ', '})" end - entry << "\n" << format_text(list_of_matching[0].summary, 68, 4) if - options[:details] + if options[:details] then + detail_tuple = matching_tuples.first + + spec = if detail_tuple.first.length == 4 then + detail_tuple.first.last + else + uri = URI.parse detail_tuple.last + Gem::SpecFetcher.fetcher.fetch_spec detail_tuple.first, uri + end + + entry << "\n" + authors = "Author#{spec.authors.length > 1 ? 's' : ''}: " + authors << spec.authors.join(', ') + entry << format_text(authors, 68, 4) + + if spec.rubyforge_project and not spec.rubyforge_project.empty? then + rubyforge = "Rubyforge: http://rubyforge.org/projects/#{spec.rubyforge_project}" + entry << "\n" << format_text(rubyforge, 68, 4) + end + + if spec.homepage and not spec.homepage.empty? then + entry << "\n" << format_text("Homepage: #{spec.homepage}", 68, 4) + end + + if spec.loaded_from then + if matching_tuples.length == 1 then + loaded_from = File.dirname File.dirname(spec.loaded_from) + entry << "\n" << " Installed at: #{loaded_from}" + else + label = 'Installed at' + matching_tuples.each do |(_,version,_,s),| + loaded_from = File.dirname File.dirname(s.loaded_from) + entry << "\n" << " #{label} (#{version}): #{loaded_from}" + label = ' ' * label.length + end + end + end + + entry << "\n\n" << format_text(spec.summary, 68, 4) + end output << entry end diff --git a/lib/rubygems/commands/sources_command.rb b/lib/rubygems/commands/sources_command.rb index 1558d79b8b..f45438463c 100644 --- a/lib/rubygems/commands/sources_command.rb +++ b/lib/rubygems/commands/sources_command.rb @@ -1,7 +1,8 @@ +require 'fileutils' require 'rubygems/command' require 'rubygems/remote_fetcher' require 'rubygems/source_info_cache' -require 'rubygems/source_info_cache_entry' +require 'rubygems/spec_fetcher' class Gem::Commands::SourcesCommand < Gem::Command @@ -21,14 +22,14 @@ class Gem::Commands::SourcesCommand < Gem::Command options[:remove] = value end - add_option '-u', '--update', 'Update source cache' do |value, options| - options[:update] = value - end - add_option '-c', '--clear-all', 'Remove all sources (clear the cache)' do |value, options| options[:clear_all] = value end + + add_option '-u', '--update', 'Update source cache' do |value, options| + options[:update] = value + end end def defaults_str @@ -36,9 +37,23 @@ class Gem::Commands::SourcesCommand < Gem::Command end def execute - options[:list] = !(options[:add] || options[:remove] || options[:clear_all] || options[:update]) + options[:list] = !(options[:add] || + options[:clear_all] || + options[:remove] || + options[:update]) if options[:clear_all] then + path = Gem::SpecFetcher.fetcher.dir + FileUtils.rm_rf path + + if not File.exist?(path) then + say "*** Removed specs cache ***" + elsif not File.writable?(path) then + say "*** Unable to remove source cache (write protected) ***" + else + say "*** Unable to remove source cache ***" + end + sic = Gem::SourceInfoCache remove_cache_file 'user', sic.user_cache_file remove_cache_file 'latest user', sic.latest_user_cache_file @@ -48,15 +63,10 @@ class Gem::Commands::SourcesCommand < Gem::Command if options[:add] then source_uri = options[:add] + uri = URI.parse source_uri - sice = Gem::SourceInfoCacheEntry.new nil, nil begin - sice.refresh source_uri, true - - Gem::SourceInfoCache.cache_data[source_uri] = sice - Gem::SourceInfoCache.cache.update - Gem::SourceInfoCache.cache.flush - + Gem::SpecFetcher.fetcher.load_specs uri, 'specs' Gem.sources << source_uri Gem.configuration.write @@ -64,15 +74,24 @@ class Gem::Commands::SourcesCommand < Gem::Command rescue URI::Error, ArgumentError say "#{source_uri} is not a URI" rescue Gem::RemoteFetcher::FetchError => e - say "Error fetching #{source_uri}:\n\t#{e.message}" - end - end + yaml_uri = uri + 'yaml' + gem_repo = Gem::RemoteFetcher.fetcher.fetch_size yaml_uri rescue false - if options[:update] then - Gem::SourceInfoCache.cache true - Gem::SourceInfoCache.cache.flush + if e.uri =~ /specs\.#{Regexp.escape Gem.marshal_version}\.gz$/ and + gem_repo then - say "source cache successfully updated" + alert_warning <<-EOF +RubyGems 1.2+ index not found for: +\t#{source_uri} + +Will cause RubyGems to revert to legacy indexes, degrading performance. + EOF + + say "#{source_uri} added to sources" + else + say "Error fetching #{source_uri}:\n\t#{e.message}" + end + end end if options[:remove] then @@ -81,14 +100,6 @@ class Gem::Commands::SourcesCommand < Gem::Command unless Gem.sources.include? source_uri then say "source #{source_uri} not present in cache" else - begin # HACK figure out how to get the cache w/o update - Gem::SourceInfoCache.cache - rescue Gem::RemoteFetcher::FetchError - end - - Gem::SourceInfoCache.cache_data.delete source_uri - Gem::SourceInfoCache.cache.update - Gem::SourceInfoCache.cache.flush Gem.sources.delete source_uri Gem.configuration.write @@ -96,6 +107,23 @@ class Gem::Commands::SourcesCommand < Gem::Command end end + if options[:update] then + fetcher = Gem::SpecFetcher.fetcher + + if fetcher.legacy_repos.empty? then + Gem.sources.each do |source_uri| + source_uri = URI.parse source_uri + fetcher.load_specs source_uri, 'specs' + fetcher.load_specs source_uri, 'latest_specs' + end + else + Gem::SourceInfoCache.cache true + Gem::SourceInfoCache.cache.flush + end + + say "source cache successfully updated" + end + if options[:list] then say "*** CURRENT SOURCES ***" say diff --git a/lib/rubygems/commands/specification_command.rb b/lib/rubygems/commands/specification_command.rb index 7c8598e53b..689f2560c9 100644 --- a/lib/rubygems/commands/specification_command.rb +++ b/lib/rubygems/commands/specification_command.rb @@ -52,9 +52,10 @@ class Gem::Commands::SpecificationCommand < Gem::Command end if remote? then - Gem::SourceInfoCache.cache_data.each do |_,sice| - specs.push(*sice.source_index.search(gem, options[:version])) - end + dep = Gem::Dependency.new gem, options[:version] + found = Gem::SpecFetcher.fetcher.fetch dep + + specs.push(*found.map { |spec,| spec }) end if specs.empty? then diff --git a/lib/rubygems/commands/stale_command.rb b/lib/rubygems/commands/stale_command.rb new file mode 100644 index 0000000000..78cbdcc00a --- /dev/null +++ b/lib/rubygems/commands/stale_command.rb @@ -0,0 +1,27 @@ +require 'rubygems/command' + +class Gem::Commands::StaleCommand < Gem::Command + def initialize + super('stale', 'List gems along with access times') + end + + def usage # :nodoc: + "#{program_name}" + end + + def execute + gem_to_atime = {} + Gem.source_index.each do |name, spec| + Dir["#{spec.full_gem_path}/**/*.*"].each do |file| + next if File.directory?(file) + stat = File.stat(file) + gem_to_atime[name] ||= stat.atime + gem_to_atime[name] = stat.atime if gem_to_atime[name] < stat.atime + end + end + + gem_to_atime.sort_by { |_, atime| atime }.each do |name, atime| + say "#{name} at #{atime.strftime '%c'}" + end + end +end diff --git a/lib/rubygems/commands/update_command.rb b/lib/rubygems/commands/update_command.rb index 31a97c4844..78baa8ba56 100644 --- a/lib/rubygems/commands/update_command.rb +++ b/lib/rubygems/commands/update_command.rb @@ -2,7 +2,7 @@ require 'rubygems/command' require 'rubygems/command_manager' require 'rubygems/install_update_options' require 'rubygems/local_remote_options' -require 'rubygems/source_info_cache' +require 'rubygems/spec_fetcher' require 'rubygems/version_option' require 'rubygems/commands/install_command' @@ -15,11 +15,10 @@ class Gem::Commands::UpdateCommand < Gem::Command def initialize super 'update', 'Update the named gems (or all installed gems) in the local repository', - :generate_rdoc => true, - :generate_ri => true, - :force => false, - :test => false, - :install_dir => Gem.dir + :generate_rdoc => true, + :generate_ri => true, + :force => false, + :test => false add_install_update_options @@ -60,21 +59,13 @@ class Gem::Commands::UpdateCommand < Gem::Command hig = {} # highest installed gems - Gem::SourceIndex.from_installed_gems.each do |name, spec| + Gem.source_index.each do |name, spec| if hig[spec.name].nil? or hig[spec.name].version < spec.version then hig[spec.name] = spec end end - pattern = if options[:args].empty? then - // - else - Regexp.union(*options[:args]) - end - - remote_gemspecs = Gem::SourceInfoCache.search pattern - - gems_to_update = which_to_update hig, remote_gemspecs + gems_to_update = which_to_update hig, options[:args] updated = [] @@ -135,20 +126,42 @@ class Gem::Commands::UpdateCommand < Gem::Command end end - def which_to_update(highest_installed_gems, remote_gemspecs) + def which_to_update(highest_installed_gems, gem_names) result = [] highest_installed_gems.each do |l_name, l_spec| - matching_gems = remote_gemspecs.select do |spec| - spec.name == l_name and Gem.platforms.any? do |platform| - platform == spec.platform + next if not gem_names.empty? and + gem_names.all? { |name| /#{name}/ !~ l_spec.name } + + dependency = Gem::Dependency.new l_spec.name, "> #{l_spec.version}" + + begin + fetcher = Gem::SpecFetcher.fetcher + spec_tuples = fetcher.find_matching dependency + rescue Gem::RemoteFetcher::FetchError => e + raise unless fetcher.warn_legacy e do + require 'rubygems/source_info_cache' + + dependency.name = '' if dependency.name == // + + specs = Gem::SourceInfoCache.search_with_source dependency + + spec_tuples = specs.map do |spec, source_uri| + [[spec.name, spec.version, spec.original_platform], source_uri] + end end end - highest_remote_gem = matching_gems.sort_by { |spec| spec.version }.last + matching_gems = spec_tuples.select do |(name, version, platform),| + name == l_name and Gem::Platform.match platform + end + + highest_remote_gem = matching_gems.sort_by do |(name, version),| + version + end.last if highest_remote_gem and - l_spec.version < highest_remote_gem.version then + l_spec.version < highest_remote_gem.first[1] then result << l_name end end diff --git a/lib/rubygems/config_file.rb b/lib/rubygems/config_file.rb index 5bca0bd14e..c657bf7f01 100644 --- a/lib/rubygems/config_file.rb +++ b/lib/rubygems/config_file.rb @@ -18,6 +18,22 @@ class Gem::ConfigFile DEFAULT_VERBOSITY = true DEFAULT_UPDATE_SOURCES = true + system_config_path = + begin + require 'Win32API' + + CSIDL_COMMON_APPDATA = 0x0023 + path = 0.chr * 260 + SHGetFolderPath = Win32API.new 'shell32', 'SHGetFolderPath', 'LLLLP', 'L' + SHGetFolderPath.call 0, CSIDL_COMMON_APPDATA, 0, 1, path + + path.strip + rescue LoadError + '/etc' + end + + SYSTEM_WIDE_CONFIG_FILE = File.join system_config_path, 'gemrc' + # List of arguments supplied to the config file object. attr_reader :args @@ -81,18 +97,8 @@ class Gem::ConfigFile @verbose = DEFAULT_VERBOSITY @update_sources = DEFAULT_UPDATE_SOURCES - begin - # HACK $SAFE ok? - @hash = open(config_file_name.dup.untaint) {|f| YAML.load(f) } - rescue ArgumentError - warn "Failed to load #{config_file_name}" - rescue Errno::ENOENT - # Ignore missing config file error. - rescue Errno::EACCES - warn "Failed to load #{config_file_name} due to permissions problem." - end - - @hash ||= {} + @hash = load_file(SYSTEM_WIDE_CONFIG_FILE) + @hash.merge!(load_file(config_file_name.dup.untaint)) # HACK these override command-line args, which is bad @backtrace = @hash[:backtrace] if @hash.key? :backtrace @@ -105,6 +111,16 @@ class Gem::ConfigFile handle_arguments arg_list end + def load_file(filename) + begin + YAML.load(File.read(filename)) if filename and File.exist?(filename) + rescue ArgumentError + warn "Failed to load #{config_file_name}" + rescue Errno::EACCES + warn "Failed to load #{config_file_name} due to permissions problem." + end or {} + end + # True if the backtrace option has been specified, or debug is on. def backtrace @backtrace or $DEBUG diff --git a/lib/rubygems/custom_require.rb b/lib/rubygems/custom_require.rb index 5ff65afb14..90e6b53959 100755 --- a/lib/rubygems/custom_require.rb +++ b/lib/rubygems/custom_require.rb @@ -26,7 +26,7 @@ module Kernel def require(path) # :nodoc: gem_original_require path rescue LoadError => load_error - if load_error.message =~ /\A[Nn]o such file to load -- #{Regexp.escape path}\z/ and + if load_error.message =~ /#{Regexp.escape path}\z/ and spec = Gem.searcher.find(path) then Gem.activate(spec.name, "= #{spec.version}") gem_original_require path diff --git a/lib/rubygems/defaults.rb b/lib/rubygems/defaults.rb index 3864e5faca..914b9f777f 100644 --- a/lib/rubygems/defaults.rb +++ b/lib/rubygems/defaults.rb @@ -2,7 +2,7 @@ module Gem # An Array of the default sources that come with RubyGems. def self.default_sources - %w[http://gems.rubyforge.org] + %w[http://gems.rubyforge.org/] end # Default home directory path to be used if an alternate value is not diff --git a/lib/rubygems/dependency.rb b/lib/rubygems/dependency.rb index be731d564e..7b9904df55 100644 --- a/lib/rubygems/dependency.rb +++ b/lib/rubygems/dependency.rb @@ -8,24 +8,54 @@ require 'rubygems' ## # The Dependency class holds a Gem name and a Gem::Requirement + class Gem::Dependency + ## + # Valid dependency types. + #-- + # When this list is updated, be sure to change + # Gem::Specification::CURRENT_SPECIFICATION_VERSION as well. + + TYPES = [ + :development, + :runtime, + ] + + ## + # Dependency name or regular expression. + attr_accessor :name + ## + # Dependency type. + + attr_reader :type + + ## + # Dependent versions. + attr_writer :version_requirements + ## + # Orders dependencies by name only. + def <=>(other) [@name] <=> [other.name] end ## - # Constructs the dependency - # - # name:: [String] name of the Gem - # version_requirements:: [String Array] version requirement (e.g. ["> 1.2"]) - # - def initialize(name, version_requirements) + # Constructs a dependency with +name+ and +requirements+. + + def initialize(name, version_requirements, type=:runtime) @name = name + + unless TYPES.include? type + raise ArgumentError, "Valid types are #{TYPES.inspect}, not #{@type.inspect}" + end + + @type = type + @version_requirements = Gem::Requirement.create version_requirements @version_requirement = nil # Avoid warnings. end @@ -48,17 +78,41 @@ class Gem::Dependency end def to_s # :nodoc: - "#{name} (#{version_requirements})" + "#{name} (#{version_requirements}, #{@type || :runtime})" end def ==(other) # :nodoc: self.class === other && self.name == other.name && + self.type == other.type && self.version_requirements == other.version_requirements end - def hash - name.hash + version_requirements.hash + ## + # Uses this dependency as a pattern to compare to the dependency +other+. + # This dependency will match if the name matches the other's name, and other + # has only an equal version requirement that satisfies this dependency. + + def =~(other) + return false unless self.class === other + + pattern = @name + pattern = /\A#{@name}\Z/ unless Regexp === pattern + + return false unless pattern =~ other.name + + reqs = other.version_requirements.requirements + + return false unless reqs.length == 1 + return false unless reqs.first.first == '=' + + version = reqs.first.last + + version_requirements.satisfied_by? version + end + + def hash # :nodoc: + name.hash + type.hash + version_requirements.hash end end diff --git a/lib/rubygems/dependency_installer.rb b/lib/rubygems/dependency_installer.rb index 7ea2c0c317..b849d37245 100644 --- a/lib/rubygems/dependency_installer.rb +++ b/lib/rubygems/dependency_installer.rb @@ -1,9 +1,12 @@ require 'rubygems' require 'rubygems/dependency_list' require 'rubygems/installer' -require 'rubygems/source_info_cache' +require 'rubygems/spec_fetcher' require 'rubygems/user_interaction' +## +# Installs a gem along with all its dependencies from local and remote gems. + class Gem::DependencyInstaller include Gem::UserInteraction @@ -25,36 +28,50 @@ class Gem::DependencyInstaller # Creates a new installer instance. # # Options are: - # :env_shebang:: See Gem::Installer::new. + # :cache_dir:: Alternate repository path to store .gem files in. # :domain:: :local, :remote, or :both. :local only searches gems in the # current directory. :remote searches only gems in Gem::sources. # :both searches both. + # :env_shebang:: See Gem::Installer::new. # :force:: See Gem::Installer#install. # :format_executable:: See Gem::Installer#initialize. - # :ignore_dependencies: Don't install any dependencies. - # :install_dir: See Gem::Installer#install. - # :security_policy: See Gem::Installer::new and Gem::Security. - # :wrappers: See Gem::Installer::new + # :ignore_dependencies:: Don't install any dependencies. + # :install_dir:: See Gem::Installer#install. + # :security_policy:: See Gem::Installer::new and Gem::Security. + # :wrappers:: See Gem::Installer::new + def initialize(options = {}) options = DEFAULT_OPTIONS.merge options - @env_shebang = options[:env_shebang] + + @bin_dir = options[:bin_dir] + @development = options[:development] @domain = options[:domain] + @env_shebang = options[:env_shebang] @force = options[:force] @format_executable = options[:format_executable] @ignore_dependencies = options[:ignore_dependencies] - @install_dir = options[:install_dir] || Gem.dir @security_policy = options[:security_policy] @wrappers = options[:wrappers] - @bin_dir = options[:bin_dir] @installed_gems = [] + + @install_dir = options[:install_dir] || Gem.dir + @cache_dir = options[:cache_dir] || @install_dir + + if options[:install_dir] then + spec_dir = File.join @install_dir, 'specifications' + @source_index = Gem::SourceIndex.from_gems_in spec_dir + else + @source_index = Gem.source_index + end end ## # Returns a list of pairs of gemspecs and source_uris that match # Gem::Dependency +dep+ from both local (Dir.pwd) and remote (Gem.sources) - # sources. Gems are sorted with newer gems preferred over older gems, and + # sources. Gems are sorted with newer gems prefered over older gems, and # local gems preferred over remote gems. + def find_gems_with_sources(dep) gems_and_sources = [] @@ -74,8 +91,7 @@ class Gem::DependencyInstaller all = requirements.length > 1 || (requirements.first != ">=" and requirements.first != ">") - found = Gem::SourceInfoCache.search_with_source dep, true, all - + found = Gem::SpecFetcher.fetcher.fetch dep, all gems_and_sources.push(*found) rescue Gem::RemoteFetcher::FetchError => e @@ -95,6 +111,7 @@ class Gem::DependencyInstaller ## # Gathers all dependencies necessary for the installation from local and # remote sources unless the ignore_dependencies was given. + def gather_dependencies specs = @specs_and_sources.map { |spec,_| spec } @@ -110,8 +127,18 @@ class Gem::DependencyInstaller next if spec.nil? or seen[spec.name] seen[spec.name] = true - spec.dependencies.each do |dep| - results = find_gems_with_sources(dep).reverse # local gems first + deps = spec.runtime_dependencies + deps |= spec.development_dependencies if @development + + deps.each do |dep| + results = find_gems_with_sources(dep).reverse + + results.reject! do |spec,| + @source_index.any? do |_, installed_spec| + dep.name == installed_spec.name and + dep.version_requirements.satisfied_by? installed_spec.version + end + end results.each do |dep_spec, source_uri| next if seen[dep_spec.name] @@ -126,6 +153,11 @@ class Gem::DependencyInstaller @gems_to_install = dependency_list.dependency_order.reverse end + ## + # Finds a spec and the source_uri it came from for gem +gem_name+ and + # +version+. Returns an Array of specs and sources required for + # installation of the gem. + def find_spec_by_name_and_version gem_name, version = Gem::Requirement.default spec_and_source = nil @@ -160,14 +192,16 @@ class Gem::DependencyInstaller if spec_and_source.nil? then raise Gem::GemNotFoundException, - "could not find #{gem_name} locally or in a repository" + "could not find gem #{gem_name} locally or in a repository" end @specs_and_sources = [spec_and_source] end ## - # Installs the gem and all its dependencies. + # Installs the gem and all its dependencies. Returns an Array of installed + # gems specifications. + def install dep_or_name, version = Gem::Requirement.default if String === dep_or_name then find_spec_by_name_and_version dep_or_name, version @@ -175,15 +209,14 @@ class Gem::DependencyInstaller @specs_and_sources = [find_gems_with_sources(dep_or_name).last] end - gather_dependencies + @installed_gems = [] - spec_dir = File.join @install_dir, 'specifications' - source_index = Gem::SourceIndex.from_gems_in spec_dir + gather_dependencies @gems_to_install.each do |spec| last = spec == @gems_to_install.last # HACK is this test for full_name acceptable? - next if source_index.any? { |n,_| n == spec.full_name } and not last + next if @source_index.any? { |n,_| n == spec.full_name } and not last # TODO: make this sorta_verbose so other users can benefit from it say "Installing gem #{spec.full_name}" if Gem.configuration.really_verbose @@ -191,7 +224,7 @@ class Gem::DependencyInstaller _, source_uri = @specs_and_sources.assoc spec begin local_gem_path = Gem::RemoteFetcher.fetcher.download spec, source_uri, - @install_dir + @cache_dir rescue Gem::RemoteFetcher::FetchError next if @force raise @@ -205,12 +238,15 @@ class Gem::DependencyInstaller :install_dir => @install_dir, :security_policy => @security_policy, :wrappers => @wrappers, - :bin_dir => @bin_dir + :bin_dir => @bin_dir, + :development => @development spec = inst.install @installed_gems << spec end + + @installed_gems end end diff --git a/lib/rubygems/dependency_list.rb b/lib/rubygems/dependency_list.rb index 81aa65bfb2..a129743914 100644 --- a/lib/rubygems/dependency_list.rb +++ b/lib/rubygems/dependency_list.rb @@ -69,7 +69,7 @@ class Gem::DependencyList # Are all the dependencies in the list satisfied? def ok? @specs.all? do |spec| - spec.dependencies.all? do |dep| + spec.runtime_dependencies.all? do |dep| @specs.find { |s| s.satisfies_requirement? dep } end end diff --git a/lib/rubygems/doc_manager.rb b/lib/rubygems/doc_manager.rb index f214269ab3..88d7964d85 100644 --- a/lib/rubygems/doc_manager.rb +++ b/lib/rubygems/doc_manager.rb @@ -9,9 +9,9 @@ require 'fileutils' module Gem class DocManager - + include UserInteraction - + # Create a document manager for the given gem spec. # # spec:: The Gem::Specification object representing the gem. @@ -22,12 +22,12 @@ module Gem @doc_dir = File.join(spec.installation_path, "doc", spec.full_name) @rdoc_args = rdoc_args.nil? ? [] : rdoc_args.split end - + # Is the RDoc documentation installed? def rdoc_installed? return File.exist?(File.join(@doc_dir, "rdoc")) end - + # Generate the RI documents for this gem spec. # # Note that if both RI and RDoc documents are generated from the @@ -102,7 +102,7 @@ module Gem args << '--quiet' args << @spec.require_paths.clone args << @spec.extra_rdoc_files - args.flatten! + args = args.flatten.map do |arg| arg.to_s end r = RDoc::RDoc.new diff --git a/lib/rubygems/indexer.rb b/lib/rubygems/indexer.rb index 9f6c0a2fc9..b45931a91d 100644 --- a/lib/rubygems/indexer.rb +++ b/lib/rubygems/indexer.rb @@ -1,5 +1,6 @@ require 'fileutils' require 'tmpdir' +require 'zlib' require 'rubygems' require 'rubygems/format' @@ -40,116 +41,303 @@ class Gem::Indexer marshal_name = "Marshal.#{Gem.marshal_version}" - @master_index = Gem::Indexer::MasterIndexBuilder.new "yaml", @directory - @marshal_index = Gem::Indexer::MarshalIndexBuilder.new marshal_name, @directory - @quick_index = Gem::Indexer::QuickIndexBuilder.new 'index', @directory + @master_index = File.join @directory, 'yaml' + @marshal_index = File.join @directory, marshal_name - quick_dir = File.join @directory, 'quick' - @latest_index = Gem::Indexer::LatestIndexBuilder.new 'latest_index', quick_dir + @quick_dir = File.join @directory, 'quick' + + @quick_marshal_dir = File.join @quick_dir, marshal_name + + @quick_index = File.join @quick_dir, 'index' + @latest_index = File.join @quick_dir, 'latest_index' + + @specs_index = File.join @directory, "specs.#{Gem.marshal_version}" + @latest_specs_index = File.join @directory, + "latest_specs.#{Gem.marshal_version}" + + files = [ + @specs_index, + "#{@specs_index}.gz", + @latest_specs_index, + "#{@latest_specs_index}.gz", + @quick_dir, + @master_index, + "#{@master_index}.Z", + @marshal_index, + "#{@marshal_index}.Z", + ] + + @files = files.map do |path| + path.sub @directory, '' + end + end + + ## + # Abbreviate the spec for downloading. Abbreviated specs are only used for + # searching, downloading and related activities and do not need deployment + # specific information (e.g. list of files). So we abbreviate the spec, + # making it much smaller for quicker downloads. + + def abbreviate(spec) + spec.files = [] + spec.test_files = [] + spec.rdoc_options = [] + spec.extra_rdoc_files = [] + spec.cert_chain = [] + spec end ## - # Build the index. - - def build_index - @master_index.build do - @quick_index.build do - @marshal_index.build do - @latest_index.build do - progress = ui.progress_reporter gem_file_list.size, - "Generating index for #{gem_file_list.size} gems in #{@dest_directory}", - "Loaded all gems" - - gem_file_list.each do |gemfile| - if File.size(gemfile.to_s) == 0 then - alert_warning "Skipping zero-length gem: #{gemfile}" - next - end - - begin - spec = Gem::Format.from_file_by_path(gemfile).spec - - unless gemfile =~ /\/#{Regexp.escape spec.original_name}.*\.gem\z/i then - alert_warning "Skipping misnamed gem: #{gemfile} => #{spec.full_name} (#{spec.original_name})" - next - end - - abbreviate spec - sanitize spec - - @master_index.add spec - @quick_index.add spec - @marshal_index.add spec - @latest_index.add spec - - progress.updated spec.original_name - - rescue SignalException => e - alert_error "Received signal, exiting" - raise - rescue Exception => e - alert_error "Unable to process #{gemfile}\n#{e.message} (#{e.class})\n\t#{e.backtrace.join "\n\t"}" - end - end - - progress.done - - say "Generating master indexes (this may take a while)" - end + # Build various indicies + + def build_indicies(index) + progress = ui.progress_reporter index.size, + "Generating quick index gemspecs for #{index.size} gems", + "Complete" + + index.each do |original_name, spec| + spec_file_name = "#{original_name}.gemspec.rz" + yaml_name = File.join @quick_dir, spec_file_name + marshal_name = File.join @quick_marshal_dir, spec_file_name + + yaml_zipped = Gem.deflate spec.to_yaml + open yaml_name, 'wb' do |io| io.write yaml_zipped end + + marshal_zipped = Gem.deflate Marshal.dump(spec) + open marshal_name, 'wb' do |io| io.write marshal_zipped end + + progress.updated original_name + end + + progress.done + + say "Generating specs index" + + open @specs_index, 'wb' do |io| + specs = index.sort.map do |_, spec| + platform = spec.original_platform + platform = Gem::Platform::RUBY if platform.nil? + [spec.name, spec.version, platform] + end + + specs = compact_specs specs + + Marshal.dump specs, io + end + + say "Generating latest specs index" + + open @latest_specs_index, 'wb' do |io| + specs = index.latest_specs.sort.map do |spec| + [spec.name, spec.version, spec.original_platform] + end + + specs = compact_specs specs + + Marshal.dump specs, io + end + + say "Generating quick index" + + quick_index = File.join @quick_dir, 'index' + open quick_index, 'wb' do |io| + io.puts index.sort.map { |_, spec| spec.original_name } + end + + say "Generating latest index" + + latest_index = File.join @quick_dir, 'latest_index' + open latest_index, 'wb' do |io| + io.puts index.latest_specs.sort.map { |spec| spec.original_name } + end + + say "Generating Marshal master index" + + open @marshal_index, 'wb' do |io| + io.write index.dump + end + + progress = ui.progress_reporter index.size, + "Generating YAML master index for #{index.size} gems (this may take a while)", + "Complete" + + open @master_index, 'wb' do |io| + io.puts "--- !ruby/object:#{index.class}" + io.puts "gems:" + + gems = index.sort_by { |name, gemspec| gemspec.sort_obj } + gems.each do |original_name, gemspec| + yaml = gemspec.to_yaml.gsub(/^/, ' ') + yaml = yaml.sub(/\A ---/, '') # there's a needed extra ' ' here + io.print " #{original_name}:" + io.puts yaml + + progress.updated original_name + end + end + + progress.done + + say "Compressing indicies" + # use gzip for future files. + + compress quick_index, 'rz' + paranoid quick_index, 'rz' + + compress latest_index, 'rz' + paranoid latest_index, 'rz' + + compress @marshal_index, 'Z' + paranoid @marshal_index, 'Z' + + compress @master_index, 'Z' + paranoid @master_index, 'Z' + + gzip @specs_index + gzip @latest_specs_index + end + + ## + # Collect specifications from .gem files from the gem directory. + + def collect_specs + index = Gem::SourceIndex.new + + progress = ui.progress_reporter gem_file_list.size, + "Loading #{gem_file_list.size} gems from #{@dest_directory}", + "Loaded all gems" + + gem_file_list.each do |gemfile| + if File.size(gemfile.to_s) == 0 then + alert_warning "Skipping zero-length gem: #{gemfile}" + next + end + + begin + spec = Gem::Format.from_file_by_path(gemfile).spec + + unless gemfile =~ /\/#{Regexp.escape spec.original_name}.*\.gem\z/i then + alert_warning "Skipping misnamed gem: #{gemfile} => #{spec.full_name} (#{spec.original_name})" + next end + + abbreviate spec + sanitize spec + + index.gems[spec.original_name] = spec + + progress.updated spec.original_name + + rescue SignalException => e + alert_error "Received signal, exiting" + raise + rescue Exception => e + alert_error "Unable to process #{gemfile}\n#{e.message} (#{e.class})\n\t#{e.backtrace.join "\n\t"}" end end + + progress.done + + index end - def install_index - verbose = Gem.configuration.really_verbose + ## + # Compacts Marshal output for the specs index data source by using identical + # objects as much as possible. - say "Moving index into production dir #{@dest_directory}" if verbose + def compact_specs(specs) + names = {} + versions = {} + platforms = {} - files = @master_index.files + @quick_index.files + @marshal_index.files + - @latest_index.files + specs.map do |(name, version, platform)| + names[name] = name unless names.include? name + versions[version] = version unless versions.include? version + platforms[platform] = platform unless platforms.include? platform - files.each do |file| - src_name = File.join @directory, file - dst_name = File.join @dest_directory, file + [names[name], versions[version], platforms[platform]] + end + end - FileUtils.rm_rf dst_name, :verbose => verbose - FileUtils.mv src_name, @dest_directory, :verbose => verbose + ## + # Compress +filename+ with +extension+. + + def compress(filename, extension) + data = Gem.read_binary filename + + zipped = Gem.deflate data + + open "#{filename}.#{extension}", 'wb' do |io| + io.write zipped end end + ## + # List of gem file names to index. + + def gem_file_list + Dir.glob(File.join(@dest_directory, "gems", "*.gem")) + end + + ## + # Builds and installs indexicies. + def generate_index FileUtils.rm_rf @directory FileUtils.mkdir_p @directory, :mode => 0700 + FileUtils.mkdir_p @quick_marshal_dir - build_index - install_index + index = collect_specs + build_indicies index + install_indicies rescue SignalException ensure FileUtils.rm_rf @directory end - # List of gem file names to index. - def gem_file_list - Dir.glob(File.join(@dest_directory, "gems", "*.gem")) + ## + # Zlib::GzipWriter wrapper that gzips +filename+ on disk. + + def gzip(filename) + Zlib::GzipWriter.open "#{filename}.gz" do |io| + io.write Gem.read_binary(filename) + end end - # Abbreviate the spec for downloading. Abbreviated specs are only - # used for searching, downloading and related activities and do not - # need deployment specific information (e.g. list of files). So we - # abbreviate the spec, making it much smaller for quicker downloads. - def abbreviate(spec) - spec.files = [] - spec.test_files = [] - spec.rdoc_options = [] - spec.extra_rdoc_files = [] - spec.cert_chain = [] - spec + ## + # Install generated indicies into the destination directory. + + def install_indicies + verbose = Gem.configuration.really_verbose + + say "Moving index into production dir #{@dest_directory}" if verbose + + @files.each do |file| + src_name = File.join @directory, file + dst_name = File.join @dest_directory, file + + FileUtils.rm_rf dst_name, :verbose => verbose + FileUtils.mv src_name, @dest_directory, :verbose => verbose + end + end + + ## + # Ensure +path+ and path with +extension+ are identical. + + def paranoid(path, extension) + data = Gem.read_binary path + compressed_data = Gem.read_binary "#{path}.#{extension}" + + unless data == Gem.inflate(compressed_data) then + raise "Compressed file #{compressed_path} does not match uncompressed file #{path}" + end end + ## # Sanitize the descriptive fields in the spec. Sometimes non-ASCII # characters will garble the site index. Non-ASCII characters will # be replaced by their XML entity equivalent. + def sanitize(spec) spec.summary = sanitize_string(spec.summary) spec.description = sanitize_string(spec.description) @@ -158,7 +346,9 @@ class Gem::Indexer spec end + ## # Sanitize a single string. + def sanitize_string(string) # HACK the #to_s is in here because RSpec has an Array of Arrays of # Strings for authors. Need a way to disallow bad values on gempsec @@ -168,9 +358,3 @@ class Gem::Indexer end -require 'rubygems/indexer/abstract_index_builder' -require 'rubygems/indexer/master_index_builder' -require 'rubygems/indexer/quick_index_builder' -require 'rubygems/indexer/marshal_index_builder' -require 'rubygems/indexer/latest_index_builder' - diff --git a/lib/rubygems/install_update_options.rb b/lib/rubygems/install_update_options.rb index 58807be62a..5202c105db 100644 --- a/lib/rubygems/install_update_options.rb +++ b/lib/rubygems/install_update_options.rb @@ -89,6 +89,12 @@ module Gem::InstallUpdateOptions 'foo_exec18') do |value, options| options[:format_executable] = value end + + add_option(:"Install/Update", "--development", + "Install any additional development", + "dependencies") do |value, options| + options[:development] = true + end end # Default options for the gem install command. diff --git a/lib/rubygems/installer.rb b/lib/rubygems/installer.rb index 9dbbca8d08..ae699a90a0 100644 --- a/lib/rubygems/installer.rb +++ b/lib/rubygems/installer.rb @@ -56,6 +56,7 @@ class Gem::Installer # foo_exec18. # :security_policy:: Use the specified security policy. See Gem::Security # :wrappers:: Install wrappers if true, symlinks if false. + def initialize(gem, options={}) @gem = gem @@ -76,6 +77,7 @@ class Gem::Installer @security_policy = options[:security_policy] @wrappers = options[:wrappers] @bin_dir = options[:bin_dir] + @development = options[:development] begin @format = Gem::Format.from_file_by_path @gem, @security_policy @@ -98,6 +100,7 @@ class Gem::Installer # cache/.gem #=> a cached copy of the installed gem # gems//... #=> extracted files # specifications/.gemspec #=> the Gem::Specification + def install # If we're forcing the install then disable security unless the security # policy says that we only install singed gems. @@ -119,7 +122,10 @@ class Gem::Installer end unless @ignore_dependencies then - @spec.dependencies.each do |dep_gem| + deps = @spec.runtime_dependencies + deps |= @spec.development_dependencies if @development + + deps.each do |dep_gem| ensure_dependency @spec, dep_gem end end @@ -150,6 +156,8 @@ class Gem::Installer @spec.loaded_from = File.join(@gem_home, 'specifications', "#{@spec.full_name}.gemspec") + Gem.source_index.add_spec @spec + return @spec rescue Zlib::GzipFile::Error raise Gem::InstallError, "gzip error installing #{@gem}" @@ -161,6 +169,7 @@ class Gem::Installer # # spec :: Gem::Specification # dependency :: Gem::Dependency + def ensure_dependency(spec, dependency) unless installation_satisfies_dependency? dependency then raise Gem::InstallError, "#{spec.name} requires #{dependency}" @@ -170,17 +179,15 @@ class Gem::Installer end ## - # True if the current installed gems satisfy the given dependency. - # - # dependency :: Gem::Dependency + # True if the gems in Gem.source_index satisfy +dependency+. + def installation_satisfies_dependency?(dependency) - current_index = Gem::SourceIndex.from_installed_gems - current_index.find_name(dependency.name, dependency.version_requirements).size > 0 + Gem.source_index.find_name(dependency.name, dependency.version_requirements).size > 0 end ## # Unpacks the gem into the given directory. - # + def unpack(directory) @gem_dir = directory @format = Gem::Format.from_file_by_path @gem, @security_policy @@ -193,7 +200,7 @@ class Gem::Installer # # spec:: [Gem::Specification] The Gem specification to output # spec_path:: [String] The location (path) to write the gemspec to - # + def write_spec rubycode = @spec.to_ruby @@ -208,7 +215,7 @@ class Gem::Installer ## # Creates windows .bat files for easy running of commands - # + def generate_windows_script(bindir, filename) if Gem.win_platform? then script_name = filename + ".bat" @@ -227,7 +234,7 @@ class Gem::Installer # If the user has asked for the gem to be installed in a directory that is # the system gem directory, then use the system bin directory, else create # (or use) a new bin dir under the gem_home. - bindir = @bin_dir ? @bin_dir : (Gem.bindir @gem_home) + bindir = @bin_dir ? @bin_dir : Gem.bindir(@gem_home) Dir.mkdir bindir unless File.exist? bindir raise Gem::FilePermissionError.new(bindir) unless File.writable? bindir @@ -252,7 +259,7 @@ class Gem::Installer # The Windows script is generated in addition to the regular one due to a # bug or misfeature in the Windows shell's pipe. See # http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-talk/193379 - # + def generate_bin_script(filename, bindir) bin_script_path = File.join bindir, formatted_program_filename(filename) @@ -260,6 +267,8 @@ class Gem::Installer # HACK some gems don't have #! in their executables, restore 2008/06 #if File.read(exec_path, 2) == '#!' then + FileUtils.rm_f bin_script_path # prior install may have been --no-wrappers + File.open bin_script_path, 'w', 0755 do |file| file.print app_script_text(filename) end @@ -277,7 +286,7 @@ class Gem::Installer ## # Creates the symlinks to run the applications in the gem. Moves # the symlink if the gem being installed has a newer version. - # + def generate_bin_symlink(filename, bindir) if Gem.win_platform? then alert_warning "Unable to use symlinks on Windows, installing wrapper" @@ -303,6 +312,7 @@ class Gem::Installer ## # Generates a #! line for +bin_file_name+'s wrapper copying arguments if # necessary. + def shebang(bin_file_name) if @env_shebang then "#!/usr/bin/env " + Gem::ConfigMap[:ruby_install_name] @@ -324,7 +334,9 @@ class Gem::Installer end end + ## # Return the text for an application file. + def app_script_text(bin_file_name) <<-TEXT #{shebang bin_file_name} @@ -349,7 +361,9 @@ load '#{bin_file_name}' TEXT end + ## # return the stub script text used to launch the true ruby script + def windows_stub_script(bindir, bin_file_name) <<-TEXT @ECHO OFF @@ -361,8 +375,10 @@ GOTO :EOF TEXT end + ## # Builds extensions. Valid types of extensions are extconf.rb files, # configure scripts and rakefiles or mkrf_conf files. + def build_extensions return if @spec.extensions.empty? say "Building native extensions. This could take a while..." @@ -418,6 +434,7 @@ Results logged to #{File.join(Dir.pwd, 'gem_make.out')} # Reads the file index and extracts each file into the gem directory. # # Ensures that files can't be installed outside the gem directory. + def extract_files expand_and_validate_gem_dir @@ -445,11 +462,15 @@ Results logged to #{File.join(Dir.pwd, 'gem_make.out')} out.write file_data end + FileUtils.chmod entry['mode'], path + say path if Gem.configuration.really_verbose end end + ## # Prefix and suffix the program filename the same as ruby. + def formatted_program_filename(filename) if @format_executable then self.class.exec_format % File.basename(filename) @@ -460,7 +481,9 @@ Results logged to #{File.join(Dir.pwd, 'gem_make.out')} private + ## # HACK Pathname is broken on windows. + def absolute_path? pathname pathname.absolute? or (Gem.win_platform? and pathname.to_s =~ /\A[a-z]:/i) end diff --git a/lib/rubygems/local_remote_options.rb b/lib/rubygems/local_remote_options.rb index 1a5410bef7..799b9d5893 100644 --- a/lib/rubygems/local_remote_options.rb +++ b/lib/rubygems/local_remote_options.rb @@ -4,27 +4,34 @@ # See LICENSE.txt for permissions. #++ +require 'uri' require 'rubygems' +## # Mixin methods for local and remote Gem::Command options. + module Gem::LocalRemoteOptions + ## # Allows OptionParser to handle HTTP URIs. + def accept_uri_http OptionParser.accept URI::HTTP do |value| begin - value = URI.parse value + uri = URI.parse value rescue URI::InvalidURIError raise OptionParser::InvalidArgument, value end - raise OptionParser::InvalidArgument, value unless value.scheme == 'http' + raise OptionParser::InvalidArgument, value unless uri.scheme == 'http' value end end + ## # Add local/remote options to the command line parser. + def add_local_remote_options add_option(:"Local/Remote", '-l', '--local', 'Restrict operations to the LOCAL domain') do |value, options| @@ -47,7 +54,9 @@ module Gem::LocalRemoteOptions add_update_sources_option end + ## # Add the --bulk-threshold option + def add_bulk_threshold_option add_option(:"Local/Remote", '-B', '--bulk-threshold COUNT', "Threshold for switching to bulk", @@ -57,7 +66,9 @@ module Gem::LocalRemoteOptions end end + ## # Add the --http-proxy option + def add_proxy_option accept_uri_http @@ -68,22 +79,28 @@ module Gem::LocalRemoteOptions end end + ## # Add the --source option + def add_source_option accept_uri_http add_option(:"Local/Remote", '--source URL', URI::HTTP, - 'Use URL as the remote source for gems') do |value, options| + 'Use URL as the remote source for gems') do |source, options| + source << '/' if source !~ /\/\z/ + if options[:added_source] then - Gem.sources << value + Gem.sources << source else options[:added_source] = true - Gem.sources.replace [value] + Gem.sources.replace [source] end end end + ## # Add the --source option + def add_update_sources_option add_option(:"Local/Remote", '-u', '--[no-]update-sources', @@ -92,12 +109,16 @@ module Gem::LocalRemoteOptions end end + ## # Is local fetching enabled? + def local? options[:domain] == :local || options[:domain] == :both end + ## # Is remote fetching enabled? + def remote? options[:domain] == :remote || options[:domain] == :both end diff --git a/lib/rubygems/platform.rb b/lib/rubygems/platform.rb index 7abc15ef94..5e932cd592 100644 --- a/lib/rubygems/platform.rb +++ b/lib/rubygems/platform.rb @@ -1,7 +1,8 @@ require 'rubygems' +## # Available list of platforms for targeting Gem installations. -# + class Gem::Platform @local = nil @@ -122,11 +123,20 @@ class Gem::Platform to_a.compact.join '-' end + ## + # Is +other+ equal to this platform? Two platforms are equal if they have + # the same CPU, OS and version. + def ==(other) self.class === other and @cpu == other.cpu and @os == other.os and @version == other.version end + ## + # Does +other+ match this platform? Two platforms match if they have the + # same CPU, or either has a CPU of 'universal', they have the same OS, and + # they have the same version, or either has no version. + def ===(other) return nil unless Gem::Platform === other @@ -140,6 +150,10 @@ class Gem::Platform (@version.nil? or other.version.nil? or @version == other.version) end + ## + # Does +other+ match this platform? If +other+ is a String it will be + # converted to a Gem::Platform first. See #=== for matching rules. + def =~(other) case other when Gem::Platform then # nop diff --git a/lib/rubygems/remote_fetcher.rb b/lib/rubygems/remote_fetcher.rb index 96775c4d00..93252fe83a 100644 --- a/lib/rubygems/remote_fetcher.rb +++ b/lib/rubygems/remote_fetcher.rb @@ -1,4 +1,5 @@ require 'net/http' +require 'stringio' require 'uri' require 'rubygems' @@ -11,15 +12,38 @@ class Gem::RemoteFetcher include Gem::UserInteraction - class FetchError < Gem::Exception; end + ## + # A FetchError exception wraps up the various possible IO and HTTP failures + # that could happen while downloading from the internet. + + class FetchError < Gem::Exception + + ## + # The URI which was being accessed when the exception happened. + + attr_accessor :uri + + def initialize(message, uri) + super message + @uri = uri + end + + def to_s # :nodoc: + "#{super} (#{uri})" + end + + end @fetcher = nil + ## # Cached RemoteFetcher instance. + def self.fetcher @fetcher ||= self.new Gem.configuration[:http_proxy] end + ## # Initialize a remote fetcher using the source URI and possible proxy # information. # @@ -29,6 +53,7 @@ class Gem::RemoteFetcher # * nil: respect environment variables (HTTP_PROXY, HTTP_PROXY_USER, # HTTP_PROXY_PASS) # * :no_proxy: ignore environment variables and _don't_ use a proxy + def initialize(proxy) Socket.do_not_reverse_lookup = true @@ -47,11 +72,13 @@ class Gem::RemoteFetcher # Moves the gem +spec+ from +source_uri+ to the cache dir unless it is # already there. If the source_uri is local the gem cache dir copy is # always replaced. + def download(spec, source_uri, install_dir = Gem.dir) + cache_dir = File.join install_dir, 'cache' gem_file_name = "#{spec.full_name}.gem" - local_gem_path = File.join install_dir, 'cache', gem_file_name + local_gem_path = File.join cache_dir, gem_file_name - Gem.ensure_gem_subdirectories install_dir + FileUtils.mkdir_p cache_dir rescue nil unless File.exist? cache_dir source_uri = URI.parse source_uri unless URI::Generic === source_uri scheme = source_uri.scheme @@ -102,21 +129,26 @@ class Gem::RemoteFetcher local_gem_path end - # Downloads +uri+. + ## + # Downloads +uri+ and returns it as a String. + def fetch_path(uri) open_uri_or_path(uri) do |input| input.read end + rescue FetchError + raise rescue Timeout::Error - raise FetchError, "timed out fetching #{uri}" + raise FetchError.new('timed out', uri) rescue IOError, SocketError, SystemCallError => e - raise FetchError, "#{e.class}: #{e} reading #{uri}" + raise FetchError.new("#{e.class}: #{e}", uri) rescue => e - message = "#{e.class}: #{e} reading #{uri}" - raise FetchError, message + raise FetchError.new("#{e.class}: #{e}", uri) end + ## # Returns the size of +uri+ in bytes. + def fetch_size(uri) return File.size(get_file_uri_path(uri)) if file_uri? uri @@ -124,30 +156,21 @@ class Gem::RemoteFetcher raise ArgumentError, 'uri is not an HTTP URI' unless URI::HTTP === uri - http = connect_to uri.host, uri.port - - request = Net::HTTP::Head.new uri.request_uri - - request.basic_auth unescape(uri.user), unescape(uri.password) unless - uri.user.nil? or uri.user.empty? + response = request uri, Net::HTTP::Head - resp = http.request request - - if resp.code !~ /^2/ then - raise Gem::RemoteSourceException, - "HTTP Response #{resp.code} fetching #{uri}" + if response.code !~ /^2/ then + raise FetchError.new("bad response #{response.message} #{response.code}", uri) end - if resp['content-length'] then - return resp['content-length'].to_i + if response['content-length'] then + return response['content-length'].to_i else - resp = http.get uri.request_uri - return resp.body.size + response = http.get uri.request_uri + return response.body.size end rescue SocketError, SystemCallError, Timeout::Error => e - raise Gem::RemoteFetcher::FetchError, - "#{e.message} (#{e.class})\n\tgetting size of #{uri}" + raise FetchError.new("#{e.message} (#{e.class})\n\tfetching size", uri) end private @@ -162,7 +185,9 @@ class Gem::RemoteFetcher URI.unescape(str) end + ## # Returns an HTTP proxy URI if one is set in the environment variables. + def get_proxy_from_env env_proxy = ENV['http_proxy'] || ENV['HTTP_PROXY'] @@ -179,104 +204,129 @@ class Gem::RemoteFetcher uri end + ## # Normalize the URI by adding "http://" if it is missing. + def normalize_uri(uri) (uri =~ /^(https?|ftp|file):/) ? uri : "http://#{uri}" end - # Connect to the source host/port, using a proxy if needed. - def connect_to(host, port) - if @proxy_uri - Net::HTTP::Proxy(@proxy_uri.host, @proxy_uri.port, unescape(@proxy_uri.user), unescape(@proxy_uri.password)).new(host, port) - else - Net::HTTP.new(host, port) + ## + # Creates or an HTTP connection based on +uri+, or retrieves an existing + # connection, using a proxy if needed. + + def connection_for(uri) + net_http_args = [uri.host, uri.port] + + if @proxy_uri then + net_http_args += [ + @proxy_uri.host, + @proxy_uri.port, + @proxy_uri.user, + @proxy_uri.password + ] + end + + connection_id = net_http_args.join ':' + @connections[connection_id] ||= Net::HTTP.new(*net_http_args) + connection = @connections[connection_id] + + if uri.scheme == 'https' and not connection.started? then + http_obj.use_ssl = true + http_obj.verify_mode = OpenSSL::SSL::VERIFY_NONE end + + connection.start unless connection.started? + + connection end + ## # Read the data from the (source based) URI, but if it is a file:// URI, # read from the filesystem instead. + def open_uri_or_path(uri, depth = 0, &block) if file_uri?(uri) open(get_file_uri_path(uri), &block) else uri = URI.parse uri unless URI::Generic === uri - net_http_args = [uri.host, uri.port] - - if @proxy_uri then - net_http_args += [ @proxy_uri.host, - @proxy_uri.port, - @proxy_uri.user, - @proxy_uri.password - ] - end - - connection_id = net_http_args.join ':' - @connections[connection_id] ||= Net::HTTP.new(*net_http_args) - connection = @connections[connection_id] - if uri.scheme == 'https' && ! connection.started? - http_obj.use_ssl = true - http_obj.verify_mode = OpenSSL::SSL::VERIFY_NONE - end - - connection.start unless connection.started? - - request = Net::HTTP::Get.new(uri.request_uri) - unless uri.nil? || uri.user.nil? || uri.user.empty? then - request.basic_auth(uri.user, uri.password) - end - - ua = "RubyGems/#{Gem::RubyGemsVersion} #{Gem::Platform.local}" - ua << " Ruby/#{RUBY_VERSION} (#{RUBY_RELEASE_DATE}" - ua << " patchlevel #{RUBY_PATCHLEVEL}" if defined? RUBY_PATCHLEVEL - ua << ")" - - request.add_field 'User-Agent', ua - request.add_field 'Connection', 'keep-alive' - request.add_field 'Keep-Alive', '30' - - # HACK work around EOFError bug in Net::HTTP - # NOTE Errno::ECONNABORTED raised a lot on Windows, and make impossible - # to install gems. - retried = false - begin - @requests[connection_id] += 1 - response = connection.request(request) - rescue EOFError, Errno::ECONNABORTED - requests = @requests[connection_id] - say "connection reset after #{requests} requests, retrying" if - Gem.configuration.really_verbose - - raise Gem::RemoteFetcher::FetchError, 'too many connection resets' if - retried - - @requests[connection_id] = 0 - - connection.finish - connection.start - retried = true - retry - end + response = request uri case response when Net::HTTPOK then block.call(StringIO.new(response.body)) if block when Net::HTTPRedirection then - raise Gem::RemoteFetcher::FetchError, "too many redirects" if depth > 10 + raise FetchError.new('too many redirects', uri) if depth > 10 + open_uri_or_path(response['Location'], depth + 1, &block) else - raise Gem::RemoteFetcher::FetchError, - "bad response #{response.message} #{response.code}" + raise FetchError.new("bad response #{response.message} #{response.code}", uri) end end end + ## + # Performs a Net::HTTP request of type +request_class+ on +uri+ returning + # a Net::HTTP response object. request maintains a table of persistent + # connections to reduce connect overhead. + + def request(uri, request_class = Net::HTTP::Get) + request = request_class.new uri.request_uri + + unless uri.nil? || uri.user.nil? || uri.user.empty? then + request.basic_auth uri.user, uri.password + end + + ua = "RubyGems/#{Gem::RubyGemsVersion} #{Gem::Platform.local}" + ua << " Ruby/#{RUBY_VERSION} (#{RUBY_RELEASE_DATE}" + ua << " patchlevel #{RUBY_PATCHLEVEL}" if defined? RUBY_PATCHLEVEL + ua << ")" + + request.add_field 'User-Agent', ua + request.add_field 'Connection', 'keep-alive' + request.add_field 'Keep-Alive', '30' + + connection = connection_for uri + + retried = false + + # HACK work around EOFError bug in Net::HTTP + # NOTE Errno::ECONNABORTED raised a lot on Windows, and make impossible + # to install gems. + begin + @requests[connection.object_id] += 1 + response = connection.request request + say "#{request.method} #{response.code} #{response.message}: #{uri}" if + Gem.configuration.really_verbose + rescue EOFError, Errno::ECONNABORTED, Errno::ECONNRESET + requests = @requests[connection.object_id] + say "connection reset after #{requests} requests, retrying" if + Gem.configuration.really_verbose + + raise FetchError.new('too many connection resets', uri) if retried + + @requests.delete connection.object_id + + connection.finish + connection.start + retried = true + retry + end + + response + end + + ## # Checks if the provided string is a file:// URI. + def file_uri?(uri) uri =~ %r{\Afile://} end + ## # Given a file:// URI, returns its local path. + def get_file_uri_path(uri) uri.sub(%r{\Afile://}, '') end diff --git a/lib/rubygems/requirement.rb b/lib/rubygems/requirement.rb index f1213152f2..c9128b5ebc 100644 --- a/lib/rubygems/requirement.rb +++ b/lib/rubygems/requirement.rb @@ -12,6 +12,7 @@ require 'rubygems/version' # # A Requirement object can actually contain multiple, er, # requirements, as in (> 1.2, < 2.0). + class Gem::Requirement include Comparable @@ -35,7 +36,7 @@ class Gem::Requirement # Version, a String, or nil. Intended to simplify client code. # # If the input is "weird", the default version requirement is returned. - # + def self.create(input) case input when Gem::Requirement then @@ -57,6 +58,7 @@ class Gem::Requirement # This comment once said: # # "A default "version requirement" can surely _only_ be '> 0'." + def self.default self.new ['>= 0'] end @@ -65,6 +67,7 @@ class Gem::Requirement # Constructs a Requirement from +requirements+ which can be a String, a # Gem::Version, or an Array of those. See parse for details on the # formatting of requirement strings. + def initialize(requirements) @requirements = case requirements when Array then @@ -77,13 +80,17 @@ class Gem::Requirement @version = nil # Avoid warnings. end + ## # Marshal raw requirements, rather than the full object - def marshal_dump + + def marshal_dump # :nodoc: [@requirements] end + ## # Load custom marshal format - def marshal_load(array) + + def marshal_load(array) # :nodoc: @requirements = array[0] @version = nil end @@ -108,20 +115,16 @@ class Gem::Requirement end ## - # Is the requirement satisfied by +version+. - # - # version:: [Gem::Version] the version to compare against - # return:: [Boolean] true if this requirement is satisfied by - # the version, otherwise false - # + # True if this requirement satisfied by the Gem::Version +version+. + def satisfied_by?(version) normalize @requirements.all? { |op, rv| satisfy?(op, version, rv) } end ## - # Is "version op required_version" satisfied? - # + # Is "+version+ +op+ +required_version+" satisfied? + def satisfy?(op, version, required_version) OPS[op].call(version, required_version) end @@ -132,6 +135,7 @@ class Gem::Requirement # The requirement can be a String or a Gem::Version. A String can be an # operator (<, <=, =, =>, >, !=, ~>), a version number, or both, operator # first. + def parse(obj) case obj when /^\s*(#{OP_RE})\s*([0-9.]+)\s*$/o then @@ -147,7 +151,7 @@ class Gem::Requirement end end - def <=>(other) + def <=>(other) # :nodoc: to_s <=> other.to_s end diff --git a/lib/rubygems/rubygems_version.rb b/lib/rubygems/rubygems_version.rb index 453f9b57b6..d4d4af0558 100644 --- a/lib/rubygems/rubygems_version.rb +++ b/lib/rubygems/rubygems_version.rb @@ -2,5 +2,5 @@ # This file is auto-generated by build scripts. # See: rake update_version module Gem - RubyGemsVersion = '1.1.1' + RubyGemsVersion = '1.1.1.1778' end diff --git a/lib/rubygems/server.rb b/lib/rubygems/server.rb index b4f58f9706..2c617ff144 100644 --- a/lib/rubygems/server.rb +++ b/lib/rubygems/server.rb @@ -4,6 +4,7 @@ require 'zlib' require 'erb' require 'rubygems' +require 'rubygems/doc_manager' ## # Gem::Server and allows users to serve gems for consumption by @@ -11,18 +12,24 @@ require 'rubygems' # # gem_server starts an HTTP server on the given port and serves the following: # * "/" - Browsing of gem spec files for installed gems -# * "/Marshal" - Full SourceIndex dump of metadata for installed gems -# * "/yaml" - YAML dump of metadata for installed gems - deprecated +# * "/specs.#{Gem.marshal_version}.gz" - specs name/version/platform index +# * "/latest_specs.#{Gem.marshal_version}.gz" - latest specs +# name/version/platform index +# * "/quick/" - Individual gemspecs # * "/gems" - Direct access to download the installable gems +# * legacy indexes: +# * "/Marshal.#{Gem.marshal_version}" - Full SourceIndex dump of metadata +# for installed gems +# * "/yaml" - YAML dump of metadata for installed gems - deprecated # # == Usage # -# gem server [-p portnum] [-d gem_path] +# gem_server = Gem::Server.new Gem.dir, 8089, false +# gem_server.run # -# port_num:: The TCP port the HTTP server will bind to -# gem_path:: -# Root gem directory containing both "cache" and "specifications" -# subdirectories. +#-- +# TODO Refactor into a real WEBrick servlet to remove code duplication. + class Gem::Server include Gem::UserInteraction @@ -36,7 +43,6 @@ class Gem::Server RubyGems Documentation Index - @@ -325,32 +331,99 @@ div.method-source-code pre { color: #ffdead; overflow: hidden; } new(options[:gemdir], options[:port], options[:daemon]).run end - def initialize(gemdir, port, daemon) + def initialize(gem_dir, port, daemon) Socket.do_not_reverse_lookup = true - @gemdir = gemdir + @gem_dir = gem_dir @port = port @daemon = daemon logger = WEBrick::Log.new nil, WEBrick::BasicLog::FATAL @server = WEBrick::HTTPServer.new :DoNotListen => true, :Logger => logger - @spec_dir = File.join @gemdir, "specifications" + @spec_dir = File.join @gem_dir, 'specifications' + + unless File.directory? @spec_dir then + raise ArgumentError, "#{@gem_dir} does not appear to be a gem repository" + end + @source_index = Gem::SourceIndex.from_gems_in @spec_dir end + def Marshal(req, res) + @source_index.refresh! + + res['date'] = File.stat(@spec_dir).mtime + + index = Marshal.dump @source_index + + if req.request_method == 'HEAD' then + res['content-length'] = index.length + return + end + + if req.path =~ /Z$/ then + res['content-type'] = 'application/x-deflate' + index = Gem.deflate index + else + res['content-type'] = 'application/octet-stream' + end + + res.body << index + end + + def latest_specs(req, res) + @source_index.refresh! + + res['content-type'] = 'application/x-gzip' + + res['date'] = File.stat(@spec_dir).mtime + + specs = @source_index.latest_specs.sort.map do |spec| + platform = spec.original_platform + platform = Gem::Platform::RUBY if platform.nil? + [spec.name, spec.version, platform] + end + + specs = Marshal.dump specs + + if req.path =~ /\.gz$/ then + specs = Gem.gzip specs + res['content-type'] = 'application/x-gzip' + else + res['content-type'] = 'application/octet-stream' + end + + if req.request_method == 'HEAD' then + res['content-length'] = specs.length + else + res.body << specs + end + end + def quick(req, res) + @source_index.refresh! + res['content-type'] = 'text/plain' res['date'] = File.stat(@spec_dir).mtime - case req.request_uri.request_uri + case req.request_uri.path when '/quick/index' then - res.body << @source_index.map { |name,_| name }.join("\n") + res.body << @source_index.map { |name,| name }.sort.join("\n") when '/quick/index.rz' then - index = @source_index.map { |name,_| name }.join("\n") - res.body << Zlib::Deflate.deflate(index) + index = @source_index.map { |name,| name }.sort.join("\n") + res['content-type'] = 'application/x-deflate' + res.body << Gem.deflate(index) + when '/quick/latest_index' then + index = @source_index.latest_specs.map { |spec| spec.full_name } + res.body << index.sort.join("\n") + when '/quick/latest_index.rz' then + index = @source_index.latest_specs.map { |spec| spec.full_name } + res['content-type'] = 'application/x-deflate' + res.body << Gem.deflate(index.sort.join("\n")) when %r|^/quick/(Marshal.#{Regexp.escape Gem.marshal_version}/)?(.*?)-([0-9.]+)(-.*?)?\.gemspec\.rz$| then dep = Gem::Dependency.new $2, $3 specs = @source_index.search dep + marshal_format = $1 selector = [$2, $3, $4].map { |s| s.inspect }.join ' ' @@ -368,17 +441,98 @@ div.method-source-code pre { color: #ffdead; overflow: hidden; } elsif specs.length > 1 then res.status = 500 res.body = "Multiple gems found matching #{selector}" - elsif $1 then # marshal quickindex instead of YAML - res.body << Zlib::Deflate.deflate(Marshal.dump(specs.first)) + elsif marshal_format then + res['content-type'] = 'application/x-deflate' + res.body << Gem.deflate(Marshal.dump(specs.first)) else # deprecated YAML format - res.body << Zlib::Deflate.deflate(specs.first.to_yaml) + res['content-type'] = 'application/x-deflate' + res.body << Gem.deflate(specs.first.to_yaml) end else - res.status = 404 - res.body = "#{req.request_uri} not found" + raise WEBrick::HTTPStatus::NotFound, "`#{req.path}' not found." end end + def root(req, res) + @source_index.refresh! + res['date'] = File.stat(@spec_dir).mtime + + raise WEBrick::HTTPStatus::NotFound, "`#{req.path}' not found." unless + req.path == '/' + + specs = [] + total_file_count = 0 + + @source_index.each do |path, spec| + total_file_count += spec.files.size + deps = spec.dependencies.map do |dep| + { "name" => dep.name, + "type" => dep.type, + "version" => dep.version_requirements.to_s, } + end + + deps = deps.sort_by { |dep| [dep["name"].downcase, dep["version"]] } + deps.last["is_last"] = true unless deps.empty? + + # executables + executables = spec.executables.sort.collect { |exec| {"executable" => exec} } + executables = nil if executables.empty? + executables.last["is_last"] = true if executables + + specs << { + "authors" => spec.authors.sort.join(", "), + "date" => spec.date.to_s, + "dependencies" => deps, + "doc_path" => "/doc_root/#{spec.full_name}/rdoc/index.html", + "executables" => executables, + "only_one_executable" => (executables && executables.size == 1), + "full_name" => spec.full_name, + "has_deps" => !deps.empty?, + "homepage" => spec.homepage, + "name" => spec.name, + "rdoc_installed" => Gem::DocManager.new(spec).rdoc_installed?, + "summary" => spec.summary, + "version" => spec.version.to_s, + } + end + + specs << { + "authors" => "Chad Fowler, Rich Kilmer, Jim Weirich, Eric Hodel and others", + "dependencies" => [], + "doc_path" => "/doc_root/rubygems-#{Gem::RubyGemsVersion}/rdoc/index.html", + "executables" => [{"executable" => 'gem', "is_last" => true}], + "only_one_executable" => true, + "full_name" => "rubygems-#{Gem::RubyGemsVersion}", + "has_deps" => false, + "homepage" => "http://rubygems.org/", + "name" => 'rubygems', + "rdoc_installed" => true, + "summary" => "RubyGems itself", + "version" => Gem::RubyGemsVersion, + } + + specs = specs.sort_by { |spec| [spec["name"].downcase, spec["version"]] } + specs.last["is_last"] = true + + # tag all specs with first_name_entry + last_spec = nil + specs.each do |spec| + is_first = last_spec.nil? || (last_spec["name"].downcase != spec["name"].downcase) + spec["first_name_entry"] = is_first + last_spec = spec + end + + # create page from template + template = ERB.new(DOC_TEMPLATE) + res['content-type'] = 'text/html' + + values = { "gem_count" => specs.size.to_s, "specs" => specs, + "total_file_count" => total_file_count.to_s } + + result = template.result binding + res.body = result + end + def run @server.listen nil, @port @@ -386,27 +540,21 @@ div.method-source-code pre { color: #ffdead; overflow: hidden; } WEBrick::Daemon.start if @daemon - @server.mount_proc("/yaml") do |req, res| - res['content-type'] = 'text/plain' - res['date'] = File.stat(@spec_dir).mtime - if req.request_method == 'HEAD' then - res['content-length'] = @source_index.to_yaml.length - else - res.body << @source_index.to_yaml - end - end + @server.mount_proc "/yaml", method(:yaml) + @server.mount_proc "/yaml.Z", method(:yaml) - @server.mount_proc("/Marshal") do |req, res| - res['content-type'] = 'text/plain' - res['date'] = File.stat(@spec_dir).mtime - if req.request_method == 'HEAD' then - res['content-length'] = Marshal.dump(@source_index).length - else - res.body << Marshal.dump(@source_index) - end - end + @server.mount_proc "/Marshal.#{Gem.marshal_version}", method(:Marshal) + @server.mount_proc "/Marshal.#{Gem.marshal_version}.Z", method(:Marshal) + + @server.mount_proc "/specs.#{Gem.marshal_version}", method(:specs) + @server.mount_proc "/specs.#{Gem.marshal_version}.gz", method(:specs) + + @server.mount_proc "/latest_specs.#{Gem.marshal_version}", + method(:latest_specs) + @server.mount_proc "/latest_specs.#{Gem.marshal_version}.gz", + method(:latest_specs) - @server.mount_proc("/quick/", &method(:quick)) + @server.mount_proc "/quick/", method(:quick) @server.mount_proc("/gem-server-rdoc-style.css") do |req, res| res['content-type'] = 'text/css' @@ -414,80 +562,12 @@ div.method-source-code pre { color: #ffdead; overflow: hidden; } res.body << RDOC_CSS end - @server.mount_proc("/") do |req, res| - specs = [] - total_file_count = 0 - - @source_index.each do |path, spec| - total_file_count += spec.files.size - deps = spec.dependencies.collect { |dep| - { "name" => dep.name, - "version" => dep.version_requirements.to_s, } - } - deps = deps.sort_by { |dep| [dep["name"].downcase, dep["version"]] } - deps.last["is_last"] = true unless deps.empty? - - # executables - executables = spec.executables.sort.collect { |exec| {"executable" => exec} } - executables = nil if executables.empty? - executables.last["is_last"] = true if executables - - specs << { - "authors" => spec.authors.sort.join(", "), - "date" => spec.date.to_s, - "dependencies" => deps, - "doc_path" => ('/doc_root/' + spec.full_name + '/rdoc/index.html'), - "executables" => executables, - "only_one_executable" => (executables && executables.size==1), - "full_name" => spec.full_name, - "has_deps" => !deps.empty?, - "homepage" => spec.homepage, - "name" => spec.name, - "rdoc_installed" => Gem::DocManager.new(spec).rdoc_installed?, - "summary" => spec.summary, - "version" => spec.version.to_s, - } - end - - specs << { - "authors" => "Chad Fowler, Rich Kilmer, Jim Weirich, Eric Hodel and others", - "dependencies" => [], - "doc_path" => "/doc_root/rubygems-#{Gem::RubyGemsVersion}/rdoc/index.html", - "executables" => [{"executable" => 'gem', "is_last" => true}], - "only_one_executable" => true, - "full_name" => "rubygems-#{Gem::RubyGemsVersion}", - "has_deps" => false, - "homepage" => "http://rubygems.org/", - "name" => 'rubygems', - "rdoc_installed" => true, - "summary" => "RubyGems itself", - "version" => Gem::RubyGemsVersion, - } - - specs = specs.sort_by { |spec| [spec["name"].downcase, spec["version"]] } - specs.last["is_last"] = true - - # tag all specs with first_name_entry - last_spec = nil - specs.each do |spec| - is_first = last_spec.nil? || (last_spec["name"].downcase != spec["name"].downcase) - spec["first_name_entry"] = is_first - last_spec = spec - end - - # create page from template - template = ERB.new(DOC_TEMPLATE) - res['content-type'] = 'text/html' - values = { "gem_count" => specs.size.to_s, "specs" => specs, - "total_file_count" => total_file_count.to_s } - result = template.result binding - res.body = result - end + @server.mount_proc "/", method(:root) paths = { "/gems" => "/cache/", "/doc_root" => "/doc/" } paths.each do |mount_point, mount_dir| @server.mount(mount_point, WEBrick::HTTPServlet::FileHandler, - File.join(@gemdir, mount_dir), true) + File.join(@gem_dir, mount_dir), true) end trap("INT") { @server.shutdown; exit! } @@ -496,5 +576,54 @@ div.method-source-code pre { color: #ffdead; overflow: hidden; } @server.start end + def specs(req, res) + @source_index.refresh! + + res['date'] = File.stat(@spec_dir).mtime + + specs = @source_index.sort.map do |_, spec| + platform = spec.original_platform + platform = Gem::Platform::RUBY if platform.nil? + [spec.name, spec.version, platform] + end + + specs = Marshal.dump specs + + if req.path =~ /\.gz$/ then + specs = Gem.gzip specs + res['content-type'] = 'application/x-gzip' + else + res['content-type'] = 'application/octet-stream' + end + + if req.request_method == 'HEAD' then + res['content-length'] = specs.length + else + res.body << specs + end + end + + def yaml(req, res) + @source_index.refresh! + + res['date'] = File.stat(@spec_dir).mtime + + index = @source_index.to_yaml + + if req.path =~ /Z$/ then + res['content-type'] = 'application/x-deflate' + index = Gem.deflate index + else + res['content-type'] = 'text/plain' + end + + if req.request_method == 'HEAD' then + res['content-length'] = index.length + return + end + + res.body << index + end + end diff --git a/lib/rubygems/source_index.rb b/lib/rubygems/source_index.rb index 8057fd1d0f..b940b83cf8 100644 --- a/lib/rubygems/source_index.rb +++ b/lib/rubygems/source_index.rb @@ -7,6 +7,7 @@ require 'rubygems' require 'rubygems/user_interaction' require 'rubygems/specification' +require 'rubygems/spec_fetcher' ## # The SourceIndex object indexes all the gems available from a @@ -27,6 +28,11 @@ class Gem::SourceIndex attr_reader :gems # :nodoc: + ## + # Directories to use to refresh this SourceIndex when calling refresh! + + attr_accessor :spec_dirs + class << self include Gem::UserInteraction @@ -39,7 +45,7 @@ class Gem::SourceIndex # +from_gems_in+. This argument is deprecated and is provided # just for backwards compatibility, and should not generally # be used. - # + # # return:: # SourceIndex instance @@ -63,7 +69,9 @@ class Gem::SourceIndex # +spec_dirs+. def from_gems_in(*spec_dirs) - self.new.load_gems_in(*spec_dirs) + source_index = new + source_index.spec_dirs = spec_dirs + source_index.refresh! end ## @@ -79,6 +87,8 @@ class Gem::SourceIndex return gemspec end alert_warning "File '#{file_name}' does not evaluate to a gem specification" + rescue SignalException, SystemExit + raise rescue SyntaxError => e alert_warning e alert_warning spec_code @@ -100,6 +110,7 @@ class Gem::SourceIndex def initialize(specifications={}) @gems = specifications + @spec_dirs = nil end ## @@ -121,8 +132,8 @@ class Gem::SourceIndex end ## - # Returns a Hash of name => Specification of the latest versions of each - # gem in this index. + # Returns an Array specifications for the latest versions of each gem in + # this index. def latest_specs result = Hash.new { |h,k| h[k] = [] } @@ -241,7 +252,9 @@ class Gem::SourceIndex when Gem::Dependency then only_platform = platform_only version_requirement = gem_pattern.version_requirements - gem_pattern = if gem_pattern.name.empty? then + gem_pattern = if Regexp === gem_pattern.name then + gem_pattern.name + elsif gem_pattern.name.empty? then // else /^#{Regexp.escape gem_pattern.name}$/ @@ -271,29 +284,43 @@ class Gem::SourceIndex ## # Replaces the gems in the source index from specifications in the - # installed_spec_directories, + # directories this source index was created from. Raises an exception if + # this source index wasn't created from a directory (via from_gems_in or + # from_installed_gems, or having spec_dirs set). def refresh! - load_gems_in(*self.class.installed_spec_directories) + raise 'source index not created from disk' if @spec_dirs.nil? + load_gems_in(*@spec_dirs) end ## # Returns an Array of Gem::Specifications that are not up to date. def outdated - dep = Gem::Dependency.new '', Gem::Requirement.default - - remotes = Gem::SourceInfoCache.search dep, true - outdateds = [] latest_specs.each do |local| name = local.name - remote = remotes.select { |spec| spec.name == name }. - sort_by { |spec| spec.version.to_ints }. - last - outdateds << name if remote and local.version < remote.version + dependency = Gem::Dependency.new name, ">= #{local.version}" + + begin + fetcher = Gem::SpecFetcher.fetcher + remotes = fetcher.find_matching dependency + remotes = remotes.map { |(name, version,),| version } + rescue Gem::RemoteFetcher::FetchError => e + raise unless fetcher.warn_legacy e do + require 'rubygems/source_info_cache' + + specs = Gem::SourceInfoCache.search_with_source dependency, true + + remotes = specs.map { |spec,| spec.version } + end + end + + latest = remotes.sort.last + + outdateds << name if latest and local.version < latest end outdateds @@ -387,7 +414,8 @@ class Gem::SourceIndex end def fetch_bulk_index(source_uri) - say "Bulk updating Gem source index for: #{source_uri}" + say "Bulk updating Gem source index for: #{source_uri}" if + Gem.configuration.verbose index = fetch_index_from(source_uri) if index.nil? then @@ -447,7 +475,7 @@ class Gem::SourceIndex def unzip(string) require 'zlib' - Zlib::Inflate.inflate(string) + Gem.inflate string end ## diff --git a/lib/rubygems/spec_fetcher.rb b/lib/rubygems/spec_fetcher.rb new file mode 100644 index 0000000000..29db889af5 --- /dev/null +++ b/lib/rubygems/spec_fetcher.rb @@ -0,0 +1,251 @@ +require 'zlib' + +require 'rubygems' +require 'rubygems/remote_fetcher' +require 'rubygems/user_interaction' + +## +# SpecFetcher handles metadata updates from remote gem repositories. + +class Gem::SpecFetcher + + include Gem::UserInteraction + + ## + # The SpecFetcher cache dir. + + attr_reader :dir # :nodoc: + + ## + # Cache of latest specs + + attr_reader :latest_specs # :nodoc: + + ## + # Cache of all spces + + attr_reader :specs # :nodoc: + + @fetcher = nil + + def self.fetcher + @fetcher ||= new + end + + def self.fetcher=(fetcher) # :nodoc: + @fetcher = fetcher + end + + def initialize + @dir = File.join Gem.user_home, '.gem', 'specs' + @update_cache = File.stat(Gem.user_home).uid == Process.uid + + @specs = {} + @latest_specs = {} + + @fetcher = Gem::RemoteFetcher.fetcher + end + + ## + # Retuns the local directory to write +uri+ to. + + def cache_dir(uri) + File.join @dir, "#{uri.host}%#{uri.port}", File.dirname(uri.path) + end + + ## + # Fetch specs matching +dependency+. If +all+ is true, all matching + # versions are returned. If +matching_platform+ is false, all platforms are + # returned. + + def fetch(dependency, all = false, matching_platform = true) + specs_and_sources = find_matching dependency, all, matching_platform + + specs_and_sources.map do |spec_tuple, source_uri| + [fetch_spec(spec_tuple, URI.parse(source_uri)), source_uri] + end + + rescue Gem::RemoteFetcher::FetchError => e + raise unless warn_legacy e do + require 'rubygems/source_info_cache' + + return Gem::SourceInfoCache.search_with_source(dependency, + matching_platform, all) + end + end + + def fetch_spec(spec, source_uri) + spec = spec - [nil, 'ruby', ''] + spec_file_name = "#{spec.join '-'}.gemspec" + + uri = source_uri + "#{Gem::MARSHAL_SPEC_DIR}#{spec_file_name}" + + cache_dir = cache_dir uri + + local_spec = File.join cache_dir, spec_file_name + + if File.exist? local_spec then + spec = Gem.read_binary local_spec + else + uri.path << '.rz' + + spec = @fetcher.fetch_path uri + spec = Gem.inflate spec + + if @update_cache then + FileUtils.mkdir_p cache_dir + + open local_spec, 'wb' do |io| + io.write spec + end + end + end + + # TODO: Investigate setting Gem::Specification#loaded_from to a URI + Marshal.load spec + end + + ## + # Find spec names that match +dependency+. If +all+ is true, all matching + # versions are returned. If +matching_platform+ is false, gems for all + # platforms are returned. + + def find_matching(dependency, all = false, matching_platform = true) + found = {} + + list(all).each do |source_uri, specs| + found[source_uri] = specs.select do |spec_name, version, spec_platform| + dependency =~ Gem::Dependency.new(spec_name, version) and + (not matching_platform or Gem::Platform.match(spec_platform)) + end + end + + specs_and_sources = [] + + found.each do |source_uri, specs| + uri_str = source_uri.to_s + specs_and_sources.push(*specs.map { |spec| [spec, uri_str] }) + end + + specs_and_sources + end + + ## + # Returns Array of gem repositories that were generated with RubyGems less + # than 1.2. + + def legacy_repos + Gem.sources.reject do |source_uri| + source_uri = URI.parse source_uri + spec_path = source_uri + "specs.#{Gem.marshal_version}.gz" + + begin + @fetcher.fetch_size spec_path + rescue Gem::RemoteFetcher::FetchError + begin + @fetcher.fetch_size(source_uri + 'yaml') # re-raise if non-repo + rescue Gem::RemoteFetcher::FetchError + alert_error "#{source_uri} does not appear to be a repository" + raise + end + false + end + end + end + + ## + # Returns a list of gems available for each source in Gem::sources. If + # +all+ is true, all versions are returned instead of only latest versions. + + def list(all = false) + list = {} + + file = all ? 'specs' : 'latest_specs' + + Gem.sources.each do |source_uri| + source_uri = URI.parse source_uri + + if all and @specs.include? source_uri then + list[source_uri] = @specs[source_uri] + elsif @latest_specs.include? source_uri then + list[source_uri] = @latest_specs[source_uri] + else + specs = load_specs source_uri, file + + cache = all ? @specs : @latest_specs + + cache[source_uri] = specs + list[source_uri] = specs + end + end + + list + end + + def load_specs(source_uri, file) + file_name = "#{file}.#{Gem.marshal_version}.gz" + + spec_path = source_uri + file_name + + cache_dir = cache_dir spec_path + + local_file = File.join(cache_dir, file_name).chomp '.gz' + + if File.exist? local_file then + local_size = File.stat(local_file).size + + remote_file = spec_path.dup + remote_file.path = remote_file.path.chomp '.gz' + remote_size = @fetcher.fetch_size remote_file + + spec_dump = Gem.read_binary local_file if remote_size == local_size + end + + unless spec_dump then + loaded = true + + spec_dump_gz = @fetcher.fetch_path spec_path + spec_dump = Gem.gunzip spec_dump_gz + end + + specs = Marshal.load spec_dump + + if loaded and @update_cache then + begin + FileUtils.mkdir_p cache_dir + + open local_file, 'wb' do |io| + Marshal.dump specs, io + end + rescue + end + end + + specs + end + + ## + # Warn about legacy repositories if +exception+ indicates only legacy + # repositories are available, and yield to the block. Returns false if the + # exception indicates some other FetchError. + + def warn_legacy(exception) + uri = exception.uri.to_s + if uri =~ /specs\.#{Regexp.escape Gem.marshal_version}\.gz$/ then + alert_warning <<-EOF +RubyGems 1.2+ index not found for: +\t#{legacy_repos.join "\n\t"} + +RubyGems will revert to legacy indexes degrading performance. + EOF + + yield + + return true + end + + false + end + +end + diff --git a/lib/rubygems/specification.rb b/lib/rubygems/specification.rb index c50910aeb4..0642a4f3e0 100644 --- a/lib/rubygems/specification.rb +++ b/lib/rubygems/specification.rb @@ -6,6 +6,7 @@ require 'rubygems' require 'rubygems/version' +require 'rubygems/requirement' require 'rubygems/platform' # :stopdoc: @@ -16,6 +17,9 @@ if RUBY_VERSION < '1.9' then t - ((t.to_f + t.gmt_offset) % 86400) end unless defined? Time.today end + +class Date; end # for ruby_code if date.rb wasn't required + # :startdoc: module Gem @@ -37,22 +41,32 @@ module Gem # class Specification + ## # Allows deinstallation of gems with legacy platforms. + attr_accessor :original_platform # :nodoc: # ------------------------- Specification version constants. + ## # The the version number of a specification that does not specify one # (i.e. RubyGems 0.7 or earlier). + NONEXISTENT_SPECIFICATION_VERSION = -1 + ## # The specification version applied to any new Specification instances # created. This should be bumped whenever something in the spec format # changes. - CURRENT_SPECIFICATION_VERSION = 2 + #-- + # When updating this number, be sure to also update #to_ruby. + CURRENT_SPECIFICATION_VERSION = 3 + + ## # An informal list of changes to the specification. The highest-valued # key should be equal to the CURRENT_SPECIFICATION_VERSION. + SPECIFICATION_VERSION_HISTORY = { -1 => ['(RubyGems versions up to and including 0.7 did not have versioned specifications)'], 1 => [ @@ -63,10 +77,13 @@ module Gem 'Added "required_rubygems_version"', 'Now forward-compatible with future versions', ], + 3 => [ + 'Added dependency types', + ], } # :stopdoc: - MARSHAL_FIELDS = { -1 => 16, 1 => 16, 2 => 16 } + MARSHAL_FIELDS = { -1 => 16, 1 => 16, 2 => 16, 3 => 16 } now = Time.at(Time.now.to_i) TODAY = now - ((now.to_i + now.gmt_offset) % 86400) @@ -335,6 +352,14 @@ module Gem read_only :dependencies + def runtime_dependencies + dependencies.select { |d| d.type == :runtime || d.type == nil } + end + + def development_dependencies + dependencies.select { |d| d.type == :development } + end + # ALIASED gemspec attributes ------------------------------------- attribute_alias_singular :executable, :executables @@ -629,27 +654,31 @@ module Gem end end - # Adds a dependency to this Gem. For example, + # Adds a development dependency to this Gem. For example, + # + # spec.add_development_dependency('jabber4r', '> 0.1', '<= 0.5') # - # spec.add_dependency('jabber4r', '> 0.1', '<= 0.5') + # Development dependencies aren't installed by default, and + # aren't activated when a gem is required. # # gem:: [String or Gem::Dependency] The Gem name/dependency. # requirements:: [default=">= 0"] The version requirements. + def add_development_dependency(gem, *requirements) + add_dependency_with_type(gem, :development, *requirements) + end + + # Adds a runtime dependency to this Gem. For example, # - def add_dependency(gem, *requirements) - requirements = if requirements.empty? then - Gem::Requirement.default - else - requirements.flatten - end + # spec.add_runtime_dependency('jabber4r', '> 0.1', '<= 0.5') + # + # gem:: [String or Gem::Dependency] The Gem name/dependency. + # requirements:: [default=">= 0"] The version requirements. + def add_runtime_dependency(gem, *requirements) + add_dependency_with_type(gem, :runtime, *requirements) + end - unless gem.respond_to?(:name) && gem.respond_to?(:version_requirements) - gem = Dependency.new(gem, requirements) - end + alias add_dependency add_runtime_dependency - dependencies << gem - end - # Returns the full name (name-version) of this Gem. Platform information # is included (name-version-platform) if it is specified (and not the # default Ruby platform). @@ -673,30 +702,31 @@ module Gem end end + ## # The full path to the gem (install path + full name). - # - # return:: [String] the full gem path - # + def full_gem_path path = File.join installation_path, 'gems', full_name return path if File.directory? path File.join installation_path, 'gems', original_name end - + + ## # The default (generated) file name of the gem. + def file_name full_name + ".gem" end - - # The root directory that the gem was installed into. - # - # return:: [String] the installation path - # + + ## + # The directory that this gem was installed into. + def installation_path - (File.dirname(@loaded_from).split(File::SEPARATOR)[0..-2]). - join(File::SEPARATOR) + path = File.dirname(@loaded_from).split(File::SEPARATOR)[0..-2] + path = path.join File::SEPARATOR + File.expand_path path end - + # Checks if this Specification meets the requirement of the supplied # dependency. # @@ -778,9 +808,11 @@ module Gem self.platform = Gem::Platform.new @platform end + ## # Returns a Ruby code representation of this specification, such that it # can be eval'ed and reconstruct the same specification later. Attributes # that still have their default values are omitted. + def to_ruby mark_version result = [] @@ -792,8 +824,6 @@ module Gem result << " s.platform = #{ruby_code original_platform}" end result << "" - result << " s.specification_version = #{specification_version} if s.respond_to? :specification_version=" - result << "" result << " s.required_rubygems_version = #{ruby_code required_rubygems_version} if s.respond_to? :required_rubygems_version=" handled = [ @@ -816,15 +846,42 @@ module Gem end end - result << "" unless dependencies.empty? + result << nil + result << " if s.respond_to? :specification_version then" + result << " current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION" + result << " s.specification_version = #{specification_version}" + result << nil + + result << " if current_version >= 3 then" + + unless dependencies.empty? then + dependencies.each do |dep| + version_reqs_param = dep.requirements_list.inspect + dep.instance_variable_set :@type, :runtime if dep.type.nil? # HACK + result << " s.add_#{dep.type}_dependency(%q<#{dep.name}>, #{version_reqs_param})" + end + end + + result << " else" - dependencies.each do |dep| - version_reqs_param = dep.requirements_list.inspect - result << " s.add_dependency(%q<#{dep.name}>, #{version_reqs_param})" + unless dependencies.empty? then + dependencies.each do |dep| + version_reqs_param = dep.requirements_list.inspect + result << " s.add_dependency(%q<#{dep.name}>, #{version_reqs_param})" + end end + result << ' end' + + result << " else" + dependencies.each do |dep| + version_reqs_param = dep.requirements_list.inspect + result << " s.add_dependency(%q<#{dep.name}>, #{version_reqs_param})" + end + result << " end" + result << "end" - result << "" + result << nil result.join "\n" end @@ -940,6 +997,22 @@ module Gem private + def add_dependency_with_type(dependency, type, *requirements) + requirements = if requirements.empty? then + Gem::Requirement.default + else + requirements.flatten + end + + unless dependency.respond_to?(:name) && + dependency.respond_to?(:version_requirements) + + dependency = Dependency.new(dependency, requirements, type) + end + + dependencies << dependency + end + def find_all_satisfiers(dep) Gem.source_index.each do |name,gem| if(gem.satisfies_requirement?(dep)) then diff --git a/lib/rubygems/test_utilities.rb b/lib/rubygems/test_utilities.rb new file mode 100644 index 0000000000..0486db2b32 --- /dev/null +++ b/lib/rubygems/test_utilities.rb @@ -0,0 +1,120 @@ +require 'tempfile' +require 'rubygems' +require 'rubygems/remote_fetcher' + +## +# A fake Gem::RemoteFetcher for use in tests or to avoid real live HTTP +# requests when testing code that uses RubyGems. +# +# Example: +# +# @fetcher = Gem::FakeFetcher.new +# @fetcher.data['http://gems.example.com/yaml'] = source_index.to_yaml +# Gem::RemoteFetcher.fetcher = @fetcher +# +# # invoke RubyGems code +# +# paths = @fetcher.paths +# assert_equal 'http://gems.example.com/yaml', paths.shift +# assert paths.empty?, paths.join(', ') +# +# See RubyGems' tests for more examples of FakeFetcher. + +class Gem::FakeFetcher + + attr_reader :data + attr_accessor :paths + + def initialize + @data = {} + @paths = [] + end + + def fetch_path(path) + path = path.to_s + @paths << path + raise ArgumentError, 'need full URI' unless path =~ %r'^http://' + data = @data[path] + + if data.nil? then + raise Gem::RemoteFetcher::FetchError.new('no data', path) + end + + data.respond_to?(:call) ? data.call : data + end + + def fetch_size(path) + path = path.to_s + @paths << path + raise ArgumentError, 'need full URI' unless path =~ %r'^http://' + data = @data[path] + + if data.nil? then + raise Gem::RemoteFetcher::FetchError.new("no data for #{path}", nil) + end + + data.respond_to?(:call) ? data.call : data.length + end + + def download spec, source_uri, install_dir = Gem.dir + name = "#{spec.full_name}.gem" + path = File.join(install_dir, 'cache', name) + + Gem.ensure_gem_subdirectories install_dir + + if source_uri =~ /^http/ then + File.open(path, "wb") do |f| + f.write fetch_path(File.join(source_uri, "gems", name)) + end + else + FileUtils.cp source_uri, path + end + + path + end + +end + +# :stopdoc: +class Gem::RemoteFetcher + + def self.fetcher=(fetcher) + @fetcher = fetcher + end + +end +# :startdoc: + +## +# A StringIO duck-typed class that uses Tempfile instead of String as the +# backing store. +#-- +# This class was added to flush out problems in Rubinius' IO implementation. + +class TempIO + + @@count = 0 + + def initialize(string = '') + @tempfile = Tempfile.new "TempIO-#{@@count += 1}" + @tempfile.binmode + @tempfile.write string + @tempfile.rewind + end + + def method_missing(meth, *args, &block) + @tempfile.send(meth, *args, &block) + end + + def respond_to?(meth) + @tempfile.respond_to? meth + end + + def string + @tempfile.flush + + Gem.read_binary @tempfile.path + end + +end + diff --git a/lib/rubygems/uninstaller.rb b/lib/rubygems/uninstaller.rb index e2b5e5372b..c5ae47b7eb 100644 --- a/lib/rubygems/uninstaller.rb +++ b/lib/rubygems/uninstaller.rb @@ -176,9 +176,10 @@ class Gem::Uninstaller end def path_ok?(spec) - match_path = File.join @gem_home, 'gems', spec.full_name + full_path = File.join @gem_home, 'gems', spec.full_name + original_path = File.join @gem_home, 'gems', spec.original_name - match_path == spec.full_gem_path + full_path == spec.full_gem_path || original_path == spec.full_gem_path end def dependencies_ok?(spec) diff --git a/lib/rubygems/user_interaction.rb b/lib/rubygems/user_interaction.rb index 8d27df8768..30a728c597 100644 --- a/lib/rubygems/user_interaction.rb +++ b/lib/rubygems/user_interaction.rb @@ -6,54 +6,71 @@ module Gem - #################################################################### - # Module that defines the default UserInteraction. Any class - # including this module will have access to the +ui+ method that - # returns the default UI. + ## + # Module that defines the default UserInteraction. Any class including this + # module will have access to the +ui+ method that returns the default UI. + module DefaultUserInteraction + ## + # The default UI is a class variable of the singleton class for this + # module. + + @ui = nil + + ## # Return the default UI. + + def self.ui + @ui ||= Gem::ConsoleUI.new + end + + ## + # Set the default UI. If the default UI is never explicitly set, a simple + # console based UserInteraction will be used automatically. + + def self.ui=(new_ui) + @ui = new_ui + end + + ## + # Use +new_ui+ for the duration of +block+. + + def self.use_ui(new_ui) + old_ui = @ui + @ui = new_ui + yield + ensure + @ui = old_ui + end + + ## + # See DefaultUserInteraction::ui + def ui DefaultUserInteraction.ui end - # Set the default UI. If the default UI is never explicitly set, a - # simple console based UserInteraction will be used automatically. + ## + # See DefaultUserInteraction::ui= + def ui=(new_ui) DefaultUserInteraction.ui = new_ui end + ## + # See DefaultUserInteraction::use_ui + def use_ui(new_ui, &block) DefaultUserInteraction.use_ui(new_ui, &block) end - # The default UI is a class variable of the singleton class for - # this module. - - @ui = nil - - class << self - def ui - @ui ||= Gem::ConsoleUI.new - end - def ui=(new_ui) - @ui = new_ui - end - def use_ui(new_ui) - old_ui = @ui - @ui = new_ui - yield - ensure - @ui = old_ui - end - end end - #################################################################### + ## # Make the default UI accessable without the "ui." prefix. Classes - # including this module may use the interaction methods on the - # default UI directly. Classes may also reference the +ui+ and - # ui= methods. + # including this module may use the interaction methods on the default UI + # directly. Classes may also reference the ui and ui= methods. # # Example: # @@ -64,22 +81,30 @@ module Gem # n = ask("What is the meaning of life?") # end # end + module UserInteraction + include DefaultUserInteraction - [ - :choose_from_list, :ask, :ask_yes_no, :say, :alert, :alert_warning, - :alert_error, :terminate_interaction - ].each do |methname| + + [:alert, + :alert_error, + :alert_warning, + :ask, + :ask_yes_no, + :choose_from_list, + :say, + :terminate_interaction ].each do |methname| class_eval %{ def #{methname}(*args) ui.#{methname}(*args) end - } + }, __FILE__, __LINE__ end end - #################################################################### + ## # StreamUI implements a simple stream based user interface. + class StreamUI attr_reader :ins, :outs, :errs @@ -89,15 +114,19 @@ module Gem @outs = out_stream @errs = err_stream end - - # Choose from a list of options. +question+ is a prompt displayed - # above the list. +list+ is a list of option strings. Returns - # the pair [option_name, option_index]. + + ## + # Choose from a list of options. +question+ is a prompt displayed above + # the list. +list+ is a list of option strings. Returns the pair + # [option_name, option_index]. + def choose_from_list(question, list) @outs.puts question + list.each_with_index do |item, index| @outs.puts " #{index+1}. #{item}" end + @outs.print "> " @outs.flush @@ -109,28 +138,32 @@ module Gem return list[result], result end - # Ask a question. Returns a true for yes, false for no. If not - # connected to a tty, raises an exception if default is nil, - # otherwise returns default. + ## + # Ask a question. Returns a true for yes, false for no. If not connected + # to a tty, raises an exception if default is nil, otherwise returns + # default. + def ask_yes_no(question, default=nil) - if not @ins.tty? then + unless @ins.tty? then if default.nil? then - raise( - Gem::OperationNotSupportedError, - "Not connected to a tty and no default specified") + raise Gem::OperationNotSupportedError, + "Not connected to a tty and no default specified" else return default end end + qstr = case default - when nil - 'yn' - when true - 'Yn' - else - 'yN' - end + when nil + 'yn' + when true + 'Yn' + else + 'yN' + end + result = nil + while result.nil? result = ask("#{question} [#{qstr}]") result = case result @@ -144,51 +177,68 @@ module Gem nil end end + return result end - - # Ask a question. Returns an answer if connected to a tty, nil - # otherwise. + + ## + # Ask a question. Returns an answer if connected to a tty, nil otherwise. + def ask(question) return nil if not @ins.tty? + @outs.print(question + " ") @outs.flush + result = @ins.gets result.chomp! if result result end - + + ## # Display a statement. + def say(statement="") @outs.puts statement end - - # Display an informational alert. + + ## + # Display an informational alert. Will ask +question+ if it is not nil. + def alert(statement, question=nil) @outs.puts "INFO: #{statement}" - return ask(question) if question + ask(question) if question end - - # Display a warning in a location expected to get error messages. + + ## + # Display a warning in a location expected to get error messages. Will + # ask +question+ if it is not nil. + def alert_warning(statement, question=nil) @errs.puts "WARNING: #{statement}" - ask(question) if question + ask(question) if question end - - # Display an error message in a location expected to get error - # messages. + + ## + # Display an error message in a location expected to get error messages. + # Will ask +question+ if it is not nil. + def alert_error(statement, question=nil) @errs.puts "ERROR: #{statement}" ask(question) if question end - # Terminate the application normally, running any exit handlers - # that might have been defined. + ## + # Terminate the application with exit code +status+, running any exit + # handlers that might have been defined. + def terminate_interaction(status = 0) raise Gem::SystemExitException, status end - # Return a progress reporter object + ## + # Return a progress reporter object chosen from the current verbosity. + def progress_reporter(*args) case Gem.configuration.verbose when nil, false @@ -200,6 +250,9 @@ module Gem end end + ## + # An absolutely silent progress reporter. + class SilentProgressReporter attr_reader :count @@ -213,6 +266,9 @@ module Gem end end + ## + # A basic dotted progress reporter. + class SimpleProgressReporter include DefaultUserInteraction @@ -228,17 +284,27 @@ module Gem @out.puts initial_message end + ## + # Prints out a dot and ignores +message+. + def updated(message) @count += 1 @out.print "." @out.flush end + ## + # Prints out the terminal message. + def done @out.puts "\n#{@terminal_message}" end + end + ## + # A progress reporter that prints out messages about the current progress. + class VerboseProgressReporter include DefaultUserInteraction @@ -254,32 +320,41 @@ module Gem @out.puts initial_message end + ## + # Prints out the position relative to the total and the +message+. + def updated(message) @count += 1 @out.puts "#{@count}/#{@total}: #{message}" end + ## + # Prints out the terminal message. + def done @out.puts @terminal_message end end end - #################################################################### - # Subclass of StreamUI that instantiates the user interaction using - # standard in, out and error. + ## + # Subclass of StreamUI that instantiates the user interaction using STDIN, + # STDOUT, and STDERR. + class ConsoleUI < StreamUI def initialize super(STDIN, STDOUT, STDERR) end end - #################################################################### + ## # SilentUI is a UI choice that is absolutely silent. + class SilentUI def method_missing(sym, *args, &block) self end end + end diff --git a/lib/rubygems/version.rb b/lib/rubygems/version.rb index 87a1bc72ed..ff4a7bf079 100644 --- a/lib/rubygems/version.rb +++ b/lib/rubygems/version.rb @@ -8,6 +8,7 @@ require 'rubygems' ## # The Version class processes string versions into comparable values + class Gem::Version include Comparable @@ -17,11 +18,8 @@ class Gem::Version attr_reader :version ## - # Checks if version string is valid format - # - # str:: [String] the version string - # return:: [Boolean] true if the string format is correct, otherwise false - # + # Returns true if +version+ is a valid version string. + def self.correct?(version) case version when Integer, /\A\s*(\d+(\.\d+)*)*\s*\z/ then true @@ -36,7 +34,7 @@ class Gem::Version # ver1 = Version.create('1.3.17') # -> (Version object) # ver2 = Version.create(ver1) # -> (ver1) # ver3 = Version.create(nil) # -> nil - # + def self.create(input) if input.respond_to? :version then input @@ -48,10 +46,9 @@ class Gem::Version end ## - # Constructs a version from the supplied string - # - # version:: [String] The version string. Format is digit.digit... - # + # Constructs a Version from the +version+ string. A version string is a + # series of digits separated by dots. + def initialize(version) raise ArgumentError, "Malformed version number string #{version}" unless self.class.correct?(version) @@ -73,7 +70,9 @@ class Gem::Version self.version = array[0] end + ## # Strip ignored trailing zeros. + def normalize @ints = build_array_from_version_string @@ -94,10 +93,8 @@ class Gem::Version end ## - # Convert version to integer array - # - # return:: [Array] list of integers - # + # Returns an integer array representation of this Version. + def to_ints normalize unless @ints @ints @@ -117,20 +114,25 @@ class Gem::Version end ## - # Compares two versions - # - # other:: [Version or .ints] other version to compare to - # return:: [Fixnum] -1, 0, 1 - # + # Compares this version with +other+ returning -1, 0, or 1 if the other + # version is larger, the same, or smaller than this one. + def <=>(other) + return nil unless self.class === other return 1 unless other @ints <=> other.ints end - alias eql? == # :nodoc: + ## + # A Version is only eql? to another version if it has the same version + # string. "1.0" is not the same version as "1". + + def eql?(other) + self.class === other and @version == other.version + end def hash # :nodoc: - to_ints.inject { |hash_code, n| hash_code + n } + @version.hash end # Return a new version object where the next to the last revision diff --git a/test/rubygems/gemutilities.rb b/test/rubygems/gemutilities.rb index 967e3dc34d..d8818d0b01 100644 --- a/test/rubygems/gemutilities.rb +++ b/test/rubygems/gemutilities.rb @@ -10,10 +10,9 @@ at_exit { $SAFE = 1 } require 'fileutils' require 'test/unit' require 'tmpdir' -require 'tempfile' require 'uri' -require 'rubygems/source_info_cache' require 'rubygems/package' +require 'rubygems/test_utilities' require File.join(File.expand_path(File.dirname(__FILE__)), 'mockgemui') @@ -27,54 +26,6 @@ module Gem end end -class FakeFetcher - - attr_reader :data - attr_accessor :uri - attr_accessor :paths - - def initialize - @data = {} - @paths = [] - @uri = nil - end - - def fetch_path(path) - path = path.to_s - @paths << path - raise ArgumentError, 'need full URI' unless path =~ %r'^http://' - data = @data[path] - raise Gem::RemoteFetcher::FetchError, "no data for #{path}" if data.nil? - data.respond_to?(:call) ? data.call : data - end - - def fetch_size(path) - path = path.to_s - @paths << path - raise ArgumentError, 'need full URI' unless path =~ %r'^http://' - data = @data[path] - raise Gem::RemoteFetcher::FetchError, "no data for #{path}" if data.nil? - data.respond_to?(:call) ? data.call : data.length - end - - def download spec, source_uri, install_dir = Gem.dir - name = "#{spec.full_name}.gem" - path = File.join(install_dir, 'cache', name) - - Gem.ensure_gem_subdirectories install_dir - - if source_uri =~ /^http/ then - File.open(path, "wb") do |f| - f.write fetch_path(File.join(source_uri, "gems", name)) - end - else - FileUtils.cp source_uri, path - end - - path - end -end - class RubyGemTestCase < Test::Unit::TestCase include Gem::DefaultUserInteraction @@ -94,8 +45,13 @@ class RubyGemTestCase < Test::Unit::TestCase @gemcache = File.join(@gemhome, "source_cache") @usrcache = File.join(@gemhome, ".gem", "user_cache") @latest_usrcache = File.join(@gemhome, ".gem", "latest_user_cache") + @userhome = File.join @tempdir, 'userhome' + + ENV['HOME'] = @userhome + Gem.instance_variable_set :@user_home, nil FileUtils.mkdir_p @gemhome + FileUtils.mkdir_p @userhome ENV['GEMCACHE'] = @usrcache Gem.use_paths(@gemhome) @@ -104,9 +60,12 @@ class RubyGemTestCase < Test::Unit::TestCase Gem.configuration.verbose = true Gem.configuration.update_sources = true - @gem_repo = "http://gems.example.com" + @gem_repo = "http://gems.example.com/" + @uri = URI.parse @gem_repo Gem.sources.replace [@gem_repo] + Gem::SpecFetcher.fetcher = nil + @orig_BASERUBY = Gem::ConfigMap[:BASERUBY] Gem::ConfigMap[:BASERUBY] = Gem::ConfigMap[:RUBY_INSTALL_NAME] @@ -131,7 +90,7 @@ class RubyGemTestCase < Test::Unit::TestCase Gem::ConfigMap[:arch] = @orig_arch if defined? Gem::RemoteFetcher then - Gem::RemoteFetcher.instance_variable_set :@fetcher, nil + Gem::RemoteFetcher.fetcher = nil end FileUtils.rm_rf @tempdir @@ -141,7 +100,6 @@ class RubyGemTestCase < Test::Unit::TestCase ENV.delete 'GEM_PATH' Gem.clear_paths - Gem::SourceInfoCache.instance_variable_set :@cache, nil end def install_gem gem @@ -154,7 +112,7 @@ class RubyGemTestCase < Test::Unit::TestCase end gem = File.join(@tempdir, "#{gem.full_name}.gem").untaint - Gem::Installer.new(gem).install + Gem::Installer.new(gem, :wrappers => true).install end def prep_cache_files(lc) @@ -231,6 +189,8 @@ class RubyGemTestCase < Test::Unit::TestCase spec.loaded_from = written_path + Gem.source_index.add_spec spec + return spec end @@ -254,6 +214,12 @@ class RubyGemTestCase < Test::Unit::TestCase end end + def util_clear_gems + FileUtils.rm_r File.join(@gemhome, 'gems') + FileUtils.rm_r File.join(@gemhome, 'specifications') + Gem.source_index.refresh! + end + def util_gem(name, version, &block) spec = quick_gem(name, version, &block) @@ -271,6 +237,16 @@ class RubyGemTestCase < Test::Unit::TestCase [spec, cache_file] end + def util_gzip(data) + out = StringIO.new + + Zlib::GzipWriter.wrap out do |io| + io.write data + end + + out.string + end + def util_make_gems init = proc do |s| s.files = %w[lib/code.rb] @@ -303,7 +279,7 @@ class RubyGemTestCase < Test::Unit::TestCase end ## - # Set the platform to +cpu+ and +os+ + # Set the platform to +arch+ def util_set_arch(arch) Gem::ConfigMap[:arch] = arch @@ -320,9 +296,7 @@ class RubyGemTestCase < Test::Unit::TestCase require 'socket' require 'rubygems/remote_fetcher' - @uri = URI.parse @gem_repo - @fetcher = FakeFetcher.new - @fetcher.uri = @uri + @fetcher = Gem::FakeFetcher.new util_make_gems @@ -338,10 +312,11 @@ class RubyGemTestCase < Test::Unit::TestCase @source_index.add_spec @a_evil9 @source_index.add_spec @c1_2 - Gem::RemoteFetcher.instance_variable_set :@fetcher, @fetcher + Gem::RemoteFetcher.fetcher = @fetcher end def util_setup_source_info_cache(*specs) + require 'rubygems/source_info_cache' require 'rubygems/source_info_cache_entry' specs = Hash[*specs.map { |spec| [spec.full_name, spec] }.flatten] @@ -356,6 +331,35 @@ class RubyGemTestCase < Test::Unit::TestCase sic.reset_cache_data Gem::SourceInfoCache.instance_variable_set :@cache, sic + + si + end + + def util_setup_spec_fetcher(*specs) + specs = Hash[*specs.map { |spec| [spec.full_name, spec] }.flatten] + si = Gem::SourceIndex.new specs + + spec_fetcher = Gem::SpecFetcher.fetcher + + spec_fetcher.specs[@uri] = [] + si.gems.sort_by { |_, spec| spec }.each do |_, spec| + spec_tuple = [spec.name, spec.version, spec.original_platform] + spec_fetcher.specs[@uri] << spec_tuple + end + + spec_fetcher.latest_specs[@uri] = [] + si.latest_specs.sort.each do |spec| + spec_tuple = [spec.name, spec.version, spec.original_platform] + spec_fetcher.latest_specs[@uri] << spec_tuple + end + + si.gems.sort_by { |_,spec| spec }.each do |_, spec| + path = "#{@gem_repo}quick/Marshal.#{Gem.marshal_version}/#{spec.original_name}.gemspec.rz" + data = Marshal.dump spec + data_deflate = Zlib::Deflate.deflate data + @fetcher.data[path] = data_deflate + end + si end @@ -384,30 +388,3 @@ class RubyGemTestCase < Test::Unit::TestCase end -class TempIO - - @@count = 0 - - def initialize(string = '') - @tempfile = Tempfile.new "TempIO-#{@@count ++ 1}" - @tempfile.binmode - @tempfile.write string - @tempfile.rewind - end - - def method_missing(meth, *args, &block) - @tempfile.send(meth, *args, &block) - end - - def respond_to?(meth) - @tempfile.respond_to? meth - end - - def string - @tempfile.flush - - Gem.read_binary @tempfile.path - end - -end - diff --git a/test/rubygems/test_config.rb b/test/rubygems/test_config.rb index 89ac0e4462..0568996c4a 100644 --- a/test/rubygems/test_config.rb +++ b/test/rubygems/test_config.rb @@ -12,11 +12,6 @@ require 'rubygems' class TestConfig < RubyGemTestCase - def test_gem_original_datadir - datadir = Config::CONFIG['datadir'] - assert_equal "#{datadir}/xyz", Config.gem_original_datadir('xyz') - end - def test_datadir datadir = Config::CONFIG['datadir'] assert_equal "#{datadir}/xyz", Config.datadir('xyz') diff --git a/test/rubygems/test_gem.rb b/test/rubygems/test_gem.rb index ab147abe65..8a9e2c6e61 100644 --- a/test/rubygems/test_gem.rb +++ b/test/rubygems/test_gem.rb @@ -27,6 +27,14 @@ class TestGem < RubyGemTestCase assert_equal expected, Gem.all_load_paths.sort end + + def test_self_available? + util_make_gems + assert(Gem.available?("a")) + assert(Gem.available?("a", "1")) + assert(Gem.available?("a", ">1")) + assert(!Gem.available?("monkeys")) + end def test_self_bindir assert_equal File.join(@gemhome, 'bin'), Gem.bindir @@ -129,7 +137,7 @@ class TestGem < RubyGemTestCase end def test_self_default_sources - assert_equal %w[http://gems.rubyforge.org], Gem.default_sources + assert_equal %w[http://gems.rubyforge.org/], Gem.default_sources end def test_self_dir @@ -237,6 +245,18 @@ class TestGem < RubyGemTestCase assert_equal [Gem.dir], Gem.path end + def test_self_path_default + if defined? APPLE_GEM_HOME + orig_APPLE_GEM_HOME = APPLE_GEM_HOME + Object.send :remove_const, :APPLE_GEM_HOME + end + Gem.instance_variable_set :@gem_path, nil + + assert_equal [Gem.default_path, Gem.dir], Gem.path + ensure + Object.const_set :APPLE_GEM_HOME, orig_APPLE_GEM_HOME + end + unless win_platform? def test_self_path_APPLE_GEM_HOME Gem.clear_paths @@ -382,7 +402,7 @@ class TestGem < RubyGemTestCase end def test_self_sources - assert_equal %w[http://gems.example.com], Gem.sources + assert_equal %w[http://gems.example.com/], Gem.sources end def test_ssl_available_eh diff --git a/test/rubygems/test_gem_command_manager.rb b/test/rubygems/test_gem_command_manager.rb index b7767f421d..ee58e89844 100644 --- a/test/rubygems/test_gem_command_manager.rb +++ b/test/rubygems/test_gem_command_manager.rb @@ -66,7 +66,7 @@ class TestGemCommandManager < RubyGemTestCase assert_equal :both, check_options[:domain] assert_equal true, check_options[:wrappers] assert_equal Gem::Requirement.default, check_options[:version] - assert_equal Gem.dir, check_options[:install_dir] + assert_equal nil, check_options[:install_dir] assert_equal nil, check_options[:bin_dir] #check settings diff --git a/test/rubygems/test_gem_commands_dependency_command.rb b/test/rubygems/test_gem_commands_dependency_command.rb index 0f0d95695d..93e772c691 100644 --- a/test/rubygems/test_gem_commands_dependency_command.rb +++ b/test/rubygems/test_gem_commands_dependency_command.rb @@ -9,6 +9,8 @@ class TestGemCommandsDependencyCommand < RubyGemTestCase @cmd = Gem::Commands::DependencyCommand.new @cmd.options[:domain] = :local + + util_setup_fake_fetcher end def test_execute @@ -16,13 +18,15 @@ class TestGemCommandsDependencyCommand < RubyGemTestCase gem.add_dependency 'bar', '> 1' end + Gem.source_index = nil + @cmd.options[:args] = %w[foo] use_ui @ui do @cmd.execute end - assert_equal "Gem foo-2\n bar (> 1)\n\n", @ui.output + assert_equal "Gem foo-2\n bar (> 1, runtime)\n\n", @ui.output assert_equal '', @ui.error end @@ -35,7 +39,7 @@ class TestGemCommandsDependencyCommand < RubyGemTestCase end end - assert_equal "No match found for foo (>= 0)\n", @ui.output + assert_equal "No gems found matching foo (>= 0)\n", @ui.output assert_equal '', @ui.error end @@ -64,6 +68,8 @@ class TestGemCommandsDependencyCommand < RubyGemTestCase gem.add_dependency 'foo' end + Gem.source_index = nil + @cmd.options[:args] = %w[foo] @cmd.options[:reverse_dependencies] = true @@ -73,9 +79,9 @@ class TestGemCommandsDependencyCommand < RubyGemTestCase expected = <<-EOF Gem foo-2 - bar (> 1) + bar (> 1, runtime) Used by - baz-2 (foo (>= 0)) + baz-2 (foo (>= 0, runtime)) EOF @@ -83,12 +89,34 @@ Gem foo-2 assert_equal '', @ui.error end + def test_execute_reverse_remote + @cmd.options[:args] = %w[foo] + @cmd.options[:reverse_dependencies] = true + @cmd.options[:domain] = :remote + + assert_raise MockGemUi::TermError do + use_ui @ui do + @cmd.execute + end + end + + expected = <<-EOF +ERROR: Only reverse dependencies for local gems are supported. + EOF + + assert_equal '', @ui.output + assert_equal expected, @ui.error + end + def test_execute_remote foo = quick_gem 'foo' do |gem| gem.add_dependency 'bar', '> 1' end - util_setup_source_info_cache foo + @fetcher = Gem::FakeFetcher.new + Gem::RemoteFetcher.fetcher = @fetcher + + util_setup_spec_fetcher foo FileUtils.rm File.join(@gemhome, 'specifications', "#{foo.full_name}.gemspec") @@ -100,9 +128,48 @@ Gem foo-2 @cmd.execute end - assert_equal "Gem foo-2\n bar (> 1)\n\n", @ui.output + assert_equal "Gem foo-2\n bar (> 1, runtime)\n\n", @ui.output assert_equal '', @ui.error end + def test_execute_remote_legacy + foo = quick_gem 'foo' do |gem| + gem.add_dependency 'bar', '> 1' + end + + @fetcher = Gem::FakeFetcher.new + Gem::RemoteFetcher.fetcher = @fetcher + + Gem::SpecFetcher.fetcher = nil + si = util_setup_source_info_cache foo + + @fetcher.data["#{@gem_repo}yaml"] = YAML.dump si + @fetcher.data["#{@gem_repo}Marshal.#{Gem.marshal_version}"] = + si.dump + + @fetcher.data["#{@gem_repo}latest_specs.#{Gem.marshal_version}.gz"] = nil + + FileUtils.rm File.join(@gemhome, 'specifications', + "#{foo.full_name}.gemspec") + + @cmd.options[:args] = %w[foo] + @cmd.options[:domain] = :remote + + use_ui @ui do + @cmd.execute + end + + assert_equal "Gem foo-2\n bar (> 1, runtime)\n\n", @ui.output + + expected = <<-EOF +WARNING: RubyGems 1.2+ index not found for: +\t#{@gem_repo} + +RubyGems will revert to legacy indexes degrading performance. + EOF + + assert_equal expected, @ui.error + end + end diff --git a/test/rubygems/test_gem_commands_environment_command.rb b/test/rubygems/test_gem_commands_environment_command.rb index 7cbb53bd88..78246b0301 100644 --- a/test/rubygems/test_gem_commands_environment_command.rb +++ b/test/rubygems/test_gem_commands_environment_command.rb @@ -27,6 +27,7 @@ class TestGemCommandsEnvironmentCommand < RubyGemTestCase assert_match %r|RUBYGEMS PREFIX: |, @ui.output assert_match %r|RUBY EXECUTABLE:.*#{Gem::ConfigMap[:RUBY_INSTALL_NAME]}|, @ui.output + assert_match %r|EXECUTABLE DIRECTORY:|, @ui.output assert_match %r|RUBYGEMS PLATFORMS:|, @ui.output assert_match %r|- #{Gem::Platform.local}|, @ui.output assert_match %r|GEM PATHS:|, @ui.output diff --git a/test/rubygems/test_gem_commands_fetch_command.rb b/test/rubygems/test_gem_commands_fetch_command.rb index 5a42e4e81e..eaa13595b7 100644 --- a/test/rubygems/test_gem_commands_fetch_command.rb +++ b/test/rubygems/test_gem_commands_fetch_command.rb @@ -14,10 +14,9 @@ class TestGemCommandsFetchCommand < RubyGemTestCase def test_execute util_setup_fake_fetcher + util_setup_spec_fetcher @a2 - @fetcher.data["#{@gem_repo}/Marshal.#{@marshal_version}"] = - @source_index.dump - @fetcher.data["#{@gem_repo}/gems/#{@a2.full_name}.gem"] = + @fetcher.data["#{@gem_repo}gems/#{@a2.full_name}.gem"] = File.read(File.join(@gemhome, 'cache', "#{@a2.full_name}.gem")) @cmd.options[:args] = [@a2.name] @@ -28,7 +27,28 @@ class TestGemCommandsFetchCommand < RubyGemTestCase end end - assert File.exist?(File.join(@tempdir, "#{@a2.full_name}.gem")) + assert File.exist?(File.join(@tempdir, "#{@a2.full_name}.gem")), + "#{@a2.full_name} fetched" + end + + def test_execute_legacy + util_setup_fake_fetcher + util_setup_source_info_cache @a2 + + @fetcher.data["#{@gem_repo}yaml"] = '' + @fetcher.data["#{@gem_repo}gems/#{@a2.full_name}.gem"] = + File.read(File.join(@gemhome, 'cache', "#{@a2.full_name}.gem")) + + @cmd.options[:args] = [@a2.name] + + use_ui @ui do + Dir.chdir @tempdir do + @cmd.execute + end + end + + assert File.exist?(File.join(@tempdir, "#{@a2.full_name}.gem")), + "#{@a2.full_name} fetched" end end diff --git a/test/rubygems/test_gem_commands_install_command.rb b/test/rubygems/test_gem_commands_install_command.rb index 101195a43e..ef04072b93 100644 --- a/test/rubygems/test_gem_commands_install_command.rb +++ b/test/rubygems/test_gem_commands_install_command.rb @@ -72,7 +72,7 @@ class TestGemCommandsInstallCommand < RubyGemTestCase end # HACK no repository was checked - assert_equal "ERROR: could not find no_such_gem locally or in a repository\n", + assert_equal "ERROR: could not find gem no_such_gem locally or in a repository\n", @ui.error end @@ -86,8 +86,7 @@ class TestGemCommandsInstallCommand < RubyGemTestCase def test_execute_nonexistent util_setup_fake_fetcher - @fetcher.data["#{@gem_repo}/Marshal.#{@marshal_version}"] = - @source_index.dump + util_setup_spec_fetcher @cmd.options[:args] = %w[nonexistent] @@ -98,18 +97,18 @@ class TestGemCommandsInstallCommand < RubyGemTestCase assert_equal 2, e.exit_code end - assert_equal "ERROR: could not find nonexistent locally or in a repository\n", + assert_equal "ERROR: could not find gem nonexistent locally or in a repository\n", @ui.error end def test_execute_remote @cmd.options[:generate_rdoc] = true @cmd.options[:generate_ri] = true + util_setup_fake_fetcher + util_setup_spec_fetcher @a2 - @fetcher.data["#{@gem_repo}/Marshal.#{@marshal_version}"] = - @source_index.dump - @fetcher.data["#{@gem_repo}/gems/#{@a2.full_name}.gem"] = + @fetcher.data["#{@gem_repo}gems/#{@a2.full_name}.gem"] = read_binary(File.join(@gemhome, 'cache', "#{@a2.full_name}.gem")) @cmd.options[:args] = [@a2.name] @@ -122,7 +121,6 @@ class TestGemCommandsInstallCommand < RubyGemTestCase end out = @ui.output.split "\n" - assert_match %r|Bulk updating|, out.shift assert_equal "Successfully installed #{@a2.full_name}", out.shift assert_equal "1 gem installed", out.shift assert_equal "Installing ri documentation for #{@a2.full_name}...", diff --git a/test/rubygems/test_gem_commands_outdated_command.rb b/test/rubygems/test_gem_commands_outdated_command.rb new file mode 100644 index 0000000000..a6668c01fc --- /dev/null +++ b/test/rubygems/test_gem_commands_outdated_command.rb @@ -0,0 +1,43 @@ +require 'test/unit' +require File.join(File.expand_path(File.dirname(__FILE__)), 'gemutilities') +require 'rubygems/commands/outdated_command' + +class TestGemCommandsOutdatedCommand < RubyGemTestCase + + def setup + super + + @cmd = Gem::Commands::OutdatedCommand.new + end + + def test_initialize + assert @cmd.handles?(%W[--platform #{Gem::Platform.local}]) + end + + def test_execute + local_01 = quick_gem 'foo', '0.1' + local_02 = quick_gem 'foo', '0.2' + remote_10 = quick_gem 'foo', '1.0' + remote_20 = quick_gem 'foo', '2.0' + + remote_spec_file = File.join @gemhome, 'specifications', + remote_10.full_name + ".gemspec" + FileUtils.rm remote_spec_file + + remote_spec_file = File.join @gemhome, 'specifications', + remote_20.full_name + ".gemspec" + FileUtils.rm remote_spec_file + + @fetcher = Gem::FakeFetcher.new + Gem::RemoteFetcher.fetcher = @fetcher + + util_setup_spec_fetcher remote_10, remote_20 + + use_ui @ui do @cmd.execute end + + assert_equal "foo (0.2 < 2.0)\n", @ui.output + assert_equal "", @ui.error + end + +end + diff --git a/test/rubygems/test_gem_commands_pristine_command.rb b/test/rubygems/test_gem_commands_pristine_command.rb index cd1d3500ae..d5d2d7f339 100644 --- a/test/rubygems/test_gem_commands_pristine_command.rb +++ b/test/rubygems/test_gem_commands_pristine_command.rb @@ -18,16 +18,24 @@ class TestGemCommandsPristineCommand < RubyGemTestCase install_gem a + foo_path = File.join @gemhome, 'gems', a.full_name, 'bin', 'foo' + + File.open foo_path, 'w' do |io| + io.puts 'I changed it!' + end + @cmd.options[:args] = %w[a] use_ui @ui do @cmd.execute end + assert_equal "#!/usr/bin/ruby\n", File.read(foo_path), foo_path + out = @ui.output.split "\n" assert_equal "Restoring gem(s) to pristine condition...", out.shift - assert_equal "#{a.full_name} is in pristine condition", out.shift + assert_equal "Restored #{a.full_name}", out.shift assert out.empty?, out.inspect end @@ -40,7 +48,7 @@ class TestGemCommandsPristineCommand < RubyGemTestCase install_gem a - gem_bin = File.join @gemhome, 'gems', "#{a.full_name}", 'bin', 'foo' + gem_bin = File.join @gemhome, 'gems', a.full_name, 'bin', 'foo' FileUtils.rm gem_bin @@ -50,11 +58,12 @@ class TestGemCommandsPristineCommand < RubyGemTestCase @cmd.execute end + assert File.exist?(gem_bin) + out = @ui.output.split "\n" assert_equal "Restoring gem(s) to pristine condition...", out.shift - assert_equal "Restoring 1 file to #{a.full_name}...", out.shift - assert_equal " #{gem_bin}", out.shift + assert_equal "Restored #{a.full_name}", out.shift assert out.empty?, out.inspect end diff --git a/test/rubygems/test_gem_commands_query_command.rb b/test/rubygems/test_gem_commands_query_command.rb index 3c86c69a41..1b65fc7633 100644 --- a/test/rubygems/test_gem_commands_query_command.rb +++ b/test/rubygems/test_gem_commands_query_command.rb @@ -7,33 +7,18 @@ class TestGemCommandsQueryCommand < RubyGemTestCase def setup super - util_make_gems - - @a2.summary = 'This is a lot of text. ' * 4 - @cmd = Gem::Commands::QueryCommand.new - @si = util_setup_source_info_cache @a1, @a2, @pl1 util_setup_fake_fetcher - @fetcher.data["#{@gem_repo}/Marshal.#{Gem.marshal_version}"] = proc do + @si = util_setup_spec_fetcher @a1, @a2, @pl1 + + @fetcher.data["#{@gem_repo}Marshal.#{Gem.marshal_version}"] = proc do raise Gem::RemoteFetcher::FetchError end end def test_execute - cache = Gem::SourceInfoCache.cache - cache.update - cache.write_cache - cache.reset_cache_data - Gem::SourceInfoCache.reset - - a2_name = @a2.full_name - @fetcher.data["#{@gem_repo}/quick/latest_index.rz"] = util_zip a2_name - @fetcher.data["#{@gem_repo}/quick/Marshal.#{Gem.marshal_version}/#{a2_name}.gemspec.rz"] = util_zip Marshal.dump(@a2) - @fetcher.data["#{@gem_repo}/Marshal.#{Gem.marshal_version}"] = - Marshal.dump @si - @cmd.handle_options %w[-r] use_ui @ui do @@ -44,10 +29,8 @@ class TestGemCommandsQueryCommand < RubyGemTestCase *** REMOTE GEMS *** -Updating metadata for 1 gems from http://gems.example.com/ -. -complete a (2) +pl (1) EOF assert_equal expected, @ui.output @@ -55,21 +38,8 @@ a (2) end def test_execute_all - cache = Gem::SourceInfoCache.cache - cache.update - cache.write_cache - cache.reset_cache_data - Gem::SourceInfoCache.reset - a1_name = @a1.full_name a2_name = @a2.full_name - @fetcher.data["#{@gem_repo}/quick/index.rz"] = - util_zip [a1_name, a2_name].join("\n") - @fetcher.data["#{@gem_repo}/quick/latest_index.rz"] = util_zip a2_name - @fetcher.data["#{@gem_repo}/quick/Marshal.#{Gem.marshal_version}/#{a1_name}.gemspec.rz"] = util_zip Marshal.dump(@a1) - @fetcher.data["#{@gem_repo}/quick/Marshal.#{Gem.marshal_version}/#{a2_name}.gemspec.rz"] = util_zip Marshal.dump(@a2) - @fetcher.data["#{@gem_repo}/Marshal.#{Gem.marshal_version}"] = - Marshal.dump @si @cmd.handle_options %w[-r --all] @@ -81,10 +51,8 @@ a (2) *** REMOTE GEMS *** -Updating metadata for 2 gems from http://gems.example.com/ -.. -complete a (2, 1) +pl (1) EOF assert_equal expected, @ui.output @@ -92,6 +60,13 @@ a (2, 1) end def test_execute_details + @a2.summary = 'This is a lot of text. ' * 4 + @a2.authors = ['Abraham Lincoln', 'Hirohito'] + @a2.homepage = 'http://a.example.com/' + @a2.rubyforge_project = 'rubygems' + + @si = util_setup_spec_fetcher @a1, @a2, @pl1 + @cmd.handle_options %w[-r -d] use_ui @ui do @@ -103,10 +78,17 @@ a (2, 1) *** REMOTE GEMS *** a (2) + Authors: Abraham Lincoln, Hirohito + Rubyforge: http://rubyforge.org/projects/rubygems + Homepage: http://a.example.com/ + This is a lot of text. This is a lot of text. This is a lot of text. This is a lot of text. pl (1) + Author: A User + Homepage: http://example.com + this is a summary EOF @@ -126,6 +108,7 @@ pl (1) assert_equal 0, e.exit_code assert_equal "true\n", @ui.output + assert_equal '', @ui.error end @@ -189,6 +172,99 @@ pl (1) assert_equal 1, e.exit_code end + def test_execute_legacy + Gem::SpecFetcher.fetcher = nil + si = util_setup_source_info_cache @a1, @a2, @pl1 + + @fetcher.data["#{@gem_repo}yaml"] = YAML.dump si + @fetcher.data["#{@gem_repo}Marshal.#{Gem.marshal_version}"] = + si.dump + + @fetcher.data["#{@gem_repo}latest_specs.#{Gem.marshal_version}.gz"] = nil + + @cmd.handle_options %w[-r] + + use_ui @ui do + @cmd.execute + end + + expected = <<-EOF + +*** REMOTE GEMS *** + +a (2) +pl (1) + EOF + + assert_equal expected, @ui.output + + expected = <<-EOF +WARNING: RubyGems 1.2+ index not found for: +\t#{@gem_repo} + +RubyGems will revert to legacy indexes degrading performance. + EOF + + assert_equal expected, @ui.error + end + + def test_execute_local_details + @a2.summary = 'This is a lot of text. ' * 4 + @a2.authors = ['Abraham Lincoln', 'Hirohito'] + @a2.homepage = 'http://a.example.com/' + @a2.rubyforge_project = 'rubygems' + + @cmd.handle_options %w[--local --details] + + use_ui @ui do + @cmd.execute + end + + expected = <<-EOF + +*** LOCAL GEMS *** + +a (2, 1) + Author: A User + Homepage: http://example.com + Installed at (2): #{@gemhome} + (1): #{@gemhome} + + this is a summary + +a_evil (9) + Author: A User + Homepage: http://example.com + Installed at: #{@gemhome} + + this is a summary + +b (2) + Author: A User + Homepage: http://example.com + Installed at: #{@gemhome} + + this is a summary + +c (1.2) + Author: A User + Homepage: http://example.com + Installed at: #{@gemhome} + + this is a summary + +pl (1) + Author: A User + Homepage: http://example.com + Installed at: #{@gemhome} + + this is a summary + EOF + + assert_equal expected, @ui.output + assert_equal '', @ui.error + end + def test_execute_no_versions @cmd.handle_options %w[-r --no-versions] diff --git a/test/rubygems/test_gem_commands_sources_command.rb b/test/rubygems/test_gem_commands_sources_command.rb index f15d44dfe8..623c732e50 100644 --- a/test/rubygems/test_gem_commands_sources_command.rb +++ b/test/rubygems/test_gem_commands_sources_command.rb @@ -8,10 +8,12 @@ class TestGemCommandsSourcesCommand < RubyGemTestCase super @cmd = Gem::Commands::SourcesCommand.new + + @new_repo = "http://beta-gems.example.com" end def test_execute - util_setup_source_info_cache + util_setup_spec_fetcher @cmd.handle_options [] use_ui @ui do @@ -34,43 +36,49 @@ class TestGemCommandsSourcesCommand < RubyGemTestCase si = Gem::SourceIndex.new si.add_spec @a1 - @fetcher.data["http://beta-gems.example.com/Marshal.#{@marshal_version}"] = - si.dump + specs = si.map do |_, spec| + [spec.name, spec.version, spec.original_platform] + end - @cmd.handle_options %w[--add http://beta-gems.example.com] + specs_dump_gz = StringIO.new + Zlib::GzipWriter.wrap specs_dump_gz do |io| + Marshal.dump specs, io + end - util_setup_source_info_cache + @fetcher.data["#{@new_repo}/specs.#{@marshal_version}.gz"] = + specs_dump_gz.string + + @cmd.handle_options %W[--add #{@new_repo}] + + util_setup_spec_fetcher use_ui @ui do @cmd.execute end + assert_equal [@gem_repo, @new_repo], Gem.sources + expected = <<-EOF -Bulk updating Gem source index for: http://beta-gems.example.com/ -http://beta-gems.example.com added to sources +#{@new_repo} added to sources EOF assert_equal expected, @ui.output assert_equal '', @ui.error - - Gem::SourceInfoCache.cache.flush - assert_equal %W[http://beta-gems.example.com #{@gem_repo}], - Gem::SourceInfoCache.cache_data.keys.sort end def test_execute_add_nonexistent_source util_setup_fake_fetcher - @fetcher.data["http://beta-gems.example.com/Marshal.#{@marshal_version}"] = - proc do - raise Gem::RemoteFetcher::FetchError, 'it died' - end + uri = "http://beta-gems.example.com/specs.#{@marshal_version}.gz" + @fetcher.data[uri] = proc do + raise Gem::RemoteFetcher::FetchError.new('it died', uri) + end - Gem::RemoteFetcher.instance_variable_set :@fetcher, @fetcher + Gem::RemoteFetcher.fetcher = @fetcher @cmd.handle_options %w[--add http://beta-gems.example.com] - util_setup_source_info_cache + util_setup_spec_fetcher use_ui @ui do @cmd.execute @@ -78,7 +86,7 @@ http://beta-gems.example.com added to sources expected = <<-EOF Error fetching http://beta-gems.example.com: -\tit died +\tit died (#{uri}) EOF assert_equal expected, @ui.output @@ -88,12 +96,14 @@ Error fetching http://beta-gems.example.com: def test_execute_add_bad_uri @cmd.handle_options %w[--add beta-gems.example.com] - util_setup_source_info_cache + util_setup_spec_fetcher use_ui @ui do @cmd.execute end + assert_equal [@gem_repo], Gem.sources + expected = <<-EOF beta-gems.example.com is not a URI EOF @@ -102,6 +112,34 @@ beta-gems.example.com is not a URI assert_equal '', @ui.error end + def test_execute_add_legacy + util_setup_fake_fetcher + util_setup_source_info_cache + + si = Gem::SourceIndex.new + si.add_spec @a1 + + @fetcher.data["#{@new_repo}/yaml"] = '' + + @cmd.handle_options %W[--add #{@new_repo}] + + use_ui @ui do + @cmd.execute + end + + assert_equal [@gem_repo], Gem.sources + + expected = <<-EOF +WARNING: RubyGems 1.2+ index not found for: +\t#{@new_repo} + +Will cause RubyGems to revert to legacy indexes, degrading performance. + EOF + + assert_equal "#{@new_repo} added to sources\n", @ui.output + assert_equal expected, @ui.error + end + def test_execute_clear_all @cmd.handle_options %w[--clear-all] @@ -116,11 +154,19 @@ beta-gems.example.com is not a URI assert File.exist?(cache.latest_system_cache_file), 'latest system cache file' + util_setup_spec_fetcher + + fetcher = Gem::SpecFetcher.fetcher + + # HACK figure out how to force directory creation via fetcher + #assert File.directory?(fetcher.dir), 'cache dir exists' + use_ui @ui do @cmd.execute end expected = <<-EOF +*** Removed specs cache *** *** Removed user source cache *** *** Removed latest user source cache *** *** Removed system source cache *** @@ -135,12 +181,13 @@ beta-gems.example.com is not a URI assert !File.exist?(cache.latest_system_cache_file), 'latest system cache file' + assert !File.exist?(fetcher.dir), 'cache dir removed' end def test_execute_remove @cmd.handle_options %W[--remove #{@gem_repo}] - util_setup_source_info_cache + util_setup_spec_fetcher use_ui @ui do @cmd.execute @@ -150,9 +197,6 @@ beta-gems.example.com is not a URI assert_equal expected, @ui.output assert_equal '', @ui.error - - Gem::SourceInfoCache.cache.flush - assert_equal [], Gem::SourceInfoCache.cache_data.keys end def test_execute_remove_no_network @@ -160,7 +204,7 @@ beta-gems.example.com is not a URI util_setup_fake_fetcher - @fetcher.data["#{@gem_repo}/Marshal.#{Gem.marshal_version}"] = proc do + @fetcher.data["#{@gem_repo}Marshal.#{Gem.marshal_version}"] = proc do raise Gem::RemoteFetcher::FetchError end @@ -172,34 +216,60 @@ beta-gems.example.com is not a URI assert_equal expected, @ui.output assert_equal '', @ui.error - - Gem::SourceInfoCache.cache.flush - assert_equal [], Gem::SourceInfoCache.cache_data.keys end def test_execute_update @cmd.handle_options %w[--update] + util_setup_fake_fetcher + source_index = util_setup_spec_fetcher @a1 + + specs = source_index.map do |name, spec| + [spec.name, spec.version, spec.original_platform] + end + + @fetcher.data["#{@gem_repo}specs.#{Gem.marshal_version}.gz"] = + util_gzip Marshal.dump(specs) + + latest_specs = source_index.latest_specs.map do |spec| + [spec.name, spec.version, spec.original_platform] + end + + @fetcher.data["#{@gem_repo}latest_specs.#{Gem.marshal_version}.gz"] = + util_gzip Marshal.dump(latest_specs) + + use_ui @ui do + @cmd.execute + end + + assert_equal "source cache successfully updated\n", @ui.output + assert_equal '', @ui.error + end + + def test_execute_update_legacy + @cmd.handle_options %w[--update] + + util_setup_fake_fetcher util_setup_source_info_cache Gem::SourceInfoCache.reset - util_setup_fake_fetcher si = Gem::SourceIndex.new si.add_spec @a1 - @fetcher.data["#{@gem_repo}/Marshal.#{@marshal_version}"] = si.dump + @fetcher.data["#{@gem_repo}yaml"] = YAML.dump si + @fetcher.data["#{@gem_repo}Marshal.#{@marshal_version}"] = si.dump use_ui @ui do @cmd.execute end expected = <<-EOF -Bulk updating Gem source index for: #{@gem_repo}/ +Bulk updating Gem source index for: #{@gem_repo} source cache successfully updated EOF assert_equal expected, @ui.output assert_equal '', @ui.error end - + end diff --git a/test/rubygems/test_gem_commands_specification_command.rb b/test/rubygems/test_gem_commands_specification_command.rb index f66f0c0d49..7ac0429f32 100644 --- a/test/rubygems/test_gem_commands_specification_command.rb +++ b/test/rubygems/test_gem_commands_specification_command.rb @@ -74,7 +74,10 @@ class TestGemCommandsSpecificationCommand < RubyGemTestCase def test_execute_remote foo = quick_gem 'foo' - util_setup_source_info_cache foo + @fetcher = Gem::FakeFetcher.new + Gem::RemoteFetcher.fetcher = @fetcher + + util_setup_spec_fetcher foo FileUtils.rm File.join(@gemhome, 'specifications', "#{foo.full_name}.gemspec") diff --git a/test/rubygems/test_gem_commands_stale_command.rb b/test/rubygems/test_gem_commands_stale_command.rb new file mode 100644 index 0000000000..6f66c854a5 --- /dev/null +++ b/test/rubygems/test_gem_commands_stale_command.rb @@ -0,0 +1,39 @@ +require 'test/unit' +require File.join(File.expand_path(File.dirname(__FILE__)), 'gemutilities') +require 'rubygems/commands/stale_command' + +class TestGemCommandsStaleCommand < RubyGemTestCase + + def setup + super + @cmd = Gem::Commands::StaleCommand.new + end + + def test_execute_sorts + files = %w[lib/foo_bar.rb Rakefile] + foo_bar = quick_gem 'foo_bar' do |gem| + gem.files = files + end + bar_baz = quick_gem 'bar_baz' do |gem| + gem.files = files + end + + files.each do |file| + filename = bar_baz.full_gem_path + "/#{file}" + FileUtils.mkdir_p(File.dirname(filename)) + FileUtils.touch(filename, :mtime => Time.now) + + filename = foo_bar.full_gem_path + "/#{file}" + FileUtils.mkdir_p(File.dirname(filename)) + FileUtils.touch(filename, :mtime => Time.now - 86400) + end + + use_ui @ui do + @cmd.execute + end + lines = @ui.output.split("\n") + assert_equal("#{foo_bar.name}-#{foo_bar.version}", lines[0].split.first) + assert_equal("#{bar_baz.name}-#{bar_baz.version}", lines[1].split.first) + end + +end diff --git a/test/rubygems/test_gem_commands_update_command.rb b/test/rubygems/test_gem_commands_update_command.rb index 6aeb53d25a..11da1f8a83 100644 --- a/test/rubygems/test_gem_commands_update_command.rb +++ b/test/rubygems/test_gem_commands_update_command.rb @@ -14,14 +14,16 @@ class TestGemCommandsUpdateCommand < RubyGemTestCase @a1_path = File.join @gemhome, 'cache', "#{@a1.full_name}.gem" @a2_path = File.join @gemhome, 'cache', "#{@a2.full_name}.gem" - @fetcher.data["#{@gem_repo}/Marshal.#{@marshal_version}"] = - @source_index.dump - @fetcher.data["#{@gem_repo}/gems/#{@a1.full_name}.gem"] = read_binary @a1_path - @fetcher.data["#{@gem_repo}/gems/#{@a2.full_name}.gem"] = read_binary @a2_path + util_setup_spec_fetcher @a1, @a2 + + @fetcher.data["#{@gem_repo}gems/#{@a1.full_name}.gem"] = + read_binary @a1_path + @fetcher.data["#{@gem_repo}gems/#{@a2.full_name}.gem"] = + read_binary @a2_path end def test_execute - util_remove_gems + util_clear_gems Gem::Installer.new(@a1_path).install @@ -33,7 +35,6 @@ class TestGemCommandsUpdateCommand < RubyGemTestCase out = @ui.output.split "\n" assert_equal "Updating installed gems", out.shift - assert_match %r|Bulk updating|, out.shift assert_equal "Updating #{@a2.name}", out.shift assert_equal "Successfully installed #{@a2.full_name}", out.shift assert_equal "Gems updated: #{@a2.name}", out.shift @@ -73,16 +74,15 @@ class TestGemCommandsUpdateCommand < RubyGemTestCase util_build_gem @a2 util_build_gem @c2 - @fetcher.data["#{@gem_repo}/Marshal.#{@marshal_version}"] = - @source_index.dump - @fetcher.data["#{@gem_repo}/gems/#{@a1.full_name}.gem"] = read_binary @a1_path - @fetcher.data["#{@gem_repo}/gems/#{@a2.full_name}.gem"] = read_binary @a2_path - @fetcher.data["#{@gem_repo}/gems/#{@b2.full_name}.gem"] = read_binary @b2_path - @fetcher.data["#{@gem_repo}/gems/#{@c1_2.full_name}.gem"] = + @fetcher.data["#{@gem_repo}gems/#{@a1.full_name}.gem"] = read_binary @a1_path + @fetcher.data["#{@gem_repo}gems/#{@a2.full_name}.gem"] = read_binary @a2_path + @fetcher.data["#{@gem_repo}gems/#{@b2.full_name}.gem"] = read_binary @b2_path + @fetcher.data["#{@gem_repo}gems/#{@c1_2.full_name}.gem"] = read_binary @c1_2_path - @fetcher.data["#{@gem_repo}/gems/#{@c2.full_name}.gem"] = read_binary @c2_path + @fetcher.data["#{@gem_repo}gems/#{@c2.full_name}.gem"] = read_binary @c2_path - util_remove_gems + util_setup_spec_fetcher @a1, @a2, @b2, @c1_2, @c2 + util_clear_gems Gem::Installer.new(@c1_2_path).install Gem::Installer.new(@a1_path).install @@ -95,9 +95,7 @@ class TestGemCommandsUpdateCommand < RubyGemTestCase out = @ui.output.split "\n" assert_equal "Updating installed gems", out.shift - assert_match %r|Bulk updating|, out.shift assert_equal "Updating #{@a2.name}", out.shift - assert_match %r|Bulk updating|, out.shift assert_equal "Successfully installed #{@c2.full_name}", out.shift assert_equal "Successfully installed #{@b2.full_name}", out.shift assert_equal "Successfully installed #{@a2.full_name}", out.shift @@ -108,7 +106,7 @@ class TestGemCommandsUpdateCommand < RubyGemTestCase end def test_execute_named - util_remove_gems + util_clear_gems Gem::Installer.new(@a1_path).install @@ -120,7 +118,6 @@ class TestGemCommandsUpdateCommand < RubyGemTestCase out = @ui.output.split "\n" assert_equal "Updating installed gems", out.shift - assert_match %r|Bulk updating|, out.shift assert_equal "Updating #{@a2.name}", out.shift assert_equal "Successfully installed #{@a2.full_name}", out.shift assert_equal "Gems updated: #{@a2.name}", out.shift @@ -129,7 +126,7 @@ class TestGemCommandsUpdateCommand < RubyGemTestCase end def test_execute_named_up_to_date - util_remove_gems + util_clear_gems Gem::Installer.new(@a2_path).install @@ -141,14 +138,13 @@ class TestGemCommandsUpdateCommand < RubyGemTestCase out = @ui.output.split "\n" assert_equal "Updating installed gems", out.shift - assert_match %r|Bulk updating|, out.shift assert_equal "Nothing to update", out.shift assert out.empty?, out.inspect end def test_execute_up_to_date - util_remove_gems + util_clear_gems Gem::Installer.new(@a2_path).install @@ -160,16 +156,10 @@ class TestGemCommandsUpdateCommand < RubyGemTestCase out = @ui.output.split "\n" assert_equal "Updating installed gems", out.shift - assert_match %r|Bulk updating|, out.shift assert_equal "Nothing to update", out.shift assert out.empty?, out.inspect end - def util_remove_gems - FileUtils.rm_r File.join(@gemhome, 'gems') - FileUtils.rm_r File.join(@gemhome, 'specifications') - end - end diff --git a/test/rubygems/test_gem_config_file.rb b/test/rubygems/test_gem_config_file.rb index e0360b0d6b..c7f6b93c7f 100644 --- a/test/rubygems/test_gem_config_file.rb +++ b/test/rubygems/test_gem_config_file.rb @@ -17,9 +17,23 @@ class TestGemConfigFile < RubyGemTestCase @temp_conf = File.join @tempdir, '.gemrc' @cfg_args = %W[--config-file #{@temp_conf}] + + @orig_SYSTEM_WIDE_CONFIG_FILE = Gem::ConfigFile::SYSTEM_WIDE_CONFIG_FILE + Gem::ConfigFile.send :remove_const, :SYSTEM_WIDE_CONFIG_FILE + Gem::ConfigFile.send :const_set, :SYSTEM_WIDE_CONFIG_FILE, + File.join(@tempdir, 'system-gemrc') + util_config_file end + def teardown + Gem::ConfigFile.send :remove_const, :SYSTEM_WIDE_CONFIG_FILE + Gem::ConfigFile.send :const_set, :SYSTEM_WIDE_CONFIG_FILE, + @orig_SYSTEM_WIDE_CONFIG_FILE + + super + end + def test_initialize assert_equal @temp_conf, @cfg.config_file_name @@ -28,7 +42,7 @@ class TestGemConfigFile < RubyGemTestCase assert_equal false, @cfg.benchmark assert_equal Gem::ConfigFile::DEFAULT_BULK_THRESHOLD, @cfg.bulk_threshold assert_equal true, @cfg.verbose - assert_equal %w[http://gems.example.com], Gem.sources + assert_equal [@gem_repo], Gem.sources File.open @temp_conf, 'w' do |fp| fp.puts ":backtrace: true" @@ -202,6 +216,23 @@ class TestGemConfigFile < RubyGemTestCase assert_equal %w[http://even-more-gems.example.com], Gem.sources end + def test_global_config_file + File.open(@temp_conf, 'w') do |fp| + fp.puts ":backtrace: true" + end + + File.open(File.join(Gem::ConfigFile::SYSTEM_WIDE_CONFIG_FILE), + 'w') do |fp| + fp.puts ":backtrace: false" + fp.puts ":bulk_threshold: 2048" + end + + util_config_file + + assert_equal 2048, @cfg.bulk_threshold + assert @cfg.backtrace + end + def util_config_file(args = @cfg_args) @cfg = Gem::ConfigFile.new args end diff --git a/test/rubygems/test_gem_dependency.rb b/test/rubygems/test_gem_dependency.rb index f280221a00..315c49d6ce 100644 --- a/test/rubygems/test_gem_dependency.rb +++ b/test/rubygems/test_gem_dependency.rb @@ -60,6 +60,21 @@ class TestGemDependency < RubyGemTestCase assert_equal Gem::Requirement.new('= 2'), dep.version_requirements end + def test_initialize_with_type + dep = Gem::Dependency.new("pkg", [], :development) + assert_equal(:development, dep.type) + end + + def test_type_is_runtime_by_default + assert_equal(:runtime, Gem::Dependency.new("pkg", []).type) + end + + def test_type_is_restricted + assert_raise(ArgumentError) do + Gem::Dependency.new("pkg", [:sometimes]) + end + end + def test_equals2 assert_equal @pkg1_0, @pkg1_0.dup assert_equal @pkg1_0.dup, @pkg1_0 @@ -74,6 +89,36 @@ class TestGemDependency < RubyGemTestCase assert_not_equal Object.new, @pkg1_0 end + def test_equals2_type + runtime = Gem::Dependency.new("pkg", []) + development = Gem::Dependency.new("pkg", [], :development) + + assert_not_equal(runtime, development) + end + + def test_equals_tilde + def dep(name, version) + Gem::Dependency.new name, version + end + + a0 = dep 'a', '0' + a1 = dep 'a', '1' + b0 = dep 'b', '0' + + pa0 = dep 'a', '>= 0' + pa0r = dep(/a/, '>= 0') + pab0r = dep(/a|b/, '>= 0') + + assert((a0 =~ a0), 'match self') + assert((pa0 =~ a0), 'match version exact') + assert((pa0 =~ a1), 'match version') + assert((pa0r =~ a0), 'match regex simple') + assert((pab0r =~ a0), 'match regex complex') + + assert(!(pa0r =~ b0), 'fail match regex') + assert(!(pa0r =~ Object.new), 'fail match Object') + end + def test_hash assert_equal @pkg1_0.hash, @pkg1_0.dup.hash assert_equal @pkg1_0.dup.hash, @pkg1_0.hash @@ -85,5 +130,11 @@ class TestGemDependency < RubyGemTestCase assert_not_equal @oth1_0.hash, @pkg1_0.hash, "names different" end + def test_hash_type + runtime = Gem::Dependency.new("pkg", []) + development = Gem::Dependency.new("pkg", [], :development) + + assert_not_equal(runtime.hash, development.hash) + end end diff --git a/test/rubygems/test_gem_dependency_installer.rb b/test/rubygems/test_gem_dependency_installer.rb index 914d4aa216..80c446a2ad 100644 --- a/test/rubygems/test_gem_dependency_installer.rb +++ b/test/rubygems/test_gem_dependency_installer.rb @@ -15,8 +15,12 @@ class TestGemDependencyInstaller < RubyGemTestCase fp.puts "#!/usr/bin/ruby" end @a1, @a1_gem = util_gem 'a', '1' do |s| s.executables << 'a_bin' end + @aa1, @aa1_gem = util_gem 'aa', '1' - @b1, @b1_gem = util_gem 'b', '1' do |s| s.add_dependency 'a' end + @b1, @b1_gem = util_gem 'b', '1' do |s| + s.add_dependency 'a' + s.add_development_dependency 'aa' + end @d1, @d1_gem = util_gem 'd', '1' @d2, @d2_gem = util_gem 'd', '2' @@ -38,15 +42,13 @@ class TestGemDependencyInstaller < RubyGemTestCase @z1, @z1_gem = util_gem 'z', '1' do |s| s.add_dependency 'y' end - si = util_setup_source_info_cache @a1, @b1, @d1, @d2, @x1_m, @x1_o, @w1, - @y1, @y1_1_p, @z1 + @fetcher = Gem::FakeFetcher.new + Gem::RemoteFetcher.fetcher = @fetcher - @fetcher = FakeFetcher.new - Gem::RemoteFetcher.instance_variable_set :@fetcher, @fetcher - @fetcher.uri = URI.parse 'http://gems.example.com' - @fetcher.data['http://gems.example.com/gems/yaml'] = si.to_yaml + si = util_setup_spec_fetcher @a1, @b1, @d1, @d2, @x1_m, @x1_o, @w1, @y1, + @y1_1_p, @z1 - FileUtils.rm_rf File.join(@gemhome, 'gems') + util_clear_gems end def test_install @@ -64,6 +66,52 @@ class TestGemDependencyInstaller < RubyGemTestCase assert_equal [@a1], inst.installed_gems end + def test_install_cache_dir + FileUtils.mv @a1_gem, @tempdir + FileUtils.mv @b1_gem, @tempdir + inst = nil + + Dir.chdir @tempdir do + inst = Gem::DependencyInstaller.new :cache_dir => @tempdir + inst.install 'b' + end + + assert_equal %w[a-1 b-1], inst.installed_gems.map { |s| s.full_name } + + assert File.exist?(File.join(@tempdir, 'cache', "#{@a1.full_name}.gem")) + assert File.exist?(File.join(@tempdir, 'cache', "#{@b1.full_name}.gem")) + end + + def test_install_dependencies_satisfied + a2, a2_gem = util_gem 'a', '2' + + FileUtils.rm_rf File.join(@gemhome, 'gems') + Gem.source_index.refresh! + + FileUtils.mv @a1_gem, @tempdir + FileUtils.mv a2_gem, @tempdir # not in index + FileUtils.mv @b1_gem, @tempdir + inst = nil + + Dir.chdir @tempdir do + inst = Gem::DependencyInstaller.new + inst.install 'a-2' + end + + FileUtils.rm File.join(@tempdir, "#{a2.full_name}.gem") + + Dir.chdir @tempdir do + inst = Gem::DependencyInstaller.new + inst.install 'b' + end + + installed = Gem::SourceIndex.from_installed_gems.map { |n,s| s.full_name } + + assert_equal %w[a-2 b-1], installed.sort + + assert_equal %w[b-1], inst.installed_gems.map { |s| s.full_name } + end + def test_install_dependency FileUtils.mv @a1_gem, @tempdir FileUtils.mv @b1_gem, @tempdir @@ -77,6 +125,20 @@ class TestGemDependencyInstaller < RubyGemTestCase assert_equal %w[a-1 b-1], inst.installed_gems.map { |s| s.full_name } end + def test_install_dependency_development + FileUtils.mv @a1_gem, @tempdir + FileUtils.mv @aa1_gem, @tempdir + FileUtils.mv @b1_gem, @tempdir + inst = nil + + Dir.chdir @tempdir do + inst = Gem::DependencyInstaller.new(:development => true) + inst.install 'b' + end + + assert_equal %w[a-1 aa-1 b-1], inst.installed_gems.map { |s| s.full_name } + end + def test_install_dependency_existing Gem::Installer.new(@a1_gem).install FileUtils.mv @a1_gem, @tempdir @@ -177,7 +239,7 @@ class TestGemDependencyInstaller < RubyGemTestCase def test_install_force FileUtils.mv @b1_gem, @tempdir - si = util_setup_source_info_cache @b1 + si = util_setup_spec_fetcher @b1 @fetcher.data['http://gems.example.com/gems/yaml'] = si.to_yaml inst = nil @@ -249,8 +311,6 @@ class TestGemDependencyInstaller < RubyGemTestCase end def test_install_domain_both_no_network - Gem::SourceInfoCache.instance_variable_set :@cache, nil - @fetcher.data["http://gems.example.com/gems/Marshal.#{@marshal_version}"] = proc do raise Gem::RemoteFetcher::FetchError @@ -272,12 +332,14 @@ class TestGemDependencyInstaller < RubyGemTestCase FileUtils.mv @b1_gem, @tempdir inst = nil + Gem.source_index.gems.delete @a1.full_name + Dir.chdir @tempdir do e = assert_raise Gem::InstallError do inst = Gem::DependencyInstaller.new :domain => :local inst.install 'b' end - assert_equal 'b requires a (>= 0)', e.message + assert_equal 'b requires a (>= 0, runtime)', e.message end assert_equal [], inst.installed_gems.map { |s| s.full_name } @@ -297,6 +359,30 @@ class TestGemDependencyInstaller < RubyGemTestCase assert_equal %w[a-1], inst.installed_gems.map { |s| s.full_name } end + def test_install_dual_repository + FileUtils.mv @a1_gem, @tempdir + FileUtils.mv @b1_gem, @tempdir + inst = nil + + gemhome2 = "#{@gemhome}2" + + Dir.chdir @tempdir do + inst = Gem::DependencyInstaller.new :install_dir => gemhome2 + inst.install 'a' + end + + ENV['GEM_HOME'] = @gemhome + ENV['GEM_PATH'] = [@gemhome, gemhome2].join ':' + Gem.clear_paths + + Dir.chdir @tempdir do + inst = Gem::DependencyInstaller.new + inst.install 'b' + end + + assert_equal %w[b-1], inst.installed_gems.map { |s| s.full_name } + end + def test_install_remote a1_data = nil File.open @a1_gem, 'rb' do |fp| @@ -337,7 +423,9 @@ class TestGemDependencyInstaller < RubyGemTestCase s.platform = Gem::Platform.new %w[cpu other_platform 1] end - si = util_setup_source_info_cache @a1, a2_o + util_clear_gems + + si = util_setup_spec_fetcher @a1, a2_o @fetcher.data['http://gems.example.com/gems/yaml'] = si.to_yaml @@ -439,7 +527,7 @@ class TestGemDependencyInstaller < RubyGemTestCase inst = Gem::DependencyInstaller.new dep = Gem::Dependency.new 'b', '>= 0' - assert_equal [[@b1, 'http://gems.example.com']], + assert_equal [[@b1, @gem_repo]], inst.find_gems_with_sources(dep) end @@ -456,7 +544,7 @@ class TestGemDependencyInstaller < RubyGemTestCase assert_equal 2, gems.length remote = gems.first assert_equal 'a-1', remote.first.full_name, 'remote spec' - assert_equal 'http://gems.example.com', remote.last, 'remote path' + assert_equal @gem_repo, remote.last, 'remote path' local = gems.last assert_equal 'a-1', local.first.full_name, 'local spec' @@ -476,12 +564,9 @@ class TestGemDependencyInstaller < RubyGemTestCase b2, = util_gem 'b', '2' c1, = util_gem 'c', '1' do |s| s.add_dependency 'b' end - si = util_setup_source_info_cache @a1, @b1, b2, c1 + util_clear_gems - @fetcher = FakeFetcher.new - Gem::RemoteFetcher.instance_variable_set :@fetcher, @fetcher - @fetcher.uri = URI.parse 'http://gems.example.com' - @fetcher.data['http://gems.example.com/gems/yaml'] = si.to_yaml + si = util_setup_spec_fetcher @a1, @b1, b2, c1 inst = Gem::DependencyInstaller.new inst.find_spec_by_name_and_version 'c' @@ -512,12 +597,9 @@ class TestGemDependencyInstaller < RubyGemTestCase def test_gather_dependencies_old_required e1, = util_gem 'e', '1' do |s| s.add_dependency 'd', '= 1' end - si = util_setup_source_info_cache @d1, @d2, e1 + util_clear_gems - @fetcher = FakeFetcher.new - Gem::RemoteFetcher.instance_variable_set :@fetcher, @fetcher - @fetcher.uri = URI.parse 'http://gems.example.com' - @fetcher.data['http://gems.example.com/gems/yaml'] = si.to_yaml + si = util_setup_spec_fetcher @d1, @d2, e1 inst = Gem::DependencyInstaller.new inst.find_spec_by_name_and_version 'e' @@ -525,5 +607,6 @@ class TestGemDependencyInstaller < RubyGemTestCase assert_equal %w[d-1 e-1], inst.gems_to_install.map { |s| s.full_name } end + end diff --git a/test/rubygems/test_gem_gem_path_searcher.rb b/test/rubygems/test_gem_gem_path_searcher.rb index d35416e867..c9da4d2b05 100644 --- a/test/rubygems/test_gem_gem_path_searcher.rb +++ b/test/rubygems/test_gem_gem_path_searcher.rb @@ -28,7 +28,10 @@ class TestGemGemPathSearcher < RubyGemTestCase @bar1 = quick_gem 'bar', '0.1' @bar2 = quick_gem 'bar', '0.2' - Gem.source_index = util_setup_source_info_cache @foo1, @foo2, @bar1, @bar2 + @fetcher = Gem::FakeFetcher.new + Gem::RemoteFetcher.fetcher = @fetcher + + Gem.source_index = util_setup_spec_fetcher @foo1, @foo2, @bar1, @bar2 @gps = Gem::GemPathSearcher.new end diff --git a/test/rubygems/test_gem_indexer.rb b/test/rubygems/test_gem_indexer.rb index 12469b5d57..5ccaaff01f 100644 --- a/test/rubygems/test_gem_indexer.rb +++ b/test/rubygems/test_gem_indexer.rb @@ -20,6 +20,10 @@ class TestGemIndexer < RubyGemTestCase util_make_gems + @d2_0 = quick_gem 'd', '2.0' + write_file File.join(*%W[gems #{@d2_0.original_name} lib code.rb]) do end + util_build_gem @d2_0 + gems = File.join(@tempdir, 'gems') FileUtils.mkdir_p gems cache_gems = File.join @gemhome, 'cache', '*.gem' @@ -39,10 +43,10 @@ class TestGemIndexer < RubyGemTestCase @indexer.generate_index end - assert File.exist?(File.join(@tempdir, 'yaml')) - assert File.exist?(File.join(@tempdir, 'yaml.Z')) - assert File.exist?(File.join(@tempdir, "Marshal.#{@marshal_version}")) - assert File.exist?(File.join(@tempdir, "Marshal.#{@marshal_version}.Z")) + assert_indexed @tempdir, 'yaml' + assert_indexed @tempdir, 'yaml.Z' + assert_indexed @tempdir, "Marshal.#{@marshal_version}" + assert_indexed @tempdir, "Marshal.#{@marshal_version}.Z" quickdir = File.join @tempdir, 'quick' marshal_quickdir = File.join quickdir, "Marshal.#{@marshal_version}" @@ -53,10 +57,33 @@ class TestGemIndexer < RubyGemTestCase assert_indexed quickdir, "index" assert_indexed quickdir, "index.rz" + quick_index = File.read File.join(quickdir, 'index') + expected = <<-EOF +a-1 +a-2 +a_evil-9 +b-2 +c-1.2 +d-2.0 +pl-1-i386-linux + EOF + + assert_equal expected, quick_index + assert_indexed quickdir, "latest_index" assert_indexed quickdir, "latest_index.rz" - assert_no_match %r|a-1|, File.read(File.join(quickdir, 'latest_index')) + latest_quick_index = File.read File.join(quickdir, 'latest_index') + expected = <<-EOF +a-2 +a_evil-9 +b-2 +c-1.2 +d-2.0 +pl-1-i386-linux + EOF + + assert_equal expected, latest_quick_index assert_indexed quickdir, "#{@a1.full_name}.gemspec.rz" assert_indexed quickdir, "#{@a2.full_name}.gemspec.rz" @@ -64,13 +91,19 @@ class TestGemIndexer < RubyGemTestCase assert_indexed quickdir, "#{@c1_2.full_name}.gemspec.rz" assert_indexed quickdir, "#{@pl1.original_name}.gemspec.rz" - deny_indexed quickdir, "#{@pl1.full_name}.gemspec.rz" + refute_indexed quickdir, "#{@pl1.full_name}.gemspec.rz" assert_indexed marshal_quickdir, "#{@a1.full_name}.gemspec.rz" assert_indexed marshal_quickdir, "#{@a2.full_name}.gemspec.rz" - deny_indexed quickdir, "#{@c1_2.full_name}.gemspec" - deny_indexed marshal_quickdir, "#{@c1_2.full_name}.gemspec" + refute_indexed quickdir, "#{@c1_2.full_name}.gemspec" + refute_indexed marshal_quickdir, "#{@c1_2.full_name}.gemspec" + + assert_indexed @tempdir, "specs.#{@marshal_version}" + assert_indexed @tempdir, "specs.#{@marshal_version}.gz" + + assert_indexed @tempdir, "latest_specs.#{@marshal_version}" + assert_indexed @tempdir, "latest_specs.#{@marshal_version}.gz" end def test_generate_index_ui @@ -79,28 +112,37 @@ class TestGemIndexer < RubyGemTestCase end expected = <<-EOF -Generating index for 6 gems in #{@tempdir} -...... +Loading 7 gems from #{@tempdir} +....... Loaded all gems -Generating master indexes (this may take a while) +Generating quick index gemspecs for 7 gems +....... +Complete +Generating specs index +Generating latest specs index +Generating quick index +Generating latest index +Generating Marshal master index +Generating YAML master index for 7 gems (this may take a while) +....... +Complete +Compressing indicies EOF assert_equal expected, @ui.output assert_equal '', @ui.error end - def test_generate_index_contents + def test_generate_index_master use_ui @ui do @indexer.generate_index end - yaml_path = File.join(@tempdir, 'yaml') - dump_path = File.join(@tempdir, "Marshal.#{@marshal_version}") + yaml_path = File.join @tempdir, 'yaml' + dump_path = File.join @tempdir, "Marshal.#{@marshal_version}" - yaml_index = YAML.load_file(yaml_path) - dump_str = nil - File.open dump_path, 'rb' do |fp| dump_str = fp.read end - dump_index = Marshal.load dump_str + yaml_index = YAML.load_file yaml_path + dump_index = Marshal.load Gem.read_binary(dump_path) dump_index.each do |_,gem| gem.send :remove_instance_variable, :@loaded @@ -110,12 +152,75 @@ Generating master indexes (this may take a while) "expected YAML and Marshal to produce identical results" end + def test_generate_index_specs + use_ui @ui do + @indexer.generate_index + end + + specs_path = File.join @tempdir, "specs.#{@marshal_version}" + + specs_dump = Gem.read_binary specs_path + specs = Marshal.load specs_dump + + expected = [ + ['a', Gem::Version.new(1), 'ruby'], + ['a', Gem::Version.new(2), 'ruby'], + ['a_evil', Gem::Version.new(9), 'ruby'], + ['b', Gem::Version.new(2), 'ruby'], + ['c', Gem::Version.new('1.2'), 'ruby'], + ['d', Gem::Version.new('2.0'), 'ruby'], + ['pl', Gem::Version.new(1), 'i386-linux'], + ] + + assert_equal expected, specs + + assert_same specs[0].first, specs[1].first, + 'identical names not identical' + + assert_same specs[0][1], specs[-1][1], + 'identical versions not identical' + + assert_same specs[0].last, specs[1].last, + 'identical platforms not identical' + + assert_not_same specs[1][1], specs[5][1], + 'different versions not different' + end + + def test_generate_index_latest_specs + use_ui @ui do + @indexer.generate_index + end + + latest_specs_path = File.join @tempdir, "latest_specs.#{@marshal_version}" + + latest_specs_dump = Gem.read_binary latest_specs_path + latest_specs = Marshal.load latest_specs_dump + + expected = [ + ['a', Gem::Version.new(2), 'ruby'], + ['a_evil', Gem::Version.new(9), 'ruby'], + ['b', Gem::Version.new(2), 'ruby'], + ['c', Gem::Version.new('1.2'), 'ruby'], + ['d', Gem::Version.new('2.0'), 'ruby'], + ['pl', Gem::Version.new(1), 'i386-linux'], + ] + + assert_equal expected, latest_specs + + assert_same latest_specs[0][1], latest_specs[2][1], + 'identical versions not identical' + + assert_same latest_specs[0].last, latest_specs[1].last, + 'identical platforms not identical' + end + def assert_indexed(dir, name) file = File.join dir, name assert File.exist?(file), "#{file} does not exist" end - def deny_indexed(dir, name) + def refute_indexed(dir, name) file = File.join dir, name assert !File.exist?(file), "#{file} exists" end diff --git a/test/rubygems/test_gem_installer.rb b/test/rubygems/test_gem_installer.rb index f7d36c66ed..edd8b472cd 100644 --- a/test/rubygems/test_gem_installer.rb +++ b/test/rubygems/test_gem_installer.rb @@ -102,7 +102,7 @@ load 'my_exec' @installer.ensure_dependency @spec, dep end - assert_equal 'a requires b (> 2)', e.message + assert_equal 'a requires b (> 2, runtime)', e.message end def test_expand_and_validate_gem_dir @@ -128,7 +128,12 @@ load 'my_exec' @installer.extract_files - assert_equal 'thefile', File.read(File.join(util_gem_dir, 'thefile')) + thefile_path = File.join(util_gem_dir, 'thefile') + assert_equal 'thefile', File.read(thefile_path) + + unless Gem.win_platform? then + assert_equal 0400, File.stat(thefile_path).mode & 0777 + end end def test_extract_files_bad_dest @@ -313,6 +318,29 @@ load 'my_exec' #assert_no_match %r|generated by RubyGems|, wrapper end + def test_generate_bin_script_wrappers + @installer.wrappers = true + util_make_exec + @installer.gem_dir = util_gem_dir + installed_exec = File.join(util_inst_bindir, "my_exec") + + real_exec = File.join util_gem_dir, 'bin', 'my_exec' + + # fake --no-wrappers for previous install + FileUtils.mkdir_p File.dirname(installed_exec) + FileUtils.ln_s real_exec, installed_exec + + @installer.generate_bin + assert_equal true, File.directory?(util_inst_bindir) + assert_equal true, File.exist?(installed_exec) + assert_equal(0100755, File.stat(installed_exec).mode) unless win_platform? + + assert_match %r|generated by RubyGems|, File.read(installed_exec) + + assert_no_match %r|generated by RubyGems|, File.read(real_exec), + 'real executable overwritten' + end + def test_generate_bin_symlink return if win_platform? #Windows FS do not support symlinks diff --git a/test/rubygems/test_gem_local_remote_options.rb b/test/rubygems/test_gem_local_remote_options.rb index d5a6651ade..e676c94f21 100644 --- a/test/rubygems/test_gem_local_remote_options.rb +++ b/test/rubygems/test_gem_local_remote_options.rb @@ -46,12 +46,13 @@ class TestGemLocalRemoteOptions < RubyGemTestCase def test_source_option @cmd.add_source_option - s1 = URI.parse 'http://more-gems.example.com' - s2 = URI.parse 'http://even-more-gems.example.com' + s1 = URI.parse 'http://more-gems.example.com/' + s2 = URI.parse 'http://even-more-gems.example.com/' + s3 = URI.parse 'http://other-gems.example.com/some_subdir' - @cmd.handle_options %W[--source #{s1} --source #{s2}] + @cmd.handle_options %W[--source #{s1} --source #{s2} --source #{s3}] - assert_equal [s1, s2], Gem.sources + assert_equal [s1.to_s, s2.to_s, "#{s3}/"], Gem.sources end def test_update_sources_option @@ -77,7 +78,7 @@ class TestGemLocalRemoteOptions < RubyGemTestCase @cmd.handle_options %W[--source #{s1}] end - assert_equal %w[http://gems.example.com], Gem.sources + assert_equal [@gem_repo], Gem.sources end end diff --git a/test/rubygems/test_gem_remote_fetcher.rb b/test/rubygems/test_gem_remote_fetcher.rb index ddadeb9fcf..1d2103bd06 100644 --- a/test/rubygems/test_gem_remote_fetcher.rb +++ b/test/rubygems/test_gem_remote_fetcher.rb @@ -24,7 +24,7 @@ require 'rubygems/remote_fetcher' # Note that the proxy server is not a *real* proxy server. But our # software doesn't really care, as long as we hit the proxy URL when a # proxy is configured. -# + class TestGemRemoteFetcher < RubyGemTestCase include Gem::DefaultUserInteraction @@ -105,7 +105,7 @@ gems: @a1, @a1_gem = util_gem 'a', '1' do |s| s.executables << 'a_bin' end - Gem::RemoteFetcher.instance_variable_set :@fetcher, nil + Gem::RemoteFetcher.fetcher = nil end def test_self_fetcher @@ -144,7 +144,7 @@ gems: def test_fetch_size_socket_error fetcher = Gem::RemoteFetcher.new nil - def fetcher.connect_to(host, port) + def fetcher.connection_for(uri) raise SocketError end @@ -153,7 +153,8 @@ gems: fetcher.fetch_size uri end - assert_equal "SocketError (SocketError)\n\tgetting size of #{uri}", e.message + assert_equal "SocketError (SocketError)\n\tfetching size (#{uri})", + e.message end def test_no_proxy @@ -182,7 +183,7 @@ gems: @test_data end - raise Gem::RemoteFetcher::FetchError, "haha!" + raise Gem::RemoteFetcher::FetchError.new("haha!", nil) end end @@ -371,7 +372,8 @@ gems: fetcher.fetch_path 'uri' end - assert_equal 'EOFError: EOFError reading uri', e.message + assert_equal 'EOFError: EOFError (uri)', e.message + assert_equal 'uri', e.uri end def test_fetch_path_socket_error @@ -383,7 +385,8 @@ gems: fetcher.fetch_path 'uri' end - assert_equal 'SocketError: SocketError reading uri', e.message + assert_equal 'SocketError: SocketError (uri)', e.message + assert_equal 'uri', e.uri end def test_fetch_path_system_call_error @@ -397,8 +400,9 @@ gems: fetcher.fetch_path 'uri' end - assert_match %r|ECONNREFUSED:.*connect\(2\) reading uri\z|, + assert_match %r|ECONNREFUSED:.*connect\(2\) \(uri\)\z|, e.message + assert_equal 'uri', e.uri end def test_get_proxy_from_env_empty @@ -494,7 +498,8 @@ gems: fetcher.send :open_uri_or_path, 'http://gems.example.com/redirect' end - assert_equal 'too many redirects', e.message + assert_equal 'too many redirects (http://gems.example.com/redirect)', + e.message end def test_zip diff --git a/test/rubygems/test_gem_server.rb b/test/rubygems/test_gem_server.rb index 24f88bf59e..dcdc766f0f 100644 --- a/test/rubygems/test_gem_server.rb +++ b/test/rubygems/test_gem_server.rb @@ -14,34 +14,69 @@ class TestGemServer < RubyGemTestCase super @a1 = quick_gem 'a', '1' + @a2 = quick_gem 'a', '2' @server = Gem::Server.new Gem.dir, process_based_port, false @req = WEBrick::HTTPRequest.new :Logger => nil @res = WEBrick::HTTPResponse.new :HTTPVersion => '1.0' end - def test_quick_index - data = StringIO.new "GET /quick/index HTTP/1.0\r\n\r\n" + def test_Marshal + data = StringIO.new "GET /Marshal.#{Gem.marshal_version} HTTP/1.0\r\n\r\n" @req.parse data - @server.quick @req, @res + @server.Marshal @req, @res assert_equal 200, @res.status, @res.body assert_match %r| \d\d:\d\d:\d\d |, @res['date'] - assert_equal 'text/plain', @res['content-type'] - assert_equal "a-1", @res.body + assert_equal 'application/octet-stream', @res['content-type'] + + si = Gem::SourceIndex.new + si.add_specs @a1, @a2 + + assert_equal si, Marshal.load(@res.body) end - def test_quick_index_rz - data = StringIO.new "GET /quick/index.rz HTTP/1.0\r\n\r\n" + def test_Marshal_Z + data = StringIO.new "GET /Marshal.#{Gem.marshal_version}.Z HTTP/1.0\r\n\r\n" @req.parse data - @server.quick @req, @res + @server.Marshal @req, @res assert_equal 200, @res.status, @res.body assert_match %r| \d\d:\d\d:\d\d |, @res['date'] - assert_equal 'text/plain', @res['content-type'] - assert_equal "a-1", Zlib::Inflate.inflate(@res.body) + assert_equal 'application/x-deflate', @res['content-type'] + + si = Gem::SourceIndex.new + si.add_specs @a1, @a2 + + assert_equal si, Marshal.load(Gem.inflate(@res.body)) + end + + def test_latest_specs + data = StringIO.new "GET /latest_specs.#{Gem.marshal_version} HTTP/1.0\r\n\r\n" + @req.parse data + + @server.latest_specs @req, @res + + assert_equal 200, @res.status, @res.body + assert_match %r| \d\d:\d\d:\d\d |, @res['date'] + assert_equal 'application/octet-stream', @res['content-type'] + assert_equal [['a', Gem::Version.new(2), 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 + + @server.latest_specs @req, @res + + assert_equal 200, @res.status, @res.body + assert_match %r| \d\d:\d\d:\d\d |, @res['date'] + assert_equal 'application/x-gzip', @res['content-type'] + assert_equal [['a', Gem::Version.new(2), Gem::Platform::RUBY]], + Marshal.load(Gem.gunzip(@res.body)) end def test_quick_a_1_gemspec_rz @@ -52,17 +87,15 @@ class TestGemServer < RubyGemTestCase assert_equal 200, @res.status, @res.body assert @res['date'] - assert_equal 'text/plain', @res['content-type'] + assert_equal 'application/x-deflate', @res['content-type'] - spec = YAML.load Zlib::Inflate.inflate(@res.body) + spec = YAML.load Gem.inflate(@res.body) assert_equal 'a', spec.name assert_equal Gem::Version.new(1), spec.version end def test_quick_a_1_mswin32_gemspec_rz a1_p = quick_gem 'a', '1' do |s| s.platform = Gem::Platform.local end - si = Gem::SourceIndex.new @a1.full_name => @a1, a1_p.full_name => a1_p - @server.source_index = si data = StringIO.new "GET /quick/a-1-#{Gem::Platform.local}.gemspec.rz HTTP/1.0\r\n\r\n" @req.parse data @@ -71,9 +104,9 @@ class TestGemServer < RubyGemTestCase assert_equal 200, @res.status, @res.body assert @res['date'] - assert_equal 'text/plain', @res['content-type'] + assert_equal 'application/x-deflate', @res['content-type'] - spec = YAML.load Zlib::Inflate.inflate(@res.body) + spec = YAML.load Gem.inflate(@res.body) assert_equal 'a', spec.name assert_equal Gem::Version.new(1), spec.version assert_equal Gem::Platform.local, spec.platform @@ -81,8 +114,6 @@ class TestGemServer < RubyGemTestCase def test_quick_common_substrings ab1 = quick_gem 'ab', '1' - si = Gem::SourceIndex.new @a1.full_name => @a1, ab1.full_name => ab1 - @server.source_index = si data = StringIO.new "GET /quick/a-1.gemspec.rz HTTP/1.0\r\n\r\n" @req.parse data @@ -91,14 +122,62 @@ class TestGemServer < RubyGemTestCase assert_equal 200, @res.status, @res.body assert @res['date'] - assert_equal 'text/plain', @res['content-type'] + assert_equal 'application/x-deflate', @res['content-type'] - spec = YAML.load Zlib::Inflate.inflate(@res.body) + spec = YAML.load Gem.inflate(@res.body) assert_equal 'a', spec.name assert_equal Gem::Version.new(1), spec.version end - def test_quick_z_9_gemspec_rz + def test_quick_index + data = StringIO.new "GET /quick/index HTTP/1.0\r\n\r\n" + @req.parse data + + @server.quick @req, @res + + assert_equal 200, @res.status, @res.body + assert_match %r| \d\d:\d\d:\d\d |, @res['date'] + assert_equal 'text/plain', @res['content-type'] + assert_equal "a-1\na-2", @res.body + end + + def test_quick_index_rz + data = StringIO.new "GET /quick/index.rz HTTP/1.0\r\n\r\n" + @req.parse data + + @server.quick @req, @res + + assert_equal 200, @res.status, @res.body + assert_match %r| \d\d:\d\d:\d\d |, @res['date'] + assert_equal 'application/x-deflate', @res['content-type'] + assert_equal "a-1\na-2", Gem.inflate(@res.body) + end + + def test_quick_latest_index + data = StringIO.new "GET /quick/latest_index HTTP/1.0\r\n\r\n" + @req.parse data + + @server.quick @req, @res + + assert_equal 200, @res.status, @res.body + assert_match %r| \d\d:\d\d:\d\d |, @res['date'] + assert_equal 'text/plain', @res['content-type'] + assert_equal 'a-2', @res.body + end + + def test_quick_latest_index_rz + data = StringIO.new "GET /quick/latest_index.rz HTTP/1.0\r\n\r\n" + @req.parse data + + @server.quick @req, @res + + assert_equal 200, @res.status, @res.body + assert_match %r| \d\d:\d\d:\d\d |, @res['date'] + assert_equal 'application/x-deflate', @res['content-type'] + assert_equal 'a-2', Gem.inflate(@res.body) + end + + def test_quick_missing data = StringIO.new "GET /quick/z-9.gemspec.rz HTTP/1.0\r\n\r\n" @req.parse data @@ -111,5 +190,112 @@ class TestGemServer < RubyGemTestCase assert_equal 404, @res.status end + def test_quick_marshal_a_1_gemspec_rz + data = StringIO.new "GET /quick/Marshal.#{Gem.marshal_version}/a-1.gemspec.rz HTTP/1.0\r\n\r\n" + @req.parse data + + @server.quick @req, @res + + assert_equal 200, @res.status, @res.body + assert @res['date'] + assert_equal 'application/x-deflate', @res['content-type'] + + spec = Marshal.load Gem.inflate(@res.body) + assert_equal 'a', spec.name + assert_equal Gem::Version.new(1), spec.version + end + + def test_quick_marshal_a_1_mswin32_gemspec_rz + a1_p = quick_gem 'a', '1' do |s| s.platform = Gem::Platform.local end + + data = StringIO.new "GET /quick/Marshal.#{Gem.marshal_version}/a-1-#{Gem::Platform.local}.gemspec.rz HTTP/1.0\r\n\r\n" + @req.parse data + + @server.quick @req, @res + + assert_equal 200, @res.status, @res.body + assert @res['date'] + assert_equal 'application/x-deflate', @res['content-type'] + + spec = Marshal.load Gem.inflate(@res.body) + assert_equal 'a', spec.name + assert_equal Gem::Version.new(1), spec.version + assert_equal Gem::Platform.local, spec.platform + end + + + def test_root + data = StringIO.new "GET / HTTP/1.0\r\n\r\n" + @req.parse data + + @server.root @req, @res + + assert_equal 200, @res.status, @res.body + assert_match %r| \d\d:\d\d:\d\d |, @res['date'] + assert_equal 'text/html', @res['content-type'] + end + + def test_specs + data = StringIO.new "GET /specs.#{Gem.marshal_version} HTTP/1.0\r\n\r\n" + @req.parse data + + @server.specs @req, @res + + assert_equal 200, @res.status, @res.body + assert_match %r| \d\d:\d\d:\d\d |, @res['date'] + assert_equal 'application/octet-stream', @res['content-type'] + + assert_equal [['a', Gem::Version.new(1), Gem::Platform::RUBY], + ['a', Gem::Version.new(2), 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 + + @server.specs @req, @res + + assert_equal 200, @res.status, @res.body + assert_match %r| \d\d:\d\d:\d\d |, @res['date'] + assert_equal 'application/x-gzip', @res['content-type'] + + assert_equal [['a', Gem::Version.new(1), Gem::Platform::RUBY], + ['a', Gem::Version.new(2), Gem::Platform::RUBY]], + Marshal.load(Gem.gunzip(@res.body)) + end + + def test_yaml + data = StringIO.new "GET /yaml.#{Gem.marshal_version} HTTP/1.0\r\n\r\n" + @req.parse data + + @server.yaml @req, @res + + assert_equal 200, @res.status, @res.body + assert_match %r| \d\d:\d\d:\d\d |, @res['date'] + assert_equal 'text/plain', @res['content-type'] + + si = Gem::SourceIndex.new + si.add_specs @a1, @a2 + + assert_equal si, YAML.load(@res.body) + end + + def test_yaml_Z + data = StringIO.new "GET /yaml.#{Gem.marshal_version}.Z HTTP/1.0\r\n\r\n" + @req.parse data + + @server.yaml @req, @res + + assert_equal 200, @res.status, @res.body + assert_match %r| \d\d:\d\d:\d\d |, @res['date'] + assert_equal 'application/x-deflate', @res['content-type'] + + si = Gem::SourceIndex.new + si.add_specs @a1, @a2 + + assert_equal si, YAML.load(Gem.inflate(@res.body)) + end + end diff --git a/test/rubygems/test_gem_source_index.rb b/test/rubygems/test_gem_source_index.rb index 140f3ad067..adb9037caa 100644 --- a/test/rubygems/test_gem_source_index.rb +++ b/test/rubygems/test_gem_source_index.rb @@ -23,6 +23,141 @@ class TestGemSourceIndex < RubyGemTestCase util_setup_fake_fetcher end + def test_self_from_gems_in + spec_dir = File.join @gemhome, 'specifications' + + FileUtils.rm_r spec_dir + + FileUtils.mkdir_p spec_dir + + a1 = quick_gem 'a', '1' do |spec| spec.author = 'author 1' end + + spec_file = File.join spec_dir, "#{a1.full_name}.gemspec" + + File.open spec_file, 'w' do |fp| + fp.write a1.to_ruby + end + + si = Gem::SourceIndex.from_gems_in spec_dir + + assert_equal [spec_dir], si.spec_dirs + assert_equal [a1.full_name], si.gems.keys + end + + def test_self_load_specification + spec_dir = File.join @gemhome, 'specifications' + + FileUtils.rm_r spec_dir + + FileUtils.mkdir_p spec_dir + + a1 = quick_gem 'a', '1' do |spec| spec.author = 'author 1' end + + spec_file = File.join spec_dir, "#{a1.full_name}.gemspec" + + File.open spec_file, 'w' do |fp| + fp.write a1.to_ruby + end + + spec = Gem::SourceIndex.load_specification spec_file + + assert_equal a1.author, spec.author + end + + def test_self_load_specification_exception + spec_dir = File.join @gemhome, 'specifications' + + FileUtils.mkdir_p spec_dir + + spec_file = File.join spec_dir, 'a-1.gemspec' + + File.open spec_file, 'w' do |fp| + fp.write 'raise Exception, "epic fail"' + end + + use_ui @ui do + assert_equal nil, Gem::SourceIndex.load_specification(spec_file) + end + + assert_equal '', @ui.output + + expected = <<-EOF +WARNING: # +raise Exception, "epic fail" +WARNING: Invalid .gemspec format in '#{spec_file}' + EOF + + assert_equal expected, @ui.error + end + + def test_self_load_specification_interrupt + spec_dir = File.join @gemhome, 'specifications' + + FileUtils.mkdir_p spec_dir + + spec_file = File.join spec_dir, 'a-1.gemspec' + + File.open spec_file, 'w' do |fp| + fp.write 'raise Interrupt, "^C"' + end + + use_ui @ui do + assert_raise Interrupt do + Gem::SourceIndex.load_specification(spec_file) + end + end + + assert_equal '', @ui.output + assert_equal '', @ui.error + end + + def test_self_load_specification_syntax_error + spec_dir = File.join @gemhome, 'specifications' + + FileUtils.mkdir_p spec_dir + + spec_file = File.join spec_dir, 'a-1.gemspec' + + File.open spec_file, 'w' do |fp| + fp.write '1 +' + end + + use_ui @ui do + assert_equal nil, Gem::SourceIndex.load_specification(spec_file) + end + + assert_equal '', @ui.output + + expected = <<-EOF +WARNING: compile error +#{spec_file}:1: syntax error, unexpected $end +WARNING: 1 + + EOF + + assert_equal expected, @ui.error + end + + def test_self_load_specification_system_exit + spec_dir = File.join @gemhome, 'specifications' + + FileUtils.mkdir_p spec_dir + + spec_file = File.join spec_dir, 'a-1.gemspec' + + File.open spec_file, 'w' do |fp| + fp.write 'raise SystemExit, "bye-bye"' + end + + use_ui @ui do + assert_raise SystemExit do + Gem::SourceIndex.load_specification(spec_file) + end + end + + assert_equal '', @ui.output + assert_equal '', @ui.error + end + def test_create_from_directory # TODO end @@ -43,16 +178,16 @@ class TestGemSourceIndex < RubyGemTestCase paths = @fetcher.paths - assert_equal "#{@gem_repo}/Marshal.#{@marshal_version}.Z", paths.shift + assert_equal "#{@gem_repo}Marshal.#{@marshal_version}.Z", paths.shift assert paths.empty?, paths.join(', ') end def test_fetch_bulk_index_error - @fetcher.data["#{@gem_repo}/Marshal.#{@marshal_version}.Z"] = proc { raise SocketError } - @fetcher.data["#{@gem_repo}/Marshal.#{@marshal_version}"] = proc { raise SocketError } - @fetcher.data["#{@gem_repo}/yaml.Z"] = proc { raise SocketError } - @fetcher.data["#{@gem_repo}/yaml"] = proc { raise SocketError } + @fetcher.data["#{@gem_repo}Marshal.#{@marshal_version}.Z"] = proc { raise SocketError } + @fetcher.data["#{@gem_repo}Marshal.#{@marshal_version}"] = proc { raise SocketError } + @fetcher.data["#{@gem_repo}yaml.Z"] = proc { raise SocketError } + @fetcher.data["#{@gem_repo}yaml"] = proc { raise SocketError } e = assert_raise Gem::RemoteSourceException do use_ui @ui do @@ -62,10 +197,10 @@ class TestGemSourceIndex < RubyGemTestCase paths = @fetcher.paths - assert_equal "#{@gem_repo}/Marshal.#{@marshal_version}.Z", paths.shift - assert_equal "#{@gem_repo}/Marshal.#{@marshal_version}", paths.shift - assert_equal "#{@gem_repo}/yaml.Z", paths.shift - assert_equal "#{@gem_repo}/yaml", paths.shift + assert_equal "#{@gem_repo}Marshal.#{@marshal_version}.Z", paths.shift + assert_equal "#{@gem_repo}Marshal.#{@marshal_version}", paths.shift + assert_equal "#{@gem_repo}yaml.Z", paths.shift + assert_equal "#{@gem_repo}yaml", paths.shift assert paths.empty?, paths.join(', ') @@ -74,12 +209,12 @@ class TestGemSourceIndex < RubyGemTestCase end def test_fetch_bulk_index_fallback - @fetcher.data["#{@gem_repo}/Marshal.#{@marshal_version}.Z"] = + @fetcher.data["#{@gem_repo}Marshal.#{@marshal_version}.Z"] = proc { raise SocketError } - @fetcher.data["#{@gem_repo}/Marshal.#{@marshal_version}"] = + @fetcher.data["#{@gem_repo}Marshal.#{@marshal_version}"] = proc { raise SocketError } - @fetcher.data["#{@gem_repo}/yaml.Z"] = proc { raise SocketError } - @fetcher.data["#{@gem_repo}/yaml"] = @source_index.to_yaml + @fetcher.data["#{@gem_repo}yaml.Z"] = proc { raise SocketError } + @fetcher.data["#{@gem_repo}yaml"] = @source_index.to_yaml use_ui @ui do fetched_index = @source_index.fetch_bulk_index @uri @@ -90,10 +225,10 @@ class TestGemSourceIndex < RubyGemTestCase paths = @fetcher.paths - assert_equal "#{@gem_repo}/Marshal.#{@marshal_version}.Z", paths.shift - assert_equal "#{@gem_repo}/Marshal.#{@marshal_version}", paths.shift - assert_equal "#{@gem_repo}/yaml.Z", paths.shift - assert_equal "#{@gem_repo}/yaml", paths.shift + assert_equal "#{@gem_repo}Marshal.#{@marshal_version}.Z", paths.shift + assert_equal "#{@gem_repo}Marshal.#{@marshal_version}", paths.shift + assert_equal "#{@gem_repo}yaml.Z", paths.shift + assert_equal "#{@gem_repo}yaml", paths.shift assert paths.empty?, paths.join(', ') end @@ -102,8 +237,8 @@ class TestGemSourceIndex < RubyGemTestCase marshal = @source_index.dump marshal[0] = (Marshal::MAJOR_VERSION - 1).chr - @fetcher.data["#{@gem_repo}/Marshal.#{@marshal_version}"] = marshal - @fetcher.data["#{@gem_repo}/yaml"] = @source_index.to_yaml + @fetcher.data["#{@gem_repo}Marshal.#{@marshal_version}"] = marshal + @fetcher.data["#{@gem_repo}yaml"] = @source_index.to_yaml use_ui @ui do fetched_index = @source_index.fetch_bulk_index @uri @@ -114,10 +249,10 @@ class TestGemSourceIndex < RubyGemTestCase paths = @fetcher.paths - assert_equal "#{@gem_repo}/Marshal.#{@marshal_version}.Z", paths.shift - assert_equal "#{@gem_repo}/Marshal.#{@marshal_version}", paths.shift - assert_equal "#{@gem_repo}/yaml.Z", paths.shift - assert_equal "#{@gem_repo}/yaml", paths.shift + assert_equal "#{@gem_repo}Marshal.#{@marshal_version}.Z", paths.shift + assert_equal "#{@gem_repo}Marshal.#{@marshal_version}", paths.shift + assert_equal "#{@gem_repo}yaml.Z", paths.shift + assert_equal "#{@gem_repo}yaml", paths.shift assert paths.empty?, paths.join(', ') end @@ -133,8 +268,8 @@ class TestGemSourceIndex < RubyGemTestCase paths = @fetcher.paths - assert_equal "#{@gem_repo}/Marshal.#{@marshal_version}.Z", paths.shift - assert_equal "#{@gem_repo}/Marshal.#{@marshal_version}", paths.shift + assert_equal "#{@gem_repo}Marshal.#{@marshal_version}.Z", paths.shift + assert_equal "#{@gem_repo}Marshal.#{@marshal_version}", paths.shift assert paths.empty?, paths.join(', ') end @@ -143,8 +278,8 @@ class TestGemSourceIndex < RubyGemTestCase index = util_zip @gem_names latest_index = util_zip [@a2.full_name, @b2.full_name].join("\n") - @fetcher.data["#{@gem_repo}/quick/index.rz"] = index - @fetcher.data["#{@gem_repo}/quick/latest_index.rz"] = latest_index + @fetcher.data["#{@gem_repo}quick/index.rz"] = index + @fetcher.data["#{@gem_repo}quick/latest_index.rz"] = latest_index quick_index = @source_index.fetch_quick_index @uri, false assert_equal [@a2.full_name, @b2.full_name].sort, @@ -152,7 +287,7 @@ class TestGemSourceIndex < RubyGemTestCase paths = @fetcher.paths - assert_equal "#{@gem_repo}/quick/latest_index.rz", paths.shift + assert_equal "#{@gem_repo}quick/latest_index.rz", paths.shift assert paths.empty?, paths.join(', ') end @@ -161,8 +296,8 @@ class TestGemSourceIndex < RubyGemTestCase index = util_zip @gem_names latest_index = util_zip [@a2.full_name, @b2.full_name].join("\n") - @fetcher.data["#{@gem_repo}/quick/index.rz"] = index - @fetcher.data["#{@gem_repo}/quick/latest_index.rz"] = latest_index + @fetcher.data["#{@gem_repo}quick/index.rz"] = index + @fetcher.data["#{@gem_repo}quick/latest_index.rz"] = latest_index quick_index = @source_index.fetch_quick_index @uri, true assert_equal [@a1.full_name, @a2.full_name, @b2.full_name].sort, @@ -170,13 +305,13 @@ class TestGemSourceIndex < RubyGemTestCase paths = @fetcher.paths - assert_equal "#{@gem_repo}/quick/index.rz", paths.shift + assert_equal "#{@gem_repo}quick/index.rz", paths.shift assert paths.empty?, paths.join(', ') end def test_fetch_quick_index_error - @fetcher.data["#{@gem_repo}/quick/index.rz"] = + @fetcher.data["#{@gem_repo}quick/index.rz"] = proc { raise Exception } e = assert_raise Gem::OperationNotSupportedError do @@ -187,7 +322,7 @@ class TestGemSourceIndex < RubyGemTestCase paths = @fetcher.paths - assert_equal "#{@gem_repo}/quick/index.rz", paths.shift + assert_equal "#{@gem_repo}quick/index.rz", paths.shift assert paths.empty?, paths.join(', ') end @@ -195,22 +330,22 @@ class TestGemSourceIndex < RubyGemTestCase def test_fetch_quick_index_fallback index = util_zip @gem_names - @fetcher.data["#{@gem_repo}/quick/index.rz"] = index + @fetcher.data["#{@gem_repo}quick/index.rz"] = index quick_index = @source_index.fetch_quick_index @uri, false assert_equal @gem_names.split, quick_index.sort paths = @fetcher.paths - assert_equal "#{@gem_repo}/quick/latest_index.rz", paths.shift - assert_equal "#{@gem_repo}/quick/index.rz", paths.shift + assert_equal "#{@gem_repo}quick/latest_index.rz", paths.shift + assert_equal "#{@gem_repo}quick/index.rz", paths.shift assert paths.empty?, paths.join(', ') end def test_fetch_quick_index_subdir latest_index = util_zip [@a2.full_name, @b2.full_name].join("\n") - repo = URI.parse "#{@gem_repo}/~nobody/mirror/" + repo = URI.parse "#{@gem_repo}~nobody/mirror/" @fetcher.data["#{repo}quick/latest_index.rz"] = latest_index @@ -226,7 +361,7 @@ class TestGemSourceIndex < RubyGemTestCase end def test_fetch_single_spec - a1_spec_url = "#{@gem_repo}/quick/Marshal.#{Gem.marshal_version}/#{@a1.full_name}.gemspec.rz" + a1_spec_url = "#{@gem_repo}quick/Marshal.#{Gem.marshal_version}/#{@a1.full_name}.gemspec.rz" @fetcher.data[a1_spec_url] = util_zip Marshal.dump(@a1) spec = @source_index.send :fetch_single_spec, URI.parse(@gem_repo), @@ -242,7 +377,7 @@ class TestGemSourceIndex < RubyGemTestCase end def test_fetch_single_spec_subdir - repo = URI.parse "#{@gem_repo}/~nobody/mirror/" + repo = URI.parse "#{@gem_repo}~nobody/mirror/" a1_spec_url = "#{repo}quick/Marshal.#{Gem.marshal_version}/#{@a1.full_name}.gemspec.rz" @fetcher.data[a1_spec_url] = util_zip Marshal.dump(@a1) @@ -259,7 +394,7 @@ class TestGemSourceIndex < RubyGemTestCase end def test_fetch_single_spec_yaml - a1_spec_url = "#{@gem_repo}/quick/#{@a1.full_name}.gemspec.rz" + a1_spec_url = "#{@gem_repo}quick/#{@a1.full_name}.gemspec.rz" @fetcher.data[a1_spec_url] = util_zip @a1.to_yaml repo = URI.parse @gem_repo @@ -270,14 +405,14 @@ class TestGemSourceIndex < RubyGemTestCase paths = @fetcher.paths - assert_equal "#{@gem_repo}/quick/Marshal.#{Gem.marshal_version}/#{@a1.full_name}.gemspec.rz", paths.shift + assert_equal "#{@gem_repo}quick/Marshal.#{Gem.marshal_version}/#{@a1.full_name}.gemspec.rz", paths.shift assert_equal a1_spec_url, paths.shift assert paths.empty?, paths.join(', ') end def test_fetch_single_spec_yaml_subdir - repo = URI.parse "#{@gem_repo}/~nobody/mirror/" + repo = URI.parse "#{@gem_repo}~nobody/mirror/" a1_spec_url = "#{repo}quick/#{@a1.full_name}.gemspec.rz" @fetcher.data[a1_spec_url] = util_zip @a1.to_yaml @@ -377,12 +512,12 @@ class TestGemSourceIndex < RubyGemTestCase end def test_outdated - util_setup_source_info_cache + util_setup_spec_fetcher assert_equal [], @source_index.outdated updated = quick_gem @a2.name, (@a2.version.bump) - util_setup_source_info_cache updated + util_setup_spec_fetcher updated assert_equal [updated.name], @source_index.outdated @@ -390,7 +525,7 @@ class TestGemSourceIndex < RubyGemTestCase s.platform = Gem::Platform.new 'x86-other_platform1' end - util_setup_source_info_cache updated, updated_platform + util_setup_spec_fetcher updated, updated_platform assert_equal [updated_platform.name], @source_index.outdated end @@ -411,6 +546,16 @@ class TestGemSourceIndex < RubyGemTestCase assert source_index.gems.include?(@a1.full_name) end + def test_refresh_bang_not_from_dir + source_index = Gem::SourceIndex.new + + e = assert_raise RuntimeError do + source_index.refresh! + end + + assert_equal 'source index not created from disk', e.message + end + def test_remove_extra @source_index.add_spec @a1 @source_index.add_spec @a2 @@ -516,8 +661,8 @@ class TestGemSourceIndex < RubyGemTestCase paths = @fetcher.paths - assert_equal "#{@gem_repo}/quick/index.rz", paths.shift - assert_equal "#{@gem_repo}/Marshal.#{@marshal_version}.Z", paths.shift + assert_equal "#{@gem_repo}quick/index.rz", paths.shift + assert_equal "#{@gem_repo}Marshal.#{@marshal_version}.Z", paths.shift assert paths.empty?, paths.join(', ') end @@ -528,7 +673,7 @@ class TestGemSourceIndex < RubyGemTestCase latest_names = [@a2, @a_evil9, @b2, @c1_2].map { |s| s.full_name } latest_index = util_zip latest_names.join("\n") - @fetcher.data["#{@gem_repo}/quick/latest_index.rz"] = latest_index + @fetcher.data["#{@gem_repo}quick/latest_index.rz"] = latest_index marshal_uri = File.join @gem_repo, "quick", "Marshal.#{@marshal_version}", "#{@b2.full_name}.gemspec.rz" @@ -541,7 +686,7 @@ class TestGemSourceIndex < RubyGemTestCase end paths = @fetcher.paths - assert_equal "#{@gem_repo}/quick/latest_index.rz", paths.shift + assert_equal "#{@gem_repo}quick/latest_index.rz", paths.shift assert_equal marshal_uri, paths.shift assert paths.empty?, paths.join(', ') @@ -554,7 +699,7 @@ class TestGemSourceIndex < RubyGemTestCase Gem.configuration = Gem::ConfigFile.new([]) quick_index = util_zip @all_gem_names.join("\n") - @fetcher.data["#{@gem_repo}/quick/index.rz"] = quick_index + @fetcher.data["#{@gem_repo}quick/index.rz"] = quick_index marshal_uri = File.join @gem_repo, "quick", "Marshal.#{@marshal_version}", "#{@b2.full_name}.gemspec.rz" @@ -567,7 +712,7 @@ class TestGemSourceIndex < RubyGemTestCase end paths = @fetcher.paths - assert_equal "#{@gem_repo}/quick/index.rz", paths.shift + assert_equal "#{@gem_repo}quick/index.rz", paths.shift assert_equal marshal_uri, paths.shift assert paths.empty?, paths.join(', ') @@ -580,12 +725,12 @@ class TestGemSourceIndex < RubyGemTestCase Gem.configuration = Gem::ConfigFile.new([]) quick_index = util_zip @all_gem_names.join("\n") - @fetcher.data["#{@gem_repo}/quick/index.rz"] = quick_index + @fetcher.data["#{@gem_repo}quick/index.rz"] = quick_index marshal_uri = File.join @gem_repo, "quick", "Marshal.#{@marshal_version}", "#{@b2.full_name}.gemspec.rz" - yaml_uri = "#{@gem_repo}/quick/#{@b2.full_name}.gemspec.rz" + yaml_uri = "#{@gem_repo}quick/#{@b2.full_name}.gemspec.rz" @fetcher.data[yaml_uri] = util_zip @b2.to_yaml use_ui @ui do @@ -595,7 +740,7 @@ class TestGemSourceIndex < RubyGemTestCase end paths = @fetcher.paths - assert_equal "#{@gem_repo}/quick/index.rz", paths.shift + assert_equal "#{@gem_repo}quick/index.rz", paths.shift assert_equal marshal_uri, paths.shift assert_equal yaml_uri, paths.shift @@ -609,7 +754,7 @@ class TestGemSourceIndex < RubyGemTestCase Gem.configuration = Gem::ConfigFile.new([]) quick_index = util_zip @all_gem_names.join("\n") - @fetcher.data["#{@gem_repo}/quick/index.rz"] = quick_index + @fetcher.data["#{@gem_repo}quick/index.rz"] = quick_index marshal_uri = File.join @gem_repo, "quick", "Marshal.#{@marshal_version}", "#{@b2.full_name}.gemspec.rz" @@ -617,7 +762,7 @@ class TestGemSourceIndex < RubyGemTestCase marshal_data[0] = (Marshal::MAJOR_VERSION - 1).chr @fetcher.data[marshal_uri] = util_zip marshal_data - yaml_uri = "#{@gem_repo}/quick/#{@b2.full_name}.gemspec.rz" + yaml_uri = "#{@gem_repo}quick/#{@b2.full_name}.gemspec.rz" @fetcher.data[yaml_uri] = util_zip @b2.to_yaml use_ui @ui do @@ -627,7 +772,7 @@ class TestGemSourceIndex < RubyGemTestCase end paths = @fetcher.paths - assert_equal "#{@gem_repo}/quick/index.rz", paths.shift + assert_equal "#{@gem_repo}quick/index.rz", paths.shift assert_equal marshal_uri, paths.shift assert_equal yaml_uri, paths.shift @@ -637,14 +782,14 @@ class TestGemSourceIndex < RubyGemTestCase end def test_update_subdir - @gem_repo = @gem_repo + "/subdir" + @gem_repo = @gem_repo + 'subdir/' util_setup_bulk_fetch true @source_index.gems.replace({}) assert_equal [], @source_index.gems.keys.sort - uri = @uri.to_s + "/subdir" + uri = @uri.to_s + 'subdir/' use_ui @ui do @source_index.update uri, true @@ -656,8 +801,8 @@ class TestGemSourceIndex < RubyGemTestCase paths = @fetcher.paths - assert_equal "#{@gem_repo}/quick/index.rz", paths.shift - assert_equal "#{@gem_repo}/Marshal.#{@marshal_version}.Z", paths.shift + assert_equal "#{@gem_repo}quick/index.rz", paths.shift + assert_equal "#{@gem_repo}Marshal.#{@marshal_version}.Z", paths.shift assert paths.empty?, paths.join(', ') end @@ -684,9 +829,9 @@ class TestGemSourceIndex < RubyGemTestCase source_index = @source_index.dump if compressed then - @fetcher.data["#{@gem_repo}/Marshal.#{@marshal_version}.Z"] = util_zip source_index + @fetcher.data["#{@gem_repo}Marshal.#{@marshal_version}.Z"] = util_zip source_index else - @fetcher.data["#{@gem_repo}/Marshal.#{@marshal_version}"] = source_index + @fetcher.data["#{@gem_repo}Marshal.#{@marshal_version}"] = source_index end end diff --git a/test/rubygems/test_gem_source_info_cache.rb b/test/rubygems/test_gem_source_info_cache.rb index 523b404280..83cd100a9d 100644 --- a/test/rubygems/test_gem_source_info_cache.rb +++ b/test/rubygems/test_gem_source_info_cache.rb @@ -36,6 +36,7 @@ class TestGemSourceInfoCache < RubyGemTestCase def teardown super Gem.sources.replace @original_sources + Gem::SourceInfoCache.instance_variable_set :@cache, nil end def test_self_cache_refreshes @@ -43,7 +44,7 @@ class TestGemSourceInfoCache < RubyGemTestCase si = Gem::SourceIndex.new si.add_spec @a1 - @fetcher.data["#{@gem_repo}/Marshal.#{@marshal_version}"] = si.dump + @fetcher.data["#{@gem_repo}Marshal.#{@marshal_version}"] = si.dump Gem.sources.replace %W[#{@gem_repo}] @@ -52,8 +53,9 @@ class TestGemSourceInfoCache < RubyGemTestCase assert_kind_of Gem::SourceInfoCache, Gem::SourceInfoCache.cache assert_equal Gem::SourceInfoCache.cache.object_id, Gem::SourceInfoCache.cache.object_id - assert_match %r|Bulk updating|, @ui.output end + + assert_match %r|Bulk updating|, @ui.output end def test_self_cache_skips_refresh_based_on_configuration @@ -61,7 +63,7 @@ class TestGemSourceInfoCache < RubyGemTestCase si = Gem::SourceIndex.new si.add_spec @a1 - @fetcher.data["#{@gem_repo}/Marshal.#{@marshal_version}"] = si.dump + @fetcher.data["#{@gem_repo}Marshal.#{@marshal_version}"] = si.dump Gem.sources.replace %w[#{@gem_repo}] @@ -78,7 +80,7 @@ class TestGemSourceInfoCache < RubyGemTestCase si = Gem::SourceIndex.new si.add_spec @a1 - @fetcher.data["#{@gem_repo}/Marshal.#{@marshal_version}"] = si.dump + @fetcher.data["#{@gem_repo}Marshal.#{@marshal_version}"] = si.dump Gem::SourceInfoCache.instance_variable_set :@cache, nil sice = Gem::SourceInfoCacheEntry.new si, 0 @@ -106,7 +108,7 @@ class TestGemSourceInfoCache < RubyGemTestCase end def test_cache_data_irreparable - @fetcher.data["#{@gem_repo}/Marshal.#{@marshal_version}"] = @source_index.dump + @fetcher.data["#{@gem_repo}Marshal.#{@marshal_version}"] = @source_index.dump data = { @gem_repo => { 'totally' => 'borked' } } diff --git a/test/rubygems/test_gem_source_info_cache_entry.rb b/test/rubygems/test_gem_source_info_cache_entry.rb index c1194e34bc..6986c9cd7f 100644 --- a/test/rubygems/test_gem_source_info_cache_entry.rb +++ b/test/rubygems/test_gem_source_info_cache_entry.rb @@ -15,9 +15,9 @@ class TestGemSourceInfoCacheEntry < RubyGemTestCase end def test_refresh - @fetcher.data["#{@gem_repo}/Marshal.#{@marshal_version}.Z"] = + @fetcher.data["#{@gem_repo}Marshal.#{@marshal_version}.Z"] = proc { raise } - @fetcher.data["#{@gem_repo}/Marshal.#{@marshal_version}"] = @si.dump + @fetcher.data["#{@gem_repo}Marshal.#{@marshal_version}"] = @si.dump use_ui @ui do @sic_e.refresh @gem_repo, true @@ -30,18 +30,20 @@ class TestGemSourceInfoCacheEntry < RubyGemTestCase a1_name = @a1.full_name a2_name = @a2.full_name - @fetcher.data["#{@gem_repo}/quick/index.rz"] = + @fetcher.data["#{@gem_repo}quick/index.rz"] = util_zip [a1_name, a2_name].join("\n") - @fetcher.data["#{@gem_repo}/quick/latest_index.rz"] = util_zip a2_name - @fetcher.data["#{@gem_repo}/quick/Marshal.#{Gem.marshal_version}/#{a1_name}.gemspec.rz"] = util_zip Marshal.dump(@a1) - @fetcher.data["#{@gem_repo}/quick/Marshal.#{Gem.marshal_version}/#{a2_name}.gemspec.rz"] = util_zip Marshal.dump(@a2) - @fetcher.data["#{@gem_repo}/Marshal.#{Gem.marshal_version}"] = + @fetcher.data["#{@gem_repo}quick/latest_index.rz"] = util_zip a2_name + @fetcher.data["#{@gem_repo}quick/Marshal.#{Gem.marshal_version}/#{a1_name}.gemspec.rz"] = util_zip Marshal.dump(@a1) + @fetcher.data["#{@gem_repo}quick/Marshal.#{Gem.marshal_version}/#{a2_name}.gemspec.rz"] = util_zip Marshal.dump(@a2) + @fetcher.data["#{@gem_repo}Marshal.#{Gem.marshal_version}"] = Marshal.dump @si sic_e = Gem::SourceInfoCacheEntry.new Gem::SourceIndex.new, 0 + assert_equal [], sic_e.source_index.map { |n,| n } + use_ui @ui do - sic_e.refresh @gem_repo, false + assert sic_e.refresh(@gem_repo, false) end assert_equal [a2_name], sic_e.source_index.map { |n,| n }.sort @@ -63,7 +65,7 @@ class TestGemSourceInfoCacheEntry < RubyGemTestCase si = Gem::SourceIndex.new si.add_spec @a1 si.add_spec @b2 - @fetcher.data["#{@gem_repo}/Marshal.#{@marshal_version}"] = si.dump + @fetcher.data["#{@gem_repo}Marshal.#{@marshal_version}"] = si.dump use_ui @ui do @sic_e.refresh @gem_repo, true diff --git a/test/rubygems/test_gem_spec_fetcher.rb b/test/rubygems/test_gem_spec_fetcher.rb new file mode 100644 index 0000000000..7539d0ff4c --- /dev/null +++ b/test/rubygems/test_gem_spec_fetcher.rb @@ -0,0 +1,303 @@ +require 'test/unit' +require File.join(File.expand_path(File.dirname(__FILE__)), 'gemutilities') +require 'rubygems/spec_fetcher' + +class TestGemSpecFetcher < RubyGemTestCase + + def setup + super + + @uri = URI.parse @gem_repo + + util_setup_fake_fetcher + + @source_index.add_spec @pl1 + + @specs = @source_index.gems.sort.map do |name, spec| + [spec.name, spec.version, spec.original_platform] + end.sort + + @fetcher.data["#{@gem_repo}specs.#{Gem.marshal_version}.gz"] = + util_gzip(Marshal.dump(@specs)) + + @latest_specs = @source_index.latest_specs.sort.map do |spec| + [spec.name, spec.version, spec.original_platform] + end + + @fetcher.data["#{@gem_repo}latest_specs.#{Gem.marshal_version}.gz"] = + util_gzip(Marshal.dump(@latest_specs)) + + @sf = Gem::SpecFetcher.new + end + + def test_fetch_all + @fetcher.data["#{@gem_repo}#{Gem::MARSHAL_SPEC_DIR}#{@a1.full_name}.gemspec.rz"] = + util_zip(Marshal.dump(@a1)) + @fetcher.data["#{@gem_repo}#{Gem::MARSHAL_SPEC_DIR}#{@a2.full_name}.gemspec.rz"] = + util_zip(Marshal.dump(@a2)) + + dep = Gem::Dependency.new 'a', 1 + specs_and_sources = @sf.fetch dep, true + + spec_names = specs_and_sources.map do |spec, source_uri| + [spec.full_name, source_uri] + end + + expected = [[@a1.full_name, @gem_repo], [@a2.full_name, @gem_repo]] + + assert_equal expected, spec_names + + assert_same specs_and_sources.first.last, specs_and_sources.last.last + end + + def test_fetch_latest + @fetcher.data["#{@gem_repo}#{Gem::MARSHAL_SPEC_DIR}#{@a1.full_name}.gemspec.rz"] = + util_zip(Marshal.dump(@a1)) + @fetcher.data["#{@gem_repo}#{Gem::MARSHAL_SPEC_DIR}#{@a2.full_name}.gemspec.rz"] = + util_zip(Marshal.dump(@a2)) + + dep = Gem::Dependency.new 'a', 1 + specs_and_sources = @sf.fetch dep + + spec_names = specs_and_sources.map do |spec, source_uri| + [spec.full_name, source_uri] + end + + assert_equal [[@a2.full_name, @gem_repo]], spec_names + end + + def test_fetch_legacy_repo + @fetcher.data["#{@gem_repo}specs.#{Gem.marshal_version}.gz"] = nil + @fetcher.data["#{@gem_repo}yaml"] = '' + util_setup_source_info_cache @a1, @a2 + + dep = Gem::Dependency.new 'a', 1 + specs = nil + + use_ui @ui do + specs = @sf.fetch dep, true + end + + expected = <<-EOF +WARNING: RubyGems 1.2+ index not found for: +\thttp://gems.example.com/ + +RubyGems will revert to legacy indexes degrading performance. + EOF + + assert_equal expected, @ui.error + + specs = specs.map { |spec, source_uri| [spec.full_name, source_uri] } + + expected = [ + [@a1.full_name, @gem_repo], + [@a2.full_name, @gem_repo], + ] + + assert_equal expected, specs + end + + def test_fetch_platform + util_set_arch 'i386-linux' + + @fetcher.data["#{@gem_repo}#{Gem::MARSHAL_SPEC_DIR}#{@pl1.original_name}.gemspec.rz"] = + util_zip(Marshal.dump(@pl1)) + + dep = Gem::Dependency.new 'pl', 1 + specs_and_sources = @sf.fetch dep + + spec_names = specs_and_sources.map do |spec, source_uri| + [spec.full_name, source_uri] + end + + assert_equal [[@pl1.full_name, @gem_repo]], spec_names + end + + def test_fetch_spec + spec_uri = "#{@gem_repo}#{Gem::MARSHAL_SPEC_DIR}#{@a1.full_name}.gemspec" + @fetcher.data["#{spec_uri}.rz"] = util_zip(Marshal.dump(@a1)) + + spec = @sf.fetch_spec ['a', Gem::Version.new(1), 'ruby'], @uri + assert_equal @a1.full_name, spec.full_name + + cache_dir = @sf.cache_dir URI.parse(spec_uri) + + cache_file = File.join cache_dir, "#{@a1.full_name}.gemspec" + + assert File.exist?(cache_file) + end + + def test_fetch_spec_cached + spec_uri = "#{@gem_repo}/#{Gem::MARSHAL_SPEC_DIR}#{@a1.full_name}.gemspec" + @fetcher.data["#{spec_uri}.rz"] = nil + + cache_dir = @sf.cache_dir URI.parse(spec_uri) + FileUtils.mkdir_p cache_dir + + cache_file = File.join cache_dir, "#{@a1.full_name}.gemspec" + + open cache_file, 'wb' do |io| + Marshal.dump @a1, io + end + + spec = @sf.fetch_spec ['a', Gem::Version.new(1), 'ruby'], @uri + assert_equal @a1.full_name, spec.full_name + end + + def test_fetch_spec_platform + @fetcher.data["#{@gem_repo}#{Gem::MARSHAL_SPEC_DIR}#{@pl1.original_name}.gemspec.rz"] = + util_zip(Marshal.dump(@pl1)) + + spec = @sf.fetch_spec ['pl', Gem::Version.new(1), 'i386-linux'], @uri + + assert_equal @pl1.full_name, spec.full_name + end + + def test_fetch_spec_platform_ruby + @fetcher.data["#{@gem_repo}#{Gem::MARSHAL_SPEC_DIR}#{@a1.full_name}.gemspec.rz"] = + util_zip(Marshal.dump(@a1)) + + spec = @sf.fetch_spec ['a', Gem::Version.new(1), nil], @uri + assert_equal @a1.full_name, spec.full_name + + spec = @sf.fetch_spec ['a', Gem::Version.new(1), ''], @uri + assert_equal @a1.full_name, spec.full_name + end + + def test_find_matching_all + dep = Gem::Dependency.new 'a', 1 + specs = @sf.find_matching dep, true + + expected = [ + [['a', Gem::Version.new(1), Gem::Platform::RUBY], @gem_repo], + [['a', Gem::Version.new(2), Gem::Platform::RUBY], @gem_repo], + ] + + assert_equal expected, specs + end + + def test_find_matching_latest + dep = Gem::Dependency.new 'a', 1 + specs = @sf.find_matching dep + + expected = [ + [['a', Gem::Version.new(2), Gem::Platform::RUBY], @gem_repo], + ] + + assert_equal expected, specs + end + + def test_find_matching_platform + util_set_arch 'i386-linux' + + dep = Gem::Dependency.new 'pl', 1 + specs = @sf.find_matching dep + + expected = [ + [['pl', Gem::Version.new(1), 'i386-linux'], @gem_repo], + ] + + assert_equal expected, specs + + util_set_arch 'i386-freebsd6' + + dep = Gem::Dependency.new 'pl', 1 + specs = @sf.find_matching dep + + assert_equal [], specs + end + + def test_find_all_platforms + util_set_arch 'i386-freebsd6' + + dep = Gem::Dependency.new 'pl', 1 + specs = @sf.find_matching dep, false, false + + expected = [ + [['pl', Gem::Version.new(1), 'i386-linux'], @gem_repo], + ] + + assert_equal expected, specs + end + + def test_list + specs = @sf.list + + assert_equal [@uri], specs.keys + assert_equal @latest_specs, specs[@uri].sort + end + + def test_list_all + specs = @sf.list true + + assert_equal [@uri], specs.keys + + assert_equal @specs, specs[@uri].sort + end + + def test_list_cache + specs = @sf.list + + assert !specs[@uri].empty? + + @fetcher.data["#{@gem_repo}/latest_specs.#{Gem.marshal_version}.gz"] = nil + + cached_specs = @sf.list + + assert_equal specs, cached_specs + end + + def test_list_cache_all + specs = @sf.list true + + assert !specs[@uri].empty? + + @fetcher.data["#{@gem_repo}/specs.#{Gem.marshal_version}.gz"] = nil + + cached_specs = @sf.list true + + assert_equal specs, cached_specs + end + + def test_load_specs + specs = @sf.load_specs @uri, 'specs' + + expected = [ + ['a', Gem::Version.new(1), Gem::Platform::RUBY], + ['a', Gem::Version.new(2), Gem::Platform::RUBY], + ['a_evil', Gem::Version.new(9), Gem::Platform::RUBY], + ['c', Gem::Version.new('1.2'), Gem::Platform::RUBY], + ['pl', Gem::Version.new(1), 'i386-linux'], + ] + + assert_equal expected, specs + + cache_dir = File.join Gem.user_home, '.gem', 'specs', 'gems.example.com%80' + assert File.exist?(cache_dir), "#{cache_dir} does not exist" + + cache_file = File.join cache_dir, "specs.#{Gem.marshal_version}" + assert File.exist?(cache_file) + end + + def test_load_specs_cached + @fetcher.data["#{@gem_repo}latest_specs.#{Gem.marshal_version}.gz"] = nil + @fetcher.data["#{@gem_repo}latest_specs.#{Gem.marshal_version}"] = + ' ' * Marshal.dump(@latest_specs).length + + cache_dir = File.join Gem.user_home, '.gem', 'specs', 'gems.example.com:80' + + FileUtils.mkdir_p cache_dir + + cache_file = File.join cache_dir, "latest_specs.#{Gem.marshal_version}" + + open cache_file, 'wb' do |io| + Marshal.dump @latest_specs, io + end + + specs = @sf.load_specs @uri, 'specs' + + assert_equal @specs, specs + end + +end + diff --git a/test/rubygems/test_gem_specification.rb b/test/rubygems/test_gem_specification.rb index 20eb12479f..003ded7bc0 100644 --- a/test/rubygems/test_gem_specification.rb +++ b/test/rubygems/test_gem_specification.rb @@ -213,6 +213,15 @@ end assert_equal 'old_platform', same_spec.original_platform end + def test_add_dependency_with_explicit_type + gem = quick_gem "awesome", "1.0" do |awesome| + awesome.add_development_dependency "monkey" + end + + monkey = gem.dependencies.detect { |d| d.name == "monkey" } + assert_equal(:development, monkey.type) + end + def test_author assert_equal 'A User', @a1.author end @@ -282,6 +291,20 @@ end assert_equal [rake, jabber, pqa], @a1.dependencies end + def test_dependencies_scoped_by_type + gem = quick_gem "awesome", "1.0" do |awesome| + awesome.add_runtime_dependency "bonobo", [] + awesome.add_development_dependency "monkey", [] + end + + bonobo = Gem::Dependency.new("bonobo", []) + monkey = Gem::Dependency.new("monkey", [], :development) + + assert_equal([bonobo, monkey], gem.dependencies) + assert_equal([bonobo], gem.runtime_dependencies) + assert_equal([monkey], gem.development_dependencies) + end + def test_description assert_equal 'This is a test description', @a1.description end @@ -423,6 +446,15 @@ end @a1.full_gem_path end + def test_full_gem_path_double_slash + gemhome = @gemhome.sub(/\w\//, '\&/') + @a1.loaded_from = File.join gemhome, 'specifications', + "#{@a1.full_name}.gemspec" + + assert_equal File.join(@gemhome, 'gems', @a1.full_name), + @a1.full_gem_path + end + def test_full_name assert_equal 'a-1', @a1.full_name @@ -531,6 +563,17 @@ end assert_equal ['A working computer'], @a1.requirements end + def test_runtime_dependencies_legacy + # legacy gems don't have a type + @a1.runtime_dependencies.each do |dep| + dep.instance_variable_set :@type, nil + end + + expected = %w[rake jabber4r pqa] + + assert_equal expected, @a1.runtime_dependencies.map { |d| d.name } + end + def test_spaceship_name s1 = quick_gem 'a', '1' s2 = quick_gem 'b', '1' @@ -570,6 +613,8 @@ end end def test_to_ruby + @a2.add_runtime_dependency 'b', '1' + @a2.dependencies.first.instance_variable_set :@type, nil @a2.required_rubygems_version = Gem::Requirement.new '> 0' ruby_code = @a2.to_ruby @@ -578,8 +623,6 @@ end s.name = %q{a} s.version = \"2\" - s.specification_version = #{Gem::Specification::CURRENT_SPECIFICATION_VERSION} if s.respond_to? :specification_version= - s.required_rubygems_version = Gem::Requirement.new(\"> 0\") if s.respond_to? :required_rubygems_version= s.authors = [\"A User\"] s.date = %q{#{Gem::Specification::TODAY.strftime "%Y-%m-%d"}} @@ -591,6 +634,19 @@ end s.require_paths = [\"lib\"] s.rubygems_version = %q{#{Gem::RubyGemsVersion}} s.summary = %q{this is a summary} + + if s.respond_to? :specification_version then + current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION + s.specification_version = #{Gem::Specification::CURRENT_SPECIFICATION_VERSION} + + if current_version >= 3 then + s.add_runtime_dependency(%q, [\"= 1\"]) + else + s.add_dependency(%q, [\"= 1\"]) + end + else + s.add_dependency(%q, [\"= 1\"]) + end end " @@ -613,8 +669,6 @@ end s.version = \"1\" s.platform = Gem::Platform.new(#{expected_platform}) - s.specification_version = 2 if s.respond_to? :specification_version= - s.required_rubygems_version = Gem::Requirement.new(\">= 0\") if s.respond_to? :required_rubygems_version= s.authors = [\"A User\"] s.date = %q{#{Gem::Specification::TODAY.strftime "%Y-%m-%d"}} @@ -633,9 +687,24 @@ end s.summary = %q{this is a summary} s.test_files = [\"test/suite.rb\"] - s.add_dependency(%q, [\"> 0.4\"]) - s.add_dependency(%q, [\"> 0.0.0\"]) - s.add_dependency(%q, [\"> 0.4\", \"<= 0.6\"]) + if s.respond_to? :specification_version then + current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION + s.specification_version = 3 + + if current_version >= 3 then + s.add_runtime_dependency(%q, [\"> 0.4\"]) + s.add_runtime_dependency(%q, [\"> 0.0.0\"]) + s.add_runtime_dependency(%q, [\"> 0.4\", \"<= 0.6\"]) + else + s.add_dependency(%q, [\"> 0.4\"]) + s.add_dependency(%q, [\"> 0.0.0\"]) + s.add_dependency(%q, [\"> 0.4\", \"<= 0.6\"]) + end + else + s.add_dependency(%q, [\"> 0.4\"]) + s.add_dependency(%q, [\"> 0.0.0\"]) + s.add_dependency(%q, [\"> 0.4\", \"<= 0.6\"]) + end end " diff --git a/test/rubygems/test_gem_uninstaller.rb b/test/rubygems/test_gem_uninstaller.rb index d6e41814ed..aadf0a39c8 100644 --- a/test/rubygems/test_gem_uninstaller.rb +++ b/test/rubygems/test_gem_uninstaller.rb @@ -15,6 +15,12 @@ class TestGemUninstaller < GemInstallerTestCase end end + def test_initialize_expand_path + uninstaller = Gem::Uninstaller.new nil, :install_dir => '/foo//bar' + + assert_match %r|/foo/bar$|, uninstaller.instance_variable_get(:@gem_home) + end + def test_remove_executables_force_keep uninstaller = Gem::Uninstaller.new nil, :executables => false @@ -39,5 +45,20 @@ class TestGemUninstaller < GemInstallerTestCase assert_equal false, File.exist?(File.join(@gemhome, 'bin', 'executable')) end + def test_path_ok_eh + uninstaller = Gem::Uninstaller.new nil + + assert_equal true, uninstaller.path_ok?(@spec) + end + + def test_path_ok_eh_legacy + uninstaller = Gem::Uninstaller.new nil + + @spec.loaded_from.gsub! @spec.full_name, '\&-legacy' + @spec.platform = 'legacy' + + assert_equal true, uninstaller.path_ok?(@spec) + end + end diff --git a/test/rubygems/test_gem_version.rb b/test/rubygems/test_gem_version.rb index 27c522c3d7..8d10700490 100644 --- a/test/rubygems/test_gem_version.rb +++ b/test/rubygems/test_gem_version.rb @@ -71,10 +71,14 @@ class TestGemVersion < RubyGemTestCase end def test_eql_eh - v = Gem::Version.new("1.2") + v1_2 = Gem::Version.new '1.2' + v1_2_0 = Gem::Version.new '1.2.0' + + assert_equal true, v1_2.eql?(@v1_2) + assert_equal true, @v1_2.eql?(v1_2) - assert_equal true, v.eql?(@v1_2) - assert_equal true, @v1_2.eql?(v) + assert_equal false, v1_2_0.eql?(@v1_2) + assert_equal false, @v1_2.eql?(v1_2_0) assert_equal false, @v1_2.eql?(@v1_3) assert_equal false, @v1_3.eql?(@v1_2) @@ -91,8 +95,13 @@ class TestGemVersion < RubyGemTestCase end def test_hash - v = Gem::Version.new("1.2") - assert_equal v.hash, @v1_2.hash + v1_2 = Gem::Version.new "1.2" + v1_2_0 = Gem::Version.new "1.2.0" + + assert_equal v1_2.hash, @v1_2.hash + + assert_not_equal v1_2_0.hash, @v1_2.hash + assert_not_equal @v1_2.hash, @v1_3.hash end diff --git a/test/rubygems/test_kernel.rb b/test/rubygems/test_kernel.rb index 3c6448e470..da31d772eb 100644 --- a/test/rubygems/test_kernel.rb +++ b/test/rubygems/test_kernel.rb @@ -52,7 +52,7 @@ class TestKernel < RubyGemTestCase gem 'a', '= 2' end - assert_match(/activate a \(= 2\)/, ex.message) + assert_match(/activate a \(= 2, runtime\)/, ex.message) assert_match(/activated a-1/, ex.message) assert $:.any? { |p| %r{a-1/lib} =~ p } -- cgit v1.2.3