1
0
Fork 0
mirror of https://github.com/ruby/ruby.git synced 2022-11-09 12:17:21 -05:00

Import RubyGems 1.1.0

git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@15873 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
This commit is contained in:
drbrain 2008-03-31 22:40:06 +00:00
parent dc8359969e
commit 8cc45aae94
82 changed files with 5776 additions and 2928 deletions

View file

@ -1,3 +1,7 @@
Tue Apr 1 07:31:58 2008 Eric Hodel <drbrain@segment7.net>
* lib/rubygems* test/rubygems*: Import RubyGems 1.1.0.
Tue Apr 1 03:20:40 2008 Nobuyoshi Nakada <nobu@ruby-lang.org> Tue Apr 1 03:20:40 2008 Nobuyoshi Nakada <nobu@ruby-lang.org>
* configure.in (RUBY_SETJMP, RUBY_LONGJMP, RUBY_JMP_BUF): prefers * configure.in (RUBY_SETJMP, RUBY_LONGJMP, RUBY_JMP_BUF): prefers

13
bin/gem
View file

@ -7,11 +7,12 @@
require 'rubygems' require 'rubygems'
require 'rubygems/gem_runner' require 'rubygems/gem_runner'
require 'rubygems/exceptions'
required_version = Gem::Requirement.new ">= 1.8.3" required_version = Gem::Requirement.new "> 1.8.3"
unless required_version.satisfied_by? Gem::Version.new(RUBY_VERSION) then unless required_version.satisfied_by? Gem.ruby_version then
abort "Expected Ruby Version #{required_version}, was #{RUBY_VERSION}" abort "Expected Ruby Version #{required_version}, was #{Gem.ruby_version}"
end end
# We need to preserve the original ARGV to use for passing gem options # We need to preserve the original ARGV to use for passing gem options
@ -19,5 +20,9 @@ end
# it...its for the source building process. # it...its for the source building process.
args = !ARGV.include?("--") ? ARGV.clone : ARGV[0...ARGV.index("--")] args = !ARGV.include?("--") ? ARGV.clone : ARGV[0...ARGV.index("--")]
Gem::GemRunner.new.run args begin
Gem::GemRunner.new.run args
rescue Gem::SystemExitException => e
exit e.exit_code
end

View file

@ -1,207 +1,214 @@
# depends on: array.rb dir.rb env.rb file.rb hash.rb module.rb regexp.rb
# empty gem_prelude.rb # empty gem_prelude.rb
# #
# p Gem::Enable # p Gem::Enable
if defined?(Gem::Enable) && Gem::Enable if defined?(Gem::Enable) && Gem::Enable then
#t = Time.now
module Kernel
def gem(gem_name, *version_requirements) module Kernel
Gem.push_gem_version_on_load_path(gem_name, *version_requirements)
end
end def gem(gem_name, *version_requirements)
Gem.push_gem_version_on_load_path(gem_name, *version_requirements)
module Gem
ConfigMap = {
:sitedir => RbConfig::CONFIG["sitedir"],
:ruby_version => RbConfig::CONFIG["ruby_version"],
:libdir => RbConfig::CONFIG["libdir"],
:sitelibdir => RbConfig::CONFIG["sitelibdir"],
:arch => RbConfig::CONFIG["arch"],
:bindir => RbConfig::CONFIG["bindir"],
:EXEEXT => RbConfig::CONFIG["EXEEXT"],
:RUBY_SO_NAME => RbConfig::CONFIG["RUBY_SO_NAME"],
:ruby_install_name => RbConfig::CONFIG["ruby_install_name"]
}
class << self
def default_dir
if defined? RUBY_FRAMEWORK_VERSION
return File.join(File.dirname(ConfigMap[:sitedir]), "Gems")
else
File.join(ConfigMap[:libdir], 'ruby', 'gems', ConfigMap[:ruby_version])
end
end
def dir
@gem_home ||= nil
set_home(ENV['GEM_HOME'] || default_dir) unless @gem_home
@gem_home
end
def path
@gem_path ||= nil
unless @gem_path
paths = [ENV['GEM_PATH']]
paths << APPLE_GEM_HOME if defined? APPLE_GEM_HOME
set_paths(paths.compact.join(File::PATH_SEPARATOR))
end
@gem_path
end
# Set the Gem home directory (as reported by +dir+).
def set_home(home)
@gem_home = home
ensure_gem_subdirectories(@gem_home)
end
def set_paths(gpaths)
if gpaths
@gem_path = gpaths.split(File::PATH_SEPARATOR)
@gem_path << Gem.dir
else
@gem_path = [Gem.dir]
end
@gem_path.uniq!
@gem_path.each do |gp| ensure_gem_subdirectories(gp) end
end
def ensure_gem_subdirectories(path)
end end
end end
module QuickLoader module Gem
ConfigMap = {
:sitedir => RbConfig::CONFIG["sitedir"],
:ruby_version => RbConfig::CONFIG["ruby_version"],
:libdir => RbConfig::CONFIG["libdir"],
:sitelibdir => RbConfig::CONFIG["sitelibdir"],
:arch => RbConfig::CONFIG["arch"],
:bindir => RbConfig::CONFIG["bindir"],
:EXEEXT => RbConfig::CONFIG["EXEEXT"],
:RUBY_SO_NAME => RbConfig::CONFIG["RUBY_SO_NAME"],
:ruby_install_name => RbConfig::CONFIG["ruby_install_name"]
}
class << self class << self
def load_full_rubygems_library
class << Gem def default_dir
Gem.methods(false).each do |method_name| if defined? RUBY_FRAMEWORK_VERSION
undef_method method_name return File.join(File.dirname(ConfigMap[:sitedir]), "Gems")
end else
File.join(ConfigMap[:libdir], 'ruby', 'gems', ConfigMap[:ruby_version])
end end
Kernel.send :undef_method, :gem
$".delete File.join(Gem::ConfigMap[:libdir], 'ruby',
Gem::ConfigMap[:ruby_version], 'rubygems.rb')
require 'rubygems'
end end
def dir
@gem_home ||= nil
set_home(ENV['GEM_HOME'] || default_dir) unless @gem_home
@gem_home
end
def path
@gem_path ||= nil
unless @gem_path
paths = [ENV['GEM_PATH']]
paths << APPLE_GEM_HOME if defined? APPLE_GEM_HOME
set_paths(paths.compact.join(File::PATH_SEPARATOR))
end
@gem_path
end
# Set the Gem home directory (as reported by +dir+).
def set_home(home)
@gem_home = home
ensure_gem_subdirectories(@gem_home)
end
def set_paths(gpaths)
if gpaths
@gem_path = gpaths.split(File::PATH_SEPARATOR)
@gem_path << Gem.dir
else
@gem_path = [Gem.dir]
end
@gem_path.uniq!
@gem_path.each do |gp| ensure_gem_subdirectories(gp) end
end
def ensure_gem_subdirectories(path)
end
end end
GemPaths = {} module QuickLoader
GemVersions = {}
class << self
def push_gem_version_on_load_path(gem_name, *version_requirements) def load_full_rubygems_library
if version_requirements.empty? class << Gem
unless GemPaths.has_key?(gem_name) Gem.methods(false).each do |method_name|
raise LoadError.new("Could not find RubyGem #{gem_name} (>= 0)\n") undef_method method_name
end
end
Kernel.send :undef_method, :gem
$".delete File.join(Gem::ConfigMap[:libdir], 'ruby',
Gem::ConfigMap[:ruby_version], 'rubygems.rb')
require 'rubygems'
end end
# highest version gems already active end
return false
else GemPaths = {}
if version_requirements.length > 1 GemVersions = {}
def push_gem_version_on_load_path(gem_name, *version_requirements)
if version_requirements.empty?
unless GemPaths.has_key?(gem_name)
raise LoadError.new("Could not find RubyGem #{gem_name} (>= 0)\n")
end
# highest version gems already active
return false
else
if version_requirements.length > 1
QuickLoader.load_full_rubygems_library
return gem(gem_name, *version_requirements)
end
requirement, version = version_requirements[0].split
requirement.strip!
if requirement == ">" || requirement == ">="
if (GemVersions[gem_name] <=> Gem.calculate_integers_for_gem_version(version)) >= 0
return false
end
elsif requirement == "~>"
loaded_version = GemVersions[gem_name]
required_version = Gem.calculate_integers_for_gem_version(version)
if loaded_version && (loaded_version[0] == required_version[0])
return false
end
end
QuickLoader.load_full_rubygems_library QuickLoader.load_full_rubygems_library
return gem(gem_name, *version_requirements) gem(gem_name, *version_requirements)
end end
requirement, version = version_requirements[0].split
requirement.strip!
if requirement == ">" || requirement == ">="
if (GemVersions[gem_name] <=> Gem.calculate_integers_for_gem_version(version)) >= 0
return false
end
elsif requirement == "~>"
loaded_version = GemVersions[gem_name]
required_version = Gem.calculate_integers_for_gem_version(version)
if loaded_version && (loaded_version[0] == required_version[0])
return false
end
end
QuickLoader.load_full_rubygems_library
gem(gem_name, *version_requirements)
end end
end
def calculate_integers_for_gem_version(gem_version) def calculate_integers_for_gem_version(gem_version)
numbers = gem_version.split(".").collect {|n| n.to_i} numbers = gem_version.split(".").collect {|n| n.to_i}
numbers.pop while numbers.last == 0 numbers.pop while numbers.last == 0
numbers << 0 if numbers.empty? numbers << 0 if numbers.empty?
numbers numbers
end end
def push_all_highest_version_gems_on_load_path def push_all_highest_version_gems_on_load_path
Gem.path.each do |path| Gem.path.each do |path|
gems_directory = File.join(path, "gems") gems_directory = File.join(path, "gems")
if File.exist?(gems_directory) if File.exist?(gems_directory)
Dir.entries(gems_directory).each do |gem_directory_name| Dir.entries(gems_directory).each do |gem_directory_name|
next if gem_directory_name == "." || gem_directory_name == ".." next if gem_directory_name == "." || gem_directory_name == ".."
dash = gem_directory_name.rindex("-") dash = gem_directory_name.rindex("-")
next if dash.nil? next if dash.nil?
gem_name = gem_directory_name[0...dash] gem_name = gem_directory_name[0...dash]
current_version = GemVersions[gem_name] current_version = GemVersions[gem_name]
new_version = calculate_integers_for_gem_version(gem_directory_name[dash+1..-1]) new_version = calculate_integers_for_gem_version(gem_directory_name[dash+1..-1])
if current_version if current_version
if (current_version <=> new_version) == -1 if (current_version <=> new_version) == -1
GemVersions[gem_name] = new_version
GemPaths[gem_name] = File.join(gems_directory, gem_directory_name)
end
else
GemVersions[gem_name] = new_version GemVersions[gem_name] = new_version
GemPaths[gem_name] = File.join(gems_directory, gem_directory_name) GemPaths[gem_name] = File.join(gems_directory, gem_directory_name)
end end
else
GemVersions[gem_name] = new_version
GemPaths[gem_name] = File.join(gems_directory, gem_directory_name)
end end
end end
end end
end
require_paths = [] require_paths = []
GemPaths.values.each do |path|
if File.exist?(File.join(path, ".require_paths")) GemPaths.values.each do |path|
require_paths.concat(File.read(File.join(path, ".require_paths")).split.map {|require_path| File.join(path, require_path)}) if File.exist?(File.join(path, ".require_paths"))
else require_paths.concat(File.read(File.join(path, ".require_paths")).split.map {|require_path| File.join(path, require_path)})
require_paths << File.join(path, "bin") if File.exist?(File.join(path, "bin")) else
require_paths << File.join(path, "lib") if File.exist?(File.join(path, "lib")) require_paths << File.join(path, "bin") if File.exist?(File.join(path, "bin"))
require_paths << File.join(path, "lib") if File.exist?(File.join(path, "lib"))
end
end end
# "tag" the first require_path inserted into the $LOAD_PATH to enable
# indexing correctly with rubygems proper when it inserts an explicitly
# gem version
unless require_paths.empty?
require_paths.first.instance_variable_set(:@gem_prelude_index, true)
end
# gem directories must come after -I and ENV['RUBYLIB']
$:[$:.index(ConfigMap[:sitelibdir]),0] = require_paths
end end
# "tag" the first require_path inserted into the $LOAD_PATH to enable def const_missing(constant)
# indexing correctly with rubygems proper when it inserts an explicitly QuickLoader.load_full_rubygems_library
# gem version Gem.const_get(constant)
unless require_paths.empty? end
require_paths.first.instance_variable_set(:@gem_prelude_index, true)
def method_missing(method, *args, &block)
QuickLoader.load_full_rubygems_library
super unless Gem.respond_to?(method)
Gem.send(method, *args, &block)
end end
# gem directories must come after -I and ENV['RUBYLIB']
$:[$:.index(ConfigMap[:sitelibdir]),0] = require_paths
end end
def const_missing(constant) extend QuickLoader
QuickLoader.load_full_rubygems_library
Gem.const_get(constant)
end
def method_missing(method, *args, &block)
QuickLoader.load_full_rubygems_library
super unless Gem.respond_to?(method)
Gem.send(method, *args, &block)
end
end end
extend QuickLoader begin
Gem.push_all_highest_version_gems_on_load_path
$" << File.join(Gem::ConfigMap[:libdir], "ruby",
Gem::ConfigMap[:ruby_version], "rubygems.rb")
rescue Exception => e
puts "Error loading gem paths on load path in gem_prelude"
puts e
puts e.backtrace.join("\n")
end
end end
begin
Gem.push_all_highest_version_gems_on_load_path
$" << File.join(Gem::ConfigMap[:libdir], "ruby",
Gem::ConfigMap[:ruby_version], "rubygems.rb")
rescue Exception => e
puts "Error loading gem paths on load path in gem_prelude"
puts e
puts e.backtrace.join("\n")
end
#puts "Gem load in #{Time.now - t} seconds"
end # Gem::Enable

File diff suppressed because it is too large Load diff

View file

@ -65,13 +65,20 @@ EOM
end end
def write_package def write_package
Package.open(@spec.file_name, "w", @signer) do |pkg| open @spec.file_name, 'wb' do |gem_io|
pkg.metadata = @spec.to_yaml Gem::Package.open gem_io, 'w', @signer do |pkg|
@spec.files.each do |file| pkg.metadata = @spec.to_yaml
next if File.directory? file
pkg.add_file_simple(file, File.stat(@spec.file_name).mode & 0777, @spec.files.each do |file|
File.size(file)) do |os| next if File.directory? file
os.write File.open(file, "rb"){|f|f.read}
stat = File.stat file
mode = stat.mode & 0777
size = stat.size
pkg.add_file_simple file, mode, size do |tar_io|
tar_io.write open(file, "rb") { |f| f.read }
end
end end
end end
end end

View file

@ -123,6 +123,7 @@ module Gem
end end
private private
def load_and_instantiate(command_name) def load_and_instantiate(command_name)
command_name = command_name.to_s command_name = command_name.to_s
retried = false retried = false

View file

@ -2,92 +2,90 @@ require 'rubygems/command'
require 'rubygems/source_index' require 'rubygems/source_index'
require 'rubygems/dependency_list' require 'rubygems/dependency_list'
module Gem class Gem::Commands::CleanupCommand < Gem::Command
module Commands
class CleanupCommand < Command def initialize
def initialize super 'cleanup',
super(
'cleanup',
'Clean up old versions of installed gems in the local repository', 'Clean up old versions of installed gems in the local repository',
{ :force => false, :test => false, :install_dir => Gem.dir
:force => false,
:test => false,
:install_dir => Gem.dir
})
add_option('-d', '--dryrun', "") do |value, options|
options[:dryrun] = true
end
end
def arguments # :nodoc: add_option('-d', '--dryrun', "") do |value, options|
"GEMNAME name of gem to cleanup" options[:dryrun] = true
end end
end
def defaults_str # :nodoc: def arguments # :nodoc:
"--no-dryrun" "GEMNAME name of gem to cleanup"
end end
def usage # :nodoc: def defaults_str # :nodoc:
"#{program_name} [GEMNAME ...]" "--no-dryrun"
end end
def execute def usage # :nodoc:
say "Cleaning up installed gems..." "#{program_name} [GEMNAME ...]"
srcindex = Gem::SourceIndex.from_installed_gems end
primary_gems = {}
srcindex.each do |name, spec| def execute
if primary_gems[spec.name].nil? or primary_gems[spec.name].version < spec.version say "Cleaning up installed gems..."
primary_gems[spec.name] = spec primary_gems = {}
end
end
gems_to_cleanup = [] Gem.source_index.each do |name, spec|
if primary_gems[spec.name].nil? or
unless options[:args].empty? then primary_gems[spec.name].version < spec.version then
options[:args].each do |gem_name| primary_gems[spec.name] = spec
specs = Gem.cache.search(/^#{gem_name}$/i)
specs.each do |spec|
gems_to_cleanup << spec
end
end
else
srcindex.each do |name, spec|
gems_to_cleanup << spec
end
end
gems_to_cleanup = gems_to_cleanup.select { |spec|
primary_gems[spec.name].version != spec.version
}
uninstall_command = Gem::CommandManager.instance['uninstall']
deplist = DependencyList.new
gems_to_cleanup.uniq.each do |spec| deplist.add(spec) end
deplist.dependency_order.each do |spec|
if options[:dryrun] then
say "Dry Run Mode: Would uninstall #{spec.full_name}"
else
say "Attempting uninstall on #{spec.full_name}"
options[:args] = [spec.name]
options[:version] = "= #{spec.version}"
options[:executables] = true
uninstall_command.merge_options(options)
begin
uninstall_command.execute
rescue Gem::DependencyRemovalException => ex
say "Unable to uninstall #{spec.full_name} ... continuing with remaining gems"
end
end
end
say "Clean Up Complete"
end end
end end
gems_to_cleanup = []
unless options[:args].empty? then
options[:args].each do |gem_name|
specs = Gem.cache.search(/^#{gem_name}$/i)
specs.each do |spec|
gems_to_cleanup << spec
end
end
else
Gem.source_index.each do |name, spec|
gems_to_cleanup << spec
end
end
gems_to_cleanup = gems_to_cleanup.select { |spec|
primary_gems[spec.name].version != spec.version
}
uninstall_command = Gem::CommandManager.instance['uninstall']
deplist = Gem::DependencyList.new
gems_to_cleanup.uniq.each do |spec| deplist.add spec end
deps = deplist.strongly_connected_components.flatten.reverse
deps.each do |spec|
if options[:dryrun] then
say "Dry Run Mode: Would uninstall #{spec.full_name}"
else
say "Attempting to uninstall #{spec.full_name}"
options[:args] = [spec.name]
options[:version] = "= #{spec.version}"
options[:executables] = false
uninstaller = Gem::Uninstaller.new spec.name, options
begin
uninstaller.uninstall
rescue Gem::DependencyRemovalException,
Gem::GemNotInHomeException => e
say "Unable to uninstall #{spec.full_name}:"
say "\t#{e.class}: #{e.message}"
end
end
end
say "Clean Up Complete"
end end
end end

View file

@ -25,19 +25,18 @@ class Gem::Commands::EnvironmentCommand < Gem::Command
def execute def execute
out = '' out = ''
arg = options[:args][0] arg = options[:args][0]
if begins?("packageversion", arg) then case arg
when /^packageversion/ then
out << Gem::RubyGemsPackageVersion out << Gem::RubyGemsPackageVersion
elsif begins?("version", arg) then when /^version/ then
out << Gem::RubyGemsVersion out << Gem::RubyGemsVersion
elsif begins?("gemdir", arg) then when /^gemdir/, /^gemhome/, /^home/, /^GEM_HOME/ then
out << Gem.dir out << Gem.dir
elsif begins?("gempath", arg) then when /^gempath/, /^path/, /^GEM_PATH/ then
out << Gem.path.join("\n") out << Gem.path.join(File::PATH_SEPARATOR)
elsif begins?("remotesources", arg) then when /^remotesources/ then
out << Gem.sources.join("\n") out << Gem.sources.join("\n")
elsif arg then when nil then
fail Gem::CommandLineError, "Unknown enviroment option [#{arg}]"
else
out = "RubyGems Environment:\n" out = "RubyGems Environment:\n"
out << " - RUBYGEMS VERSION: #{Gem::RubyGemsVersion} (#{Gem::RubyGemsPackageVersion})\n" out << " - RUBYGEMS VERSION: #{Gem::RubyGemsVersion} (#{Gem::RubyGemsPackageVersion})\n"
@ -75,6 +74,9 @@ class Gem::Commands::EnvironmentCommand < Gem::Command
Gem.sources.each do |s| Gem.sources.each do |s|
out << " - #{s}\n" out << " - #{s}\n"
end end
else
fail Gem::CommandLineError, "Unknown enviroment option [#{arg}]"
end end
say out say out
true true

View file

@ -44,17 +44,15 @@ class Gem::Commands::FetchCommand < Gem::Command
spec, source_uri = specs_and_sources.last spec, source_uri = specs_and_sources.last
gem_file = "#{spec.full_name}.gem" if spec.nil? then
alert_error "Could not find #{gem_name} in any repository"
gem_path = File.join source_uri, 'gems', gem_file next
gem = Gem::RemoteFetcher.fetcher.fetch_path gem_path
File.open gem_file, 'wb' do |fp|
fp.write gem
end end
say "Downloaded #{gem_file}" path = Gem::RemoteFetcher.fetcher.download spec, source_uri
FileUtils.mv path, "#{spec.full_name}.gem"
say "Downloaded #{spec.full_name}"
end end
end end

View file

@ -62,13 +62,15 @@ class Gem::Commands::InstallCommand < Gem::Command
:install_dir => options[:install_dir], :install_dir => options[:install_dir],
:security_policy => options[:security_policy], :security_policy => options[:security_policy],
:wrappers => options[:wrappers], :wrappers => options[:wrappers],
:bin_dir => options[:bin_dir]
} }
exit_code = 0
get_all_gem_names.each do |gem_name| get_all_gem_names.each do |gem_name|
begin begin
inst = Gem::DependencyInstaller.new gem_name, options[:version], inst = Gem::DependencyInstaller.new install_options
install_options inst.install gem_name, options[:version]
inst.install
inst.installed_gems.each do |spec| inst.installed_gems.each do |spec|
say "Successfully installed #{spec.full_name}" say "Successfully installed #{spec.full_name}"
@ -77,8 +79,10 @@ class Gem::Commands::InstallCommand < Gem::Command
installed_gems.push(*inst.installed_gems) installed_gems.push(*inst.installed_gems)
rescue Gem::InstallError => e rescue Gem::InstallError => e
alert_error "Error installing #{gem_name}:\n\t#{e.message}" alert_error "Error installing #{gem_name}:\n\t#{e.message}"
exit_code |= 1
rescue Gem::GemNotFoundException => e rescue Gem::GemNotFoundException => e
alert_error e.message alert_error e.message
exit_code |= 2
# rescue => e # rescue => e
# # TODO: Fix this handle to allow the error to propagate to # # TODO: Fix this handle to allow the error to propagate to
# # the top level handler. Examine the other errors as # # the top level handler. Examine the other errors as
@ -121,6 +125,8 @@ class Gem::Commands::InstallCommand < Gem::Command
end end
end end
end end
raise Gem::SystemExitException, exit_code
end end
end end

View file

@ -6,10 +6,8 @@ module Gem
class ListCommand < QueryCommand class ListCommand < QueryCommand
def initialize def initialize
super( super 'list', 'Display gems whose name starts with STRING'
'list',
'Display all gems whose name starts with STRING'
)
remove_option('--name-matches') remove_option('--name-matches')
end end

View file

@ -2,7 +2,7 @@ require 'yaml'
require 'zlib' require 'zlib'
require 'rubygems/command' require 'rubygems/command'
require 'rubygems/gem_open_uri' require 'open-uri'
class Gem::Commands::MirrorCommand < Gem::Command class Gem::Commands::MirrorCommand < Gem::Command

View file

@ -1,15 +1,25 @@
require 'rubygems/command' require 'rubygems/command'
require 'rubygems/local_remote_options' require 'rubygems/local_remote_options'
require 'rubygems/source_info_cache' require 'rubygems/source_info_cache'
require 'rubygems/version_option'
class Gem::Commands::QueryCommand < Gem::Command class Gem::Commands::QueryCommand < Gem::Command
include Gem::LocalRemoteOptions include Gem::LocalRemoteOptions
include Gem::VersionOption
def initialize(name = 'query', def initialize(name = 'query',
summary = 'Query gem information in local or remote repositories') summary = 'Query gem information in local or remote repositories')
super name, summary, super name, summary,
:name => /.*/, :domain => :local, :details => false, :versions => true :name => //, :domain => :local, :details => false, :versions => true,
:installed => false, :version => Gem::Requirement.default
add_option('-i', '--[no-]installed',
'Check for installed gem') do |value, options|
options[:installed] = value
end
add_version_option
add_option('-n', '--name-matches REGEXP', add_option('-n', '--name-matches REGEXP',
'Name of gem(s) to query on matches the', 'Name of gem(s) to query on matches the',
@ -28,33 +38,70 @@ class Gem::Commands::QueryCommand < Gem::Command
options[:details] = false unless value options[:details] = false unless value
end end
add_option('-a', '--all',
'Display all gem versions') do |value, options|
options[:all] = value
end
add_local_remote_options add_local_remote_options
end end
def defaults_str # :nodoc: def defaults_str # :nodoc:
"--local --name-matches '.*' --no-details --versions" "--local --name-matches // --no-details --versions --no-installed"
end end
def execute def execute
exit_code = 0
name = options[:name] name = options[:name]
if options[:installed] then
if name.source.empty? then
alert_error "You must specify a gem name"
exit_code |= 4
elsif installed? name.source, options[:version] then
say "true"
else
say "false"
exit_code |= 1
end
raise Gem::SystemExitException, exit_code
end
if local? then if local? then
say say
say "*** LOCAL GEMS ***" say "*** LOCAL GEMS ***"
say say
output_query_results Gem.cache.search(name)
output_query_results Gem.source_index.search(name)
end end
if remote? then if remote? then
say say
say "*** REMOTE GEMS ***" say "*** REMOTE GEMS ***"
say say
output_query_results Gem::SourceInfoCache.search(name)
begin
Gem::SourceInfoCache.cache.refresh options[:all]
rescue Gem::RemoteFetcher::FetchError
# no network
end
output_query_results Gem::SourceInfoCache.search(name, false, true)
end end
end end
private private
##
# Check if gem +name+ version +version+ is installed.
def installed?(name, version = Gem::Requirement.default)
dep = Gem::Dependency.new name, version
!Gem.source_index.search(dep).empty?
end
def output_query_results(gemspecs) def output_query_results(gemspecs)
output = [] output = []
gem_list_with_version = {} gem_list_with_version = {}
@ -98,7 +145,7 @@ class Gem::Commands::QueryCommand < Gem::Command
## ##
# Used for wrapping and indenting text # Used for wrapping and indenting text
#
def format_text(text, wrap, indent=0) def format_text(text, wrap, indent=0)
result = [] result = []
work = text.dup work = text.dup

View file

@ -39,8 +39,11 @@ class Gem::Commands::SourcesCommand < Gem::Command
options[:list] = !(options[:add] || options[:remove] || options[:clear_all] || options[:update]) options[:list] = !(options[:add] || options[:remove] || options[:clear_all] || options[:update])
if options[:clear_all] then if options[:clear_all] then
remove_cache_file("user", Gem::SourceInfoCache.user_cache_file) sic = Gem::SourceInfoCache
remove_cache_file("system", Gem::SourceInfoCache.system_cache_file) remove_cache_file 'user', sic.user_cache_file
remove_cache_file 'latest user', sic.latest_user_cache_file
remove_cache_file 'system', sic.system_cache_file
remove_cache_file 'latest system', sic.latest_system_cache_file
end end
if options[:add] then if options[:add] then
@ -48,7 +51,7 @@ class Gem::Commands::SourcesCommand < Gem::Command
sice = Gem::SourceInfoCacheEntry.new nil, nil sice = Gem::SourceInfoCacheEntry.new nil, nil
begin begin
sice.refresh source_uri sice.refresh source_uri, true
Gem::SourceInfoCache.cache_data[source_uri] = sice Gem::SourceInfoCache.cache_data[source_uri] = sice
Gem::SourceInfoCache.cache.update Gem::SourceInfoCache.cache.update
@ -66,7 +69,7 @@ class Gem::Commands::SourcesCommand < Gem::Command
end end
if options[:update] then if options[:update] then
Gem::SourceInfoCache.cache.refresh Gem::SourceInfoCache.cache.refresh true
Gem::SourceInfoCache.cache.flush Gem::SourceInfoCache.cache.flush
say "source cache successfully updated" say "source cache successfully updated"
@ -78,6 +81,11 @@ class Gem::Commands::SourcesCommand < Gem::Command
unless Gem.sources.include? source_uri then unless Gem.sources.include? source_uri then
say "source #{source_uri} not present in cache" say "source #{source_uri} not present in cache"
else 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_data.delete source_uri
Gem::SourceInfoCache.cache.update Gem::SourceInfoCache.cache.update
Gem::SourceInfoCache.cache.flush Gem::SourceInfoCache.cache.flush
@ -100,11 +108,12 @@ class Gem::Commands::SourcesCommand < Gem::Command
private private
def remove_cache_file(desc, fn) def remove_cache_file(desc, path)
FileUtils.rm_rf fn rescue nil FileUtils.rm_rf path
if ! File.exist?(fn)
if not File.exist?(path) then
say "*** Removed #{desc} source cache ***" say "*** Removed #{desc} source cache ***"
elsif ! File.writable?(fn) elsif not File.writable?(path) then
say "*** Unable to remove #{desc} source cache (write protected) ***" say "*** Unable to remove #{desc} source cache (write protected) ***"
else else
say "*** Unable to remove #{desc} source cache ***" say "*** Unable to remove #{desc} source cache ***"

View file

@ -3,6 +3,7 @@ require 'rubygems/command'
require 'rubygems/local_remote_options' require 'rubygems/local_remote_options'
require 'rubygems/version_option' require 'rubygems/version_option'
require 'rubygems/source_info_cache' require 'rubygems/source_info_cache'
require 'rubygems/format'
class Gem::Commands::SpecificationCommand < Gem::Command class Gem::Commands::SpecificationCommand < Gem::Command
@ -41,13 +42,16 @@ class Gem::Commands::SpecificationCommand < Gem::Command
gem = get_one_gem_name gem = get_one_gem_name
if local? then if local? then
source_index = Gem::SourceIndex.from_installed_gems if File.exist? gem then
specs.push(*source_index.search(/\A#{gem}\z/, options[:version])) specs << Gem::Format.from_file_by_path(gem).spec rescue nil
end
if specs.empty? then
specs.push(*Gem.source_index.search(/\A#{gem}\z/, options[:version]))
end
end end
if remote? then if remote? then
alert_warning "Remote information is not complete\n\n"
Gem::SourceInfoCache.cache_data.each do |_,sice| Gem::SourceInfoCache.cache_data.each do |_,sice|
specs.push(*sice.source_index.search(gem, options[:version])) specs.push(*sice.source_index.search(gem, options[:version]))
end end

View file

@ -35,6 +35,11 @@ module Gem
options[:install_dir] = File.expand_path(value) options[:install_dir] = File.expand_path(value)
end end
add_option('-n', '--bindir DIR',
'Directory to remove binaries from') do |value, options|
options[:bin_dir] = File.expand_path(value)
end
add_version_option add_version_option
add_platform_option add_platform_option
end end
@ -54,7 +59,13 @@ module Gem
def execute def execute
get_all_gem_names.each do |gem_name| get_all_gem_names.each do |gem_name|
Gem::Uninstaller.new(gem_name, options).uninstall begin
Gem::Uninstaller.new(gem_name, options).uninstall
rescue Gem::GemNotInHomeException => e
spec = e.spec
alert("In order to remove #{spec.name}, please execute:\n" \
"\tgem uninstall #{spec.name} --install-dir=#{spec.installation_path}")
end
end end
end end
end end

View file

@ -38,6 +38,7 @@ class Gem::Commands::UnpackCommand < Gem::Command
def execute def execute
gemname = get_one_gem_name gemname = get_one_gem_name
path = get_path(gemname, options[:version]) path = get_path(gemname, options[:version])
if path then if path then
basename = File.basename(path).sub(/\.gem$/, '') basename = File.basename(path).sub(/\.gem$/, '')
target_dir = File.expand_path File.join(options[:target], basename) target_dir = File.expand_path File.join(options[:target], basename)
@ -66,16 +67,27 @@ class Gem::Commands::UnpackCommand < Gem::Command
# source directories? # source directories?
def get_path(gemname, version_req) def get_path(gemname, version_req)
return gemname if gemname =~ /\.gem$/i return gemname if gemname =~ /\.gem$/i
specs = Gem::SourceIndex.from_installed_gems.search(/\A#{gemname}\z/, version_req)
specs = Gem::source_index.search(/\A#{gemname}\z/, version_req)
selected = specs.sort_by { |s| s.version }.last selected = specs.sort_by { |s| s.version }.last
return nil if selected.nil? return nil if selected.nil?
# We expect to find (basename).gem in the 'cache' directory. # We expect to find (basename).gem in the 'cache' directory.
# Furthermore, the name match must be exact (ignoring case). # Furthermore, the name match must be exact (ignoring case).
if gemname =~ /^#{selected.name}$/i if gemname =~ /^#{selected.name}$/i
filename = selected.full_name + '.gem' filename = selected.full_name + '.gem'
return File.join(Gem.dir, 'cache', filename) path = nil
Gem.path.find do |gem_dir|
path = File.join gem_dir, 'cache', filename
File.exist? path
end
path
else else
return nil nil
end end
end end

View file

@ -1,8 +1,10 @@
require 'rubygems/command' require 'rubygems/command'
require 'rubygems/command_manager'
require 'rubygems/install_update_options' require 'rubygems/install_update_options'
require 'rubygems/local_remote_options' require 'rubygems/local_remote_options'
require 'rubygems/source_info_cache' require 'rubygems/source_info_cache'
require 'rubygems/version_option' require 'rubygems/version_option'
require 'rubygems/commands/install_command'
class Gem::Commands::UpdateCommand < Gem::Command class Gem::Commands::UpdateCommand < Gem::Command
@ -45,7 +47,7 @@ class Gem::Commands::UpdateCommand < Gem::Command
def execute def execute
if options[:system] then if options[:system] then
say "Updating RubyGems..." say "Updating RubyGems"
unless options[:args].empty? then unless options[:args].empty? then
fail "No gem names are allowed with the --system option" fail "No gem names are allowed with the --system option"
@ -53,10 +55,10 @@ class Gem::Commands::UpdateCommand < Gem::Command
options[:args] = ["rubygems-update"] options[:args] = ["rubygems-update"]
else else
say "Updating installed gems..." say "Updating installed gems"
end end
hig = highest_installed_gems = {} hig = {}
Gem::SourceIndex.from_installed_gems.each do |name, spec| Gem::SourceIndex.from_installed_gems.each do |name, spec|
if hig[spec.name].nil? or hig[spec.name].version < spec.version then if hig[spec.name].nil? or hig[spec.name].version < spec.version then
@ -64,25 +66,28 @@ class Gem::Commands::UpdateCommand < Gem::Command
end end
end end
remote_gemspecs = Gem::SourceInfoCache.search(//) pattern = if options[:args].empty? then
//
else
Regexp.union(*options[:args])
end
gems_to_update = if options[:args].empty? then remote_gemspecs = Gem::SourceInfoCache.search pattern
which_to_update(highest_installed_gems, remote_gemspecs)
else
options[:args]
end
options[:domain] = :remote # install from remote source gems_to_update = which_to_update hig, remote_gemspecs
updated = []
# HACK use the real API # HACK use the real API
install_command = Gem::CommandManager.instance['install']
gems_to_update.uniq.sort.each do |name| gems_to_update.uniq.sort.each do |name|
say "Attempting remote update of #{name}" next if updated.any? { |spec| spec.name == name }
options[:args] = [name] say "Updating #{name}"
options[:ignore_dependencies] = true # HACK skip seen gems instead installer = Gem::DependencyInstaller.new options
install_command.merge_options(options) installer.install name
install_command.execute installer.installed_gems.each do |spec|
updated << spec
say "Successfully installed #{spec.full_name}"
end
end end
if gems_to_update.include? "rubygems-update" then if gems_to_update.include? "rubygems-update" then
@ -97,12 +102,10 @@ class Gem::Commands::UpdateCommand < Gem::Command
say "RubyGems system software updated" if installed say "RubyGems system software updated" if installed
else else
updated = gems_to_update.uniq.sort.collect { |g| g.to_s }
if updated.empty? then if updated.empty? then
say "Nothing to update" say "Nothing to update"
else else
say "Gems updated: #{updated.join ', '}" say "Gems updated: #{updated.map { |spec| spec.name }.join ', '}"
end end
end end
end end

View file

@ -28,7 +28,7 @@ module Kernel
rescue LoadError => load_error rescue LoadError => load_error
if load_error.message =~ /\A[Nn]o such file to load -- #{Regexp.escape path}\z/ and if load_error.message =~ /\A[Nn]o such file to load -- #{Regexp.escape path}\z/ and
spec = Gem.searcher.find(path) then spec = Gem.searcher.find(path) then
Gem.activate(spec.name, false, "= #{spec.version}") Gem.activate(spec.name, "= #{spec.version}")
gem_original_require path gem_original_require path
else else
raise load_error raise load_error

View file

@ -11,6 +11,9 @@ module Gem
if defined? RUBY_FRAMEWORK_VERSION then if defined? RUBY_FRAMEWORK_VERSION then
File.join File.dirname(ConfigMap[:sitedir]), 'Gems', File.join File.dirname(ConfigMap[:sitedir]), 'Gems',
ConfigMap[:ruby_version] ConfigMap[:ruby_version]
elsif defined? RUBY_ENGINE then
File.join ConfigMap[:libdir], RUBY_ENGINE, 'gems',
ConfigMap[:ruby_version]
else else
File.join ConfigMap[:libdir], 'ruby', 'gems', ConfigMap[:ruby_version] File.join ConfigMap[:libdir], 'ruby', 'gems', ConfigMap[:ruby_version]
end end
@ -29,7 +32,11 @@ module Gem
# The default directory for binaries # The default directory for binaries
def self.default_bindir def self.default_bindir
Config::CONFIG['bindir'] if defined? RUBY_FRAMEWORK_VERSION then # mac framework support
'/usr/bin'
else # generic install
ConfigMap[:bindir]
end
end end
# The default system-wide source info cache directory. # The default system-wide source info cache directory.

View file

@ -22,8 +22,7 @@ class Gem::DependencyInstaller
} }
## ##
# Creates a new installer instance that will install +gem_name+ using # Creates a new installer instance.
# version requirement +version+ and +options+.
# #
# Options are: # Options are:
# :env_shebang:: See Gem::Installer::new. # :env_shebang:: See Gem::Installer::new.
@ -36,7 +35,7 @@ class Gem::DependencyInstaller
# :install_dir: See Gem::Installer#install. # :install_dir: See Gem::Installer#install.
# :security_policy: See Gem::Installer::new and Gem::Security. # :security_policy: See Gem::Installer::new and Gem::Security.
# :wrappers: See Gem::Installer::new # :wrappers: See Gem::Installer::new
def initialize(gem_name, version = nil, options = {}) def initialize(options = {})
options = DEFAULT_OPTIONS.merge options options = DEFAULT_OPTIONS.merge options
@env_shebang = options[:env_shebang] @env_shebang = options[:env_shebang]
@domain = options[:domain] @domain = options[:domain]
@ -46,49 +45,9 @@ class Gem::DependencyInstaller
@install_dir = options[:install_dir] || Gem.dir @install_dir = options[:install_dir] || Gem.dir
@security_policy = options[:security_policy] @security_policy = options[:security_policy]
@wrappers = options[:wrappers] @wrappers = options[:wrappers]
@bin_dir = options[:bin_dir]
@installed_gems = [] @installed_gems = []
spec_and_source = nil
glob = if File::ALT_SEPARATOR then
gem_name.gsub File::ALT_SEPARATOR, File::SEPARATOR
else
gem_name
end
local_gems = Dir["#{glob}*"].sort.reverse
unless local_gems.empty? then
local_gems.each do |gem_file|
next unless gem_file =~ /gem$/
begin
spec = Gem::Format.from_file_by_path(gem_file).spec
spec_and_source = [spec, gem_file]
break
rescue SystemCallError, Gem::Package::FormatError
end
end
end
if spec_and_source.nil? then
version ||= Gem::Requirement.default
@dep = Gem::Dependency.new gem_name, version
spec_and_sources = find_gems_with_sources(@dep).reverse
spec_and_source = spec_and_sources.find do |spec, source|
Gem::Platform.match spec.platform
end
end
if spec_and_source.nil? then
raise Gem::GemNotFoundException,
"could not find #{gem_name} locally or in a repository"
end
@specs_and_sources = [spec_and_source]
gather_dependencies
end end
## ##
@ -107,73 +66,32 @@ class Gem::DependencyInstaller
end end
if @domain == :both or @domain == :remote then if @domain == :both or @domain == :remote then
gems_and_sources.push(*Gem::SourceInfoCache.search_with_source(dep, true)) begin
requirements = dep.version_requirements.requirements.map do |req, ver|
req
end
all = requirements.length > 1 ||
requirements.first != ">=" || requirements.first != ">"
found = Gem::SourceInfoCache.search_with_source dep, true, all
gems_and_sources.push(*found)
rescue Gem::RemoteFetcher::FetchError => e
if Gem.configuration.really_verbose then
say "Error fetching remote data:\t\t#{e.message}"
say "Falling back to local-only install"
end
@domain = :local
end
end end
gems_and_sources.sort_by do |gem, source| gems_and_sources.sort_by do |gem, source|
[gem, source !~ /^http:\/\// ? 1 : 0] # local gems win [gem, source =~ /^http:\/\// ? 0 : 1] # local gems win
end end
end end
##
# 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)
gem_file_name = "#{spec.full_name}.gem"
local_gem_path = File.join @install_dir, 'cache', gem_file_name
Gem.ensure_gem_subdirectories @install_dir
source_uri = URI.parse source_uri unless URI::Generic === source_uri
scheme = source_uri.scheme
# URI.parse gets confused by MS Windows paths with forward slashes.
scheme = nil if scheme =~ /^[a-z]$/i
case scheme
when 'http' then
unless File.exist? local_gem_path then
begin
say "Downloading gem #{gem_file_name}" if
Gem.configuration.really_verbose
remote_gem_path = source_uri + "gems/#{gem_file_name}"
gem = Gem::RemoteFetcher.fetcher.fetch_path remote_gem_path
rescue Gem::RemoteFetcher::FetchError
raise if spec.original_platform == spec.platform
alternate_name = "#{spec.name}-#{spec.version}-#{spec.original_platform}.gem"
say "Failed, downloading gem #{alternate_name}" if
Gem.configuration.really_verbose
remote_gem_path = source_uri + "gems/#{alternate_name}"
gem = Gem::RemoteFetcher.fetcher.fetch_path remote_gem_path
end
File.open local_gem_path, 'wb' do |fp|
fp.write gem
end
end
when nil, 'file' then # TODO test for local overriding cache
begin
FileUtils.cp source_uri.to_s, local_gem_path
rescue Errno::EACCES
local_gem_path = source_uri.to_s
end
say "Using local gem #{local_gem_path}" if
Gem.configuration.really_verbose
else
raise Gem::InstallError, "unsupported URI scheme #{source_uri.scheme}"
end
local_gem_path
end
## ##
# Gathers all dependencies necessary for the installation from local and # Gathers all dependencies necessary for the installation from local and
# remote sources unless the ignore_dependencies was given. # remote sources unless the ignore_dependencies was given.
@ -208,9 +126,57 @@ class Gem::DependencyInstaller
@gems_to_install = dependency_list.dependency_order.reverse @gems_to_install = dependency_list.dependency_order.reverse
end end
def find_spec_by_name_and_version gem_name, version = Gem::Requirement.default
spec_and_source = nil
glob = if File::ALT_SEPARATOR then
gem_name.gsub File::ALT_SEPARATOR, File::SEPARATOR
else
gem_name
end
local_gems = Dir["#{glob}*"].sort.reverse
unless local_gems.empty? then
local_gems.each do |gem_file|
next unless gem_file =~ /gem$/
begin
spec = Gem::Format.from_file_by_path(gem_file).spec
spec_and_source = [spec, gem_file]
break
rescue SystemCallError, Gem::Package::FormatError
end
end
end
if spec_and_source.nil? then
dep = Gem::Dependency.new gem_name, version
spec_and_sources = find_gems_with_sources(dep).reverse
spec_and_source = spec_and_sources.find { |spec, source|
Gem::Platform.match spec.platform
}
end
if spec_and_source.nil? then
raise Gem::GemNotFoundException,
"could not find #{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.
def install 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
else
@specs_and_sources = [find_gems_with_sources(dep_or_name).last]
end
gather_dependencies
spec_dir = File.join @install_dir, 'specifications' spec_dir = File.join @install_dir, 'specifications'
source_index = Gem::SourceIndex.from_gems_in spec_dir source_index = Gem::SourceIndex.from_gems_in spec_dir
@ -219,10 +185,11 @@ class Gem::DependencyInstaller
# HACK is this test for full_name acceptable? # 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 say "Installing gem #{spec.full_name}" if Gem.configuration.really_verbose
_, source_uri = @specs_and_sources.assoc spec _, source_uri = @specs_and_sources.assoc spec
local_gem_path = download spec, source_uri local_gem_path = Gem::RemoteFetcher.fetcher.download spec, source_uri
inst = Gem::Installer.new local_gem_path, inst = Gem::Installer.new local_gem_path,
:env_shebang => @env_shebang, :env_shebang => @env_shebang,
@ -231,7 +198,8 @@ class Gem::DependencyInstaller
:ignore_dependencies => @ignore_dependencies, :ignore_dependencies => @ignore_dependencies,
:install_dir => @install_dir, :install_dir => @install_dir,
:security_policy => @security_policy, :security_policy => @security_policy,
:wrappers => @wrappers :wrappers => @wrappers,
:bin_dir => @bin_dir
spec = inst.install spec = inst.install

View file

@ -13,7 +13,10 @@ class Gem::DependencyRemovalException < Gem::Exception; end
## ##
# Raised when attempting to uninstall a gem that isn't in GEM_HOME. # Raised when attempting to uninstall a gem that isn't in GEM_HOME.
class Gem::GemNotInHomeException < Gem::Exception; end
class Gem::GemNotInHomeException < Gem::Exception
attr_accessor :spec
end
class Gem::DocumentError < Gem::Exception; end class Gem::DocumentError < Gem::Exception; end
@ -65,3 +68,17 @@ class Gem::RemoteSourceException < Gem::Exception; end
class Gem::VerificationError < Gem::Exception; end class Gem::VerificationError < Gem::Exception; end
##
# Raised to indicate that a system exit should occur with the specified
# exit_code
class Gem::SystemExitException < SystemExit
attr_accessor :exit_code
def initialize(exit_code)
@exit_code = exit_code
super "Exiting RubyGems with exit_code #{exit_code}"
end
end

View file

@ -43,15 +43,12 @@ module Gem
# check for old version gem # check for old version gem
if File.read(file_path, 20).include?("MD5SUM =") if File.read(file_path, 20).include?("MD5SUM =")
#alert_warning "Gem #{file_path} is in old format."
require 'rubygems/old_format' require 'rubygems/old_format'
format = OldFormat.from_file_by_path(file_path) format = OldFormat.from_file_by_path(file_path)
else else
begin open file_path, Gem.binary_mode do |io|
f = File.open(file_path, 'rb') format = from_io io, file_path, security_policy
format = from_io(f, file_path, security_policy)
ensure
f.close unless f.closed?
end end
end end
@ -65,15 +62,24 @@ module Gem
# io:: [IO] Stream from which to read the gem # io:: [IO] Stream from which to read the gem
# #
def self.from_io(io, gem_path="(io)", security_policy = nil) def self.from_io(io, gem_path="(io)", security_policy = nil)
format = self.new(gem_path) format = new gem_path
Package.open_from_io(io, 'r', security_policy) do |pkg|
Package.open io, 'r', security_policy do |pkg|
format.spec = pkg.metadata format.spec = pkg.metadata
format.file_entries = [] format.file_entries = []
pkg.each do |entry| pkg.each do |entry|
format.file_entries << [{"size" => entry.size, "mode" => entry.mode, size = entry.header.size
"path" => entry.full_name}, entry.read] mode = entry.header.mode
format.file_entries << [{
"size" => size, "mode" => mode, "path" => entry.full_name,
},
entry.read
]
end end
end end
format format
end end

View file

@ -11,6 +11,7 @@ end
## ##
# Top level class for building the gem repository index. # Top level class for building the gem repository index.
class Gem::Indexer class Gem::Indexer
include Gem::UserInteraction include Gem::UserInteraction
@ -25,7 +26,9 @@ class Gem::Indexer
attr_reader :directory attr_reader :directory
##
# Create an indexer that will index the gems in +directory+. # Create an indexer that will index the gems in +directory+.
def initialize(directory) def initialize(directory)
unless ''.respond_to? :to_xs then unless ''.respond_to? :to_xs then
fail "Gem::Indexer requires that the XML Builder library be installed:" \ fail "Gem::Indexer requires that the XML Builder library be installed:" \
@ -39,52 +42,60 @@ class Gem::Indexer
@master_index = Gem::Indexer::MasterIndexBuilder.new "yaml", @directory @master_index = Gem::Indexer::MasterIndexBuilder.new "yaml", @directory
@marshal_index = Gem::Indexer::MarshalIndexBuilder.new marshal_name, @directory @marshal_index = Gem::Indexer::MarshalIndexBuilder.new marshal_name, @directory
@quick_index = Gem::Indexer::QuickIndexBuilder.new "index", @directory @quick_index = Gem::Indexer::QuickIndexBuilder.new 'index', @directory
quick_dir = File.join @directory, 'quick'
@latest_index = Gem::Indexer::LatestIndexBuilder.new 'latest_index', quick_dir
end end
##
# Build the index. # Build the index.
def build_index def build_index
@master_index.build do @master_index.build do
@quick_index.build do @quick_index.build do
@marshal_index.build do @marshal_index.build do
progress = ui.progress_reporter gem_file_list.size, @latest_index.build do
progress = ui.progress_reporter gem_file_list.size,
"Generating index for #{gem_file_list.size} gems in #{@dest_directory}", "Generating index for #{gem_file_list.size} gems in #{@dest_directory}",
"Loaded all gems" "Loaded all gems"
gem_file_list.each do |gemfile| gem_file_list.each do |gemfile|
if File.size(gemfile.to_s) == 0 then if File.size(gemfile.to_s) == 0 then
alert_warning "Skipping zero-length gem: #{gemfile}" 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 next
end end
abbreviate spec begin
sanitize spec spec = Gem::Format.from_file_by_path(gemfile).spec
@master_index.add spec unless gemfile =~ /\/#{Regexp.escape spec.original_name}.*\.gem\z/i then
@quick_index.add spec alert_warning "Skipping misnamed gem: #{gemfile} => #{spec.full_name} (#{spec.original_name})"
@marshal_index.add spec next
end
progress.updated spec.original_name abbreviate spec
sanitize spec
rescue SignalException => e @master_index.add spec
alert_error "Recieved signal, exiting" @quick_index.add spec
raise @marshal_index.add spec
rescue Exception => e @latest_index.add spec
alert_error "Unable to process #{gemfile}\n#{e.message} (#{e.class})\n\t#{e.backtrace.join "\n\t"}"
end progress.updated spec.original_name
rescue SignalException => e
alert_error "Recieved 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 end
progress.done
say "Generating master indexes (this may take a while)"
end end
end end
end end
@ -95,14 +106,15 @@ class Gem::Indexer
say "Moving index into production dir #{@dest_directory}" if verbose say "Moving index into production dir #{@dest_directory}" if verbose
files = @master_index.files + @quick_index.files + @marshal_index.files files = @master_index.files + @quick_index.files + @marshal_index.files +
@latest_index.files
files.each do |file| files.each do |file|
relative_name = file[/\A#{Regexp.escape @directory}.(.*)/, 1] src_name = File.join @directory, file
dest_name = File.join @dest_directory, relative_name dst_name = File.join @dest_directory, file
FileUtils.rm_rf dest_name, :verbose => verbose FileUtils.rm_rf dst_name, :verbose => verbose
FileUtils.mv file, @dest_directory, :verbose => verbose FileUtils.mv src_name, @dest_directory, :verbose => verbose
end end
end end
@ -160,4 +172,5 @@ require 'rubygems/indexer/abstract_index_builder'
require 'rubygems/indexer/master_index_builder' require 'rubygems/indexer/master_index_builder'
require 'rubygems/indexer/quick_index_builder' require 'rubygems/indexer/quick_index_builder'
require 'rubygems/indexer/marshal_index_builder' require 'rubygems/indexer/marshal_index_builder'
require 'rubygems/indexer/latest_index_builder'

View file

@ -22,16 +22,18 @@ class Gem::Indexer::AbstractIndexBuilder
@files = [] @files = []
end end
##
# Build a Gem index. Yields to block to handle the details of the # Build a Gem index. Yields to block to handle the details of the
# actual building. Calls +begin_index+, +end_index+ and +cleanup+ at # actual building. Calls +begin_index+, +end_index+ and +cleanup+ at
# appropriate times to customize basic operations. # appropriate times to customize basic operations.
def build def build
FileUtils.mkdir_p @directory unless File.exist? @directory FileUtils.mkdir_p @directory unless File.exist? @directory
raise "not a directory: #{@directory}" unless File.directory? @directory raise "not a directory: #{@directory}" unless File.directory? @directory
file_path = File.join @directory, @filename file_path = File.join @directory, @filename
@files << file_path @files << @filename
File.open file_path, "wb" do |file| File.open file_path, "wb" do |file|
@file = file @file = file
@ -39,14 +41,20 @@ class Gem::Indexer::AbstractIndexBuilder
yield yield
end_index end_index
end end
cleanup cleanup
ensure ensure
@file = nil @file = nil
end end
##
# Compress the given file. # Compress the given file.
def compress(filename, ext="rz") def compress(filename, ext="rz")
zipped = zip(File.open(filename, 'rb'){ |fp| fp.read }) data = open filename, 'rb' do |fp| fp.read end
zipped = zip data
File.open "#{filename}.#{ext}", "wb" do |file| File.open "#{filename}.#{ext}", "wb" do |file|
file.write zipped file.write zipped
end end

View file

@ -0,0 +1,35 @@
require 'rubygems/indexer'
##
# Construct the latest Gem index file.
class Gem::Indexer::LatestIndexBuilder < Gem::Indexer::AbstractIndexBuilder
def start_index
super
@index = Gem::SourceIndex.new
end
def end_index
super
latest = @index.latest_specs.sort.map { |spec| spec.original_name }
@file.write latest.join("\n")
end
def cleanup
super
compress @file.path
@files.delete 'latest_index' # HACK installed via QuickIndexBuilder :/
end
def add(spec)
@index.add_spec(spec)
end
end

View file

@ -1,6 +1,8 @@
require 'rubygems/indexer' require 'rubygems/indexer'
##
# Construct the master Gem index file. # Construct the master Gem index file.
class Gem::Indexer::MasterIndexBuilder < Gem::Indexer::AbstractIndexBuilder class Gem::Indexer::MasterIndexBuilder < Gem::Indexer::AbstractIndexBuilder
def start_index def start_index
@ -10,6 +12,7 @@ class Gem::Indexer::MasterIndexBuilder < Gem::Indexer::AbstractIndexBuilder
def end_index def end_index
super super
@file.puts "--- !ruby/object:#{@index.class}" @file.puts "--- !ruby/object:#{@index.class}"
@file.puts "gems:" @file.puts "gems:"
@ -28,11 +31,9 @@ class Gem::Indexer::MasterIndexBuilder < Gem::Indexer::AbstractIndexBuilder
index_file_name = File.join @directory, @filename index_file_name = File.join @directory, @filename
compress index_file_name, "Z" compress index_file_name, "Z"
compressed_file_name = "#{index_file_name}.Z" paranoid index_file_name, "#{index_file_name}.Z"
paranoid index_file_name, compressed_file_name @files << "#{@filename}.Z"
@files << compressed_file_name
end end
def add(spec) def add(spec)
@ -41,12 +42,12 @@ class Gem::Indexer::MasterIndexBuilder < Gem::Indexer::AbstractIndexBuilder
private private
def paranoid(fn, compressed_fn) def paranoid(path, compressed_path)
data = File.open(fn, 'rb') do |fp| fp.read end data = Gem.read_binary path
compressed_data = File.open(compressed_fn, 'rb') do |fp| fp.read end compressed_data = Gem.read_binary compressed_path
if data != unzip(compressed_data) then if data != unzip(compressed_data) then
fail "Compressed file #{compressed_fn} does not match uncompressed file #{fn}" raise "Compressed file #{compressed_path} does not match uncompressed file #{path}"
end end
end end

View file

@ -1,7 +1,9 @@
require 'rubygems/indexer' require 'rubygems/indexer'
##
# Construct a quick index file and all of the individual specs to support # Construct a quick index file and all of the individual specs to support
# incremental loading. # incremental loading.
class Gem::Indexer::QuickIndexBuilder < Gem::Indexer::AbstractIndexBuilder class Gem::Indexer::QuickIndexBuilder < Gem::Indexer::AbstractIndexBuilder
def initialize(filename, directory) def initialize(filename, directory)
@ -13,12 +15,12 @@ class Gem::Indexer::QuickIndexBuilder < Gem::Indexer::AbstractIndexBuilder
def cleanup def cleanup
super super
quick_index_file = File.join(@directory, @filename) quick_index_file = File.join @directory, @filename
compress quick_index_file compress quick_index_file
# the complete quick index is in a directory, so move it as a whole # the complete quick index is in a directory, so move it as a whole
@files.delete quick_index_file @files.delete 'index'
@files << @directory @files << 'quick'
end end
def add(spec) def add(spec)

View file

@ -25,6 +25,12 @@ module Gem::InstallUpdateOptions
options[:install_dir] = File.expand_path(value) options[:install_dir] = File.expand_path(value)
end end
add_option(:"Install/Update", '-n', '--bindir DIR',
'Directory where binary files are',
'located') do |value, options|
options[:bin_dir] = File.expand_path(value)
end
add_option(:"Install/Update", '-d', '--[no-]rdoc', add_option(:"Install/Update", '-d', '--[no-]rdoc',
'Generate RDoc documentation for the gem on', 'Generate RDoc documentation for the gem on',
'install') do |value, options| 'install') do |value, options|

View file

@ -63,7 +63,8 @@ class Gem::Installer
:force => false, :force => false,
:install_dir => Gem.dir, :install_dir => Gem.dir,
:exec_format => false, :exec_format => false,
:env_shebang => false :env_shebang => false,
:bin_dir => nil
}.merge options }.merge options
@env_shebang = options[:env_shebang] @env_shebang = options[:env_shebang]
@ -74,6 +75,7 @@ class Gem::Installer
@format_executable = options[:format_executable] @format_executable = options[:format_executable]
@security_policy = options[:security_policy] @security_policy = options[:security_policy]
@wrappers = options[:wrappers] @wrappers = options[:wrappers]
@bin_dir = options[:bin_dir]
begin begin
@format = Gem::Format.from_file_by_path @gem, @security_policy @format = Gem::Format.from_file_by_path @gem, @security_policy
@ -104,7 +106,7 @@ class Gem::Installer
unless @force then unless @force then
if rrv = @spec.required_ruby_version then if rrv = @spec.required_ruby_version then
unless rrv.satisfied_by? Gem::Version.new(RUBY_VERSION) then unless rrv.satisfied_by? Gem.ruby_version then
raise Gem::InstallError, "#{@spec.name} requires Ruby version #{rrv}" raise Gem::InstallError, "#{@spec.name} requires Ruby version #{rrv}"
end end
end end
@ -225,7 +227,7 @@ class Gem::Installer
# If the user has asked for the gem to be installed in a directory that is # 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 # the system gem directory, then use the system bin directory, else create
# (or use) a new bin dir under the gem_home. # (or use) a new bin dir under the gem_home.
bindir = Gem.bindir @gem_home bindir = @bin_dir ? @bin_dir : (Gem.bindir @gem_home)
Dir.mkdir bindir unless File.exist? bindir Dir.mkdir bindir unless File.exist? bindir
raise Gem::FilePermissionError.new(bindir) unless File.writable? bindir raise Gem::FilePermissionError.new(bindir) unless File.writable? bindir
@ -303,7 +305,7 @@ class Gem::Installer
# necessary. # necessary.
def shebang(bin_file_name) def shebang(bin_file_name)
if @env_shebang then if @env_shebang then
"#!/usr/bin/env ruby" "#!/usr/bin/env " + Gem::ConfigMap[:ruby_install_name]
else else
path = File.join @gem_dir, @spec.bindir, bin_file_name path = File.join @gem_dir, @spec.bindir, bin_file_name
@ -352,10 +354,10 @@ TEXT
<<-TEXT <<-TEXT
@ECHO OFF @ECHO OFF
IF NOT "%~f0" == "~f0" GOTO :WinNT IF NOT "%~f0" == "~f0" GOTO :WinNT
@"#{Gem.ruby}" "#{File.join(bindir, bin_file_name)}" %1 %2 %3 %4 %5 %6 %7 %8 %9 @"#{File.basename(Gem.ruby)}" "#{File.join(bindir, bin_file_name)}" %1 %2 %3 %4 %5 %6 %7 %8 %9
GOTO :EOF GOTO :EOF
:WinNT :WinNT
"%~dp0ruby.exe" "%~dpn0" %* @"#{File.basename(Gem.ruby)}" "%~dpn0" %*
TEXT TEXT
end end

View file

@ -45,768 +45,15 @@ module Gem::Package
class TooLongFileName < Error; end class TooLongFileName < Error; end
class FormatError < Error; end class FormatError < Error; end
module FSyncDir def self.open(io, mode = "r", signer = nil, &block)
private tar_type = case mode
def fsync_dir(dirname) when 'r' then TarInput
# make sure this hits the disc when 'w' then TarOutput
begin else
dir = open(dirname, "r") raise "Unknown Package open mode"
dir.fsync end
rescue # ignore IOError if it's an unpatched (old) Ruby
ensure tar_type.open(io, signer, &block)
dir.close if dir rescue nil
end
end
end
class TarHeader
FIELDS = [:name, :mode, :uid, :gid, :size, :mtime, :checksum, :typeflag,
:linkname, :magic, :version, :uname, :gname, :devmajor,
:devminor, :prefix]
FIELDS.each {|x| attr_reader x}
def self.new_from_stream(stream)
data = stream.read(512)
fields = data.unpack("A100" + # record name
"A8A8A8" + # mode, uid, gid
"A12A12" + # size, mtime
"A8A" + # checksum, typeflag
"A100" + # linkname
"A6A2" + # magic, version
"A32" + # uname
"A32" + # gname
"A8A8" + # devmajor, devminor
"A155") # prefix
name = fields.shift
mode = fields.shift.oct
uid = fields.shift.oct
gid = fields.shift.oct
size = fields.shift.oct
mtime = fields.shift.oct
checksum = fields.shift.oct
typeflag = fields.shift
linkname = fields.shift
magic = fields.shift
version = fields.shift.oct
uname = fields.shift
gname = fields.shift
devmajor = fields.shift.oct
devminor = fields.shift.oct
prefix = fields.shift
empty = (data == "\0" * 512)
new(:name=>name, :mode=>mode, :uid=>uid, :gid=>gid, :size=>size,
:mtime=>mtime, :checksum=>checksum, :typeflag=>typeflag,
:magic=>magic, :version=>version, :uname=>uname, :gname=>gname,
:devmajor=>devmajor, :devminor=>devminor, :prefix=>prefix,
:empty => empty )
end
def initialize(vals)
unless vals[:name] && vals[:size] && vals[:prefix] && vals[:mode]
raise ArgumentError, ":name, :size, :prefix and :mode required"
end
vals[:uid] ||= 0
vals[:gid] ||= 0
vals[:mtime] ||= 0
vals[:checksum] ||= ""
vals[:typeflag] ||= "0"
vals[:magic] ||= "ustar"
vals[:version] ||= "00"
vals[:uname] ||= "wheel"
vals[:gname] ||= "wheel"
vals[:devmajor] ||= 0
vals[:devminor] ||= 0
FIELDS.each {|x| instance_variable_set "@#{x.to_s}", vals[x]}
@empty = vals[:empty]
end
def empty?
@empty
end
def to_s
update_checksum
header(checksum)
end
def update_checksum
h = header(" " * 8)
@checksum = oct(calculate_checksum(h), 6)
end
private
def oct(num, len)
"%0#{len}o" % num
end
def calculate_checksum(hdr)
#hdr.split('').map { |c| c[0] }.inject { |a, b| a + b } # HACK rubinius
hdr.unpack("C*").inject{|a,b| a+b}
end
def header(chksum)
# struct tarfile_entry_posix {
# char name[100]; # ASCII + (Z unless filled)
# char mode[8]; # 0 padded, octal, null
# char uid[8]; # ditto
# char gid[8]; # ditto
# char size[12]; # 0 padded, octal, null
# char mtime[12]; # 0 padded, octal, null
# char checksum[8]; # 0 padded, octal, null, space
# char typeflag[1]; # file: "0" dir: "5"
# char linkname[100]; # ASCII + (Z unless filled)
# char magic[6]; # "ustar\0"
# char version[2]; # "00"
# char uname[32]; # ASCIIZ
# char gname[32]; # ASCIIZ
# char devmajor[8]; # 0 padded, octal, null
# char devminor[8]; # o padded, octal, null
# char prefix[155]; # ASCII + (Z unless filled)
# };
arr = [name, oct(mode, 7), oct(uid, 7), oct(gid, 7), oct(size, 11),
oct(mtime, 11), chksum, " ", typeflag, linkname, magic, version,
uname, gname, oct(devmajor, 7), oct(devminor, 7), prefix]
str = arr.pack("a100a8a8a8a12a12" + # name, mode, uid, gid, size, mtime
"a7aaa100a6a2" + # chksum, typeflag, linkname, magic, version
"a32a32a8a8a155") # uname, gname, devmajor, devminor, prefix
str + "\0" * ((512 - str.size) % 512)
end
end
class TarWriter
class FileOverflow < StandardError; end
class BlockNeeded < StandardError; end
class BoundedStream
attr_reader :limit, :written
def initialize(io, limit)
@io = io
@limit = limit
@written = 0
end
def write(data)
if data.size + @written > @limit
raise FileOverflow,
"You tried to feed more data than fits in the file."
end
@io.write data
@written += data.size
data.size
end
end
class RestrictedStream
def initialize(anIO)
@io = anIO
end
def write(data)
@io.write data
end
end
def self.new(anIO)
writer = super(anIO)
return writer unless block_given?
begin
yield writer
ensure
writer.close
end
nil
end
def initialize(anIO)
@io = anIO
@closed = false
end
def add_file_simple(name, mode, size)
raise BlockNeeded unless block_given?
raise ClosedIO if @closed
name, prefix = split_name(name)
header = TarHeader.new(:name => name, :mode => mode,
:size => size, :prefix => prefix).to_s
@io.write header
os = BoundedStream.new(@io, size)
yield os
#FIXME: what if an exception is raised in the block?
min_padding = size - os.written
@io.write("\0" * min_padding)
remainder = (512 - (size % 512)) % 512
@io.write("\0" * remainder)
end
def add_file(name, mode)
raise BlockNeeded unless block_given?
raise ClosedIO if @closed
raise NonSeekableIO unless @io.respond_to? :pos=
name, prefix = split_name(name)
init_pos = @io.pos
@io.write "\0" * 512 # placeholder for the header
yield RestrictedStream.new(@io)
#FIXME: what if an exception is raised in the block?
#FIXME: what if an exception is raised in the block?
size = @io.pos - init_pos - 512
remainder = (512 - (size % 512)) % 512
@io.write("\0" * remainder)
final_pos = @io.pos
@io.pos = init_pos
header = TarHeader.new(:name => name, :mode => mode,
:size => size, :prefix => prefix).to_s
@io.write header
@io.pos = final_pos
end
def mkdir(name, mode)
raise ClosedIO if @closed
name, prefix = split_name(name)
header = TarHeader.new(:name => name, :mode => mode, :typeflag => "5",
:size => 0, :prefix => prefix).to_s
@io.write header
nil
end
def flush
raise ClosedIO if @closed
@io.flush if @io.respond_to? :flush
end
def close
#raise ClosedIO if @closed
return if @closed
@io.write "\0" * 1024
@closed = true
end
private
def split_name name
raise TooLongFileName if name.size > 256
if name.size <= 100
prefix = ""
else
parts = name.split(/\//)
newname = parts.pop
nxt = ""
loop do
nxt = parts.pop
break if newname.size + 1 + nxt.size > 100
newname = nxt + "/" + newname
end
prefix = (parts + [nxt]).join "/"
name = newname
raise TooLongFileName if name.size > 100 || prefix.size > 155
end
return name, prefix
end
end
class TarReader
include Gem::Package
class UnexpectedEOF < StandardError; end
module InvalidEntry
def read(len=nil); raise ClosedIO; end
def getc; raise ClosedIO; end
def rewind; raise ClosedIO; end
end
class Entry
TarHeader::FIELDS.each{|x| attr_reader x}
def initialize(header, anIO)
@io = anIO
@name = header.name
@mode = header.mode
@uid = header.uid
@gid = header.gid
@size = header.size
@mtime = header.mtime
@checksum = header.checksum
@typeflag = header.typeflag
@linkname = header.linkname
@magic = header.magic
@version = header.version
@uname = header.uname
@gname = header.gname
@devmajor = header.devmajor
@devminor = header.devminor
@prefix = header.prefix
@read = 0
@orig_pos = @io.pos
end
def read(len = nil)
return nil if @read >= @size
len ||= @size - @read
max_read = [len, @size - @read].min
ret = @io.read(max_read)
@read += ret.size
ret
end
def getc
return nil if @read >= @size
ret = @io.getc
@read += 1 if ret
ret
end
def is_directory?
@typeflag == "5"
end
def is_file?
@typeflag == "0"
end
def eof?
@read >= @size
end
def pos
@read
end
def rewind
raise NonSeekableIO unless @io.respond_to? :pos=
@io.pos = @orig_pos
@read = 0
end
alias_method :is_directory, :is_directory?
alias_method :is_file, :is_file?
def bytes_read
@read
end
def full_name
if @prefix != ""
File.join(@prefix, @name)
else
@name
end
end
def close
invalidate
end
private
def invalidate
extend InvalidEntry
end
end
def self.new(anIO)
reader = super(anIO)
return reader unless block_given?
begin
yield reader
ensure
reader.close
end
nil
end
def initialize(anIO)
@io = anIO
@init_pos = anIO.pos
end
def each(&block)
each_entry(&block)
end
# do not call this during a #each or #each_entry iteration
def rewind
if @init_pos == 0
raise NonSeekableIO unless @io.respond_to? :rewind
@io.rewind
else
raise NonSeekableIO unless @io.respond_to? :pos=
@io.pos = @init_pos
end
end
def each_entry
loop do
return if @io.eof?
header = TarHeader.new_from_stream(@io)
return if header.empty?
entry = Entry.new header, @io
size = entry.size
yield entry
skip = (512 - (size % 512)) % 512
if @io.respond_to? :seek
# avoid reading...
@io.seek(size - entry.bytes_read, IO::SEEK_CUR)
else
pending = size - entry.bytes_read
while pending > 0
bread = @io.read([pending, 4096].min).size
raise UnexpectedEOF if @io.eof?
pending -= bread
end
end
@io.read(skip) # discard trailing zeros
# make sure nobody can use #read, #getc or #rewind anymore
entry.close
end
end
def close
end
end
class TarInput
include FSyncDir
include Enumerable
attr_reader :metadata
class << self; private :new end
def initialize(io, security_policy = nil)
@io = io
@tarreader = TarReader.new(@io)
has_meta = false
data_sig, meta_sig, data_dgst, meta_dgst = nil, nil, nil, nil
dgst_algo = security_policy ? Gem::Security::OPT[:dgst_algo] : nil
@tarreader.each do |entry|
case entry.full_name
when "metadata"
@metadata = load_gemspec(entry.read)
has_meta = true
break
when "metadata.gz"
begin
# if we have a security_policy, then pre-read the metadata file
# and calculate it's digest
sio = nil
if security_policy
Gem.ensure_ssl_available
sio = StringIO.new(entry.read)
meta_dgst = dgst_algo.digest(sio.string)
sio.rewind
end
gzis = Zlib::GzipReader.new(sio || entry)
# YAML wants an instance of IO
@metadata = load_gemspec(gzis)
has_meta = true
ensure
gzis.close unless gzis.nil?
end
when 'metadata.gz.sig'
meta_sig = entry.read
when 'data.tar.gz.sig'
data_sig = entry.read
when 'data.tar.gz'
if security_policy
Gem.ensure_ssl_available
data_dgst = dgst_algo.digest(entry.read)
end
end
end
if security_policy then
Gem.ensure_ssl_available
# map trust policy from string to actual class (or a serialized YAML
# file, if that exists)
if String === security_policy then
if Gem::Security::Policy.key? security_policy then
# load one of the pre-defined security policies
security_policy = Gem::Security::Policy[security_policy]
elsif File.exist? security_policy then
# FIXME: this doesn't work yet
security_policy = YAML.load File.read(security_policy)
else
raise Gem::Exception, "Unknown trust policy '#{security_policy}'"
end
end
if data_sig && data_dgst && meta_sig && meta_dgst then
# the user has a trust policy, and we have a signed gem
# file, so use the trust policy to verify the gem signature
begin
security_policy.verify_gem(data_sig, data_dgst, @metadata.cert_chain)
rescue Exception => e
raise "Couldn't verify data signature: #{e}"
end
begin
security_policy.verify_gem(meta_sig, meta_dgst, @metadata.cert_chain)
rescue Exception => e
raise "Couldn't verify metadata signature: #{e}"
end
elsif security_policy.only_signed
raise Gem::Exception, "Unsigned gem"
else
# FIXME: should display warning here (trust policy, but
# either unsigned or badly signed gem file)
end
end
@tarreader.rewind
@fileops = Gem::FileOperations.new
raise FormatError, "No metadata found!" unless has_meta
end
# Attempt to YAML-load a gemspec from the given _io_ parameter. Return
# nil if it fails.
def load_gemspec(io)
Gem::Specification.from_yaml(io)
rescue Gem::Exception
nil
end
def self.open(filename, security_policy = nil, &block)
open_from_io(File.open(filename, "rb"), security_policy, &block)
end
def self.open_from_io(io, security_policy = nil, &block)
raise "Want a block" unless block_given?
begin
is = new(io, security_policy)
yield is
ensure
is.close if is
end
end
def each(&block)
@tarreader.each do |entry|
next unless entry.full_name == "data.tar.gz"
is = zipped_stream(entry)
begin
TarReader.new(is) do |inner|
inner.each(&block)
end
ensure
is.close if is
end
end
@tarreader.rewind
end
# Return an IO stream for the zipped entry.
#
# NOTE: Originally this method used two approaches, Return a GZipReader
# directly, or read the GZipReader into a string and return a StringIO on
# the string. The string IO approach was used for versions of ZLib before
# 1.2.1 to avoid buffer errors on windows machines. Then we found that
# errors happened with 1.2.1 as well, so we changed the condition. Then
# we discovered errors occurred with versions as late as 1.2.3. At this
# point (after some benchmarking to show we weren't seriously crippling
# the unpacking speed) we threw our hands in the air and declared that
# this method would use the String IO approach on all platforms at all
# times. And that's the way it is.
def zipped_stream(entry)
if defined? Rubinius then
zis = Zlib::GzipReader.new entry
dis = zis.read
is = StringIO.new(dis)
else
# This is Jamis Buck's ZLib workaround for some unknown issue
entry.read(10) # skip the gzip header
zis = Zlib::Inflate.new(-Zlib::MAX_WBITS)
is = StringIO.new(zis.inflate(entry.read))
end
ensure
zis.finish if zis
end
def extract_entry(destdir, entry, expected_md5sum = nil)
if entry.is_directory?
dest = File.join(destdir, entry.full_name)
if file_class.dir? dest
@fileops.chmod entry.mode, dest, :verbose=>false
else
@fileops.mkdir_p(dest, :mode => entry.mode, :verbose=>false)
end
fsync_dir dest
fsync_dir File.join(dest, "..")
return
end
# it's a file
md5 = Digest::MD5.new if expected_md5sum
destdir = File.join(destdir, File.dirname(entry.full_name))
@fileops.mkdir_p(destdir, :mode => 0755, :verbose=>false)
destfile = File.join(destdir, File.basename(entry.full_name))
@fileops.chmod(0600, destfile, :verbose=>false) rescue nil # Errno::ENOENT
file_class.open(destfile, "wb", entry.mode) do |os|
loop do
data = entry.read(4096)
break unless data
md5 << data if expected_md5sum
os.write(data)
end
os.fsync
end
@fileops.chmod(entry.mode, destfile, :verbose=>false)
fsync_dir File.dirname(destfile)
fsync_dir File.join(File.dirname(destfile), "..")
if expected_md5sum && expected_md5sum != md5.hexdigest
raise BadCheckSum
end
end
def close
@io.close
@tarreader.close
end
private
def file_class
File
end
end
class TarOutput
class << self; private :new end
def initialize(io)
@io = io
@external = TarWriter.new @io
end
def external_handle
@external
end
def self.open(filename, signer = nil, &block)
io = File.open(filename, "wb")
open_from_io(io, signer, &block)
nil
end
def self.open_from_io(io, signer = nil, &block)
outputter = new(io)
metadata = nil
set_meta = lambda{|x| metadata = x}
raise "Want a block" unless block_given?
begin
data_sig, meta_sig = nil, nil
outputter.external_handle.add_file("data.tar.gz", 0644) do |inner|
begin
sio = signer ? StringIO.new : nil
os = Zlib::GzipWriter.new(sio || inner)
TarWriter.new(os) do |inner_tar_stream|
klass = class << inner_tar_stream; self end
klass.send(:define_method, :metadata=, &set_meta)
block.call inner_tar_stream
end
ensure
os.flush
os.finish
#os.close
# if we have a signing key, then sign the data
# digest and return the signature
data_sig = nil
if signer
dgst_algo = Gem::Security::OPT[:dgst_algo]
dig = dgst_algo.digest(sio.string)
data_sig = signer.sign(dig)
inner.write(sio.string)
end
end
end
# if we have a data signature, then write it to the gem too
if data_sig
sig_file = 'data.tar.gz.sig'
outputter.external_handle.add_file(sig_file, 0644) do |os|
os.write(data_sig)
end
end
outputter.external_handle.add_file("metadata.gz", 0644) do |os|
begin
sio = signer ? StringIO.new : nil
gzos = Zlib::GzipWriter.new(sio || os)
gzos.write metadata
ensure
gzos.flush
gzos.finish
# if we have a signing key, then sign the metadata
# digest and return the signature
if signer
dgst_algo = Gem::Security::OPT[:dgst_algo]
dig = dgst_algo.digest(sio.string)
meta_sig = signer.sign(dig)
os.write(sio.string)
end
end
end
# if we have a metadata signature, then write to the gem as
# well
if meta_sig
sig_file = 'metadata.gz.sig'
outputter.external_handle.add_file(sig_file, 0644) do |os|
os.write(meta_sig)
end
end
ensure
outputter.close
end
nil
end
def close
@external.close
@io.close
end
end
#FIXME: refactor the following 2 methods
def self.open(dest, mode = "r", signer = nil, &block)
raise "Block needed" unless block_given?
case mode
when "r"
security_policy = signer
TarInput.open(dest, security_policy, &block)
when "w"
TarOutput.open(dest, signer, &block)
else
raise "Unknown Package open mode"
end
end
def self.open_from_io(io, mode = "r", signer = nil, &block)
raise "Block needed" unless block_given?
case mode
when "r"
security_policy = signer
TarInput.open_from_io(io, security_policy, &block)
when "w"
TarOutput.open_from_io(io, signer, &block)
else
raise "Unknown Package open mode"
end
end end
def self.pack(src, destname, signer = nil) def self.pack(src, destname, signer = nil)
@ -836,19 +83,13 @@ module Gem::Package
end end
end end
class << self
def file_class
File
end
def dir_class
Dir
end
def find_class # HACK kill me
Find
end
end
end end
require 'rubygems/package/f_sync_dir'
require 'rubygems/package/tar_header'
require 'rubygems/package/tar_input'
require 'rubygems/package/tar_output'
require 'rubygems/package/tar_reader'
require 'rubygems/package/tar_reader/entry'
require 'rubygems/package/tar_writer'

View file

@ -0,0 +1,24 @@
#++
# Copyright (C) 2004 Mauricio Julio Fernández Pradier
# See LICENSE.txt for additional licensing information.
#--
require 'rubygems/package'
module Gem::Package::FSyncDir
private
##
# make sure this hits the disc
def fsync_dir(dirname)
dir = open dirname, 'r'
dir.fsync
rescue # ignore IOError if it's an unpatched (old) Ruby
ensure
dir.close if dir rescue nil
end
end

View file

@ -0,0 +1,245 @@
#++
# Copyright (C) 2004 Mauricio Julio Fernández Pradier
# See LICENSE.txt for additional licensing information.
#--
require 'rubygems/package'
##
#--
# struct tarfile_entry_posix {
# char name[100]; # ASCII + (Z unless filled)
# char mode[8]; # 0 padded, octal, null
# char uid[8]; # ditto
# char gid[8]; # ditto
# char size[12]; # 0 padded, octal, null
# char mtime[12]; # 0 padded, octal, null
# char checksum[8]; # 0 padded, octal, null, space
# char typeflag[1]; # file: "0" dir: "5"
# char linkname[100]; # ASCII + (Z unless filled)
# char magic[6]; # "ustar\0"
# char version[2]; # "00"
# char uname[32]; # ASCIIZ
# char gname[32]; # ASCIIZ
# char devmajor[8]; # 0 padded, octal, null
# char devminor[8]; # o padded, octal, null
# char prefix[155]; # ASCII + (Z unless filled)
# };
#++
class Gem::Package::TarHeader
FIELDS = [
:checksum,
:devmajor,
:devminor,
:gid,
:gname,
:linkname,
:magic,
:mode,
:mtime,
:name,
:prefix,
:size,
:typeflag,
:uid,
:uname,
:version,
]
PACK_FORMAT = 'a100' + # name
'a8' + # mode
'a8' + # uid
'a8' + # gid
'a12' + # size
'a12' + # mtime
'a7a' + # chksum
'a' + # typeflag
'a100' + # linkname
'a6' + # magic
'a2' + # version
'a32' + # uname
'a32' + # gname
'a8' + # devmajor
'a8' + # devminor
'a155' # prefix
UNPACK_FORMAT = 'A100' + # name
'A8' + # mode
'A8' + # uid
'A8' + # gid
'A12' + # size
'A12' + # mtime
'A8' + # checksum
'A' + # typeflag
'A100' + # linkname
'A6' + # magic
'A2' + # version
'A32' + # uname
'A32' + # gname
'A8' + # devmajor
'A8' + # devminor
'A155' # prefix
attr_reader(*FIELDS)
def self.from(stream)
header = stream.read 512
empty = (header == "\0" * 512)
fields = header.unpack UNPACK_FORMAT
name = fields.shift
mode = fields.shift.oct
uid = fields.shift.oct
gid = fields.shift.oct
size = fields.shift.oct
mtime = fields.shift.oct
checksum = fields.shift.oct
typeflag = fields.shift
linkname = fields.shift
magic = fields.shift
version = fields.shift.oct
uname = fields.shift
gname = fields.shift
devmajor = fields.shift.oct
devminor = fields.shift.oct
prefix = fields.shift
new :name => name,
:mode => mode,
:uid => uid,
:gid => gid,
:size => size,
:mtime => mtime,
:checksum => checksum,
:typeflag => typeflag,
:linkname => linkname,
:magic => magic,
:version => version,
:uname => uname,
:gname => gname,
:devmajor => devmajor,
:devminor => devminor,
:prefix => prefix,
:empty => empty
# HACK unfactor for Rubinius
#new :name => fields.shift,
# :mode => fields.shift.oct,
# :uid => fields.shift.oct,
# :gid => fields.shift.oct,
# :size => fields.shift.oct,
# :mtime => fields.shift.oct,
# :checksum => fields.shift.oct,
# :typeflag => fields.shift,
# :linkname => fields.shift,
# :magic => fields.shift,
# :version => fields.shift.oct,
# :uname => fields.shift,
# :gname => fields.shift,
# :devmajor => fields.shift.oct,
# :devminor => fields.shift.oct,
# :prefix => fields.shift,
# :empty => empty
end
def initialize(vals)
unless vals[:name] && vals[:size] && vals[:prefix] && vals[:mode] then
raise ArgumentError, ":name, :size, :prefix and :mode required"
end
vals[:uid] ||= 0
vals[:gid] ||= 0
vals[:mtime] ||= 0
vals[:checksum] ||= ""
vals[:typeflag] ||= "0"
vals[:magic] ||= "ustar"
vals[:version] ||= "00"
vals[:uname] ||= "wheel"
vals[:gname] ||= "wheel"
vals[:devmajor] ||= 0
vals[:devminor] ||= 0
FIELDS.each do |name|
instance_variable_set "@#{name}", vals[name]
end
@empty = vals[:empty]
end
def empty?
@empty
end
def ==(other)
self.class === other and
@checksum == other.checksum and
@devmajor == other.devmajor and
@devminor == other.devminor and
@gid == other.gid and
@gname == other.gname and
@linkname == other.linkname and
@magic == other.magic and
@mode == other.mode and
@mtime == other.mtime and
@name == other.name and
@prefix == other.prefix and
@size == other.size and
@typeflag == other.typeflag and
@uid == other.uid and
@uname == other.uname and
@version == other.version
end
def to_s
update_checksum
header
end
def update_checksum
header = header " " * 8
@checksum = oct calculate_checksum(header), 6
end
private
def calculate_checksum(header)
header.unpack("C*").inject { |a, b| a + b }
end
def header(checksum = @checksum)
header = [
name,
oct(mode, 7),
oct(uid, 7),
oct(gid, 7),
oct(size, 11),
oct(mtime, 11),
checksum,
" ",
typeflag,
linkname,
magic,
oct(version, 2),
uname,
gname,
oct(devmajor, 7),
oct(devminor, 7),
prefix
]
header = header.pack PACK_FORMAT
header << ("\0" * ((512 - header.size) % 512))
end
def oct(num, len)
"%0#{len}o" % num
end
end

View file

@ -0,0 +1,219 @@
#++
# Copyright (C) 2004 Mauricio Julio Fernández Pradier
# See LICENSE.txt for additional licensing information.
#--
require 'rubygems/package'
class Gem::Package::TarInput
include Gem::Package::FSyncDir
include Enumerable
attr_reader :metadata
private_class_method :new
def self.open(io, security_policy = nil, &block)
is = new io, security_policy
yield is
ensure
is.close if is
end
def initialize(io, security_policy = nil)
@io = io
@tarreader = Gem::Package::TarReader.new @io
has_meta = false
data_sig, meta_sig, data_dgst, meta_dgst = nil, nil, nil, nil
dgst_algo = security_policy ? Gem::Security::OPT[:dgst_algo] : nil
@tarreader.each do |entry|
case entry.full_name
when "metadata"
@metadata = load_gemspec entry.read
has_meta = true
when "metadata.gz"
begin
# if we have a security_policy, then pre-read the metadata file
# and calculate it's digest
sio = nil
if security_policy
Gem.ensure_ssl_available
sio = StringIO.new(entry.read)
meta_dgst = dgst_algo.digest(sio.string)
sio.rewind
end
gzis = Zlib::GzipReader.new(sio || entry)
# YAML wants an instance of IO
@metadata = load_gemspec(gzis)
has_meta = true
ensure
gzis.close unless gzis.nil?
end
when 'metadata.gz.sig'
meta_sig = entry.read
when 'data.tar.gz.sig'
data_sig = entry.read
when 'data.tar.gz'
if security_policy
Gem.ensure_ssl_available
data_dgst = dgst_algo.digest(entry.read)
end
end
end
if security_policy then
Gem.ensure_ssl_available
# map trust policy from string to actual class (or a serialized YAML
# file, if that exists)
if String === security_policy then
if Gem::Security::Policy.key? security_policy then
# load one of the pre-defined security policies
security_policy = Gem::Security::Policy[security_policy]
elsif File.exist? security_policy then
# FIXME: this doesn't work yet
security_policy = YAML.load File.read(security_policy)
else
raise Gem::Exception, "Unknown trust policy '#{security_policy}'"
end
end
if data_sig && data_dgst && meta_sig && meta_dgst then
# the user has a trust policy, and we have a signed gem
# file, so use the trust policy to verify the gem signature
begin
security_policy.verify_gem(data_sig, data_dgst, @metadata.cert_chain)
rescue Exception => e
raise "Couldn't verify data signature: #{e}"
end
begin
security_policy.verify_gem(meta_sig, meta_dgst, @metadata.cert_chain)
rescue Exception => e
raise "Couldn't verify metadata signature: #{e}"
end
elsif security_policy.only_signed
raise Gem::Exception, "Unsigned gem"
else
# FIXME: should display warning here (trust policy, but
# either unsigned or badly signed gem file)
end
end
@tarreader.rewind
@fileops = Gem::FileOperations.new
raise Gem::Package::FormatError, "No metadata found!" unless has_meta
end
def close
@io.close
@tarreader.close
end
def each(&block)
@tarreader.each do |entry|
next unless entry.full_name == "data.tar.gz"
is = zipped_stream entry
begin
Gem::Package::TarReader.new is do |inner|
inner.each(&block)
end
ensure
is.close if is
end
end
@tarreader.rewind
end
def extract_entry(destdir, entry, expected_md5sum = nil)
if entry.directory? then
dest = File.join(destdir, entry.full_name)
if File.dir? dest then
@fileops.chmod entry.header.mode, dest, :verbose=>false
else
@fileops.mkdir_p dest, :mode => entry.header.mode, :verbose => false
end
fsync_dir dest
fsync_dir File.join(dest, "..")
return
end
# it's a file
md5 = Digest::MD5.new if expected_md5sum
destdir = File.join destdir, File.dirname(entry.full_name)
@fileops.mkdir_p destdir, :mode => 0755, :verbose => false
destfile = File.join destdir, File.basename(entry.full_name)
@fileops.chmod 0600, destfile, :verbose => false rescue nil # Errno::ENOENT
open destfile, "wb", entry.header.mode do |os|
loop do
data = entry.read 4096
break unless data
# HACK shouldn't we check the MD5 before writing to disk?
md5 << data if expected_md5sum
os.write(data)
end
os.fsync
end
@fileops.chmod entry.header.mode, destfile, :verbose => false
fsync_dir File.dirname(destfile)
fsync_dir File.join(File.dirname(destfile), "..")
if expected_md5sum && expected_md5sum != md5.hexdigest then
raise Gem::Package::BadCheckSum
end
end
# Attempt to YAML-load a gemspec from the given _io_ parameter. Return
# nil if it fails.
def load_gemspec(io)
Gem::Specification.from_yaml io
rescue Gem::Exception
nil
end
##
# Return an IO stream for the zipped entry.
#
# NOTE: Originally this method used two approaches, Return a GZipReader
# directly, or read the GZipReader into a string and return a StringIO on
# the string. The string IO approach was used for versions of ZLib before
# 1.2.1 to avoid buffer errors on windows machines. Then we found that
# errors happened with 1.2.1 as well, so we changed the condition. Then
# we discovered errors occurred with versions as late as 1.2.3. At this
# point (after some benchmarking to show we weren't seriously crippling
# the unpacking speed) we threw our hands in the air and declared that
# this method would use the String IO approach on all platforms at all
# times. And that's the way it is.
def zipped_stream(entry)
if defined? Rubinius then
zis = Zlib::GzipReader.new entry
dis = zis.read
is = StringIO.new(dis)
else
# This is Jamis Buck's Zlib workaround for some unknown issue
entry.read(10) # skip the gzip header
zis = Zlib::Inflate.new(-Zlib::MAX_WBITS)
is = StringIO.new(zis.inflate(entry.read))
end
ensure
zis.finish if zis
end
end

View file

@ -0,0 +1,143 @@
#++
# Copyright (C) 2004 Mauricio Julio Fernández Pradier
# See LICENSE.txt for additional licensing information.
#--
require 'rubygems/package'
##
# TarOutput is a wrapper to TarWriter that builds gem-format tar file.
#
# Gem-format tar files contain the following files:
# [data.tar.gz] A gzipped tar file containing the files that compose the gem
# which will be extracted into the gem/ dir on installation.
# [metadata.gz] A YAML format Gem::Specification.
# [data.tar.gz.sig] A signature for the gem's data.tar.gz.
# [metadata.gz.sig] A signature for the gem's metadata.gz.
#
# See TarOutput::open for usage details.
class Gem::Package::TarOutput
##
# Creates a new TarOutput which will yield a TarWriter object for the
# data.tar.gz portion of a gem-format tar file.
#
# See #initialize for details on +io+ and +signer+.
#
# See #add_gem_contents for details on adding metadata to the tar file.
def self.open(io, signer = nil, &block) # :yield: data_tar_writer
tar_outputter = new io, signer
tar_outputter.add_gem_contents(&block)
tar_outputter.add_metadata
tar_outputter.add_signatures
ensure
tar_outputter.close
end
##
# Creates a new TarOutput that will write a gem-format tar file to +io+. If
# +signer+ is given, the data.tar.gz and metadata.gz will be signed and
# the signatures will be added to the tar file.
def initialize(io, signer)
@io = io
@signer = signer
@tar_writer = Gem::Package::TarWriter.new @io
@metadata = nil
@data_signature = nil
@meta_signature = nil
end
##
# Yields a TarWriter for the data.tar.gz inside a gem-format tar file.
# The yielded TarWriter has been extended with a #metadata= method for
# attaching a YAML format Gem::Specification which will be written by
# add_metadata.
def add_gem_contents
@tar_writer.add_file "data.tar.gz", 0644 do |inner|
sio = @signer ? StringIO.new : nil
Zlib::GzipWriter.wrap(sio || inner) do |os|
Gem::Package::TarWriter.new os do |data_tar_writer|
def data_tar_writer.metadata() @metadata end
def data_tar_writer.metadata=(metadata) @metadata = metadata end
yield data_tar_writer
@metadata = data_tar_writer.metadata
end
end
# if we have a signing key, then sign the data
# digest and return the signature
if @signer then
digest = Gem::Security::OPT[:dgst_algo].digest sio.string
@data_signature = @signer.sign digest
inner.write sio.string
end
end
self
end
##
# Adds metadata.gz to the gem-format tar file which was saved from a
# previous #add_gem_contents call.
def add_metadata
return if @metadata.nil?
@tar_writer.add_file "metadata.gz", 0644 do |io|
begin
sio = @signer ? StringIO.new : nil
gzos = Zlib::GzipWriter.new(sio || io)
gzos.write @metadata
ensure
gzos.flush
gzos.finish
# if we have a signing key, then sign the metadata digest and return
# the signature
if @signer then
digest = Gem::Security::OPT[:dgst_algo].digest sio.string
@meta_signature = @signer.sign digest
io.write sio.string
end
end
end
end
##
# Adds data.tar.gz.sig and metadata.gz.sig to the gem-format tar files if
# a Gem::Security::Signer was sent to initialize.
def add_signatures
if @data_signature then
@tar_writer.add_file 'data.tar.gz.sig', 0644 do |io|
io.write @data_signature
end
end
if @meta_signature then
@tar_writer.add_file 'metadata.gz.sig', 0644 do |io|
io.write @meta_signature
end
end
end
##
# Closes the TarOutput.
def close
@tar_writer.close
end
end

View file

@ -0,0 +1,86 @@
#++
# Copyright (C) 2004 Mauricio Julio Fernández Pradier
# See LICENSE.txt for additional licensing information.
#--
require 'rubygems/package'
class Gem::Package::TarReader
include Gem::Package
class UnexpectedEOF < StandardError; end
def self.new(io)
reader = super
return reader unless block_given?
begin
yield reader
ensure
reader.close
end
nil
end
def initialize(io)
@io = io
@init_pos = io.pos
end
def close
end
def each
loop do
return if @io.eof?
header = Gem::Package::TarHeader.from @io
return if header.empty?
entry = Gem::Package::TarReader::Entry.new header, @io
size = entry.header.size
yield entry
skip = (512 - (size % 512)) % 512
if @io.respond_to? :seek then
# avoid reading...
@io.seek(size - entry.bytes_read, IO::SEEK_CUR)
else
pending = size - entry.bytes_read
while pending > 0 do
bread = @io.read([pending, 4096].min).size
raise UnexpectedEOF if @io.eof?
pending -= bread
end
end
@io.read skip # discard trailing zeros
# make sure nobody can use #read, #getc or #rewind anymore
entry.close
end
end
alias each_entry each
##
# NOTE: Do not call #rewind during #each
def rewind
if @init_pos == 0 then
raise Gem::Package::NonSeekableIO unless @io.respond_to? :rewind
@io.rewind
else
raise Gem::Package::NonSeekableIO unless @io.respond_to? :pos=
@io.pos = @init_pos
end
end
end

View file

@ -0,0 +1,99 @@
#++
# Copyright (C) 2004 Mauricio Julio Fernández Pradier
# See LICENSE.txt for additional licensing information.
#--
require 'rubygems/package'
class Gem::Package::TarReader::Entry
attr_reader :header
def initialize(header, io)
@closed = false
@header = header
@io = io
@orig_pos = @io.pos
@read = 0
end
def check_closed # :nodoc:
raise IOError, "closed #{self.class}" if closed?
end
def bytes_read
@read
end
def close
@closed = true
end
def closed?
@closed
end
def eof?
check_closed
@read >= @header.size
end
def full_name
if @header.prefix != "" then
File.join @header.prefix, @header.name
else
@header.name
end
end
def getc
check_closed
return nil if @read >= @header.size
ret = @io.getc
@read += 1 if ret
ret
end
def directory?
@header.typeflag == "5"
end
def file?
@header.typeflag == "0"
end
def pos
check_closed
bytes_read
end
def read(len = nil)
check_closed
return nil if @read >= @header.size
len ||= @header.size - @read
max_read = [len, @header.size - @read].min
ret = @io.read max_read
@read += ret.size
ret
end
def rewind
check_closed
raise Gem::Package::NonSeekableIO unless @io.respond_to? :pos=
@io.pos = @orig_pos
@read = 0
end
end

View file

@ -0,0 +1,180 @@
#++
# Copyright (C) 2004 Mauricio Julio Fernández Pradier
# See LICENSE.txt for additional licensing information.
#--
require 'rubygems/package'
class Gem::Package::TarWriter
class FileOverflow < StandardError; end
class BoundedStream
attr_reader :limit, :written
def initialize(io, limit)
@io = io
@limit = limit
@written = 0
end
def write(data)
if data.size + @written > @limit
raise FileOverflow, "You tried to feed more data than fits in the file."
end
@io.write data
@written += data.size
data.size
end
end
class RestrictedStream
def initialize(io)
@io = io
end
def write(data)
@io.write data
end
end
def self.new(io)
writer = super
return writer unless block_given?
begin
yield writer
ensure
writer.close
end
nil
end
def initialize(io)
@io = io
@closed = false
end
def add_file(name, mode)
check_closed
raise Gem::Package::NonSeekableIO unless @io.respond_to? :pos=
name, prefix = split_name name
init_pos = @io.pos
@io.write "\0" * 512 # placeholder for the header
yield RestrictedStream.new(@io) if block_given?
size = @io.pos - init_pos - 512
remainder = (512 - (size % 512)) % 512
@io.write "\0" * remainder
final_pos = @io.pos
@io.pos = init_pos
header = Gem::Package::TarHeader.new :name => name, :mode => mode,
:size => size, :prefix => prefix
@io.write header
@io.pos = final_pos
self
end
def add_file_simple(name, mode, size)
check_closed
name, prefix = split_name name
header = Gem::Package::TarHeader.new(:name => name, :mode => mode,
:size => size, :prefix => prefix).to_s
@io.write header
os = BoundedStream.new @io, size
yield os if block_given?
min_padding = size - os.written
@io.write("\0" * min_padding)
remainder = (512 - (size % 512)) % 512
@io.write("\0" * remainder)
self
end
def check_closed
raise IOError, "closed #{self.class}" if closed?
end
def close
check_closed
@io.write "\0" * 1024
flush
@closed = true
end
def closed?
@closed
end
def flush
check_closed
@io.flush if @io.respond_to? :flush
end
def mkdir(name, mode)
check_closed
name, prefix = split_name(name)
header = Gem::Package::TarHeader.new :name => name, :mode => mode,
:typeflag => "5", :size => 0,
:prefix => prefix
@io.write header
self
end
def split_name(name) # :nodoc:
raise Gem::Package::TooLongFileName if name.size > 256
if name.size <= 100 then
prefix = ""
else
parts = name.split(/\//)
newname = parts.pop
nxt = ""
loop do
nxt = parts.pop
break if newname.size + 1 + nxt.size > 100
newname = nxt + "/" + newname
end
prefix = (parts + [nxt]).join "/"
name = newname
if name.size > 100 or prefix.size > 155 then
raise Gem::Package::TooLongFileName
end
end
return name, prefix
end
end

View file

@ -2,7 +2,6 @@ require 'net/http'
require 'uri' require 'uri'
require 'rubygems' require 'rubygems'
require 'rubygems/gem_open_uri'
## ##
# RemoteFetcher handles the details of fetching gems and gem information from # RemoteFetcher handles the details of fetching gems and gem information from
@ -10,6 +9,8 @@ require 'rubygems/gem_open_uri'
class Gem::RemoteFetcher class Gem::RemoteFetcher
include Gem::UserInteraction
class FetchError < Gem::Exception; end class FetchError < Gem::Exception; end
@fetcher = nil @fetcher = nil
@ -29,6 +30,10 @@ class Gem::RemoteFetcher
# HTTP_PROXY_PASS) # HTTP_PROXY_PASS)
# * <tt>:no_proxy</tt>: ignore environment variables and _don't_ use a proxy # * <tt>:no_proxy</tt>: ignore environment variables and _don't_ use a proxy
def initialize(proxy) def initialize(proxy)
Socket.do_not_reverse_lookup = true
@connections = {}
@requests = Hash.new 0
@proxy_uri = @proxy_uri =
case proxy case proxy
when :no_proxy then nil when :no_proxy then nil
@ -38,6 +43,65 @@ class Gem::RemoteFetcher
end end
end end
##
# 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)
gem_file_name = "#{spec.full_name}.gem"
local_gem_path = File.join install_dir, 'cache', gem_file_name
Gem.ensure_gem_subdirectories install_dir
source_uri = URI.parse source_uri unless URI::Generic === source_uri
scheme = source_uri.scheme
# URI.parse gets confused by MS Windows paths with forward slashes.
scheme = nil if scheme =~ /^[a-z]$/i
case scheme
when 'http' then
unless File.exist? local_gem_path then
begin
say "Downloading gem #{gem_file_name}" if
Gem.configuration.really_verbose
remote_gem_path = source_uri + "gems/#{gem_file_name}"
gem = Gem::RemoteFetcher.fetcher.fetch_path remote_gem_path
rescue Gem::RemoteFetcher::FetchError
raise if spec.original_platform == spec.platform
alternate_name = "#{spec.original_name}.gem"
say "Failed, downloading gem #{alternate_name}" if
Gem.configuration.really_verbose
remote_gem_path = source_uri + "gems/#{alternate_name}"
gem = Gem::RemoteFetcher.fetcher.fetch_path remote_gem_path
end
File.open local_gem_path, 'wb' do |fp|
fp.write gem
end
end
when nil, 'file' then # TODO test for local overriding cache
begin
FileUtils.cp source_uri.to_s, local_gem_path
rescue Errno::EACCES
local_gem_path = source_uri.to_s
end
say "Using local gem #{local_gem_path}" if
Gem.configuration.really_verbose
else
raise Gem::InstallError, "unsupported URI scheme #{source_uri.scheme}"
end
local_gem_path
end
# Downloads +uri+. # Downloads +uri+.
def fetch_path(uri) def fetch_path(uri)
open_uri_or_path(uri) do |input| open_uri_or_path(uri) do |input|
@ -47,9 +111,8 @@ class Gem::RemoteFetcher
raise FetchError, "timed out fetching #{uri}" raise FetchError, "timed out fetching #{uri}"
rescue IOError, SocketError, SystemCallError => e rescue IOError, SocketError, SystemCallError => e
raise FetchError, "#{e.class}: #{e} reading #{uri}" raise FetchError, "#{e.class}: #{e} reading #{uri}"
rescue OpenURI::HTTPError => e rescue => e
body = e.io.readlines.join "\n\t" message = "#{e.class}: #{e} reading #{uri}"
message = "#{e.class}: #{e} reading #{uri}\n\t#{body}"
raise FetchError, message raise FetchError, message
end end
@ -83,7 +146,8 @@ class Gem::RemoteFetcher
end end
rescue SocketError, SystemCallError, Timeout::Error => e rescue SocketError, SystemCallError, Timeout::Error => e
raise FetchError, "#{e.message} (#{e.class})\n\tgetting size of #{uri}" raise Gem::RemoteFetcher::FetchError,
"#{e.message} (#{e.class})\n\tgetting size of #{uri}"
end end
private private
@ -131,26 +195,77 @@ class Gem::RemoteFetcher
# Read the data from the (source based) URI, but if it is a file:// URI, # Read the data from the (source based) URI, but if it is a file:// URI,
# read from the filesystem instead. # read from the filesystem instead.
def open_uri_or_path(uri, &block) def open_uri_or_path(uri, depth = 0, &block)
if file_uri?(uri) if file_uri?(uri)
open(get_file_uri_path(uri), &block) open(get_file_uri_path(uri), &block)
else else
connection_options = {
"User-Agent" => "RubyGems/#{Gem::RubyGemsVersion} #{Gem::Platform.local}"
}
if @proxy_uri
http_proxy_url = "#{@proxy_uri.scheme}://#{@proxy_uri.host}:#{@proxy_uri.port}"
connection_options[:proxy_http_basic_authentication] = [http_proxy_url, unescape(@proxy_uri.user)||'', unescape(@proxy_uri.password)||'']
end
uri = URI.parse uri unless URI::Generic === uri uri = URI.parse uri unless URI::Generic === uri
unless uri.nil? || uri.user.nil? || uri.user.empty? then net_http_args = [uri.host, uri.port]
connection_options[:http_basic_authentication] =
[unescape(uri.user), unescape(uri.password)] if @proxy_uri then
net_http_args += [ @proxy_uri.host,
@proxy_uri.port,
@proxy_uri.user,
@proxy_uri.password
]
end end
open(uri, connection_options, &block) 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
retried = false
begin
@requests[connection_id] += 1
response = connection.request(request)
rescue EOFError
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
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
open_uri_or_path(response['Location'], depth + 1, &block)
else
raise Gem::RemoteFetcher::FetchError,
"bad response #{response.message} #{response.code}"
end
end end
end end

View file

@ -16,6 +16,8 @@ class Gem::Requirement
include Comparable include Comparable
attr_reader :requirements
OPS = { OPS = {
"=" => lambda { |v, r| v == r }, "=" => lambda { |v, r| v == r },
"!=" => lambda { |v, r| v != r }, "!=" => lambda { |v, r| v != r },

View file

@ -2,5 +2,5 @@
# This file is auto-generated by build scripts. # This file is auto-generated by build scripts.
# See: rake update_version # See: rake update_version
module Gem module Gem
RubyGemsVersion = '1.0.1' RubyGemsVersion = '1.1.0'
end end

View file

@ -4,6 +4,7 @@
# See LICENSE.txt for permissions. # See LICENSE.txt for permissions.
#++ #++
require 'rubygems'
require 'rubygems/gem_openssl' require 'rubygems/gem_openssl'
# = Signed Gems README # = Signed Gems README

View file

@ -1,7 +1,7 @@
require 'webrick' require 'webrick'
require 'rdoc/template'
require 'yaml' require 'yaml'
require 'zlib' require 'zlib'
require 'erb'
require 'rubygems' require 'rubygems'
@ -27,107 +27,87 @@ class Gem::Server
include Gem::UserInteraction include Gem::UserInteraction
DOC_TEMPLATE = <<-WEBPAGE DOC_TEMPLATE = <<-'WEBPAGE'
<?xml version="1.0" encoding="iso-8859-1"?> <?xml version="1.0" encoding="iso-8859-1"?>
<!DOCTYPE html <!DOCTYPE html
PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<title>RubyGems Documentation Index</title>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
<link rel="stylesheet" href="gem-server-rdoc-style.css" type="text/css" media="screen" />
</head>
<body>
<div id="fileHeader">
<h1>RubyGems Documentation Index</h1>
</div>
<!-- banner header -->
<div id="bodyContent">
<div id="contextContent">
<div id="description">
<h1>Summary</h1>
<p>There are %gem_count% gems installed:</p>
<p>
START:specs
IFNOT:is_last
<a href="#%name%">%name%</a>,
ENDIF:is_last
IF:is_last
<a href="#%name%">%name%</a>.
ENDIF:is_last
END:specs
<h1>Gems</h1>
<dl>
START:specs
<dt>
IF:first_name_entry
<a name="%name%"></a>
ENDIF:first_name_entry
<b>%name% %version%</b>
IF:rdoc_installed
<a href="%doc_path%">[rdoc]</a>
ENDIF:rdoc_installed
IFNOT:rdoc_installed
<span title="rdoc not installed">[rdoc]</span>
ENDIF:rdoc_installed
IF:homepage
<a href="%homepage%" title="%homepage%">[www]</a>
ENDIF:homepage
IFNOT:homepage
<span title="no homepage available">[www]</span>
ENDIF:homepage
IF:has_deps
- depends on
START:dependencies
IFNOT:is_last
<a href="#%name%" title="%version%">%name%</a>,
ENDIF:is_last
IF:is_last
<a href="#%name%" title="%version%">%name%</a>.
ENDIF:is_last
END:dependencies
ENDIF:has_deps
</dt>
<dd>
%summary%
IF:executables
<br/>
IF:only_one_executable
Executable is
ENDIF:only_one_executable
IFNOT:only_one_executable
Executables are
ENDIF:only_one_executable
START:executables
IFNOT:is_last
<span class="context-item-name">%executable%</span>,
ENDIF:is_last
IF:is_last
<span class="context-item-name">%executable%</span>.
ENDIF:is_last
END:executables
ENDIF:executables
<br/>
<br/>
</dd>
END:specs
</dl>
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<title>RubyGems Documentation Index</title>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
<link rel="stylesheet" href="gem-server-rdoc-style.css" type="text/css" media="screen" />
</head>
<body>
<div id="fileHeader">
<h1>RubyGems Documentation Index</h1>
</div> </div>
</div> <!-- banner header -->
<div id="bodyContent">
<div id="contextContent">
<div id="description">
<h1>Summary</h1>
<p>There are <%=values["gem_count"]%> gems installed:</p>
<p>
<%= values["specs"].map { |v| "<a href=\"#{v["name"]}\">#{v["name"]}</a>" }.join ', ' %>.
<h1>Gems</h1>
<dl>
<% values["specs"].each do |spec| %>
<dt>
<% if spec["first_name_entry"] then %>
<a name="<%=spec["name"]%>"></a>
<% end %>
<b><%=spec["name"]%> <%=spec["version"]%></b>
<% if spec["rdoc_installed"] then %>
<a href="<%=spec["doc_path"]%>">[rdoc]</a>
<% else %>
<span title="rdoc not installed">[rdoc]</span>
<% end %>
<% if spec["homepage"] then %>
<a href="<%=spec["homepage"]%>" title="<%=spec["homepage"]%>">[www]</a>
<% else %>
<span title="no homepage available">[www]</span>
<% end %>
<% if spec["has_deps"] then %>
- depends on
<%= spec["dependencies"].map { |v| "<a href=\"#{v["name"]}\">#{v["name"]}</a>" }.join ', ' %>.
<% end %>
</dt>
<dd>
<%=spec["summary"]%>
<% if spec["executables"] then %>
<br/>
<% if spec["only_one_executable"] then %>
Executable is
<% else %>
Executables are
<%end%>
<%= spec["executables"].map { |v| "<span class=\"context-item-name\">#{v["executable"]}</span>"}.join ', ' %>.
<%end%>
<br/>
<br/>
</dd>
<% end %>
</dl>
</div>
</div>
</div>
<div id="validator-badges">
<p><small><a href="http://validator.w3.org/check/referer">[Validate]</a></small></p>
</div> </div>
<div id="validator-badges"> </body>
<p><small><a href="http://validator.w3.org/check/referer">[Validate]</a></small></p> </html>
</div>
</body>
</html>
WEBPAGE WEBPAGE
# CSS is copy & paste from rdoc-style.css, RDoc V1.0.1 - 20041108 # CSS is copy & paste from rdoc-style.css, RDoc V1.0.1 - 20041108
@ -496,11 +476,12 @@ div.method-source-code pre { color: #ffdead; overflow: hidden; }
end end
# create page from template # create page from template
template = TemplatePage.new(DOC_TEMPLATE) template = ERB.new(DOC_TEMPLATE)
res['content-type'] = 'text/html' res['content-type'] = 'text/html'
template.write_html_on res.body, values = { "gem_count" => specs.size.to_s, "specs" => specs,
"gem_count" => specs.size.to_s, "specs" => specs, "total_file_count" => total_file_count.to_s }
"total_file_count" => total_file_count.to_s result = template.result binding
res.body = result
end end
paths = { "/gems" => "/cache/", "/doc_root" => "/doc/" } paths = { "/gems" => "/cache/", "/doc_root" => "/doc/" }

View file

@ -8,437 +8,512 @@ require 'rubygems'
require 'rubygems/user_interaction' require 'rubygems/user_interaction'
require 'rubygems/specification' require 'rubygems/specification'
module Gem ##
# The SourceIndex object indexes all the gems available from a
# particular source (e.g. a list of gem directories, or a remote
# source). A SourceIndex maps a gem full name to a gem
# specification.
#
# NOTE:: The class used to be named Cache, but that became
# confusing when cached source fetchers where introduced. The
# constant Gem::Cache is an alias for this class to allow old
# YAMLized source index objects to load properly.
# The SourceIndex object indexes all the gems available from a class Gem::SourceIndex
# particular source (e.g. a list of gem directories, or a remote
# source). A SourceIndex maps a gem full name to a gem
# specification.
#
# NOTE:: The class used to be named Cache, but that became
# confusing when cached source fetchers where introduced. The
# constant Gem::Cache is an alias for this class to allow old
# YAMLized source index objects to load properly.
#
class SourceIndex
include Enumerable include Enumerable
include Gem::UserInteraction
class << self
include Gem::UserInteraction include Gem::UserInteraction
# Class Methods. ------------------------------------------------- ##
class << self # Factory method to construct a source index instance for a given
include Gem::UserInteraction # path.
# Factory method to construct a source index instance for a given
# path.
#
# deprecated::
# If supplied, from_installed_gems will act just like
# +from_gems_in+. This argument is deprecated and is provided
# just for backwards compatibility, and should not generally
# be used.
#
# return::
# SourceIndex instance
#
def from_installed_gems(*deprecated)
if deprecated.empty?
from_gems_in(*installed_spec_directories)
else
from_gems_in(*deprecated) # HACK warn
end
end
# Return a list of directories in the current gem path that
# contain specifications.
#
# return::
# List of directory paths (all ending in "../specifications").
#
def installed_spec_directories
Gem.path.collect { |dir| File.join(dir, "specifications") }
end
# Factory method to construct a source index instance for a
# given path.
#
# spec_dirs::
# List of directories to search for specifications. Each
# directory should have a "specifications" subdirectory
# containing the gem specifications.
#
# return::
# SourceIndex instance
#
def from_gems_in(*spec_dirs)
self.new.load_gems_in(*spec_dirs)
end
# Load a specification from a file (eval'd Ruby code)
#
# file_name:: [String] The .gemspec file
# return:: Specification instance or nil if an error occurs
#
def load_specification(file_name)
begin
spec_code = File.read(file_name).untaint
gemspec = eval spec_code, binding, file_name
if gemspec.is_a?(Gem::Specification)
gemspec.loaded_from = file_name
return gemspec
end
alert_warning "File '#{file_name}' does not evaluate to a gem specification"
rescue SyntaxError => e
alert_warning e
alert_warning spec_code
rescue Exception => e
alert_warning(e.inspect.to_s + "\n" + spec_code)
alert_warning "Invalid .gemspec format in '#{file_name}'"
end
return nil
end
end
# Instance Methods -----------------------------------------------
# Constructs a source index instance from the provided
# specifications
# #
# specifications:: # deprecated::
# [Hash] hash of [Gem name, Gem::Specification] pairs # If supplied, from_installed_gems will act just like
# # +from_gems_in+. This argument is deprecated and is provided
def initialize(specifications={}) # just for backwards compatibility, and should not generally
@gems = specifications # be used.
end #
# return::
# Reconstruct the source index from the list of source # SourceIndex instance
# directories.
def load_gems_in(*spec_dirs)
@gems.clear
specs = Dir.glob File.join("{#{spec_dirs.join(',')}}", "*.gemspec")
specs.each do |file_name| def from_installed_gems(*deprecated)
gemspec = self.class.load_specification(file_name.untaint) if deprecated.empty?
add_spec(gemspec) if gemspec from_gems_in(*installed_spec_directories)
end
self
end
# Returns a Hash of name => Specification of the latest versions of each
# gem in this index.
def latest_specs
result, latest = Hash.new { |h,k| h[k] = [] }, {}
self.each do |_, spec| # SourceIndex is not a hash, so we're stuck with each
name = spec.name
curr_ver = spec.version
prev_ver = latest[name]
next unless prev_ver.nil? or curr_ver >= prev_ver
if prev_ver.nil? or curr_ver > prev_ver then
result[name].clear
latest[name] = curr_ver
end
result[name] << spec
end
result.values.flatten
end
# Add a gem specification to the source index.
def add_spec(gem_spec)
@gems[gem_spec.full_name] = gem_spec
end
# Remove a gem specification named +full_name+.
def remove_spec(full_name)
@gems.delete(full_name)
end
# Iterate over the specifications in the source index.
def each(&block) # :yields: gem.full_name, gem
@gems.each(&block)
end
# The gem specification given a full gem spec name.
def specification(full_name)
@gems[full_name]
end
# The signature for the source index. Changes in the signature
# indicate a change in the index.
def index_signature
require 'rubygems/digest/sha2'
Gem::SHA256.new.hexdigest(@gems.keys.sort.join(',')).to_s
end
# The signature for the given gem specification.
def gem_signature(gem_full_name)
require 'rubygems/digest/sha2'
Gem::SHA256.new.hexdigest(@gems[gem_full_name].to_yaml).to_s
end
def size
@gems.size
end
alias length size
# Find a gem by an exact match on the short name.
def find_name(gem_name, version_requirement = Gem::Requirement.default)
search(/^#{gem_name}$/, version_requirement)
end
# Search for a gem by Gem::Dependency +gem_pattern+. If +only_platform+
# is true, only gems matching Gem::Platform.local will be returned. An
# Array of matching Gem::Specification objects is returned.
#
# For backwards compatibility, a String or Regexp pattern may be passed as
# +gem_pattern+, and a Gem::Requirement for +platform_only+. This
# behavior is deprecated and will be removed.
def search(gem_pattern, platform_only = false)
version_requirement = nil
only_platform = false
case gem_pattern # TODO warn after 2008/03, remove three months after
when Regexp then
version_requirement = platform_only || Gem::Requirement.default
when Gem::Dependency then
only_platform = platform_only
version_requirement = gem_pattern.version_requirements
gem_pattern = if gem_pattern.name.empty? then
//
else
/^#{Regexp.escape gem_pattern.name}$/
end
else else
version_requirement = platform_only || Gem::Requirement.default from_gems_in(*deprecated) # HACK warn
gem_pattern = /#{gem_pattern}/i
end end
unless Gem::Requirement === version_requirement then
version_requirement = Gem::Requirement.create version_requirement
end
specs = @gems.values.select do |spec|
spec.name =~ gem_pattern and
version_requirement.satisfied_by? spec.version
end
if only_platform then
specs = specs.select do |spec|
Gem::Platform.match spec.platform
end
end
specs.sort_by { |s| s.sort_obj }
end end
# Refresh the source index from the local file system. ##
# # Return a list of directories in the current gem path that
# return:: Returns a pointer to itself. # contain specifications.
# #
def refresh! # return::
load_gems_in(self.class.installed_spec_directories) # List of directory paths (all ending in "../specifications").
def installed_spec_directories
Gem.path.collect { |dir| File.join(dir, "specifications") }
end end
# Returns an Array of Gem::Specifications that are not up to date. ##
# # Creates a new SourceIndex from the ruby format gem specifications in
def outdated # +spec_dirs+.
dep = Gem::Dependency.new '', Gem::Requirement.default
remotes = Gem::SourceInfoCache.search dep, true def from_gems_in(*spec_dirs)
self.new.load_gems_in(*spec_dirs)
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
end
outdateds
end end
def update(source_uri) ##
use_incremental = false # Loads a ruby-format specification from +file_name+ and returns the
# loaded spec.
def load_specification(file_name)
begin begin
gem_names = fetch_quick_index source_uri spec_code = File.read(file_name).untaint
remove_extra gem_names gemspec = eval spec_code, binding, file_name
missing_gems = find_missing gem_names if gemspec.is_a?(Gem::Specification)
gemspec.loaded_from = file_name
return gemspec
end
alert_warning "File '#{file_name}' does not evaluate to a gem specification"
rescue SyntaxError => e
alert_warning e
alert_warning spec_code
rescue Exception => e
alert_warning(e.inspect.to_s + "\n" + spec_code)
alert_warning "Invalid .gemspec format in '#{file_name}'"
end
return nil
end
return false if missing_gems.size.zero? end
say "missing #{missing_gems.size} gems" if ##
missing_gems.size > 0 and Gem.configuration.really_verbose # Constructs a source index instance from the provided
# specifications
#
# specifications::
# [Hash] hash of [Gem name, Gem::Specification] pairs
use_incremental = missing_gems.size <= Gem.configuration.bulk_threshold def initialize(specifications={})
rescue Gem::OperationNotSupportedError => ex @gems = specifications
alert_error "Falling back to bulk fetch: #{ex.message}" if end
Gem.configuration.really_verbose
use_incremental = false ##
# Reconstruct the source index from the specifications in +spec_dirs+.
def load_gems_in(*spec_dirs)
@gems.clear
spec_dirs.reverse_each do |spec_dir|
spec_files = Dir.glob File.join(spec_dir, '*.gemspec')
spec_files.each do |spec_file|
gemspec = self.class.load_specification spec_file.untaint
add_spec gemspec if gemspec
end
end
self
end
##
# Returns a Hash of name => Specification of the latest versions of each
# gem in this index.
def latest_specs
result = Hash.new { |h,k| h[k] = [] }
latest = {}
sort.each do |_, spec|
name = spec.name
curr_ver = spec.version
prev_ver = latest.key?(name) ? latest[name].version : nil
next unless prev_ver.nil? or curr_ver >= prev_ver or
latest[name].platform != Gem::Platform::RUBY
if prev_ver.nil? or
(curr_ver > prev_ver and spec.platform == Gem::Platform::RUBY) then
result[name].clear
latest[name] = spec
end end
if use_incremental then if spec.platform != Gem::Platform::RUBY then
update_with_missing(source_uri, missing_gems) result[name].delete_if do |result_spec|
else result_spec.platform == spec.platform
new_index = fetch_bulk_index(source_uri) end
@gems.replace(new_index.gems)
end end
true result[name] << spec
end end
def ==(other) # :nodoc: result.values.flatten
self.class === other and @gems == other.gems end
##
# Add a gem specification to the source index.
def add_spec(gem_spec)
@gems[gem_spec.full_name] = gem_spec
end
##
# Add gem specifications to the source index.
def add_specs(*gem_specs)
gem_specs.each do |spec|
add_spec spec
end
end
##
# Remove a gem specification named +full_name+.
def remove_spec(full_name)
@gems.delete(full_name)
end
##
# Iterate over the specifications in the source index.
def each(&block) # :yields: gem.full_name, gem
@gems.each(&block)
end
##
# The gem specification given a full gem spec name.
def specification(full_name)
@gems[full_name]
end
##
# The signature for the source index. Changes in the signature indicate a
# change in the index.
def index_signature
require 'rubygems/digest/sha2'
Gem::SHA256.new.hexdigest(@gems.keys.sort.join(',')).to_s
end
##
# The signature for the given gem specification.
def gem_signature(gem_full_name)
require 'rubygems/digest/sha2'
Gem::SHA256.new.hexdigest(@gems[gem_full_name].to_yaml).to_s
end
def size
@gems.size
end
alias length size
##
# Find a gem by an exact match on the short name.
def find_name(gem_name, version_requirement = Gem::Requirement.default)
search(/^#{gem_name}$/, version_requirement)
end
##
# Search for a gem by Gem::Dependency +gem_pattern+. If +only_platform+
# is true, only gems matching Gem::Platform.local will be returned. An
# Array of matching Gem::Specification objects is returned.
#
# For backwards compatibility, a String or Regexp pattern may be passed as
# +gem_pattern+, and a Gem::Requirement for +platform_only+. This
# behavior is deprecated and will be removed.
def search(gem_pattern, platform_only = false)
version_requirement = nil
only_platform = false
case gem_pattern # TODO warn after 2008/03, remove three months after
when Regexp then
version_requirement = platform_only || Gem::Requirement.default
when Gem::Dependency then
only_platform = platform_only
version_requirement = gem_pattern.version_requirements
gem_pattern = if gem_pattern.name.empty? then
//
else
/^#{Regexp.escape gem_pattern.name}$/
end
else
version_requirement = platform_only || Gem::Requirement.default
gem_pattern = /#{gem_pattern}/i
end end
def dump unless Gem::Requirement === version_requirement then
Marshal.dump(self) version_requirement = Gem::Requirement.create version_requirement
end end
protected specs = @gems.values.select do |spec|
spec.name =~ gem_pattern and
attr_reader :gems version_requirement.satisfied_by? spec.version
private
def fetcher
require 'rubygems/remote_fetcher'
Gem::RemoteFetcher.fetcher
end end
def fetch_index_from(source_uri) if only_platform then
@fetch_error = nil specs = specs.select do |spec|
Gem::Platform.match spec.platform
end
end
indexes = %W[ specs.sort_by { |s| s.sort_obj }
end
##
# Refresh the source index from the local file system.
#
# return:: Returns a pointer to itself.
def refresh!
load_gems_in(self.class.installed_spec_directories)
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
end
outdateds
end
##
# Updates this SourceIndex from +source_uri+. If +all+ is false, only the
# latest gems are fetched.
def update(source_uri, all)
source_uri = URI.parse source_uri unless URI::Generic === source_uri
source_uri.path += '/' unless source_uri.path =~ /\/$/
use_incremental = false
begin
gem_names = fetch_quick_index source_uri, all
remove_extra gem_names
missing_gems = find_missing gem_names
return false if missing_gems.size.zero?
say "Missing metadata for #{missing_gems.size} gems" if
missing_gems.size > 0 and Gem.configuration.really_verbose
use_incremental = missing_gems.size <= Gem.configuration.bulk_threshold
rescue Gem::OperationNotSupportedError => ex
alert_error "Falling back to bulk fetch: #{ex.message}" if
Gem.configuration.really_verbose
use_incremental = false
end
if use_incremental then
update_with_missing(source_uri, missing_gems)
else
new_index = fetch_bulk_index(source_uri)
@gems.replace(new_index.gems)
end
true
end
def ==(other) # :nodoc:
self.class === other and @gems == other.gems
end
def dump
Marshal.dump(self)
end
protected
attr_reader :gems
private
def fetcher
require 'rubygems/remote_fetcher'
Gem::RemoteFetcher.fetcher
end
def fetch_index_from(source_uri)
@fetch_error = nil
indexes = %W[
Marshal.#{Gem.marshal_version}.Z Marshal.#{Gem.marshal_version}.Z
Marshal.#{Gem.marshal_version} Marshal.#{Gem.marshal_version}
yaml.Z yaml.Z
yaml yaml
] ]
indexes.each do |name| indexes.each do |name|
spec_data = nil spec_data = nil
begin index = source_uri + name
spec_data = fetcher.fetch_path("#{source_uri}/#{name}")
spec_data = unzip(spec_data) if name =~ /\.Z$/
if name =~ /Marshal/ then
return Marshal.load(spec_data)
else
return YAML.load(spec_data)
end
rescue => e
if Gem.configuration.really_verbose then
alert_error "Unable to fetch #{name}: #{e.message}"
end
@fetch_error = e
end
end
nil
end
def fetch_bulk_index(source_uri)
say "Bulk updating Gem source index for: #{source_uri}"
index = fetch_index_from(source_uri)
if index.nil? then
raise Gem::RemoteSourceException,
"Error fetching remote gem cache: #{@fetch_error}"
end
@fetch_error = nil
index
end
# Get the quick index needed for incremental updates.
def fetch_quick_index(source_uri)
zipped_index = fetcher.fetch_path source_uri + '/quick/index.rz'
unzip(zipped_index).split("\n")
rescue ::Exception => ex
raise Gem::OperationNotSupportedError,
"No quick index found: " + ex.message
end
# Make a list of full names for all the missing gemspecs.
def find_missing(spec_names)
spec_names.find_all { |full_name|
specification(full_name).nil?
}
end
def remove_extra(spec_names)
dictionary = spec_names.inject({}) { |h, k| h[k] = true; h }
each do |name, spec|
remove_spec name unless dictionary.include? name
end
end
# Unzip the given string.
def unzip(string)
require 'zlib'
Zlib::Inflate.inflate(string)
end
# Tries to fetch Marshal representation first, then YAML
def fetch_single_spec(source_uri, spec_name)
@fetch_error = nil
begin begin
marshal_uri = source_uri + "/quick/Marshal.#{Gem.marshal_version}/#{spec_name}.gemspec.rz" spec_data = fetcher.fetch_path index
zipped = fetcher.fetch_path marshal_uri spec_data = unzip(spec_data) if name =~ /\.Z$/
return Marshal.load(unzip(zipped))
rescue => ex
@fetch_error = ex
if Gem.configuration.really_verbose then
say "unable to fetch marshal gemspec #{marshal_uri}: #{ex.class} - #{ex}"
end
end
begin if name =~ /Marshal/ then
yaml_uri = source_uri + "/quick/#{spec_name}.gemspec.rz" return Marshal.load(spec_data)
zipped = fetcher.fetch_path yaml_uri
return YAML.load(unzip(zipped))
rescue => ex
@fetch_error = ex
if Gem.configuration.really_verbose then
say "unable to fetch YAML gemspec #{yaml_uri}: #{ex.class} - #{ex}"
end
end
nil
end
# Update the cached source index with the missing names.
def update_with_missing(source_uri, missing_names)
progress = ui.progress_reporter(missing_names.size,
"Updating metadata for #{missing_names.size} gems from #{source_uri}")
missing_names.each do |spec_name|
gemspec = fetch_single_spec(source_uri, spec_name)
if gemspec.nil? then
ui.say "Failed to download spec #{spec_name} from #{source_uri}:\n" \
"\t#{@fetch_error.message}"
else else
add_spec gemspec return YAML.load(spec_data)
progress.updated spec_name
end end
@fetch_error = nil rescue => e
if Gem.configuration.really_verbose then
alert_error "Unable to fetch #{name}: #{e.message}"
end
@fetch_error = e
end end
progress.done
progress.count
end end
nil
end end
# Cache is an alias for SourceIndex to allow older YAMLized source def fetch_bulk_index(source_uri)
# index objects to load properly. say "Bulk updating Gem source index for: #{source_uri}"
Cache = SourceIndex
index = fetch_index_from(source_uri)
if index.nil? then
raise Gem::RemoteSourceException,
"Error fetching remote gem cache: #{@fetch_error}"
end
@fetch_error = nil
index
end
##
# Get the quick index needed for incremental updates.
def fetch_quick_index(source_uri, all)
index = all ? 'index' : 'latest_index'
zipped_index = fetcher.fetch_path source_uri + "quick/#{index}.rz"
unzip(zipped_index).split("\n")
rescue ::Exception => e
unless all then
say "Latest index not found, using quick index" if
Gem.configuration.really_verbose
fetch_quick_index source_uri, true
else
raise Gem::OperationNotSupportedError,
"No quick index found: #{e.message}"
end
end
##
# Make a list of full names for all the missing gemspecs.
def find_missing(spec_names)
spec_names.find_all { |full_name|
specification(full_name).nil?
}
end
def remove_extra(spec_names)
dictionary = spec_names.inject({}) { |h, k| h[k] = true; h }
each do |name, spec|
remove_spec name unless dictionary.include? name
end
end
##
# Unzip the given string.
def unzip(string)
require 'zlib'
Zlib::Inflate.inflate(string)
end
##
# Tries to fetch Marshal representation first, then YAML
def fetch_single_spec(source_uri, spec_name)
@fetch_error = nil
begin
marshal_uri = source_uri + "quick/Marshal.#{Gem.marshal_version}/#{spec_name}.gemspec.rz"
zipped = fetcher.fetch_path marshal_uri
return Marshal.load(unzip(zipped))
rescue => ex
@fetch_error = ex
if Gem.configuration.really_verbose then
say "unable to fetch marshal gemspec #{marshal_uri}: #{ex.class} - #{ex}"
end
end
begin
yaml_uri = source_uri + "quick/#{spec_name}.gemspec.rz"
zipped = fetcher.fetch_path yaml_uri
return YAML.load(unzip(zipped))
rescue => ex
@fetch_error = ex
if Gem.configuration.really_verbose then
say "unable to fetch YAML gemspec #{yaml_uri}: #{ex.class} - #{ex}"
end
end
nil
end
##
# Update the cached source index with the missing names.
def update_with_missing(source_uri, missing_names)
progress = ui.progress_reporter(missing_names.size,
"Updating metadata for #{missing_names.size} gems from #{source_uri}")
missing_names.each do |spec_name|
gemspec = fetch_single_spec(source_uri, spec_name)
if gemspec.nil? then
ui.say "Failed to download spec #{spec_name} from #{source_uri}:\n" \
"\t#{@fetch_error.message}"
else
add_spec gemspec
progress.updated spec_name
end
@fetch_error = nil
end
progress.done
progress.count
end
end
module Gem
# :stopdoc:
# Cache is an alias for SourceIndex to allow older YAMLized source index
# objects to load properly.
Cache = SourceIndex
# :starddoc:
end end

View file

@ -4,6 +4,7 @@ require 'rubygems'
require 'rubygems/source_info_cache_entry' require 'rubygems/source_info_cache_entry'
require 'rubygems/user_interaction' require 'rubygems/user_interaction'
##
# SourceInfoCache stores a copy of the gem index for each gem source. # SourceInfoCache stores a copy of the gem index for each gem source.
# #
# There are two possible cache locations, the system cache and the user cache: # There are two possible cache locations, the system cache and the user cache:
@ -25,7 +26,7 @@ require 'rubygems/user_interaction'
# @source_index => Gem::SourceIndex # @source_index => Gem::SourceIndex
# ... # ...
# } # }
#
class Gem::SourceInfoCache class Gem::SourceInfoCache
include Gem::UserInteraction include Gem::UserInteraction
@ -37,7 +38,7 @@ class Gem::SourceInfoCache
def self.cache def self.cache
return @cache if @cache return @cache if @cache
@cache = new @cache = new
@cache.refresh if Gem.configuration.update_sources @cache.refresh false if Gem.configuration.update_sources
@cache @cache
end end
@ -45,86 +46,178 @@ class Gem::SourceInfoCache
cache.cache_data cache.cache_data
end end
# Search all source indexes for +pattern+. ##
def self.search(pattern, platform_only = false) # The name of the system cache file.
cache.search pattern, platform_only
def self.latest_system_cache_file
File.join File.dirname(system_cache_file),
"latest_#{File.basename system_cache_file}"
end end
# Search all source indexes for +pattern+. Only returns gems matching ##
# Gem.platforms when +only_platform+ is true. See #search_with_source. # The name of the latest user cache file.
def self.search_with_source(pattern, only_platform = false)
cache.search_with_source(pattern, only_platform) def self.latest_user_cache_file
File.join File.dirname(user_cache_file),
"latest_#{File.basename user_cache_file}"
end
##
# Search all source indexes. See Gem::SourceInfoCache#search.
def self.search(*args)
cache.search(*args)
end
##
# Search all source indexes returning the source_uri. See
# Gem::SourceInfoCache#search_with_source.
def self.search_with_source(*args)
cache.search_with_source(*args)
end
##
# The name of the system cache file. (class method)
def self.system_cache_file
@system_cache_file ||= Gem.default_system_source_cache_dir
end
##
# The name of the user cache file.
def self.user_cache_file
@user_cache_file ||=
ENV['GEMCACHE'] || Gem.default_user_source_cache_dir
end end
def initialize # :nodoc: def initialize # :nodoc:
@cache_data = nil @cache_data = nil
@cache_file = nil @cache_file = nil
@dirty = false @dirty = false
@only_latest = true
end end
##
# The most recent cache data. # The most recent cache data.
def cache_data def cache_data
return @cache_data if @cache_data return @cache_data if @cache_data
cache_file # HACK writable check cache_file # HACK writable check
begin @only_latest = true
# Marshal loads 30-40% faster from a String, and 2MB on 20061116 is small
data = File.open cache_file, 'rb' do |fp| fp.read end
@cache_data = Marshal.load data
@cache_data.each do |url, sice| @cache_data = read_cache_data latest_cache_file
next unless sice.is_a?(Hash)
update
cache = sice['cache']
size = sice['size']
if cache.is_a?(Gem::SourceIndex) and size.is_a?(Numeric) then
new_sice = Gem::SourceInfoCacheEntry.new cache, size
@cache_data[url] = new_sice
else # irreperable, force refetch.
reset_cache_for(url)
end
end
@cache_data
rescue => e
if Gem.configuration.really_verbose then
say "Exception during cache_data handling: #{ex.class} - #{ex}"
say "Cache file was: #{cache_file}"
say "\t#{e.backtrace.join "\n\t"}"
end
reset_cache_data
end
end
def reset_cache_for(url)
say "Reseting cache for #{url}" if Gem.configuration.really_verbose
sice = Gem::SourceInfoCacheEntry.new Gem::SourceIndex.new, 0
sice.refresh url # HACK may be unnecessary, see ::cache and #refresh
@cache_data[url] = sice
@cache_data @cache_data
end end
def reset_cache_data ##
@cache_data = {} # The name of the cache file.
end
# The name of the cache file to be read
def cache_file def cache_file
return @cache_file if @cache_file return @cache_file if @cache_file
@cache_file = (try_file(system_cache_file) or @cache_file = (try_file(system_cache_file) or
try_file(user_cache_file) or try_file(user_cache_file) or
raise "unable to locate a writable cache file") raise "unable to locate a writable cache file")
end end
##
# Force cache file to be reset, useful for integration testing of rubygems
def reset_cache_file
@cache_file = nil
end
##
# Write the cache to a local file (if it is dirty). # Write the cache to a local file (if it is dirty).
def flush def flush
write_cache if @dirty write_cache if @dirty
@dirty = false @dirty = false
end end
# Refreshes each source in the cache from its repository. def latest_cache_data
def refresh latest_cache_data = {}
cache_data.each do |repo, sice|
latest = sice.source_index.latest_specs
new_si = Gem::SourceIndex.new
new_si.add_specs(*latest)
latest_sice = Gem::SourceInfoCacheEntry.new new_si, sice.size
latest_cache_data[repo] = latest_sice
end
latest_cache_data
end
##
# The name of the latest cache file.
def latest_cache_file
File.join File.dirname(cache_file), "latest_#{File.basename cache_file}"
end
##
# The name of the latest system cache file.
def latest_system_cache_file
self.class.latest_system_cache_file
end
##
# The name of the latest user cache file.
def latest_user_cache_file
self.class.latest_user_cache_file
end
def read_all_cache_data
if @only_latest then
@only_latest = false
@cache_data = read_cache_data cache_file
end
end
def read_cache_data(file)
# Marshal loads 30-40% faster from a String, and 2MB on 20061116 is small
data = open file, 'rb' do |fp| fp.read end
cache_data = Marshal.load data
cache_data.each do |url, sice|
next unless sice.is_a?(Hash)
update
cache = sice['cache']
size = sice['size']
if cache.is_a?(Gem::SourceIndex) and size.is_a?(Numeric) then
new_sice = Gem::SourceInfoCacheEntry.new cache, size
cache_data[url] = new_sice
else # irreperable, force refetch.
reset_cache_for url, cache_data
end
end
cache_data
rescue => e
if Gem.configuration.really_verbose then
say "Exception during cache_data handling: #{e.class} - #{e}"
say "Cache file was: #{file}"
say "\t#{e.backtrace.join "\n\t"}"
end
{}
end
##
# Refreshes each source in the cache from its repository. If +all+ is
# false, only latest gems are updated.
def refresh(all)
Gem.sources.each do |source_uri| Gem.sources.each do |source_uri|
cache_entry = cache_data[source_uri] cache_entry = cache_data[source_uri]
if cache_entry.nil? then if cache_entry.nil? then
@ -132,14 +225,34 @@ class Gem::SourceInfoCache
cache_data[source_uri] = cache_entry cache_data[source_uri] = cache_entry
end end
update if cache_entry.refresh source_uri update if cache_entry.refresh source_uri, all
end end
flush flush
end end
# Searches all source indexes for +pattern+. def reset_cache_for(url, cache_data)
def search(pattern, platform_only = false) say "Reseting cache for #{url}" if Gem.configuration.really_verbose
sice = Gem::SourceInfoCacheEntry.new Gem::SourceIndex.new, 0
sice.refresh url, false # HACK may be unnecessary, see ::cache and #refresh
cache_data[url] = sice
cache_data
end
def reset_cache_data
@cache_data = nil
end
##
# Searches all source indexes. See Gem::SourceIndex#search for details on
# +pattern+ and +platform_only+. If +all+ is set to true, the full index
# will be loaded before searching.
def search(pattern, platform_only = false, all = false)
read_all_cache_data if all
cache_data.map do |source_uri, sic_entry| cache_data.map do |source_uri, sic_entry|
next unless Gem.sources.include? source_uri next unless Gem.sources.include? source_uri
sic_entry.source_index.search pattern, platform_only sic_entry.source_index.search pattern, platform_only
@ -150,7 +263,9 @@ class Gem::SourceInfoCache
# only gems matching Gem.platforms will be selected. Returns an Array of # only gems matching Gem.platforms will be selected. Returns an Array of
# pairs containing the Gem::Specification found and the source_uri it was # pairs containing the Gem::Specification found and the source_uri it was
# found at. # found at.
def search_with_source(pattern, only_platform = false) def search_with_source(pattern, only_platform = false, all = false)
read_all_cache_data if all
results = [] results = []
cache_data.map do |source_uri, sic_entry| cache_data.map do |source_uri, sic_entry|
@ -164,69 +279,76 @@ class Gem::SourceInfoCache
results results
end end
# Mark the cache as updated (i.e. dirty). ##
def update
@dirty = true
end
# The name of the system cache file.
def system_cache_file
self.class.system_cache_file
end
# The name of the system cache file. (class method)
def self.system_cache_file
@system_cache_file ||= Gem.default_system_source_cache_dir
end
# The name of the user cache file.
def user_cache_file
self.class.user_cache_file
end
# The name of the user cache file. (class method)
def self.user_cache_file
@user_cache_file ||=
ENV['GEMCACHE'] || Gem.default_user_source_cache_dir
end
# Write data to the proper cache.
def write_cache
open cache_file, "wb" do |f|
f.write Marshal.dump(cache_data)
end
end
# Set the source info cache data directly. This is mainly used for unit # Set the source info cache data directly. This is mainly used for unit
# testing when we don't want to read a file system to grab the cached source # testing when we don't want to read a file system to grab the cached source
# index information. The +hash+ should map a source URL into a # index information. The +hash+ should map a source URL into a
# SourceInfoCacheEntry. # SourceInfoCacheEntry.
def set_cache_data(hash) def set_cache_data(hash)
@cache_data = hash @cache_data = hash
update update
end end
private ##
# The name of the system cache file.
def system_cache_file
self.class.system_cache_file
end
##
# Determine if +path+ is a candidate for a cache file. Returns +path+ if
# it is, nil if not.
def try_file(path)
return path if File.writable? path
return nil if File.exist? path
dir = File.dirname path
# Determine if +fn+ is a candidate for a cache file. Return fn if
# it is. Return nil if it is not.
def try_file(fn)
return fn if File.writable?(fn)
return nil if File.exist?(fn)
dir = File.dirname(fn)
unless File.exist? dir then unless File.exist? dir then
begin begin
FileUtils.mkdir_p(dir) FileUtils.mkdir_p dir
rescue RuntimeError, SystemCallError rescue RuntimeError, SystemCallError
return nil return nil
end end
end end
if File.writable?(dir)
File.open(fn, "wb") { |f| f << Marshal.dump({}) } if File.writable? dir then
return fn open path, "wb" do |io| io.write Marshal.dump({}) end
return path
end end
nil nil
end end
##
# Mark the cache as updated (i.e. dirty).
def update
@dirty = true
end
##
# The name of the user cache file.
def user_cache_file
self.class.user_cache_file
end
##
# Write data to the proper cache.
def write_cache
open cache_file, 'wb' do |io|
io.write Marshal.dump(cache_data)
end
open latest_cache_file, 'wb' do |io|
io.write Marshal.dump(latest_cache_data)
end
end
end end

View file

@ -3,24 +3,31 @@ require 'rubygems/source_index'
require 'rubygems/remote_fetcher' require 'rubygems/remote_fetcher'
## ##
# Entrys held by a SourceInfoCache. # Entries held by a SourceInfoCache.
class Gem::SourceInfoCacheEntry class Gem::SourceInfoCacheEntry
##
# The source index for this cache entry. # The source index for this cache entry.
attr_reader :source_index attr_reader :source_index
##
# The size of the of the source entry. Used to determine if the # The size of the of the source entry. Used to determine if the
# source index has changed. # source index has changed.
attr_reader :size attr_reader :size
##
# Create a cache entry. # Create a cache entry.
def initialize(si, size) def initialize(si, size)
@source_index = si || Gem::SourceIndex.new({}) @source_index = si || Gem::SourceIndex.new({})
@size = size @size = size
@all = false
end end
def refresh(source_uri) def refresh(source_uri, all)
begin begin
marshal_uri = URI.join source_uri.to_s, "Marshal.#{Gem.marshal_version}" marshal_uri = URI.join source_uri.to_s, "Marshal.#{Gem.marshal_version}"
remote_size = Gem::RemoteFetcher.fetcher.fetch_size marshal_uri remote_size = Gem::RemoteFetcher.fetcher.fetch_size marshal_uri
@ -29,9 +36,12 @@ class Gem::SourceInfoCacheEntry
remote_size = Gem::RemoteFetcher.fetcher.fetch_size yaml_uri remote_size = Gem::RemoteFetcher.fetcher.fetch_size yaml_uri
end end
return false if @size == remote_size # TODO Use index_signature instead of size? # TODO Use index_signature instead of size?
updated = @source_index.update source_uri return false if @size == remote_size and @all
updated = @source_index.update source_uri, all
@size = remote_size @size = remote_size
@all = all
updated updated
end end

View file

@ -13,7 +13,7 @@ require 'rubygems/platform'
if RUBY_VERSION < '1.9' then if RUBY_VERSION < '1.9' then
def Time.today def Time.today
t = Time.now t = Time.now
t - ((t.to_i + t.gmt_offset) % 86400) t - ((t.to_f + t.gmt_offset) % 86400)
end unless defined? Time.today end unless defined? Time.today
end end
# :startdoc: # :startdoc:

View file

@ -12,7 +12,7 @@ require 'rubygems/user_interaction'
## ##
# An Uninstaller. # An Uninstaller.
#
class Gem::Uninstaller class Gem::Uninstaller
include Gem::UserInteraction include Gem::UserInteraction
@ -21,8 +21,8 @@ class Gem::Uninstaller
# Constructs an Uninstaller instance # Constructs an Uninstaller instance
# #
# gem:: [String] The Gem name to uninstall # gem:: [String] The Gem name to uninstall
#
def initialize(gem, options) def initialize(gem, options = {})
@gem = gem @gem = gem
@version = options[:version] || Gem::Requirement.default @version = options[:version] || Gem::Requirement.default
gem_home = options[:install_dir] || Gem.dir gem_home = options[:install_dir] || Gem.dir
@ -30,12 +30,13 @@ class Gem::Uninstaller
@force_executables = options[:executables] @force_executables = options[:executables]
@force_all = options[:all] @force_all = options[:all]
@force_ignore = options[:ignore] @force_ignore = options[:ignore]
@bin_dir = options[:bin_dir]
end end
## ##
# Performs the uninstall of the Gem. This removes the spec, the # Performs the uninstall of the Gem. This removes the spec, the
# Gem directory, and the cached .gem file, # Gem directory, and the cached .gem file,
#
def uninstall def uninstall
list = Gem.source_index.search(/^#{@gem}$/, @version) list = Gem.source_index.search(/^#{@gem}$/, @version)
@ -66,18 +67,14 @@ class Gem::Uninstaller
end end
## ##
# Remove executables and batch files (windows only) for the gem as # Removes installed executables and batch files (windows only) for
# it is being installed # +gemspec+.
#
# gemspec::[Specification] the gem whose executables need to be removed.
#
def remove_executables(gemspec) def remove_executables(gemspec)
return if gemspec.nil? return if gemspec.nil?
if gemspec.executables.size > 0 then if gemspec.executables.size > 0 then
bindir = Gem.bindir @gem_home bindir = @bin_dir ? @bin_dir : (Gem.bindir @gem_home)
raise Gem::FilePermissionError, bindir unless File.writable? bindir
list = Gem.source_index.search(gemspec.name).delete_if { |spec| list = Gem.source_index.search(gemspec.name).delete_if { |spec|
spec.version == gemspec.version spec.version == gemspec.version
@ -93,14 +90,19 @@ class Gem::Uninstaller
return if executables.size == 0 return if executables.size == 0
answer = @force_executables || ask_yes_no( answer = if @force_executables.nil? then
"Remove executables:\n" \ ask_yes_no("Remove executables:\n" \
"\t#{gemspec.executables.join(", ")}\n\nin addition to the gem?", "\t#{gemspec.executables.join(", ")}\n\nin addition to the gem?",
true) # " # appease ruby-mode - don't ask true) # " # appease ruby-mode - don't ask
else
@force_executables
end
unless answer then unless answer then
say "Executables and scripts will remain installed." say "Executables and scripts will remain installed."
else else
raise Gem::FilePermissionError, bindir unless File.writable? bindir
gemspec.executables.each do |exe_name| gemspec.executables.each do |exe_name|
say "Removing #{exe_name}" say "Removing #{exe_name}"
FileUtils.rm_f File.join(bindir, exe_name) FileUtils.rm_f File.join(bindir, exe_name)
@ -110,23 +112,22 @@ class Gem::Uninstaller
end end
end end
##
# Removes all gems in +list+.
# #
# list:: the list of all gems to remove # NOTE: removes uninstalled gems from +list+.
#
# Warning: this method modifies the +list+ parameter. Once it has
# uninstalled a gem, it is removed from that list.
#
def remove_all(list) def remove_all(list)
list.dup.each { |gem| remove(gem, list) } list.dup.each { |spec| remove spec, list }
end end
# ##
# spec:: the spec of the gem to be uninstalled # spec:: the spec of the gem to be uninstalled
# list:: the list of all such gems # list:: the list of all such gems
# #
# Warning: this method modifies the +list+ parameter. Once it has # Warning: this method modifies the +list+ parameter. Once it has
# uninstalled a gem, it is removed from that list. # uninstalled a gem, it is removed from that list.
#
def remove(spec, list) def remove(spec, list)
unless dependencies_ok? spec then unless dependencies_ok? spec then
raise Gem::DependencyRemovalException, raise Gem::DependencyRemovalException,
@ -134,10 +135,11 @@ class Gem::Uninstaller
end end
unless path_ok? spec then unless path_ok? spec then
alert("In order to remove #{spec.name}, please execute:\n" \ e = Gem::GemNotInHomeException.new \
"\tgem uninstall #{spec.name} --install-dir=#{spec.installation_path}")
raise Gem::GemNotInHomeException,
"Gem is not installed in directory #{@gem_home}" "Gem is not installed in directory #{@gem_home}"
e.spec = spec
raise e
end end
raise Gem::FilePermissionError, spec.installation_path unless raise Gem::FilePermissionError, spec.installation_path unless
@ -182,8 +184,8 @@ class Gem::Uninstaller
def dependencies_ok?(spec) def dependencies_ok?(spec)
return true if @force_ignore return true if @force_ignore
srcindex = Gem::SourceIndex.from_installed_gems source_index = Gem::SourceIndex.from_installed_gems
deplist = Gem::DependencyList.from_source_index srcindex deplist = Gem::DependencyList.from_source_index source_index
deplist.ok_to_remove?(spec.full_name) || ask_if_ok(spec) deplist.ok_to_remove?(spec.full_name) || ask_if_ok(spec)
end end

View file

@ -68,7 +68,7 @@ module Gem
include DefaultUserInteraction include DefaultUserInteraction
[ [
:choose_from_list, :ask, :ask_yes_no, :say, :alert, :alert_warning, :choose_from_list, :ask, :ask_yes_no, :say, :alert, :alert_warning,
:alert_error, :terminate_interaction!, :terminate_interaction :alert_error, :terminate_interaction
].each do |methname| ].each do |methname|
class_eval %{ class_eval %{
def #{methname}(*args) def #{methname}(*args)
@ -182,16 +182,10 @@ module Gem
ask(question) if question ask(question) if question
end end
# Terminate the application immediately without running any exit
# handlers.
def terminate_interaction!(status=-1)
exit!(status)
end
# Terminate the appliation normally, running any exit handlers # Terminate the appliation normally, running any exit handlers
# that might have been defined. # that might have been defined.
def terminate_interaction(status=0) def terminate_interaction(status = 0)
exit(status) raise Gem::SystemExitException, status
end end
# Return a progress reporter object # Return a progress reporter object

View file

@ -0,0 +1,86 @@
require 'test/unit'
require File.join(File.expand_path(File.dirname(__FILE__)), 'gemutilities')
require 'rubygems/installer'
class Gem::Installer
attr_accessor :gem_dir
attr_writer :format
attr_writer :gem_home
attr_writer :env_shebang
attr_writer :ignore_dependencies
attr_writer :format_executable
attr_writer :security_policy
attr_writer :spec
attr_writer :wrappers
end
class GemInstallerTestCase < RubyGemTestCase
def setup
super
@spec = quick_gem "a"
@gem = File.join @tempdir, "#{@spec.full_name}.gem"
util_build_gem @spec
FileUtils.mv File.join(@gemhome, 'cache', "#{@spec.full_name}.gem"),
@tempdir
@installer = Gem::Installer.new @gem
@installer.gem_dir = util_gem_dir
@installer.gem_home = @gemhome
@installer.spec = @spec
end
def util_gem_bindir(version = '2')
File.join util_gem_dir(version), "bin"
end
def util_gem_dir(version = '2')
File.join @gemhome, "gems", "a-#{version}" # HACK
end
def util_inst_bindir
File.join @gemhome, "bin"
end
def util_make_exec(version = '2', shebang = "#!/usr/bin/ruby")
@spec.executables = ["my_exec"]
FileUtils.mkdir_p util_gem_bindir(version)
exec_file = @installer.formatted_program_filename "my_exec"
exec_path = File.join util_gem_bindir(version), exec_file
File.open exec_path, 'w' do |f|
f.puts shebang
end
end
def util_setup_gem(ui = @ui) # HACK fix use_ui to make this automatic
@spec.files = File.join('lib', 'code.rb')
@spec.executables << 'executable'
@spec.extensions << File.join('ext', 'a', 'mkrf_conf.rb')
Dir.chdir @tempdir do
FileUtils.mkdir_p 'bin'
FileUtils.mkdir_p 'lib'
FileUtils.mkdir_p File.join('ext', 'a')
File.open File.join('bin', 'executable'), 'w' do |f| f.puts '1' end
File.open File.join('lib', 'code.rb'), 'w' do |f| f.puts '1' end
File.open File.join('ext', 'a', 'mkrf_conf.rb'), 'w' do |f|
f << <<-EOF
File.open 'Rakefile', 'w' do |rf| rf.puts "task :default" end
EOF
end
use_ui ui do
FileUtils.rm @gem
Gem::Builder.new(@spec).build
end
end
@installer = Gem::Installer.new @gem
end
end

View file

@ -0,0 +1,146 @@
require File.join(File.expand_path(File.dirname(__FILE__)), 'gemutilities')
require 'rubygems/package'
class File
# straight from setup.rb
def self.dir?(path)
# for corrupted windows stat()
File.directory?((path[-1,1] == '/') ? path : path + '/')
end
def self.read_b(name)
File.open(name, "rb") { |f| f.read }
end
end
class TarTestCase < RubyGemTestCase
def ASCIIZ(str, length)
str + "\0" * (length - str.length)
end
def SP(s)
s + " "
end
def SP_Z(s)
s + " \0"
end
def Z(s)
s + "\0"
end
def assert_headers_equal(expected, actual)
expected = expected.to_s unless String === expected
actual = actual.to_s unless String === actual
fields = %w[
name 100
mode 8
uid 8
gid 8
size 12
mtime 12
checksum 8
typeflag 1
linkname 100
magic 6
version 2
uname 32
gname 32
devmajor 8
devminor 8
prefix 155
]
offset = 0
until fields.empty? do
name = fields.shift
length = fields.shift.to_i
if name == "checksum" then
chksum_off = offset
offset += length
next
end
assert_equal expected[offset, length], actual[offset, length],
"Field #{name} of the tar header differs."
offset += length
end
assert_equal expected[chksum_off, 8], actual[chksum_off, 8]
end
def calc_checksum(header)
sum = header.unpack("C*").inject{|s,a| s + a}
SP(Z(to_oct(sum, 6)))
end
def header(type, fname, dname, length, mode, checksum = nil)
checksum ||= " " * 8
arr = [ # struct tarfile_entry_posix
ASCIIZ(fname, 100), # char name[100]; ASCII + (Z unless filled)
Z(to_oct(mode, 7)), # char mode[8]; 0 padded, octal null
Z(to_oct(0, 7)), # char uid[8]; ditto
Z(to_oct(0, 7)), # char gid[8]; ditto
Z(to_oct(length, 11)), # char size[12]; 0 padded, octal, null
Z(to_oct(0, 11)), # char mtime[12]; 0 padded, octal, null
checksum, # char checksum[8]; 0 padded, octal, null, space
type, # char typeflag[1]; file: "0" dir: "5"
"\0" * 100, # char linkname[100]; ASCII + (Z unless filled)
"ustar\0", # char magic[6]; "ustar\0"
"00", # char version[2]; "00"
ASCIIZ("wheel", 32), # char uname[32]; ASCIIZ
ASCIIZ("wheel", 32), # char gname[32]; ASCIIZ
Z(to_oct(0, 7)), # char devmajor[8]; 0 padded, octal, null
Z(to_oct(0, 7)), # char devminor[8]; 0 padded, octal, null
ASCIIZ(dname, 155) # char prefix[155]; ASCII + (Z unless filled)
]
format = "C100C8C8C8C12C12C8CC100C6C2C32C32C8C8C155"
h = if RUBY_VERSION >= "1.9" then
arr.join
else
arr = arr.join("").split(//).map{|x| x[0]}
arr.pack format
end
ret = h + "\0" * (512 - h.size)
assert_equal(512, ret.size)
ret
end
def tar_dir_header(name, prefix, mode)
h = header("5", name, prefix, 0, mode)
checksum = calc_checksum(h)
header("5", name, prefix, 0, mode, checksum)
end
def tar_file_header(fname, dname, mode, length)
h = header("0", fname, dname, length, mode)
checksum = calc_checksum(h)
header("0", fname, dname, length, mode, checksum)
end
def to_oct(n, pad_size)
"%0#{pad_size}o" % n
end
def util_entry(tar)
io = TempIO.new tar
header = Gem::Package::TarHeader.from io
entry = Gem::Package::TarReader::Entry.new header, io
end
def util_dir_entry
util_entry tar_dir_header("foo", "bar", 0)
end
end

View file

@ -10,9 +10,10 @@ at_exit { $SAFE = 1 }
require 'fileutils' require 'fileutils'
require 'test/unit' require 'test/unit'
require 'tmpdir' require 'tmpdir'
require 'tempfile'
require 'uri' require 'uri'
require 'rubygems/gem_open_uri'
require 'rubygems/source_info_cache' require 'rubygems/source_info_cache'
require 'rubygems/package'
require File.join(File.expand_path(File.dirname(__FILE__)), 'mockgemui') require File.join(File.expand_path(File.dirname(__FILE__)), 'mockgemui')
@ -56,6 +57,20 @@ class FakeFetcher
data.respond_to?(:call) ? data.call : data.length data.respond_to?(:call) ? data.call : data.length
end end
def download spec, source_uri, install_dir = Gem.dir
name = "#{spec.full_name}.gem"
path = File.join(install_dir, 'cache', name)
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 end
class RubyGemTestCase < Test::Unit::TestCase class RubyGemTestCase < Test::Unit::TestCase
@ -76,6 +91,7 @@ class RubyGemTestCase < Test::Unit::TestCase
@gemhome = File.join @tempdir, "gemhome" @gemhome = File.join @tempdir, "gemhome"
@gemcache = File.join(@gemhome, "source_cache") @gemcache = File.join(@gemhome, "source_cache")
@usrcache = File.join(@gemhome, ".gem", "user_cache") @usrcache = File.join(@gemhome, ".gem", "user_cache")
@latest_usrcache = File.join(@gemhome, ".gem", "latest_user_cache")
FileUtils.mkdir_p @gemhome FileUtils.mkdir_p @gemhome
@ -101,6 +117,11 @@ class RubyGemTestCase < Test::Unit::TestCase
end end
@marshal_version = "#{Marshal::MAJOR_VERSION}.#{Marshal::MINOR_VERSION}" @marshal_version = "#{Marshal::MAJOR_VERSION}.#{Marshal::MINOR_VERSION}"
@private_key = File.expand_path File.join(File.dirname(__FILE__),
'private_key.pem')
@public_cert = File.expand_path File.join(File.dirname(__FILE__),
'public_cert.pem')
end end
def teardown def teardown
@ -135,25 +156,52 @@ class RubyGemTestCase < Test::Unit::TestCase
end end
def prep_cache_files(lc) def prep_cache_files(lc)
[ [lc.system_cache_file, 'sys'], @usr_si ||= Gem::SourceIndex.new
[lc.user_cache_file, 'usr'], @usr_sice ||= Gem::SourceInfoCacheEntry.new @usr_si, 0
].each do |fn, data|
FileUtils.mkdir_p File.dirname(fn).untaint @sys_si ||= Gem::SourceIndex.new
open(fn.dup.untaint, "wb") { |f| f.write(Marshal.dump({'key' => data})) } @sys_sice ||= Gem::SourceInfoCacheEntry.new @sys_si, 0
latest_si = Gem::SourceIndex.new
latest_si.add_specs(*@sys_si.latest_specs)
latest_sys_sice = Gem::SourceInfoCacheEntry.new latest_si, 0
latest_si = Gem::SourceIndex.new
latest_si.add_specs(*@usr_si.latest_specs)
latest_usr_sice = Gem::SourceInfoCacheEntry.new latest_si, 0
[ [lc.system_cache_file, @sys_sice],
[lc.latest_system_cache_file, latest_sys_sice],
[lc.user_cache_file, @usr_sice],
[lc.latest_user_cache_file, latest_usr_sice],
].each do |filename, data|
FileUtils.mkdir_p File.dirname(filename).untaint
open filename.dup.untaint, 'wb' do |f|
f.write Marshal.dump({ @gem_repo => data })
end
end end
end end
def read_cache(fn) def read_cache(path)
open(fn.dup.untaint) { |f| Marshal.load f.read } open path.dup.untaint, 'rb' do |io|
Marshal.load io.read
end
end
def read_binary(path)
Gem.read_binary path
end end
def write_file(path) def write_file(path)
path = File.join(@gemhome, path) path = File.join(@gemhome, path)
dir = File.dirname path dir = File.dirname path
FileUtils.mkdir_p dir FileUtils.mkdir_p dir
File.open(path, "w") { |io|
yield(io) open path, 'wb' do |io|
} yield io
end
path path
end end
@ -204,6 +252,23 @@ class RubyGemTestCase < Test::Unit::TestCase
end end
end end
def util_gem(name, version, &block)
spec = quick_gem(name, version, &block)
util_build_gem spec
cache_file = File.join @tempdir, 'gems', "#{spec.original_name}.gem"
FileUtils.mv File.join(@gemhome, 'cache', "#{spec.original_name}.gem"),
cache_file
FileUtils.rm File.join(@gemhome, 'specifications',
"#{spec.full_name}.gemspec")
spec.loaded_from = nil
spec.loaded = false
[spec, cache_file]
end
def util_make_gems def util_make_gems
init = proc do |s| init = proc do |s|
s.files = %w[lib/code.rb] s.files = %w[lib/code.rb]
@ -212,6 +277,7 @@ class RubyGemTestCase < Test::Unit::TestCase
@a1 = quick_gem('a', '1', &init) @a1 = quick_gem('a', '1', &init)
@a2 = quick_gem('a', '2', &init) @a2 = quick_gem('a', '2', &init)
@a_evil9 = quick_gem('a_evil', '9', &init)
@b2 = quick_gem('b', '2', &init) @b2 = quick_gem('b', '2', &init)
@c1_2 = quick_gem('c', '1.2', &init) @c1_2 = quick_gem('c', '1.2', &init)
@pl1 = quick_gem 'pl', '1' do |s| # l for legacy @pl1 = quick_gem 'pl', '1' do |s| # l for legacy
@ -227,7 +293,7 @@ class RubyGemTestCase < Test::Unit::TestCase
write_file File.join(*%W[gems #{@c1_2.original_name} lib code.rb]) do end write_file File.join(*%W[gems #{@c1_2.original_name} lib code.rb]) do end
write_file File.join(*%W[gems #{@pl1.original_name} lib code.rb]) do end write_file File.join(*%W[gems #{@pl1.original_name} lib code.rb]) do end
[@a1, @a2, @b2, @c1_2, @pl1].each { |spec| util_build_gem spec } [@a1, @a2, @a_evil9, @b2, @c1_2, @pl1].each { |spec| util_build_gem spec }
FileUtils.rm_r File.join(@gemhome, 'gems', @pl1.original_name) FileUtils.rm_r File.join(@gemhome, 'gems', @pl1.original_name)
@ -256,32 +322,19 @@ class RubyGemTestCase < Test::Unit::TestCase
@fetcher = FakeFetcher.new @fetcher = FakeFetcher.new
@fetcher.uri = @uri @fetcher.uri = @uri
@gem1 = quick_gem 'gem_one' do |gem| util_make_gems
gem.files = %w[Rakefile lib/gem_one.rb]
end
@gem2 = quick_gem 'gem_two' do |gem| @all_gems = [@a1, @a2, @a_evil9, @b2, @c1_2].sort
gem.files = %w[Rakefile lib/gem_two.rb]
end
@gem3 = quick_gem 'gem_three' do |gem| # missing gem
gem.files = %w[Rakefile lib/gem_three.rb]
end
# this gem has a higher version and longer name than the gem we want
@gem4 = quick_gem 'gem_one_evil', '666' do |gem|
gem.files = %w[Rakefile lib/gem_one.rb]
end
@all_gems = [@gem1, @gem2, @gem3, @gem4].sort
@all_gem_names = @all_gems.map { |gem| gem.full_name } @all_gem_names = @all_gems.map { |gem| gem.full_name }
gem_names = [@gem1.full_name, @gem2.full_name, @gem4.full_name] gem_names = [@a1.full_name, @a2.full_name, @b2.full_name]
@gem_names = gem_names.sort.join("\n") @gem_names = gem_names.sort.join("\n")
@source_index = Gem::SourceIndex.new @gem1.full_name => @gem1, @source_index = Gem::SourceIndex.new
@gem2.full_name => @gem2, @source_index.add_spec @a1
@gem4.full_name => @gem4 @source_index.add_spec @a2
@source_index.add_spec @a_evil9
@source_index.add_spec @c1_2
Gem::RemoteFetcher.instance_variable_set :@fetcher, @fetcher Gem::RemoteFetcher.instance_variable_set :@fetcher, @fetcher
end end
@ -294,7 +347,12 @@ class RubyGemTestCase < Test::Unit::TestCase
sice = Gem::SourceInfoCacheEntry.new si, 0 sice = Gem::SourceInfoCacheEntry.new si, 0
sic = Gem::SourceInfoCache.new sic = Gem::SourceInfoCache.new
sic.set_cache_data( { @gem_repo => sice } ) sic.set_cache_data( { @gem_repo => sice } )
sic.update
sic.write_cache
sic.reset_cache_data
Gem::SourceInfoCache.instance_variable_set :@cache, sic Gem::SourceInfoCache.instance_variable_set :@cache, sic
si si
end end
@ -313,3 +371,30 @@ class RubyGemTestCase < Test::Unit::TestCase
end 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

View file

@ -15,9 +15,8 @@ class MockGemUi < Gem::StreamUI
def initialize(input="") def initialize(input="")
super(StringIO.new(input), StringIO.new, StringIO.new) super(StringIO.new(input), StringIO.new, StringIO.new)
@terminated = false @terminated = false
@banged = false
end end
def input def input
@ins.string @ins.string
end end
@ -30,22 +29,15 @@ class MockGemUi < Gem::StreamUI
@errs.string @errs.string
end end
def banged?
@banged
end
def terminated? def terminated?
@terminated @terminated
end end
def terminate_interaction!(status=1)
@terminated = true
@banged = true
fail TermError
end
def terminate_interaction(status=0) def terminate_interaction(status=0)
@terminated = true @terminated = true
fail TermError
raise TermError
end end
end end

View file

@ -0,0 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEAm24C6xixiAxO+i1f3L8XRMwrmLkt6BvT60mZ7g8HsklH3af7
KNHA6vo/G6sujs2UsNO4HY8BTEneiVOXXWQlcsJ+Z5wEPlIu4zFueAmLefx+n9lE
ulNIUDoyUenKX4spoMRnX8k4lXL05ho/6JFq0JdDY2DmAaQ4vvTz5mh9kZiybtHQ
fzcpbA51uY+sjdQRCPDHyUUfh0SmWJlLYMwcBdVeCiGUPBLi+iP5x1btO4uiJK6Q
IMaV1H3SUCYtKGQKl7qwFd8k8ZBcHYOtmK61tupg3vqWQc0em6SxPj5lws8+1MVK
twBNIDx24jF4ntxBRNKMZ7FN5SHbobAgDYkPAQIDAQABAoIBAGQilgK8X/PUajVH
clEXU3hhSV0VQHwfIYKeYms6h6zXBVPKW0dLC0zXeDztJgueasMZQ67XaPCrTpGO
px/l2zJ6F1HM8/bqn4aDXDY9f/xRLYryQRMBgL8fHzgitNylHWaT4j2Vt7yg2SI9
mxrMRNKqASJPVR+Nm3l6+n9gpjVb99wEucWplPPHI6KhXLYPZOqSwt+zaH5roz3k
UQmMs0Bs4hF1SzVl0n+KNoXHOwswVrmBWXgWvm2OhnwY2e26jfejc8toJc/ShAJ7
C9exnrdimcgEKbd22Sum4G00CDYhcrG5LHHqkgwifcAEVctrvBZBZHGgpxlO8a8U
eF2Vr7kCgYEAykdrBlzp7Fn9xzUInBQ3NXTTYAq51lpuJdmHQmPuTSY0buoHkd9f
xbUCZ2qR9QAesrx4hI0qGLetc8IOKDoWx2rPepCCvO3Kx61o1SB5fAvBue03qVoq
HqACX3Uk24Em8zAz9xuP13ETH/wU7sUbUxRHMCre6ZDmlxn4g5l+Nl8CgYEAxLVl
22yBx0dfRr3UsHY9rxll2gIlnfnYfiJzq8wetzt/TfztRV5ILz7FyWqL5d7IoqkA
fT2V4HAasRJASnKohwJe7z5M/H2ExwkGNFvY+jefb2CoUl5WouK9AlhbqBk3zmHi
sY5GqQkAp/kHMntEin+sErJw6mkgAGdser3a9p8CgYEAqi31w++tunRnxw4+RRnY
7Pdx0k6T1NxV6TAe1ONAHNY0rM/mOHqml65W7GzDiU1lhlh8SIB/VzZJDqfHw15D
xdh94A7uf0bMILwrA4wDyTIW9Xa3Kpq57vQNqwPiU25QN69pOM+Ob+IpBfLOJafc
+kOINOUMj5Kh/aQS6Zzci58CgYEAk24dlFKEBjbRCvU2FrfYTYcsljPru7ZJc2gg
588J6m0WYf5CWy5pzbcviGFpzvSlzXv7GOLylQ+QgcxbETFUbDPzsT4xd0AgJwj1
dIKuYgMUZOa94VZBer2TydEtiRS1heJJhKhM/1329u4nXceTvHYqIq1JAfeee48I
eAoZtaMCgYBz1FjWFQnMTD5nmyPEEZneoBPAR5+9jwOps+IYOoHtazoMFszzd0qo
JZW3Ihn9KRrVSxfFApKS/ZwjiZ+tJUk7DE/v/0l0sszefY7s8b0pL1lpeZSoL71e
QoG1WLXUiDV3BRlmyOAF1h3p12KRTLgwubN51ajECwcs3QwE+ZT8Gg==
-----END RSA PRIVATE KEY-----

View file

@ -0,0 +1,20 @@
-----BEGIN CERTIFICATE-----
MIIDNjCCAh6gAwIBAgIBADANBgkqhkiG9w0BAQUFADBBMRAwDgYDVQQDDAdkcmJy
YWluMRgwFgYKCZImiZPyLGQBGRYIc2VnbWVudDcxEzARBgoJkiaJk/IsZAEZFgNu
ZXQwHhcNMDcxMjIxMDIwNDE0WhcNMDgxMjIwMDIwNDE0WjBBMRAwDgYDVQQDDAdk
cmJyYWluMRgwFgYKCZImiZPyLGQBGRYIc2VnbWVudDcxEzARBgoJkiaJk/IsZAEZ
FgNuZXQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCbbgLrGLGIDE76
LV/cvxdEzCuYuS3oG9PrSZnuDweySUfdp/so0cDq+j8bqy6OzZSw07gdjwFMSd6J
U5ddZCVywn5nnAQ+Ui7jMW54CYt5/H6f2US6U0hQOjJR6cpfiymgxGdfyTiVcvTm
Gj/okWrQl0NjYOYBpDi+9PPmaH2RmLJu0dB/NylsDnW5j6yN1BEI8MfJRR+HRKZY
mUtgzBwF1V4KIZQ8EuL6I/nHVu07i6IkrpAgxpXUfdJQJi0oZAqXurAV3yTxkFwd
g62YrrW26mDe+pZBzR6bpLE+PmXCzz7UxUq3AE0gPHbiMXie3EFE0oxnsU3lIduh
sCANiQ8BAgMBAAGjOTA3MAkGA1UdEwQCMAAwCwYDVR0PBAQDAgSwMB0GA1UdDgQW
BBS5k4Z75VSpdM0AclG2UvzFA/VW5DANBgkqhkiG9w0BAQUFAAOCAQEAHagT4lfX
kP/hDaiwGct7XPuVGbrOsKRVD59FF5kETBxEc9UQ1clKWngf8JoVuEoKD774dW19
bU0GOVWO+J6FMmT/Cp7nuFJ79egMf/gy4gfUfQMuvfcr6DvZUPIs9P/TlK59iMYF
DIOQ3DxdF3rMzztNUCizN4taVscEsjCcgW6WkUJnGdqlu3OHWpQxZBJkBTjPCoc6
UW6on70SFPmAy/5Cq0OJNGEWBfgD9q7rrs/X8GGwUWqXb85RXnUVi/P8Up75E0ag
14jEc90kN+C7oI/AGCBN0j6JnEtYIEJZibjjDJTSMWlUKKkj30kq7hlUC2CepJ4v
x52qPcexcYZR7w==
-----END CERTIFICATE-----

View file

@ -19,6 +19,7 @@ class TestGem < RubyGemTestCase
expected = [ expected = [
File.join(@gemhome, *%W[gems #{@a1.full_name} lib]), File.join(@gemhome, *%W[gems #{@a1.full_name} lib]),
File.join(@gemhome, *%W[gems #{@a2.full_name} lib]), File.join(@gemhome, *%W[gems #{@a2.full_name} lib]),
File.join(@gemhome, *%W[gems #{@a_evil9.full_name} lib]),
File.join(@gemhome, *%W[gems #{@b2.full_name} lib]), File.join(@gemhome, *%W[gems #{@b2.full_name} lib]),
File.join(@gemhome, *%W[gems #{@c1_2.full_name} lib]), File.join(@gemhome, *%W[gems #{@c1_2.full_name} lib]),
File.join(@gemhome, *%W[gems #{@pl1.full_name} lib]), File.join(@gemhome, *%W[gems #{@pl1.full_name} lib]),
@ -213,6 +214,7 @@ class TestGem < RubyGemTestCase
expected = [ expected = [
File.join(@gemhome, *%W[gems #{@a2.full_name} lib]), File.join(@gemhome, *%W[gems #{@a2.full_name} lib]),
File.join(@gemhome, *%W[gems #{@a_evil9.full_name} lib]),
File.join(@gemhome, *%W[gems #{@b2.full_name} lib]), File.join(@gemhome, *%W[gems #{@b2.full_name} lib]),
File.join(@gemhome, *%W[gems #{@c1_2.full_name} lib]), File.join(@gemhome, *%W[gems #{@c1_2.full_name} lib]),
File.join(@gemhome, *%W[gems #{@pl1.full_name} lib]), File.join(@gemhome, *%W[gems #{@pl1.full_name} lib]),
@ -226,7 +228,7 @@ class TestGem < RubyGemTestCase
install_gem foo install_gem foo
Gem.source_index = nil Gem.source_index = nil
Gem.activate 'foo', false Gem.activate 'foo'
assert_equal true, Gem.loaded_specs.keys.include?('foo') assert_equal true, Gem.loaded_specs.keys.include?('foo')
end end
@ -235,9 +237,29 @@ class TestGem < RubyGemTestCase
assert_equal [Gem.dir], Gem.path assert_equal [Gem.dir], Gem.path
end end
def test_self_path_APPLE_GEM_HOME
Gem.clear_paths
Gem.const_set :APPLE_GEM_HOME, '/tmp/apple_gem_home'
assert Gem.path.include?('/tmp/apple_gem_home')
ensure
Gem.send :remove_const, :APPLE_GEM_HOME
end
def test_self_path_APPLE_GEM_HOME_GEM_PATH
Gem.clear_paths
ENV['GEM_PATH'] = @gemhome
Gem.const_set :APPLE_GEM_HOME, '/tmp/apple_gem_home'
assert !Gem.path.include?('/tmp/apple_gem_home')
ensure
Gem.send :remove_const, :APPLE_GEM_HOME
end
def test_self_path_ENV_PATH def test_self_path_ENV_PATH
Gem.clear_paths Gem.clear_paths
path_count = Gem.path.size path_count = Gem.path.size
path_count -= 1 if defined? APPLE_GEM_HOME
Gem.clear_paths Gem.clear_paths
util_ensure_gem_dirs util_ensure_gem_dirs
@ -257,8 +279,8 @@ class TestGem < RubyGemTestCase
ENV['GEM_PATH'] = dirs.join File::PATH_SEPARATOR ENV['GEM_PATH'] = dirs.join File::PATH_SEPARATOR
assert_equal @gemhome, Gem.dir assert_equal @gemhome, Gem.dir
paths = [Gem.dir] paths = [Gem.dir]
paths << APPLE_GEM_HOME if defined? APPLE_GEM_HOME
assert_equal @additional + paths, Gem.path assert_equal @additional + paths, Gem.path
end end
@ -270,8 +292,8 @@ class TestGem < RubyGemTestCase
ENV['GEM_PATH'] = @additional.join(File::PATH_SEPARATOR) ENV['GEM_PATH'] = @additional.join(File::PATH_SEPARATOR)
assert_equal @gemhome, Gem.dir assert_equal @gemhome, Gem.dir
paths = [Gem.dir] paths = [Gem.dir]
paths.insert(0, APPLE_GEM_HOME) if defined? APPLE_GEM_HOME
assert_equal @additional + paths, Gem.path assert_equal @additional + paths, Gem.path
end end
@ -284,6 +306,18 @@ class TestGem < RubyGemTestCase
assert_equal File.dirname(File.dirname(file_name)), Gem.prefix assert_equal File.dirname(File.dirname(file_name)), Gem.prefix
end end
def test_self_prefix_odd
orig_sitelibdir = Gem::ConfigMap[:sitelibdir]
file_name = File.expand_path __FILE__
prefix = File.join File.dirname(File.dirname(file_name)), 'lib'
Gem::ConfigMap[:sitelibdir] = prefix.sub(/[\w]\//, '\&/')
assert_nil Gem.prefix
ensure
Gem::ConfigMap[:sitelibdir] = orig_sitelibdir
end
def test_self_required_location def test_self_required_location
util_make_gems util_make_gems
@ -295,6 +329,13 @@ class TestGem < RubyGemTestCase
Gem.required_location("a", "code.rb", "= 2") Gem.required_location("a", "code.rb", "= 2")
end end
def test_self_ruby_version
version = RUBY_VERSION.dup
version << ".#{RUBY_PATCHLEVEL}" if defined? RUBY_PATCHLEVEL
assert_equal Gem::Version.new(version), Gem.ruby_version
end
def test_self_searcher def test_self_searcher
assert_kind_of Gem::GemPathSearcher, Gem.searcher assert_kind_of Gem::GemPathSearcher, Gem.searcher
end end

View file

@ -67,11 +67,12 @@ class TestGemCommandManager < RubyGemTestCase
assert_equal true, check_options[:wrappers] assert_equal true, check_options[:wrappers]
assert_equal Gem::Requirement.default, check_options[:version] assert_equal Gem::Requirement.default, check_options[:version]
assert_equal Gem.dir, check_options[:install_dir] assert_equal Gem.dir, check_options[:install_dir]
assert_equal nil, check_options[:bin_dir]
#check settings #check settings
check_options = nil check_options = nil
@command_manager.process_args( @command_manager.process_args(
"install --force --test --local --rdoc --install-dir . --version 3.0 --no-wrapper") "install --force --test --local --rdoc --install-dir . --version 3.0 --no-wrapper --bindir . ")
assert_equal true, check_options[:test] assert_equal true, check_options[:test]
assert_equal true, check_options[:generate_rdoc] assert_equal true, check_options[:generate_rdoc]
assert_equal true, check_options[:force] assert_equal true, check_options[:force]
@ -79,6 +80,7 @@ class TestGemCommandManager < RubyGemTestCase
assert_equal false, check_options[:wrappers] assert_equal false, check_options[:wrappers]
assert_equal Gem::Requirement.new('3.0'), check_options[:version] assert_equal Gem::Requirement.new('3.0'), check_options[:version]
assert_equal Dir.pwd, check_options[:install_dir] assert_equal Dir.pwd, check_options[:install_dir]
assert_equal Dir.pwd, check_options[:bin_dir]
#check remote domain #check remote domain
check_options = nil check_options = nil
@ -164,7 +166,7 @@ class TestGemCommandManager < RubyGemTestCase
#check defaults #check defaults
@command_manager.process_args("query") @command_manager.process_args("query")
assert_equal(/.*/, check_options[:name]) assert_equal(//, check_options[:name])
assert_equal :local, check_options[:domain] assert_equal :local, check_options[:domain]
assert_equal false, check_options[:details] assert_equal false, check_options[:details]

View file

@ -62,6 +62,21 @@ class TestGemCommandsEnvironmentCommand < RubyGemTestCase
assert_equal '', @ui.error assert_equal '', @ui.error
end end
def test_execute_gempath_multiple
Gem.clear_paths
path = [@gemhome, "#{@gemhome}2"].join File::PATH_SEPARATOR
ENV['GEM_PATH'] = path
@cmd.send :handle_options, %w[gempath]
use_ui @ui do
@cmd.execute
end
assert_equal "#{Gem.path.join File::PATH_SEPARATOR}\n", @ui.output
assert_equal '', @ui.error
end
def test_execute_packageversion def test_execute_packageversion
@cmd.send :handle_options, %w[packageversion] @cmd.send :handle_options, %w[packageversion]

View file

@ -15,13 +15,12 @@ class TestGemCommandsFetchCommand < RubyGemTestCase
def test_execute def test_execute
util_setup_fake_fetcher util_setup_fake_fetcher
util_build_gem @gem1
@fetcher.data["#{@gem_repo}/Marshal.#{@marshal_version}"] = @fetcher.data["#{@gem_repo}/Marshal.#{@marshal_version}"] =
@source_index.dump @source_index.dump
@fetcher.data["#{@gem_repo}/gems/#{@gem1.full_name}.gem"] = @fetcher.data["#{@gem_repo}/gems/#{@a2.full_name}.gem"] =
File.read(File.join(@gemhome, 'cache', "#{@gem1.full_name}.gem")) File.read(File.join(@gemhome, 'cache', "#{@a2.full_name}.gem"))
@cmd.options[:args] = [@gem1.name] @cmd.options[:args] = [@a2.name]
use_ui @ui do use_ui @ui do
Dir.chdir @tempdir do Dir.chdir @tempdir do
@ -29,7 +28,7 @@ class TestGemCommandsFetchCommand < RubyGemTestCase
end end
end end
assert File.exist?(File.join(@tempdir, "#{@gem1.full_name}.gem")) assert File.exist?(File.join(@tempdir, "#{@a2.full_name}.gem"))
end end
end end

View file

@ -34,25 +34,26 @@ class TestGemCommandsInstallCommand < RubyGemTestCase
util_setup_fake_fetcher util_setup_fake_fetcher
@cmd.options[:domain] = :local @cmd.options[:domain] = :local
gem1 = quick_gem 'gem_one' FileUtils.mv File.join(@gemhome, 'cache', "#{@a2.full_name}.gem"),
util_build_gem gem1
FileUtils.mv File.join(@gemhome, 'cache', "#{@gem1.full_name}.gem"),
File.join(@tempdir) File.join(@tempdir)
@cmd.options[:args] = [gem1.name] @cmd.options[:args] = [@a2.name]
use_ui @ui do use_ui @ui do
orig_dir = Dir.pwd orig_dir = Dir.pwd
begin begin
Dir.chdir @tempdir Dir.chdir @tempdir
@cmd.execute e = assert_raises Gem::SystemExitException do
@cmd.execute
end
assert_equal 0, e.exit_code
ensure ensure
Dir.chdir orig_dir Dir.chdir orig_dir
end end
end end
out = @ui.output.split "\n" out = @ui.output.split "\n"
assert_equal "Successfully installed #{@gem1.full_name}", out.shift assert_equal "Successfully installed #{@a2.full_name}", out.shift
assert_equal "1 gem installed", out.shift assert_equal "1 gem installed", out.shift
assert out.empty?, out.inspect assert out.empty?, out.inspect
end end
@ -61,14 +62,17 @@ class TestGemCommandsInstallCommand < RubyGemTestCase
util_setup_fake_fetcher util_setup_fake_fetcher
@cmd.options[:domain] = :local @cmd.options[:domain] = :local
@cmd.options[:args] = %w[gem_one] @cmd.options[:args] = %w[no_such_gem]
use_ui @ui do use_ui @ui do
@cmd.execute e = assert_raises Gem::SystemExitException do
@cmd.execute
end
assert_equal 2, e.exit_code
end end
# HACK no repository was checked # HACK no repository was checked
assert_equal "ERROR: could not find gem_one locally or in a repository\n", assert_equal "ERROR: could not find no_such_gem locally or in a repository\n",
@ui.error @ui.error
end end
@ -88,7 +92,10 @@ class TestGemCommandsInstallCommand < RubyGemTestCase
@cmd.options[:args] = %w[nonexistent] @cmd.options[:args] = %w[nonexistent]
use_ui @ui do use_ui @ui do
@cmd.execute e = assert_raises Gem::SystemExitException do
@cmd.execute
end
assert_equal 2, e.exit_code
end end
assert_equal "ERROR: could not find nonexistent locally or in a repository\n", assert_equal "ERROR: could not find nonexistent locally or in a repository\n",
@ -100,25 +107,27 @@ class TestGemCommandsInstallCommand < RubyGemTestCase
@cmd.options[:generate_ri] = true @cmd.options[:generate_ri] = true
util_setup_fake_fetcher util_setup_fake_fetcher
util_build_gem @gem1
@fetcher.data["#{@gem_repo}/Marshal.#{@marshal_version}"] = @fetcher.data["#{@gem_repo}/Marshal.#{@marshal_version}"] =
@source_index.dump @source_index.dump
@fetcher.data["#{@gem_repo}/gems/gem_one-0.0.2.gem"] = @fetcher.data["#{@gem_repo}/gems/#{@a2.full_name}.gem"] =
File.read(File.join(@gemhome, 'cache', "#{@gem1.full_name}.gem")) read_binary(File.join(@gemhome, 'cache', "#{@a2.full_name}.gem"))
@cmd.options[:args] = [@gem1.name] @cmd.options[:args] = [@a2.name]
use_ui @ui do use_ui @ui do
@cmd.execute e = assert_raises Gem::SystemExitException do
@cmd.execute
end
assert_equal 0, e.exit_code
end end
out = @ui.output.split "\n" out = @ui.output.split "\n"
assert_match %r|Bulk updating|, out.shift assert_match %r|Bulk updating|, out.shift
assert_equal "Successfully installed #{@gem1.full_name}", out.shift assert_equal "Successfully installed #{@a2.full_name}", out.shift
assert_equal "1 gem installed", out.shift assert_equal "1 gem installed", out.shift
assert_equal "Installing ri documentation for #{@gem1.full_name}...", assert_equal "Installing ri documentation for #{@a2.full_name}...",
out.shift out.shift
assert_equal "Installing RDoc documentation for #{@gem1.full_name}...", assert_equal "Installing RDoc documentation for #{@a2.full_name}...",
out.shift out.shift
assert out.empty?, out.inspect assert out.empty?, out.inspect
end end
@ -127,31 +136,30 @@ class TestGemCommandsInstallCommand < RubyGemTestCase
util_setup_fake_fetcher util_setup_fake_fetcher
@cmd.options[:domain] = :local @cmd.options[:domain] = :local
gem1 = quick_gem 'gem_one' FileUtils.mv File.join(@gemhome, 'cache', "#{@a2.full_name}.gem"),
util_build_gem gem1
FileUtils.mv File.join(@gemhome, 'cache', "#{@gem1.full_name}.gem"),
File.join(@tempdir) File.join(@tempdir)
gem2 = quick_gem 'gem_two' FileUtils.mv File.join(@gemhome, 'cache', "#{@b2.full_name}.gem"),
util_build_gem gem2
FileUtils.mv File.join(@gemhome, 'cache', "#{@gem2.full_name}.gem"),
File.join(@tempdir) File.join(@tempdir)
@cmd.options[:args] = [gem1.name, gem2.name] @cmd.options[:args] = [@a2.name, @b2.name]
use_ui @ui do use_ui @ui do
orig_dir = Dir.pwd orig_dir = Dir.pwd
begin begin
Dir.chdir @tempdir Dir.chdir @tempdir
@cmd.execute e = assert_raises Gem::SystemExitException do
@cmd.execute
end
assert_equal 0, e.exit_code
ensure ensure
Dir.chdir orig_dir Dir.chdir orig_dir
end end
end end
out = @ui.output.split "\n" out = @ui.output.split "\n"
assert_equal "Successfully installed #{@gem1.full_name}", out.shift assert_equal "Successfully installed #{@a2.full_name}", out.shift
assert_equal "Successfully installed #{@gem2.full_name}", out.shift assert_equal "Successfully installed #{@b2.full_name}", out.shift
assert_equal "2 gems installed", out.shift assert_equal "2 gems installed", out.shift
assert out.empty?, out.inspect assert out.empty?, out.inspect
end end

View file

@ -7,20 +7,31 @@ class TestGemCommandsQueryCommand < RubyGemTestCase
def setup def setup
super super
@foo_gem = quick_gem 'foo' do |spec| util_make_gems
spec.summary = 'This is a lot of text. ' * 5
end @a2.summary = 'This is a lot of text. ' * 4
@foo_gem_p = quick_gem 'foo' do |spec|
spec.summary = 'This is a lot of text. ' * 5
spec.platform = Gem::Platform::CURRENT
end
@bar_gem = quick_gem 'bar'
@cmd = Gem::Commands::QueryCommand.new @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
raise Gem::RemoteFetcher::FetchError
end
end end
def test_execute def test_execute
util_setup_source_info_cache @foo_gem, @foo_gem_p cache = Gem::SourceInfoCache.cache
cache.update
cache.write_cache
cache.reset_cache_data
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] @cmd.handle_options %w[-r]
@ -32,7 +43,43 @@ class TestGemCommandsQueryCommand < RubyGemTestCase
*** REMOTE GEMS *** *** REMOTE GEMS ***
foo (2) a (2)
EOF
assert_equal expected, @ui.output
assert_equal '', @ui.error
end
def test_execute_all
cache = Gem::SourceInfoCache.cache
cache.update
cache.write_cache
cache.reset_cache_data
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]
use_ui @ui do
@cmd.execute
end
expected = <<-EOF
*** REMOTE GEMS ***
Updating metadata for 1 gems from http://gems.example.com/
.
complete
a (2, 1)
EOF EOF
assert_equal expected, @ui.output assert_equal expected, @ui.output
@ -40,8 +87,6 @@ foo (2)
end end
def test_execute_details def test_execute_details
util_setup_source_info_cache @foo_gem
@cmd.handle_options %w[-r -d] @cmd.handle_options %w[-r -d]
use_ui @ui do use_ui @ui do
@ -52,18 +97,94 @@ foo (2)
*** REMOTE GEMS *** *** REMOTE GEMS ***
foo (2) a (2, 1)
This is a lot of text. This is a lot of text. This is a lot of This is a lot of text. This is a lot of text. This is a lot of text.
text. This is a lot of text. This is a lot of text. This is a lot of text.
pl (1)
this is a summary
EOF EOF
assert_equal expected, @ui.output assert_equal expected, @ui.output
assert_equal '', @ui.error assert_equal '', @ui.error
end end
def test_execute_no_versions def test_execute_installed
util_setup_source_info_cache @foo_gem, @bar_gem @cmd.handle_options %w[-n c --installed]
e = assert_raise Gem::SystemExitException do
use_ui @ui do
@cmd.execute
end
end
assert_equal 0, e.exit_code
assert_equal "true\n", @ui.output
assert_equal '', @ui.error
end
def test_execute_installed_no_name
@cmd.handle_options %w[--installed]
e = assert_raise Gem::SystemExitException do
use_ui @ui do
@cmd.execute
end
end
assert_equal '', @ui.output
assert_equal "ERROR: You must specify a gem name\n", @ui.error
assert_equal 4, e.exit_code
end
def test_execute_installed_not_installed
@cmd.handle_options %w[-n not_installed --installed]
e = assert_raise Gem::SystemExitException do
use_ui @ui do
@cmd.execute
end
end
assert_equal "false\n", @ui.output
assert_equal '', @ui.error
assert_equal 1, e.exit_code
end
def test_execute_installed_version
@cmd.handle_options %w[-n c --installed --version 1.2]
e = assert_raise Gem::SystemExitException do
use_ui @ui do
@cmd.execute
end
end
assert_equal "true\n", @ui.output
assert_equal '', @ui.error
assert_equal 0, e.exit_code
end
def test_execute_installed_version_not_installed
@cmd.handle_options %w[-n c --installed --version 2]
e = assert_raise Gem::SystemExitException do
use_ui @ui do
@cmd.execute
end
end
assert_equal "false\n", @ui.output
assert_equal '', @ui.error
assert_equal 1, e.exit_code
end
def test_execute_no_versions
@cmd.handle_options %w[-r --no-versions] @cmd.handle_options %w[-r --no-versions]
use_ui @ui do use_ui @ui do
@ -74,8 +195,8 @@ foo (2)
*** REMOTE GEMS *** *** REMOTE GEMS ***
bar a
foo pl
EOF EOF
assert_equal expected, @ui.output assert_equal expected, @ui.output

View file

@ -20,7 +20,7 @@ class TestGemCommandsServerCommand < RubyGemTestCase
@cmd.send :handle_options, %w[-p 9999 -d /nonexistent --daemon] @cmd.send :handle_options, %w[-p 9999 -d /nonexistent --daemon]
assert_equal true, @cmd.options[:daemon] assert_equal true, @cmd.options[:daemon]
assert_equal '/nonexistent', @cmd.options[:gemdir] assert_equal File.expand_path('/nonexistent'), @cmd.options[:gemdir]
assert_equal 9999, @cmd.options[:port] assert_equal 9999, @cmd.options[:port]
end end
end end

View file

@ -31,10 +31,11 @@ class TestGemCommandsSourcesCommand < RubyGemTestCase
def test_execute_add def test_execute_add
util_setup_fake_fetcher util_setup_fake_fetcher
@si = Gem::SourceIndex.new @gem1.full_name => @gem1.name si = Gem::SourceIndex.new
si.add_spec @a1
@fetcher.data["http://beta-gems.example.com/Marshal.#{@marshal_version}"] = @fetcher.data["http://beta-gems.example.com/Marshal.#{@marshal_version}"] =
@si.dump si.dump
@cmd.handle_options %w[--add http://beta-gems.example.com] @cmd.handle_options %w[--add http://beta-gems.example.com]
@ -45,7 +46,7 @@ class TestGemCommandsSourcesCommand < RubyGemTestCase
end end
expected = <<-EOF expected = <<-EOF
Bulk updating Gem source index for: http://beta-gems.example.com Bulk updating Gem source index for: http://beta-gems.example.com/
http://beta-gems.example.com added to sources http://beta-gems.example.com added to sources
EOF EOF
@ -60,14 +61,11 @@ http://beta-gems.example.com added to sources
def test_execute_add_nonexistent_source def test_execute_add_nonexistent_source
util_setup_fake_fetcher util_setup_fake_fetcher
@si = Gem::SourceIndex.new @gem1.full_name => @gem1.name
@fetcher.data["http://beta-gems.example.com/Marshal.#{@marshal_version}"] = @fetcher.data["http://beta-gems.example.com/Marshal.#{@marshal_version}"] =
proc do proc do
raise Gem::RemoteFetcher::FetchError, 'it died' raise Gem::RemoteFetcher::FetchError, 'it died'
end end
Gem::RemoteFetcher.instance_variable_set :@fetcher, @fetcher Gem::RemoteFetcher.instance_variable_set :@fetcher, @fetcher
@cmd.handle_options %w[--add http://beta-gems.example.com] @cmd.handle_options %w[--add http://beta-gems.example.com]
@ -104,6 +102,41 @@ beta-gems.example.com is not a URI
assert_equal '', @ui.error assert_equal '', @ui.error
end end
def test_execute_clear_all
@cmd.handle_options %w[--clear-all]
util_setup_source_info_cache
cache = Gem::SourceInfoCache.cache
cache.update
cache.write_cache
assert File.exist?(cache.system_cache_file),
'system cache file'
assert File.exist?(cache.latest_system_cache_file),
'latest system cache file'
use_ui @ui do
@cmd.execute
end
expected = <<-EOF
*** Removed user source cache ***
*** Removed latest user source cache ***
*** Removed system source cache ***
*** Removed latest system source cache ***
EOF
assert_equal expected, @ui.output
assert_equal '', @ui.error
assert !File.exist?(cache.system_cache_file),
'system cache file'
assert !File.exist?(cache.latest_system_cache_file),
'latest system cache file'
end
def test_execute_remove def test_execute_remove
@cmd.handle_options %W[--remove #{@gem_repo}] @cmd.handle_options %W[--remove #{@gem_repo}]
@ -122,20 +155,43 @@ beta-gems.example.com is not a URI
assert_equal [], Gem::SourceInfoCache.cache_data.keys assert_equal [], Gem::SourceInfoCache.cache_data.keys
end end
def test_execute_remove_no_network
@cmd.handle_options %W[--remove #{@gem_repo}]
util_setup_fake_fetcher
@fetcher.data["#{@gem_repo}/Marshal.#{Gem.marshal_version}"] = proc do
raise Gem::RemoteFetcher::FetchError
end
use_ui @ui do
@cmd.execute
end
expected = "#{@gem_repo} removed from sources\n"
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 def test_execute_update
@cmd.handle_options %w[--update] @cmd.handle_options %w[--update]
util_setup_source_info_cache util_setup_source_info_cache
util_setup_fake_fetcher util_setup_fake_fetcher
@si = Gem::SourceIndex.new @gem1.full_name => @gem1.name si = Gem::SourceIndex.new
@fetcher.data["#{@gem_repo}/Marshal.#{@marshal_version}"] = @si.dump si.add_spec @a1
@fetcher.data["#{@gem_repo}/Marshal.#{@marshal_version}"] = si.dump
use_ui @ui do use_ui @ui do
@cmd.execute @cmd.execute
end end
expected = <<-EOF expected = <<-EOF
Bulk updating Gem source index for: #{@gem_repo} Bulk updating Gem source index for: #{@gem_repo}/
source cache successfully updated source cache successfully updated
EOF EOF

View file

@ -12,6 +12,7 @@ class TestGemCommandsSpecificationCommand < RubyGemTestCase
def test_execute def test_execute
foo = quick_gem 'foo' foo = quick_gem 'foo'
Gem.source_index.add_spec foo
@cmd.options[:args] = %w[foo] @cmd.options[:args] = %w[foo]
@ -87,7 +88,6 @@ class TestGemCommandsSpecificationCommand < RubyGemTestCase
assert_match %r|\A--- !ruby/object:Gem::Specification|, @ui.output assert_match %r|\A--- !ruby/object:Gem::Specification|, @ui.output
assert_match %r|name: foo|, @ui.output assert_match %r|name: foo|, @ui.output
assert_equal "WARNING: Remote information is not complete\n\n", @ui.error
end end
end end

View file

@ -17,15 +17,57 @@ class TestGemCommandsUnpackCommand < RubyGemTestCase
@cmd.options[:args] = %w[a] @cmd.options[:args] = %w[a]
use_ui @ui do use_ui @ui do
Dir.chdir @tempdir do Dir.chdir @tempdir do
@cmd.execute @cmd.execute
end
end end
end
assert File.exist?(File.join(@tempdir, 'a-2')) assert File.exist?(File.join(@tempdir, 'a-2'))
end end
def test_execute_gem_path
util_make_gems
Gem.clear_paths
gemhome2 = File.join @tempdir, 'gemhome2'
Gem.send :set_paths, [gemhome2, @gemhome].join(File::PATH_SEPARATOR)
Gem.send :set_home, gemhome2
@cmd.options[:args] = %w[a]
use_ui @ui do
Dir.chdir @tempdir do
@cmd.execute
end
end
assert File.exist?(File.join(@tempdir, 'a-2'))
end
def test_execute_gem_path_missing
util_make_gems
Gem.clear_paths
gemhome2 = File.join @tempdir, 'gemhome2'
Gem.send :set_paths, [gemhome2, @gemhome].join(File::PATH_SEPARATOR)
Gem.send :set_home, gemhome2
@cmd.options[:args] = %w[z]
use_ui @ui do
Dir.chdir @tempdir do
@cmd.execute
end
end
assert_equal '', @ui.output
end
def test_execute_with_target_option def test_execute_with_target_option
util_make_gems util_make_gems

View file

@ -0,0 +1,174 @@
require 'test/unit'
require File.join(File.expand_path(File.dirname(__FILE__)), 'gemutilities')
require 'rubygems/commands/update_command'
class TestGemCommandsUpdateCommand < RubyGemTestCase
def setup
super
@cmd = Gem::Commands::UpdateCommand.new
util_setup_fake_fetcher
@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
end
def test_execute
util_remove_gems
Gem::Installer.new(@a1_path).install
@cmd.options[:args] = []
use_ui @ui do
@cmd.execute
end
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
assert out.empty?, out.inspect
end
# before:
# a1 -> c1.2
# after:
# a2 -> b2 # new dependency
# a2 -> c2
def test_execute_dependencies
@a1.add_dependency 'c', '1.2'
@c2 = quick_gem 'c', '2' do |s|
s.files = %w[lib/code.rb]
s.require_paths = %w[lib]
end
@a2.add_dependency 'c', '2'
@a2.add_dependency 'b', '2'
@b2_path = File.join @gemhome, 'cache', "#{@b2.full_name}.gem"
@c1_2_path = File.join @gemhome, 'cache', "#{@c1_2.full_name}.gem"
@c2_path = File.join @gemhome, 'cache', "#{@c2.full_name}.gem"
@source_index = Gem::SourceIndex.new
@source_index.add_spec @a1
@source_index.add_spec @a2
@source_index.add_spec @b2
@source_index.add_spec @c1_2
@source_index.add_spec @c2
util_build_gem @a1
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"] =
read_binary @c1_2_path
@fetcher.data["#{@gem_repo}/gems/#{@c2.full_name}.gem"] = read_binary @c2_path
util_remove_gems
Gem::Installer.new(@c1_2_path).install
Gem::Installer.new(@a1_path).install
@cmd.options[:args] = []
use_ui @ui do
@cmd.execute
end
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 #{@c2.full_name}", out.shift
assert_equal "Successfully installed #{@b2.full_name}", out.shift
assert_equal "Successfully installed #{@a2.full_name}", out.shift
assert_equal "Gems updated: #{@c2.name}, #{@b2.name}, #{@a2.name}",
out.shift
assert out.empty?, out.inspect
end
def test_execute_named
util_remove_gems
Gem::Installer.new(@a1_path).install
@cmd.options[:args] = [@a1.name]
use_ui @ui do
@cmd.execute
end
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
assert out.empty?, out.inspect
end
def test_execute_named_up_to_date
util_remove_gems
Gem::Installer.new(@a2_path).install
@cmd.options[:args] = [@a2.name]
use_ui @ui do
@cmd.execute
end
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
Gem::Installer.new(@a2_path).install
@cmd.options[:args] = []
use_ui @ui do
@cmd.execute
end
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

View file

@ -54,8 +54,8 @@ class TestGemDependencyInstaller < RubyGemTestCase
inst = nil inst = nil
Dir.chdir @tempdir do Dir.chdir @tempdir do
inst = Gem::DependencyInstaller.new 'a' inst = Gem::DependencyInstaller.new
inst.install inst.install 'a'
end end
assert_equal Gem::SourceIndex.new(@a1.full_name => @a1), assert_equal Gem::SourceIndex.new(@a1.full_name => @a1),
@ -70,8 +70,8 @@ class TestGemDependencyInstaller < RubyGemTestCase
inst = nil inst = nil
Dir.chdir @tempdir do Dir.chdir @tempdir do
inst = Gem::DependencyInstaller.new 'b' inst = Gem::DependencyInstaller.new
inst.install inst.install 'b'
end end
assert_equal %w[a-1 b-1], inst.installed_gems.map { |s| s.full_name } assert_equal %w[a-1 b-1], inst.installed_gems.map { |s| s.full_name }
@ -84,8 +84,8 @@ class TestGemDependencyInstaller < RubyGemTestCase
inst = nil inst = nil
Dir.chdir @tempdir do Dir.chdir @tempdir do
inst = Gem::DependencyInstaller.new 'b' inst = Gem::DependencyInstaller.new
inst.install inst.install 'b'
end end
assert_equal %w[b-1], inst.installed_gems.map { |s| s.full_name } assert_equal %w[b-1], inst.installed_gems.map { |s| s.full_name }
@ -102,8 +102,8 @@ class TestGemDependencyInstaller < RubyGemTestCase
inst = nil inst = nil
Dir.chdir @tempdir do Dir.chdir @tempdir do
inst = Gem::DependencyInstaller.new 'f' inst = Gem::DependencyInstaller.new
inst.install inst.install 'f'
end end
assert_equal %w[f-2], inst.installed_gems.map { |s| s.full_name } assert_equal %w[f-2], inst.installed_gems.map { |s| s.full_name }
@ -114,19 +114,49 @@ class TestGemDependencyInstaller < RubyGemTestCase
inst = nil inst = nil
Dir.chdir @tempdir do Dir.chdir @tempdir do
inst = Gem::DependencyInstaller.new 'a-1.gem' inst = Gem::DependencyInstaller.new :domain => :local
inst.install inst.install 'a-1.gem'
end end
assert_equal %w[a-1], inst.installed_gems.map { |s| s.full_name } assert_equal %w[a-1], inst.installed_gems.map { |s| s.full_name }
end end
def test_install_local_dependency
FileUtils.mv @a1_gem, @tempdir
FileUtils.mv @b1_gem, @tempdir
inst = nil
Dir.chdir @tempdir do
inst = Gem::DependencyInstaller.new :domain => :local
inst.install 'b-1.gem'
end
assert_equal %w[a-1 b-1], inst.installed_gems.map { |s| s.full_name }
end
def test_install_local_dependency_installed
FileUtils.mv @a1_gem, @tempdir
FileUtils.mv @b1_gem, @tempdir
inst = nil
Dir.chdir @tempdir do
Gem::Installer.new('a-1.gem').install
inst = Gem::DependencyInstaller.new :domain => :local
inst.install 'b-1.gem'
end
assert_equal %w[b-1], inst.installed_gems.map { |s| s.full_name }
end
def test_install_local_subdir def test_install_local_subdir
inst = nil inst = nil
Dir.chdir @tempdir do Dir.chdir @tempdir do
inst = Gem::DependencyInstaller.new 'gems/a-1.gem' inst = Gem::DependencyInstaller.new :domain => :local
inst.install inst.install 'gems/a-1.gem'
end end
assert_equal %w[a-1], inst.installed_gems.map { |s| s.full_name } assert_equal %w[a-1], inst.installed_gems.map { |s| s.full_name }
@ -137,12 +167,11 @@ class TestGemDependencyInstaller < RubyGemTestCase
inst = nil inst = nil
Dir.chdir @tempdir do Dir.chdir @tempdir do
inst = Gem::DependencyInstaller.new 'a', nil, :env_shebang => true, inst = Gem::DependencyInstaller.new :env_shebang => true, :wrappers => true
:wrappers => true inst.install 'a'
inst.install
end end
assert_match %r|\A#!/usr/bin/env ruby\n|, assert_match %r|\A#!/usr/bin/env #{Gem::ConfigMap[:RUBY_INSTALL_NAME]}\n|,
File.read(File.join(@gemhome, 'bin', 'a_bin')) File.read(File.join(@gemhome, 'bin', 'a_bin'))
end end
@ -153,8 +182,8 @@ class TestGemDependencyInstaller < RubyGemTestCase
inst = nil inst = nil
Dir.chdir @tempdir do Dir.chdir @tempdir do
inst = Gem::DependencyInstaller.new 'b', nil, :force => true inst = Gem::DependencyInstaller.new :force => true
inst.install inst.install 'b'
end end
assert_equal %w[b-1], inst.installed_gems.map { |s| s.full_name } assert_equal %w[b-1], inst.installed_gems.map { |s| s.full_name }
@ -165,8 +194,8 @@ class TestGemDependencyInstaller < RubyGemTestCase
inst = nil inst = nil
Dir.chdir @tempdir do Dir.chdir @tempdir do
inst = Gem::DependencyInstaller.new 'b', nil, :ignore_dependencies => true inst = Gem::DependencyInstaller.new :ignore_dependencies => true
inst.install inst.install 'b'
end end
assert_equal %w[b-1], inst.installed_gems.map { |s| s.full_name } assert_equal %w[b-1], inst.installed_gems.map { |s| s.full_name }
@ -179,8 +208,8 @@ class TestGemDependencyInstaller < RubyGemTestCase
inst = nil inst = nil
Dir.chdir @tempdir do Dir.chdir @tempdir do
inst = Gem::DependencyInstaller.new 'a', nil, :install_dir => gemhome2 inst = Gem::DependencyInstaller.new :install_dir => gemhome2
inst.install inst.install 'a'
end end
assert_equal %w[a-1], inst.installed_gems.map { |s| s.full_name } assert_equal %w[a-1], inst.installed_gems.map { |s| s.full_name }
@ -201,8 +230,8 @@ class TestGemDependencyInstaller < RubyGemTestCase
inst = nil inst = nil
Dir.chdir @tempdir do Dir.chdir @tempdir do
inst = Gem::DependencyInstaller.new 'b', nil, :domain => :both inst = Gem::DependencyInstaller.new :domain => :both
inst.install inst.install 'b'
end end
assert_equal %w[a-1 b-1], inst.installed_gems.map { |s| s.full_name } assert_equal %w[a-1 b-1], inst.installed_gems.map { |s| s.full_name }
@ -217,14 +246,34 @@ class TestGemDependencyInstaller < RubyGemTestCase
assert_equal b1_expected, b1.loaded_from assert_equal b1_expected, b1.loaded_from
end 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
end
FileUtils.mv @a1_gem, @tempdir
FileUtils.mv @b1_gem, @tempdir
inst = nil
Dir.chdir @tempdir do
inst = Gem::DependencyInstaller.new :domain => :both
inst.install 'b'
end
assert_equal %w[a-1 b-1], inst.installed_gems.map { |s| s.full_name }
end
def test_install_domain_local def test_install_domain_local
FileUtils.mv @b1_gem, @tempdir FileUtils.mv @b1_gem, @tempdir
inst = nil inst = nil
Dir.chdir @tempdir do Dir.chdir @tempdir do
e = assert_raise Gem::InstallError do e = assert_raise Gem::InstallError do
inst = Gem::DependencyInstaller.new 'b', nil, :domain => :local inst = Gem::DependencyInstaller.new :domain => :local
inst.install inst.install 'b'
end end
assert_equal 'b requires a (>= 0)', e.message assert_equal 'b requires a (>= 0)', e.message
end end
@ -240,8 +289,43 @@ class TestGemDependencyInstaller < RubyGemTestCase
@fetcher.data['http://gems.example.com/gems/a-1.gem'] = a1_data @fetcher.data['http://gems.example.com/gems/a-1.gem'] = a1_data
inst = Gem::DependencyInstaller.new 'a', nil, :domain => :remote inst = Gem::DependencyInstaller.new :domain => :remote
inst.install inst.install 'a'
assert_equal %w[a-1], inst.installed_gems.map { |s| s.full_name }
end
def test_install_remote
a1_data = nil
File.open @a1_gem, 'rb' do |fp|
a1_data = fp.read
end
@fetcher.data['http://gems.example.com/gems/a-1.gem'] = a1_data
inst = Gem::DependencyInstaller.new
Dir.chdir @tempdir do
inst.install 'a'
end
assert_equal %w[a-1], inst.installed_gems.map { |s| s.full_name }
end
def test_install_remote_dep
a1_data = nil
File.open @a1_gem, 'rb' do |fp|
a1_data = fp.read
end
@fetcher.data['http://gems.example.com/gems/a-1.gem'] = a1_data
inst = Gem::DependencyInstaller.new
Dir.chdir @tempdir do
dep = Gem::Dependency.new @a1.name, @a1.version
inst.install dep
end
assert_equal %w[a-1], inst.installed_gems.map { |s| s.full_name } assert_equal %w[a-1], inst.installed_gems.map { |s| s.full_name }
end end
@ -266,8 +350,8 @@ class TestGemDependencyInstaller < RubyGemTestCase
@fetcher.data["http://gems.example.com/gems/#{a2_o.full_name}.gem"] = @fetcher.data["http://gems.example.com/gems/#{a2_o.full_name}.gem"] =
a2_o_data a2_o_data
inst = Gem::DependencyInstaller.new 'a', nil, :domain => :remote inst = Gem::DependencyInstaller.new :domain => :remote
inst.install inst.install 'a'
assert_equal %w[a-1], inst.installed_gems.map { |s| s.full_name } assert_equal %w[a-1], inst.installed_gems.map { |s| s.full_name }
end end
@ -278,8 +362,8 @@ class TestGemDependencyInstaller < RubyGemTestCase
inst = nil inst = nil
Dir.chdir @tempdir do Dir.chdir @tempdir do
inst = Gem::DependencyInstaller.new 'a' inst = Gem::DependencyInstaller.new
inst.install inst.install 'a'
end end
assert_equal Gem::SourceIndex.new(@a1.full_name => @a1), assert_equal Gem::SourceIndex.new(@a1.full_name => @a1),
@ -290,13 +374,17 @@ class TestGemDependencyInstaller < RubyGemTestCase
if defined? OpenSSL then if defined? OpenSSL then
def test_install_security_policy def test_install_security_policy
FileUtils.mv @a1_gem, @cache_dir data = File.open(@a1_gem, 'rb') { |f| f.read }
FileUtils.mv @b1_gem, @cache_dir @fetcher.data['http://gems.example.com/gems/a-1.gem'] = data
data = File.open(@b1_gem, 'rb') { |f| f.read }
@fetcher.data['http://gems.example.com/gems/b-1.gem'] = data
policy = Gem::Security::HighSecurity policy = Gem::Security::HighSecurity
inst = Gem::DependencyInstaller.new 'b', nil, :security_policy => policy inst = Gem::DependencyInstaller.new :security_policy => policy
e = assert_raise Gem::Exception do e = assert_raise Gem::Exception do
inst.install inst.install 'b'
end end
assert_equal 'Unsigned gem', e.message assert_equal 'Unsigned gem', e.message
@ -305,145 +393,48 @@ class TestGemDependencyInstaller < RubyGemTestCase
end end
end end
def test_install_wrappers # Wrappers don't work on mswin
FileUtils.mv @a1_gem, @cache_dir unless win_platform? then
inst = Gem::DependencyInstaller.new 'a', :wrappers => true def test_install_no_wrappers
@fetcher.data['http://gems.example.com/gems/a-1.gem'] = read_binary(@a1_gem)
inst.install inst = Gem::DependencyInstaller.new :wrappers => false
inst.install 'a'
assert_match %r|This file was generated by RubyGems.|, assert_no_match(%r|This file was generated by RubyGems.|,
File.read(File.join(@gemhome, 'bin', 'a_bin')) File.read(File.join(@gemhome, 'bin', 'a_bin')))
end
end end
def test_install_version def test_install_version
FileUtils.mv @d1_gem, @cache_dir data = File.open(@d2_gem, 'rb') { |f| f.read }
FileUtils.mv @d2_gem, @cache_dir @fetcher.data['http://gems.example.com/gems/d-2.gem'] = data
inst = Gem::DependencyInstaller.new 'd', '= 1'
inst.install data = File.open(@d1_gem, 'rb') { |f| f.read }
@fetcher.data['http://gems.example.com/gems/d-1.gem'] = data
inst = Gem::DependencyInstaller.new
inst.install 'd', '= 1'
assert_equal %w[d-1], inst.installed_gems.map { |s| s.full_name } assert_equal %w[d-1], inst.installed_gems.map { |s| s.full_name }
end end
def test_install_version_default def test_install_version_default
FileUtils.mv @d1_gem, @cache_dir data = File.open(@d2_gem, 'rb') { |f| f.read }
FileUtils.mv @d2_gem, @cache_dir @fetcher.data['http://gems.example.com/gems/d-2.gem'] = data
inst = Gem::DependencyInstaller.new 'd'
inst.install data = File.open(@d1_gem, 'rb') { |f| f.read }
@fetcher.data['http://gems.example.com/gems/d-1.gem'] = data
inst = Gem::DependencyInstaller.new
inst.install 'd'
assert_equal %w[d-2], inst.installed_gems.map { |s| s.full_name } assert_equal %w[d-2], inst.installed_gems.map { |s| s.full_name }
end end
def test_download
a1_data = nil
File.open @a1_gem, 'rb' do |fp|
a1_data = fp.read
end
@fetcher.data['http://gems.example.com/gems/a-1.gem'] = a1_data
inst = Gem::DependencyInstaller.new 'a'
a1_cache_gem = File.join(@gemhome, 'cache', "#{@a1.full_name}.gem")
assert_equal a1_cache_gem, inst.download(@a1, 'http://gems.example.com')
assert File.exist?(a1_cache_gem)
end
def test_download_cached
FileUtils.mv @a1_gem, @cache_dir
inst = Gem::DependencyInstaller.new 'a'
assert_equal File.join(@gemhome, 'cache', "#{@a1.full_name}.gem"),
inst.download(@a1, 'http://gems.example.com')
end
def test_download_local
FileUtils.mv @a1_gem, @tempdir
local_path = File.join @tempdir, "#{@a1.full_name}.gem"
inst = nil
Dir.chdir @tempdir do
inst = Gem::DependencyInstaller.new 'a'
end
assert_equal File.join(@gemhome, 'cache', "#{@a1.full_name}.gem"),
inst.download(@a1, local_path)
end
def test_download_install_dir
a1_data = nil
File.open @a1_gem, 'rb' do |fp|
a1_data = fp.read
end
@fetcher.data['http://gems.example.com/gems/a-1.gem'] = a1_data
install_dir = File.join @tempdir, 'more_gems'
inst = Gem::DependencyInstaller.new 'a', nil, :install_dir => install_dir
a1_cache_gem = File.join install_dir, 'cache', "#{@a1.full_name}.gem"
assert_equal a1_cache_gem, inst.download(@a1, 'http://gems.example.com')
assert File.exist?(a1_cache_gem)
end
unless win_platform? then # File.chmod doesn't work
def test_download_local_read_only
FileUtils.mv @a1_gem, @tempdir
local_path = File.join @tempdir, "#{@a1.full_name}.gem"
inst = nil
File.chmod 0555, File.join(@gemhome, 'cache')
Dir.chdir @tempdir do
inst = Gem::DependencyInstaller.new 'a'
end
assert_equal File.join(@tempdir, "#{@a1.full_name}.gem"),
inst.download(@a1, local_path)
ensure
File.chmod 0755, File.join(@gemhome, 'cache')
end
end
def test_download_platform_legacy
original_platform = 'old-platform'
e1, e1_gem = util_gem 'e', '1' do |s|
s.platform = Gem::Platform::CURRENT
s.instance_variable_set :@original_platform, original_platform
end
e1_data = nil
File.open e1_gem, 'rb' do |fp|
e1_data = fp.read
end
@fetcher.data["http://gems.example.com/gems/e-1-#{original_platform}.gem"] = e1_data
inst = Gem::DependencyInstaller.new 'a'
e1_cache_gem = File.join(@gemhome, 'cache', "#{e1.full_name}.gem")
assert_equal e1_cache_gem, inst.download(e1, 'http://gems.example.com')
assert File.exist?(e1_cache_gem)
end
def test_download_unsupported
inst = Gem::DependencyInstaller.new 'a'
e = assert_raise Gem::InstallError do
inst.download @a1, 'ftp://gems.rubyforge.org'
end
assert_equal 'unsupported URI scheme ftp', e.message
end
def test_find_gems_gems_with_sources def test_find_gems_gems_with_sources
inst = Gem::DependencyInstaller.new 'a' inst = Gem::DependencyInstaller.new
dep = Gem::Dependency.new 'b', '>= 0' dep = Gem::Dependency.new 'b', '>= 0'
assert_equal [[@b1, 'http://gems.example.com']], assert_equal [[@b1, 'http://gems.example.com']],
@ -452,7 +443,7 @@ class TestGemDependencyInstaller < RubyGemTestCase
def test_find_gems_with_sources_local def test_find_gems_with_sources_local
FileUtils.mv @a1_gem, @tempdir FileUtils.mv @a1_gem, @tempdir
inst = Gem::DependencyInstaller.new 'b' inst = Gem::DependencyInstaller.new
dep = Gem::Dependency.new 'a', '>= 0' dep = Gem::Dependency.new 'a', '>= 0'
gems = nil gems = nil
@ -462,7 +453,7 @@ class TestGemDependencyInstaller < RubyGemTestCase
assert_equal 2, gems.length assert_equal 2, gems.length
remote = gems.first remote = gems.first
assert_equal @a1, remote.first, 'remote spec' assert_equal 'a-1', remote.first.full_name, 'remote spec'
assert_equal 'http://gems.example.com', remote.last, 'remote path' assert_equal 'http://gems.example.com', remote.last, 'remote path'
local = gems.last local = gems.last
@ -472,7 +463,9 @@ class TestGemDependencyInstaller < RubyGemTestCase
end end
def test_gather_dependencies def test_gather_dependencies
inst = Gem::DependencyInstaller.new 'b' inst = Gem::DependencyInstaller.new
inst.find_spec_by_name_and_version 'b'
inst.gather_dependencies
assert_equal %w[a-1 b-1], inst.gems_to_install.map { |s| s.full_name } assert_equal %w[a-1 b-1], inst.gems_to_install.map { |s| s.full_name }
end end
@ -488,7 +481,9 @@ class TestGemDependencyInstaller < RubyGemTestCase
@fetcher.uri = URI.parse 'http://gems.example.com' @fetcher.uri = URI.parse 'http://gems.example.com'
@fetcher.data['http://gems.example.com/gems/yaml'] = si.to_yaml @fetcher.data['http://gems.example.com/gems/yaml'] = si.to_yaml
inst = Gem::DependencyInstaller.new 'c' inst = Gem::DependencyInstaller.new
inst.find_spec_by_name_and_version 'c'
inst.gather_dependencies
assert_equal %w[b-2 c-1], inst.gems_to_install.map { |s| s.full_name } assert_equal %w[b-2 c-1], inst.gems_to_install.map { |s| s.full_name }
end end
@ -496,14 +491,18 @@ class TestGemDependencyInstaller < RubyGemTestCase
def test_gather_dependencies_platform_alternate def test_gather_dependencies_platform_alternate
util_set_arch 'cpu-my_platform1' util_set_arch 'cpu-my_platform1'
inst = Gem::DependencyInstaller.new 'w' inst = Gem::DependencyInstaller.new
inst.find_spec_by_name_and_version 'w'
inst.gather_dependencies
assert_equal %w[x-1-cpu-my_platform-1 w-1], assert_equal %w[x-1-cpu-my_platform-1 w-1],
inst.gems_to_install.map { |s| s.full_name } inst.gems_to_install.map { |s| s.full_name }
end end
def test_gather_dependencies_platform_bump def test_gather_dependencies_platform_bump
inst = Gem::DependencyInstaller.new 'z' inst = Gem::DependencyInstaller.new
inst.find_spec_by_name_and_version 'z'
inst.gather_dependencies
assert_equal %w[y-1 z-1], inst.gems_to_install.map { |s| s.full_name } assert_equal %w[y-1 z-1], inst.gems_to_install.map { |s| s.full_name }
end end
@ -518,27 +517,11 @@ class TestGemDependencyInstaller < RubyGemTestCase
@fetcher.uri = URI.parse 'http://gems.example.com' @fetcher.uri = URI.parse 'http://gems.example.com'
@fetcher.data['http://gems.example.com/gems/yaml'] = si.to_yaml @fetcher.data['http://gems.example.com/gems/yaml'] = si.to_yaml
inst = Gem::DependencyInstaller.new 'e' inst = Gem::DependencyInstaller.new
inst.find_spec_by_name_and_version 'e'
inst.gather_dependencies
assert_equal %w[d-1 e-1], inst.gems_to_install.map { |s| s.full_name } assert_equal %w[d-1 e-1], inst.gems_to_install.map { |s| s.full_name }
end end
def util_gem(name, version, &block)
spec = quick_gem(name, version, &block)
util_build_gem spec
cache_file = File.join @tempdir, 'gems', "#{spec.original_name}.gem"
FileUtils.mv File.join(@gemhome, 'cache', "#{spec.original_name}.gem"),
cache_file
FileUtils.rm File.join(@gemhome, 'specifications',
"#{spec.full_name}.gemspec")
spec.loaded_from = nil
spec.loaded = false
[spec, cache_file]
end
end end

View file

@ -47,16 +47,19 @@ class TestGemExtConfigureBuilder < RubyGemTestCase
end end
end end
expected = %r|configure failed: shell_error_msg = %r{(\./configure: No such file or directory)|(Can't open \./configure)}
sh_prefix_configure = "sh ./configure --prefix="
expected = %r(configure failed:
sh \./configure --prefix=#{Regexp.escape @dest_path} #{Regexp.escape sh_prefix_configure}#{Regexp.escape @dest_path}
.*?: \./configure: No such file or directory .*?: #{shell_error_msg}
| )
assert_match expected, error.message assert_match expected, error.message
assert_equal "sh ./configure --prefix=#{@dest_path}", output.shift assert_equal "#{sh_prefix_configure}#{@dest_path}", output.shift
assert_match %r|\./configure: No such file or directory\n|, output.shift assert_match %r(#{shell_error_msg}\n), output.shift
assert_equal true, output.empty? assert_equal true, output.empty?
end end

View file

@ -22,7 +22,7 @@ class TestGemFormat < RubyGemTestCase
gems = Dir[File.join(@gemhome, 'cache', '*.gem')] gems = Dir[File.join(@gemhome, 'cache', '*.gem')]
names = [@a1, @a2, @b2, @c1_2, @pl1].map do |spec| names = [@a1, @a2, @a_evil9, @b2, @c1_2, @pl1].map do |spec|
spec.original_name spec.original_name
end end

View file

@ -53,6 +53,11 @@ class TestGemIndexer < RubyGemTestCase
assert_indexed quickdir, "index" assert_indexed quickdir, "index"
assert_indexed quickdir, "index.rz" assert_indexed quickdir, "index.rz"
assert_indexed quickdir, "latest_index"
assert_indexed quickdir, "latest_index.rz"
assert_no_match %r|a-1|, File.read(File.join(quickdir, 'latest_index'))
assert_indexed quickdir, "#{@a1.full_name}.gemspec.rz" assert_indexed quickdir, "#{@a1.full_name}.gemspec.rz"
assert_indexed quickdir, "#{@a2.full_name}.gemspec.rz" assert_indexed quickdir, "#{@a2.full_name}.gemspec.rz"
assert_indexed quickdir, "#{@b2.full_name}.gemspec.rz" assert_indexed quickdir, "#{@b2.full_name}.gemspec.rz"
@ -74,8 +79,8 @@ class TestGemIndexer < RubyGemTestCase
end end
expected = <<-EOF expected = <<-EOF
Generating index for 5 gems in #{@tempdir} Generating index for 6 gems in #{@tempdir}
..... ......
Loaded all gems Loaded all gems
Generating master indexes (this may take a while) Generating master indexes (this may take a while)
EOF EOF

View file

@ -4,63 +4,10 @@
# See LICENSE.txt for permissions. # See LICENSE.txt for permissions.
#++ #++
require 'test/unit' require File.join(File.expand_path(File.dirname(__FILE__)),
require File.join(File.expand_path(File.dirname(__FILE__)), 'gemutilities') 'gem_installer_test_case')
require 'rubygems/installer'
class Gem::Installer class TestGemInstaller < GemInstallerTestCase
attr_accessor :gem_dir
attr_writer :format
attr_writer :gem_home
attr_writer :env_shebang
attr_writer :ignore_dependencies
attr_writer :format_executable
attr_writer :security_policy
attr_writer :spec
attr_writer :wrappers
end
class TestGemInstaller < RubyGemTestCase
def setup
super
@spec = quick_gem "a"
@gem = File.join @tempdir, "#{@spec.full_name}.gem"
util_build_gem @spec
FileUtils.mv File.join(@gemhome, 'cache', "#{@spec.full_name}.gem"),
@tempdir
@installer = Gem::Installer.new @gem
@installer.gem_dir = util_gem_dir
@installer.gem_home = @gemhome
@installer.spec = @spec
end
def util_gem_dir(version = '2')
File.join @gemhome, "gems", "a-#{version}" # HACK
end
def util_gem_bindir(version = '2')
File.join util_gem_dir(version), "bin"
end
def util_inst_bindir
File.join @gemhome, "bin"
end
def util_make_exec(version = '2', shebang = "#!/usr/bin/ruby")
@spec.executables = ["my_exec"]
FileUtils.mkdir_p util_gem_bindir(version)
exec_file = @installer.formatted_program_filename "my_exec"
exec_path = File.join util_gem_bindir(version), exec_file
File.open exec_path, 'w' do |f|
f.puts shebang
end
end
def test_app_script_text def test_app_script_text
util_make_exec '2', '' util_make_exec '2', ''
@ -162,7 +109,7 @@ load 'my_exec'
@installer.gem_dir = '/nonexistent' @installer.gem_dir = '/nonexistent'
expanded_gem_dir = @installer.send(:expand_and_validate_gem_dir) expanded_gem_dir = @installer.send(:expand_and_validate_gem_dir)
if win_platform? if win_platform?
expected = File.join(Config::CONFIG['bindir'][0..2], 'nonexistent').downcase expected = File.expand_path('/nonexistent').downcase
expanded_gem_dir = expanded_gem_dir.downcase expanded_gem_dir = expanded_gem_dir.downcase
else else
expected = '/nonexistent' expected = '/nonexistent'
@ -768,7 +715,7 @@ load 'my_exec'
@installer.env_shebang = true @installer.env_shebang = true
shebang = @installer.shebang 'my_exec' shebang = @installer.shebang 'my_exec'
assert_equal "#!/usr/bin/env ruby", shebang assert_equal "#!/usr/bin/env #{Gem::ConfigMap[:RUBY_INSTALL_NAME]}", shebang
end end
def test_shebang_nested def test_shebang_nested
@ -855,31 +802,5 @@ load 'my_exec'
File.join @gemhome, 'cache', "#{spec.full_name}.gem" File.join @gemhome, 'cache', "#{spec.full_name}.gem"
end end
def util_setup_gem
@spec.files = File.join('lib', 'code.rb')
@spec.executables << 'executable'
@spec.extensions << File.join('ext', 'a', 'mkrf_conf.rb')
Dir.chdir @tempdir do
FileUtils.mkdir_p 'bin'
FileUtils.mkdir_p 'lib'
FileUtils.mkdir_p File.join('ext', 'a')
File.open File.join('bin', 'executable'), 'w' do |f| f.puts '1' end
File.open File.join('lib', 'code.rb'), 'w' do |f| f.puts '1' end
File.open File.join('ext', 'a', 'mkrf_conf.rb'), 'w' do |f|
f << <<-EOF
File.open 'Rakefile', 'w' do |rf| rf.puts "task :default" end
EOF
end
use_ui @ui do
FileUtils.rm @gem
Gem::Builder.new(@spec).build
end
end
@installer = Gem::Installer.new @gem
end
end end

View file

@ -0,0 +1,137 @@
#--
# Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others.
# All rights reserved.
# See LICENSE.txt for permissions.
#++
require File.join(File.expand_path(File.dirname(__FILE__)),
'gem_package_tar_test_case')
require 'rubygems/package'
class TestGemPackageTarHeader < TarTestCase
def setup
super
header = {
:name => 'x',
:mode => 0644,
:uid => 1000,
:gid => 10000,
:size => 100,
:mtime => 12345,
:typeflag => '0',
:linkname => 'link',
:uname => 'user',
:gname => 'group',
:devmajor => 1,
:devminor => 2,
:prefix => 'y',
}
@tar_header = Gem::Package::TarHeader.new header
end
def test_self_from
io = TempIO.new @tar_header.to_s
new_header = Gem::Package::TarHeader.from io
assert_headers_equal @tar_header, new_header
end
def test_initialize
assert_equal '', @tar_header.checksum, 'checksum'
assert_equal 1, @tar_header.devmajor, 'devmajor'
assert_equal 2, @tar_header.devminor, 'devminor'
assert_equal 10000, @tar_header.gid, 'gid'
assert_equal 'group', @tar_header.gname, 'gname'
assert_equal 'link', @tar_header.linkname, 'linkname'
assert_equal 'ustar', @tar_header.magic, 'magic'
assert_equal 0644, @tar_header.mode, 'mode'
assert_equal 12345, @tar_header.mtime, 'mtime'
assert_equal 'x', @tar_header.name, 'name'
assert_equal 'y', @tar_header.prefix, 'prefix'
assert_equal 100, @tar_header.size, 'size'
assert_equal '0', @tar_header.typeflag, 'typeflag'
assert_equal 1000, @tar_header.uid, 'uid'
assert_equal 'user', @tar_header.uname, 'uname'
assert_equal '00', @tar_header.version, 'version'
assert !@tar_header.empty?, 'empty'
end
def test_initialize_bad
assert_raises ArgumentError do
Gem::Package::TarHeader.new :name => '', :size => '', :mode => ''
end
assert_raises ArgumentError do
Gem::Package::TarHeader.new :name => '', :size => '', :prefix => ''
end
assert_raises ArgumentError do
Gem::Package::TarHeader.new :name => '', :prefix => '', :mode => ''
end
assert_raises ArgumentError do
Gem::Package::TarHeader.new :prefix => '', :size => '', :mode => ''
end
end
def test_empty_eh
assert !@tar_header.empty?
@tar_header = Gem::Package::TarHeader.new :name => 'x', :prefix => '',
:mode => 0, :size => 0,
:empty => true
assert @tar_header.empty?
end
def test_equals2
assert_equal @tar_header, @tar_header
assert_equal @tar_header, @tar_header.dup
end
def test_to_s
expected = <<-EOF.split("\n").join
x\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000
\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000
\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000
\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000
\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000
\000\000\000\0000000644\0000001750\0000023420\00000000000144\00000000030071
\000012467\000 0link\000\000\000\000\000\000\000\000\000\000\000\000\000\000
\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000
\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000
\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000
\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000
\000\000\000\000\000\000ustar\00000user\000\000\000\000\000\000\000\000\000
\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000
group\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000
\000\000\000\000\000\000\000\000\0000000001\0000000002\000y\000\000\000\000
\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000
\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000
\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000
\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000
\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000
\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000
\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000
\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000
\000\000\000\000\000\000\000\000\000\000
EOF
assert_headers_equal expected, @tar_header
end
def test_update_checksum
assert_equal '', @tar_header.checksum
@tar_header.update_checksum
assert_equal '012467', @tar_header.checksum
end
end

View file

@ -0,0 +1,119 @@
#--
# Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others.
# All rights reserved.
# See LICENSE.txt for permissions.
#++
require File.join(File.expand_path(File.dirname(__FILE__)),
'gem_package_tar_test_case')
require 'rubygems/package/tar_input'
class TestGemPackageTarInput < TarTestCase
# Sometimes the setgid bit doesn't take. Don't know if this is a problem on
# all systems, or just some. But for now, we will ignore it in the tests.
SETGID_BIT = 02000
def setup
super
inner_tar = tar_file_header("bla", "", 0612, 10)
inner_tar += "0123456789" + "\0" * 502
inner_tar += tar_file_header("foo", "", 0636, 5)
inner_tar += "01234" + "\0" * 507
inner_tar += tar_dir_header("__dir__", "", 0600)
inner_tar += "\0" * 1024
str = TempIO.new
begin
os = Zlib::GzipWriter.new str
os.write inner_tar
ensure
os.finish
end
str.rewind
@file = File.join @tempdir, 'bla.tar'
File.open @file, 'wb' do |f|
f.write tar_file_header("data.tar.gz", "", 0644, str.string.size)
f.write str.string
f.write "\0" * ((512 - (str.string.size % 512)) % 512 )
@spec = Gem::Specification.new do |spec|
spec.author = "Mauricio :)"
end
meta = @spec.to_yaml
f.write tar_file_header("metadata", "", 0644, meta.size)
f.write meta + "\0" * (1024 - meta.size)
f.write "\0" * 1024
end
@entry_names = %w{bla foo __dir__}
@entry_sizes = [10, 5, 0]
#FIXME: are these modes system dependent?
@entry_modes = [0100612, 0100636, 040600]
@entry_files = %W[#{@tempdir}/bla #{@tempdir}/foo]
@entry_contents = %w[0123456789 01234]
end
def test_each_works
open @file, 'rb' do |io|
Gem::Package::TarInput.open io do |tar_input|
count = 0
tar_input.each_with_index do |entry, i|
count = i
assert_kind_of Gem::Package::TarReader::Entry, entry
assert_equal @entry_names[i], entry.header.name
assert_equal @entry_sizes[i], entry.header.size
end
assert_equal 2, count
assert_equal @spec, tar_input.metadata
end
end
end
def test_extract_entry_works
open @file, 'rb' do |io|
Gem::Package::TarInput.open io do |tar_input|
assert_equal @spec, tar_input.metadata
count = 0
tar_input.each_with_index do |entry, i|
count = i
tar_input.extract_entry @tempdir, entry
name = File.join @tempdir, entry.header.name
if entry.directory?
assert File.dir?(name)
else
assert File.file?(name)
assert_equal @entry_sizes[i], File.stat(name).size
#FIXME: win32? !!
end
unless Gem.win_platform? then
assert_equal @entry_modes[i], File.stat(name).mode & (~SETGID_BIT)
end
end
assert_equal 2, count
end
end
@entry_files.each_with_index do |x, i|
assert File.file?(x)
assert_equal @entry_contents[i], File.read_b(x)
end
end
end

View file

@ -0,0 +1,104 @@
#--
# Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others.
# All rights reserved.
# See LICENSE.txt for permissions.
#++
require File.join(File.expand_path(File.dirname(__FILE__)),
'gem_package_tar_test_case')
require 'rubygems/package/tar_output'
class TestGemPackageTarOutput < TarTestCase
def setup
super
@file = File.join @tempdir, 'bla2.tar'
end
def test_self_open
open @file, 'wb' do |tar_io|
Gem::Package::TarOutput.open tar_io do |tar_writer|
tar_writer.add_file_simple 'README', 0, 17 do |io|
io.write "This is a README\n"
end
tar_writer.metadata = "This is some metadata\n"
end
end
files = util_extract
name, data = files.shift
assert_equal 'data.tar.gz', name
gz = Zlib::GzipReader.new StringIO.new(data)
Gem::Package::TarReader.new gz do |tar_reader|
tar_reader.each do |entry|
assert_equal 'README', entry.full_name
assert_equal "This is a README\n", entry.read
end
end
gz.close
name, data = files.shift
assert_equal 'metadata.gz', name
gz = Zlib::GzipReader.new StringIO.new(data)
assert_equal "This is some metadata\n", gz.read
assert files.empty?
ensure
gz.close if gz
end
if defined? OpenSSL then
def test_self_open_signed
signer = Gem::Security::Signer.new @private_key, [@public_cert]
open @file, 'wb' do |tar_io|
Gem::Package::TarOutput.open tar_io, signer do |tar_writer|
tar_writer.add_file_simple 'README', 0, 17 do |io|
io.write "This is a README\n"
end
tar_writer.metadata = "This is some metadata\n"
end
end
files = util_extract
name, data = files.shift
assert_equal 'data.tar.gz', name
name, data = files.shift
assert_equal 'metadata.gz', name
name, data = files.shift
assert_equal 'data.tar.gz.sig', name
name, data = files.shift
assert_equal 'metadata.gz.sig', name
assert files.empty?
end
end
def util_extract
files = []
open @file, 'rb' do |io|
Gem::Package::TarReader.new io do |tar_reader|
tar_reader.each do |entry|
files << [entry.full_name, entry.read]
end
end
end
files
end
end

View file

@ -0,0 +1,53 @@
#--
# Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others.
# All rights reserved.
# See LICENSE.txt for permissions.
#++
require File.join(File.expand_path(File.dirname(__FILE__)),
'gem_package_tar_test_case')
require 'rubygems/package'
class TestGemPackageTarReader < TarTestCase
def test_each_entry
tar = tar_dir_header "foo", "bar", 0
tar << tar_file_header("bar", "baz", 0, 0)
io = TempIO.new tar
entries = 0
Gem::Package::TarReader.new io do |tar_reader|
tar_reader.each_entry do |entry|
assert_kind_of Gem::Package::TarReader::Entry, entry
entries += 1
end
end
assert_equal 2, entries
end
def test_rewind
content = ('a'..'z').to_a.join(" ")
str = tar_file_header("lib/foo", "", 010644, content.size) + content +
"\0" * (512 - content.size)
str << "\0" * 1024
Gem::Package::TarReader.new(TempIO.new(str)) do |tar_reader|
3.times do
tar_reader.rewind
i = 0
tar_reader.each_entry do |entry|
assert_equal(content, entry.read)
i += 1
end
assert_equal(1, i)
end
end
end
end

View file

@ -0,0 +1,116 @@
#--
# Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others.
# All rights reserved.
# See LICENSE.txt for permissions.
#++
require File.join(File.expand_path(File.dirname(__FILE__)),
'gem_package_tar_test_case')
require 'rubygems/package'
class TestGemPackageTarReaderEntry < TarTestCase
def setup
super
@contents = ('a'..'z').to_a.join * 100
@tar = ''
@tar << tar_file_header("lib/foo", "", 0, @contents.size)
@tar << @contents
@tar << "\0" * (512 - (@tar.size % 512))
@entry = util_entry @tar
end
def test_bytes_read
assert_equal 0, @entry.bytes_read
@entry.getc
assert_equal 1, @entry.bytes_read
end
def test_close
@entry.close
assert @entry.bytes_read
e = assert_raise IOError do @entry.eof? end
assert_equal 'closed Gem::Package::TarReader::Entry', e.message
e = assert_raise IOError do @entry.getc end
assert_equal 'closed Gem::Package::TarReader::Entry', e.message
e = assert_raise IOError do @entry.pos end
assert_equal 'closed Gem::Package::TarReader::Entry', e.message
e = assert_raise IOError do @entry.read end
assert_equal 'closed Gem::Package::TarReader::Entry', e.message
e = assert_raise IOError do @entry.rewind end
assert_equal 'closed Gem::Package::TarReader::Entry', e.message
end
def test_closed_eh
@entry.close
assert @entry.closed?
end
def test_eof_eh
@entry.read
assert @entry.eof?
end
def test_full_name
assert_equal 'lib/foo', @entry.full_name
end
def test_getc
assert_equal ?a, @entry.getc
end
def test_directory_eh
assert_equal false, @entry.directory?
assert_equal true, util_dir_entry.directory?
end
def test_file_eh
assert_equal true, @entry.file?
assert_equal false, util_dir_entry.file?
end
def test_pos
assert_equal 0, @entry.pos
@entry.getc
assert_equal 1, @entry.pos
end
def test_read
assert_equal @contents, @entry.read
end
def test_read_big
assert_equal @contents, @entry.read(@contents.size * 2)
end
def test_read_small
assert_equal @contents[0...100], @entry.read(100)
end
def test_rewind
char = @entry.getc
@entry.rewind
assert_equal 0, @entry.pos
assert_equal char, @entry.getc
end
end

View file

@ -0,0 +1,151 @@
#--
# Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others.
# All rights reserved.
# See LICENSE.txt for permissions.
#++
require File.join(File.expand_path(File.dirname(__FILE__)),
'gem_package_tar_test_case')
require 'rubygems/package/tar_writer'
class TestTarWriter < TarTestCase
def setup
super
@data = 'abcde12345'
@io = TempIO.new
@tar_writer = Gem::Package::TarWriter.new @io
end
def teardown
@tar_writer.close unless @tar_writer.closed?
super
end
def test_add_file
@tar_writer.add_file 'x', 0644 do |f| f.write 'a' * 10 end
assert_headers_equal(tar_file_header('x', '', 0644, 10),
@io.string[0, 512])
assert_equal "aaaaaaaaaa#{"\0" * 502}", @io.string[512, 512]
assert_equal 1024, @io.pos
end
def test_add_file_simple
@tar_writer.add_file_simple 'x', 0644, 10 do |io| io.write "a" * 10 end
assert_headers_equal(tar_file_header('x', '', 0644, 10),
@io.string[0, 512])
assert_equal "aaaaaaaaaa#{"\0" * 502}", @io.string[512, 512]
assert_equal 1024, @io.pos
end
def test_add_file_simple_padding
@tar_writer.add_file_simple 'x', 0, 100
assert_headers_equal tar_file_header('x', '', 0, 100),
@io.string[0, 512]
assert_equal "\0" * 512, @io.string[512, 512]
end
def test_add_file_simple_data
@tar_writer.add_file_simple("lib/foo/bar", 0, 10) { |f| f.write @data }
@tar_writer.flush
assert_equal @data + ("\0" * (512-@data.size)),
@io.string[512, 512]
end
def test_add_file_simple_size
assert_raise Gem::Package::TarWriter::FileOverflow do
@tar_writer.add_file_simple("lib/foo/bar", 0, 10) do |io|
io.write "1" * 11
end
end
end
def test_add_file_unseekable
assert_raise Gem::Package::NonSeekableIO do
Gem::Package::TarWriter.new(Object.new).add_file 'x', 0
end
end
def test_close
@tar_writer.close
assert_equal "\0" * 1024, @io.string
e = assert_raise IOError do
@tar_writer.close
end
assert_equal 'closed Gem::Package::TarWriter', e.message
e = assert_raise IOError do
@tar_writer.flush
end
assert_equal 'closed Gem::Package::TarWriter', e.message
e = assert_raise IOError do
@tar_writer.add_file 'x', 0
end
assert_equal 'closed Gem::Package::TarWriter', e.message
e = assert_raise IOError do
@tar_writer.add_file_simple 'x', 0, 0
end
assert_equal 'closed Gem::Package::TarWriter', e.message
e = assert_raise IOError do
@tar_writer.mkdir 'x', 0
end
assert_equal 'closed Gem::Package::TarWriter', e.message
end
def test_mkdir
@tar_writer.mkdir 'foo', 0644
assert_headers_equal tar_dir_header('foo', '', 0644),
@io.string[0, 512]
assert_equal 512, @io.pos
end
def test_split_name
assert_equal ['b' * 100, 'a' * 155],
@tar_writer.split_name("#{'a' * 155}/#{'b' * 100}")
assert_equal ["#{'qwer/' * 19}bla", 'a' * 151],
@tar_writer.split_name("#{'a' * 151}/#{'qwer/' * 19}bla")
end
def test_split_name_too_long_name
name = File.join 'a', 'b' * 100
assert_equal ['b' * 100, 'a'], @tar_writer.split_name(name)
assert_raise Gem::Package::TooLongFileName do
name = File.join 'a', 'b' * 101
@tar_writer.split_name name
end
end
def test_split_name_too_long_prefix
name = File.join 'a' * 155, 'b'
assert_equal ['b', 'a' * 155], @tar_writer.split_name(name)
assert_raise Gem::Package::TooLongFileName do
name = File.join 'a' * 156, 'b'
@tar_writer.split_name name
end
end
def test_split_name_too_long_total
assert_raise Gem::Package::TooLongFileName do
@tar_writer.split_name 'a' * 257
end
end
end

View file

@ -97,6 +97,13 @@ gems:
@server_uri = base_server_uri + "/yaml" @server_uri = base_server_uri + "/yaml"
@server_z_uri = base_server_uri + "/yaml.Z" @server_z_uri = base_server_uri + "/yaml.Z"
# REFACTOR: copied from test_gem_dependency_installer.rb
@gems_dir = File.join @tempdir, 'gems'
@cache_dir = File.join @gemhome, 'cache'
FileUtils.mkdir @gems_dir
@a1, @a1_gem = util_gem 'a', '1' do |s| s.executables << 'a_bin' end
Gem::RemoteFetcher.instance_variable_set :@fetcher, nil Gem::RemoteFetcher.instance_variable_set :@fetcher, nil
end end
@ -156,6 +163,140 @@ gems:
end end
end end
def util_fuck_with_fetcher data, blow = false
fetcher = Gem::RemoteFetcher.fetcher
fetcher.instance_variable_set :@test_data, data
unless blow then
def fetcher.fetch_path arg
@test_arg = arg
@test_data
end
else
def fetcher.fetch_path arg
# OMG I'm such an ass
class << self; remove_method :fetch_path; end
def self.fetch_path arg
@test_arg = arg
@test_data
end
raise Gem::RemoteFetcher::FetchError, "haha!"
end
end
fetcher
end
def test_download
a1_data = nil
File.open @a1_gem, 'rb' do |fp|
a1_data = fp.read
end
fetcher = util_fuck_with_fetcher a1_data
a1_cache_gem = File.join(@gemhome, 'cache', "#{@a1.full_name}.gem")
assert_equal a1_cache_gem, fetcher.download(@a1, 'http://gems.example.com')
assert_equal("http://gems.example.com/gems/a-1.gem",
fetcher.instance_variable_get(:@test_arg).to_s)
assert File.exist?(a1_cache_gem)
end
def test_download_cached
FileUtils.mv @a1_gem, @cache_dir
inst = Gem::RemoteFetcher.fetcher
assert_equal File.join(@gemhome, 'cache', "#{@a1.full_name}.gem"),
inst.download(@a1, 'http://gems.example.com')
end
def test_download_local
FileUtils.mv @a1_gem, @tempdir
local_path = File.join @tempdir, "#{@a1.full_name}.gem"
inst = nil
Dir.chdir @tempdir do
inst = Gem::RemoteFetcher.fetcher
end
assert_equal File.join(@gemhome, 'cache', "#{@a1.full_name}.gem"),
inst.download(@a1, local_path)
end
def test_download_install_dir
a1_data = nil
File.open @a1_gem, 'rb' do |fp|
a1_data = fp.read
end
fetcher = util_fuck_with_fetcher a1_data
install_dir = File.join @tempdir, 'more_gems'
a1_cache_gem = File.join install_dir, 'cache', "#{@a1.full_name}.gem"
actual = fetcher.download(@a1, 'http://gems.example.com', install_dir)
assert_equal a1_cache_gem, actual
assert_equal("http://gems.example.com/gems/a-1.gem",
fetcher.instance_variable_get(:@test_arg).to_s)
assert File.exist?(a1_cache_gem)
end
unless win_platform? then # File.chmod doesn't work
def test_download_local_read_only
FileUtils.mv @a1_gem, @tempdir
local_path = File.join @tempdir, "#{@a1.full_name}.gem"
inst = nil
File.chmod 0555, File.join(@gemhome, 'cache')
Dir.chdir @tempdir do
inst = Gem::RemoteFetcher.fetcher
end
assert_equal File.join(@tempdir, "#{@a1.full_name}.gem"),
inst.download(@a1, local_path)
ensure
File.chmod 0755, File.join(@gemhome, 'cache')
end
end
def test_download_platform_legacy
original_platform = 'old-platform'
e1, e1_gem = util_gem 'e', '1' do |s|
s.platform = Gem::Platform::CURRENT
s.instance_variable_set :@original_platform, original_platform
end
e1_data = nil
File.open e1_gem, 'rb' do |fp|
e1_data = fp.read
end
fetcher = util_fuck_with_fetcher e1_data, :blow_chunks
e1_cache_gem = File.join(@gemhome, 'cache', "#{e1.full_name}.gem")
assert_equal e1_cache_gem, fetcher.download(e1, 'http://gems.example.com')
assert_equal("http://gems.example.com/gems/#{e1.original_name}.gem",
fetcher.instance_variable_get(:@test_arg).to_s)
assert File.exist?(e1_cache_gem)
end
def test_download_unsupported
inst = Gem::RemoteFetcher.fetcher
e = assert_raise Gem::InstallError do
inst.download @a1, 'ftp://gems.rubyforge.org'
end
assert_equal 'unsupported URI scheme ftp', e.message
end
def test_explicit_proxy def test_explicit_proxy
use_ui @ui do use_ui @ui do
fetcher = Gem::RemoteFetcher.new @proxy_uri fetcher = Gem::RemoteFetcher.new @proxy_uri
@ -232,22 +373,6 @@ gems:
assert_equal 'EOFError: EOFError reading uri', e.message assert_equal 'EOFError: EOFError reading uri', e.message
end end
def test_fetch_path_open_uri_http_error
fetcher = Gem::RemoteFetcher.new nil
def fetcher.open_uri_or_path(uri)
io = StringIO.new 'went boom'
err = OpenURI::HTTPError.new 'error', io
raise err
end
e = assert_raise Gem::RemoteFetcher::FetchError do
fetcher.fetch_path 'uri'
end
assert_equal "OpenURI::HTTPError: error reading uri\n\twent boom", e.message
end
def test_fetch_path_socket_error def test_fetch_path_socket_error
fetcher = Gem::RemoteFetcher.new nil fetcher = Gem::RemoteFetcher.new nil
@ -324,6 +449,53 @@ gems:
end end
end end
def test_open_uri_or_path
fetcher = Gem::RemoteFetcher.new nil
conn = Object.new
def conn.started?() true end
def conn.request(req)
unless defined? @requested then
@requested = true
res = Net::HTTPRedirection.new nil, 301, nil
res.add_field 'Location', 'http://gems.example.com/real_path'
res
else
res = Net::HTTPOK.new nil, 200, nil
def res.body() 'real_path' end
res
end
end
conn = { 'gems.example.com:80' => conn }
fetcher.instance_variable_set :@connections, conn
fetcher.send :open_uri_or_path, 'http://gems.example.com/redirect' do |io|
assert_equal 'real_path', io.read
end
end
def test_open_uri_or_path_limited_redirects
fetcher = Gem::RemoteFetcher.new nil
conn = Object.new
def conn.started?() true end
def conn.request(req)
res = Net::HTTPRedirection.new nil, 301, nil
res.add_field 'Location', 'http://gems.example.com/redirect'
res
end
conn = { 'gems.example.com:80' => conn }
fetcher.instance_variable_set :@connections, conn
e = assert_raise Gem::RemoteFetcher::FetchError do
fetcher.send :open_uri_or_path, 'http://gems.example.com/redirect'
end
assert_equal 'too many redirects', e.message
end
def test_zip def test_zip
use_ui @ui do use_ui @ui do
self.class.enable_zip = true self.class.enable_zip = true

View file

@ -36,7 +36,8 @@ class TestGemSourceIndex < RubyGemTestCase
use_ui @ui do use_ui @ui do
fetched_index = @source_index.fetch_bulk_index @uri fetched_index = @source_index.fetch_bulk_index @uri
assert_equal [@gem1.full_name, @gem4.full_name, @gem2.full_name].sort, assert_equal [@a1.full_name, @a2.full_name, @a_evil9.full_name,
@c1_2.full_name].sort,
fetched_index.gems.map { |n,s| n }.sort fetched_index.gems.map { |n,s| n }.sort
end end
@ -82,7 +83,8 @@ class TestGemSourceIndex < RubyGemTestCase
use_ui @ui do use_ui @ui do
fetched_index = @source_index.fetch_bulk_index @uri fetched_index = @source_index.fetch_bulk_index @uri
assert_equal [@gem1.full_name, @gem4.full_name, @gem2.full_name].sort, assert_equal [@a1.full_name, @a2.full_name, @a_evil9.full_name,
@c1_2.full_name].sort,
fetched_index.gems.map { |n,s| n }.sort fetched_index.gems.map { |n,s| n }.sort
end end
@ -105,7 +107,8 @@ class TestGemSourceIndex < RubyGemTestCase
use_ui @ui do use_ui @ui do
fetched_index = @source_index.fetch_bulk_index @uri fetched_index = @source_index.fetch_bulk_index @uri
assert_equal [@gem1.full_name, @gem4.full_name, @gem2.full_name].sort, assert_equal [@a1.full_name, @a2.full_name, @a_evil9.full_name,
@c1_2.full_name].sort,
fetched_index.gems.map { |n,s| n }.sort fetched_index.gems.map { |n,s| n }.sort
end end
@ -123,7 +126,8 @@ class TestGemSourceIndex < RubyGemTestCase
util_setup_bulk_fetch false util_setup_bulk_fetch false
use_ui @ui do use_ui @ui do
fetched_index = @source_index.fetch_bulk_index @uri fetched_index = @source_index.fetch_bulk_index @uri
assert_equal [@gem1.full_name, @gem4.full_name, @gem2.full_name].sort, assert_equal [@a1.full_name, @a2.full_name, @a_evil9.full_name,
@c1_2.full_name].sort,
fetched_index.gems.map { |n,s| n }.sort fetched_index.gems.map { |n,s| n }.sort
end end
@ -136,11 +140,32 @@ class TestGemSourceIndex < RubyGemTestCase
end end
def test_fetch_quick_index def test_fetch_quick_index
quick_index = util_zip @gem_names index = util_zip @gem_names
@fetcher.data["#{@gem_repo}/quick/index.rz"] = quick_index latest_index = util_zip [@a2.full_name, @b2.full_name].join("\n")
quick_index = @source_index.fetch_quick_index @uri @fetcher.data["#{@gem_repo}/quick/index.rz"] = index
assert_equal [@gem1.full_name, @gem4.full_name, @gem2.full_name].sort, @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,
quick_index.sort
paths = @fetcher.paths
assert_equal "#{@gem_repo}/quick/latest_index.rz", paths.shift
assert paths.empty?, paths.join(', ')
end
def test_fetch_quick_index_all
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
quick_index = @source_index.fetch_quick_index @uri, true
assert_equal [@a1.full_name, @a2.full_name, @b2.full_name].sort,
quick_index.sort quick_index.sort
paths = @fetcher.paths paths = @fetcher.paths
@ -155,7 +180,7 @@ class TestGemSourceIndex < RubyGemTestCase
proc { raise Exception } proc { raise Exception }
e = assert_raise Gem::OperationNotSupportedError do e = assert_raise Gem::OperationNotSupportedError do
@source_index.fetch_quick_index @uri @source_index.fetch_quick_index @uri, true
end end
assert_equal 'No quick index found: Exception', e.message assert_equal 'No quick index found: Exception', e.message
@ -167,41 +192,201 @@ class TestGemSourceIndex < RubyGemTestCase
assert paths.empty?, paths.join(', ') assert paths.empty?, paths.join(', ')
end end
def test_fetch_quick_index_fallback
index = util_zip @gem_names
@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 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/"
@fetcher.data["#{repo}quick/latest_index.rz"] = latest_index
quick_index = @source_index.fetch_quick_index repo, false
assert_equal [@a2.full_name, @b2.full_name].sort,
quick_index.sort
paths = @fetcher.paths
assert_equal "#{repo}quick/latest_index.rz", paths.shift
assert paths.empty?, paths.join(', ')
end
def test_fetch_single_spec
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),
@a1.full_name
assert_equal @a1.full_name, spec.full_name
paths = @fetcher.paths
assert_equal a1_spec_url, paths.shift
assert paths.empty?, paths.join(', ')
end
def test_fetch_single_spec_subdir
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)
spec = @source_index.send :fetch_single_spec, repo, @a1.full_name
assert_equal @a1.full_name, spec.full_name
paths = @fetcher.paths
assert_equal a1_spec_url, paths.shift
assert paths.empty?, paths.join(', ')
end
def test_fetch_single_spec_yaml
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
spec = @source_index.send :fetch_single_spec, repo, @a1.full_name
assert_equal @a1.full_name, spec.full_name
paths = @fetcher.paths
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/"
a1_spec_url = "#{repo}quick/#{@a1.full_name}.gemspec.rz"
@fetcher.data[a1_spec_url] = util_zip @a1.to_yaml
spec = @source_index.send :fetch_single_spec, repo, @a1.full_name
assert_equal @a1.full_name, spec.full_name
paths = @fetcher.paths
assert_equal "#{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_find_missing def test_find_missing
missing = @source_index.find_missing [@gem3.full_name] missing = @source_index.find_missing [@b2.full_name]
assert_equal [@gem3.full_name], missing assert_equal [@b2.full_name], missing
end end
def test_find_missing_none_missing def test_find_missing_none_missing
missing = @source_index.find_missing @gem_names.split missing = @source_index.find_missing [
@a1.full_name, @a2.full_name, @c1_2.full_name
]
assert_equal [], missing assert_equal [], missing
end end
def test_latest_specs def test_latest_specs
spec = quick_gem @gem1.name, '1' p1_ruby = quick_gem 'p', '1'
@source_index.add_spec spec p1_platform = quick_gem 'p', '1' do |spec|
spec.platform = Gem::Platform::CURRENT
end
a1_platform = quick_gem @a1.name, (@a1.version) do |s|
s.platform = Gem::Platform.new 'x86-my_platform1'
end
a2_platform = quick_gem @a2.name, (@a2.version) do |s|
s.platform = Gem::Platform.new 'x86-my_platform1'
end
a2_platform_other = quick_gem @a2.name, (@a2.version) do |s|
s.platform = Gem::Platform.new 'x86-other_platform1'
end
a3_platform_other = quick_gem @a2.name, (@a2.version.bump) do |s|
s.platform = Gem::Platform.new 'x86-other_platform1'
end
@source_index.add_spec p1_ruby
@source_index.add_spec p1_platform
@source_index.add_spec a1_platform
@source_index.add_spec a2_platform
@source_index.add_spec a2_platform_other
@source_index.add_spec a3_platform_other
expected = [ expected = [
@gem1.full_name, @a2.full_name,
@gem2.full_name, a2_platform.full_name,
@gem4.full_name, a3_platform_other.full_name,
@c1_2.full_name,
@a_evil9.full_name,
p1_ruby.full_name,
p1_platform.full_name,
].sort ].sort
assert_equal expected, @source_index.latest_specs.map { |s| s.full_name }.sort latest_specs = @source_index.latest_specs.map { |s| s.full_name }.sort
assert_equal expected, latest_specs
end
def test_load_gems_in
spec_dir1 = File.join @gemhome, 'specifications'
spec_dir2 = File.join @tempdir, 'gemhome2', 'specifications'
FileUtils.rm_r spec_dir1
FileUtils.mkdir_p spec_dir1
FileUtils.mkdir_p spec_dir2
a1 = quick_gem 'a', '1' do |spec| spec.author = 'author 1' end
a2 = quick_gem 'a', '1' do |spec| spec.author = 'author 2' end
File.open File.join(spec_dir1, "#{a1.full_name}.gemspec"), 'w' do |fp|
fp.write a1.to_ruby
end
File.open File.join(spec_dir2, "#{a2.full_name}.gemspec"), 'w' do |fp|
fp.write a2.to_ruby
end
@source_index.load_gems_in spec_dir1, spec_dir2
assert_equal a1.author, @source_index.specification(a1.full_name).author
end end
def test_outdated def test_outdated
sic = Gem::SourceInfoCache.new util_setup_source_info_cache
Gem::SourceInfoCache.instance_variable_set :@cache, sic
assert_equal [], @source_index.outdated assert_equal [], @source_index.outdated
updated = quick_gem @gem1.name, (@gem1.version.bump) updated = quick_gem @a2.name, (@a2.version.bump)
util_setup_source_info_cache updated util_setup_source_info_cache updated
assert_equal [updated.name], @source_index.outdated assert_equal [updated.name], @source_index.outdated
updated_platform = quick_gem @gem1.name, (updated.version.bump) do |s| updated_platform = quick_gem @a2.name, (updated.version.bump) do |s|
s.platform = Gem::Platform.new 'x86-other_platform1' s.platform = Gem::Platform.new 'x86-other_platform1'
end end
@ -211,28 +396,34 @@ class TestGemSourceIndex < RubyGemTestCase
end end
def test_remove_extra def test_remove_extra
@source_index.remove_extra [@gem1.full_name] @source_index.add_spec @a1
assert_equal [@gem1.full_name], @source_index.gems.map { |n,s| n } @source_index.add_spec @a2
@source_index.remove_extra [@a1.full_name]
assert_equal [@a1.full_name], @source_index.gems.map { |n,s| n }
end end
def test_remove_extra_no_changes def test_remove_extra_no_changes
gems = @gem_names.split.sort gems = [@a1.full_name, @a2.full_name]
@source_index.add_spec @a1
@source_index.add_spec @a2
@source_index.remove_extra gems @source_index.remove_extra gems
assert_equal gems, @source_index.gems.map { |n,s| n }.sort assert_equal gems, @source_index.gems.map { |n,s| n }.sort
end end
def test_search def test_search
assert_equal [@gem1, @gem4], @source_index.search("gem_one") assert_equal [@a1, @a2, @a_evil9], @source_index.search('a')
assert_equal [@gem1], @source_index.search("gem_one", "= 2") assert_equal [@a2], @source_index.search('a', '= 2')
assert_equal [], @source_index.search("bogusstring") assert_equal [], @source_index.search('bogusstring')
assert_equal [], @source_index.search("gem_one", "= 3.2.1") assert_equal [], @source_index.search('a', '= 3')
@a1 = quick_gem 'a', '1' source_index = Gem::SourceIndex.new
@a2 = quick_gem 'a', '2' source_index.add_spec @a1
source_index.add_spec @a2
source_index = Gem::SourceIndex.new @a1.full_name => @a1,
@a2.full_name => @a2
assert_equal [@a1], source_index.search(@a1.name, '= 1') assert_equal [@a1], source_index.search(@a1.name, '= 1')
@ -276,7 +467,7 @@ class TestGemSourceIndex < RubyGemTestCase
end end
def test_specification def test_specification
assert_equal @gem1, @source_index.specification(@gem1.full_name) assert_equal @a1, @source_index.specification(@a1.full_name)
assert_nil @source_index.specification("foo-1.2.4") assert_nil @source_index.specification("foo-1.2.4")
end end
@ -298,9 +489,11 @@ class TestGemSourceIndex < RubyGemTestCase
assert_equal [], @source_index.gems.keys.sort assert_equal [], @source_index.gems.keys.sort
use_ui @ui do use_ui @ui do
@source_index.update @uri @source_index.update @uri, true
assert_equal @gem_names.split, @source_index.gems.keys.sort assert_equal [@a1.full_name, @a2.full_name, @a_evil9.full_name,
@c1_2.full_name],
@source_index.gems.keys.sort
end end
paths = @fetcher.paths paths = @fetcher.paths
@ -315,15 +508,42 @@ class TestGemSourceIndex < RubyGemTestCase
old_gem_conf = Gem.configuration old_gem_conf = Gem.configuration
Gem.configuration = Gem::ConfigFile.new([]) Gem.configuration = Gem::ConfigFile.new([])
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
marshal_uri = File.join @gem_repo, "quick", "Marshal.#{@marshal_version}",
"#{@b2.full_name}.gemspec.rz"
@fetcher.data[marshal_uri] = util_zip Marshal.dump(@b2)
use_ui @ui do
@source_index.update @uri, false
assert_equal latest_names, @source_index.gems.keys.sort
end
paths = @fetcher.paths
assert_equal "#{@gem_repo}/quick/latest_index.rz", paths.shift
assert_equal marshal_uri, paths.shift
assert paths.empty?, paths.join(', ')
ensure
Gem.configuration = old_gem_conf
end
def test_update_incremental_all
old_gem_conf = Gem.configuration
Gem.configuration = Gem::ConfigFile.new([])
quick_index = util_zip @all_gem_names.join("\n") 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}", marshal_uri = File.join @gem_repo, "quick", "Marshal.#{@marshal_version}",
"#{@gem3.full_name}.gemspec.rz" "#{@b2.full_name}.gemspec.rz"
@fetcher.data[marshal_uri] = util_zip Marshal.dump(@gem3) @fetcher.data[marshal_uri] = util_zip Marshal.dump(@b2)
use_ui @ui do use_ui @ui do
@source_index.update @uri @source_index.update @uri, true
assert_equal @all_gem_names, @source_index.gems.keys.sort assert_equal @all_gem_names, @source_index.gems.keys.sort
end end
@ -345,13 +565,13 @@ class TestGemSourceIndex < RubyGemTestCase
@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}", marshal_uri = File.join @gem_repo, "quick", "Marshal.#{@marshal_version}",
"#{@gem3.full_name}.gemspec.rz" "#{@b2.full_name}.gemspec.rz"
yaml_uri = "#{@gem_repo}/quick/#{@gem3.full_name}.gemspec.rz" yaml_uri = "#{@gem_repo}/quick/#{@b2.full_name}.gemspec.rz"
@fetcher.data[yaml_uri] = util_zip @gem3.to_yaml @fetcher.data[yaml_uri] = util_zip @b2.to_yaml
use_ui @ui do use_ui @ui do
@source_index.update @uri @source_index.update @uri, true
assert_equal @all_gem_names, @source_index.gems.keys.sort assert_equal @all_gem_names, @source_index.gems.keys.sort
end end
@ -374,16 +594,16 @@ class TestGemSourceIndex < RubyGemTestCase
@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}", marshal_uri = File.join @gem_repo, "quick", "Marshal.#{@marshal_version}",
"#{@gem3.full_name}.gemspec.rz" "#{@b2.full_name}.gemspec.rz"
marshal_data = Marshal.dump(@gem3) marshal_data = Marshal.dump(@b2)
marshal_data[0] = (Marshal::MAJOR_VERSION - 1).chr marshal_data[0] = (Marshal::MAJOR_VERSION - 1).chr
@fetcher.data[marshal_uri] = util_zip marshal_data @fetcher.data[marshal_uri] = util_zip marshal_data
yaml_uri = "#{@gem_repo}/quick/#{@gem3.full_name}.gemspec.rz" yaml_uri = "#{@gem_repo}/quick/#{@b2.full_name}.gemspec.rz"
@fetcher.data[yaml_uri] = util_zip @gem3.to_yaml @fetcher.data[yaml_uri] = util_zip @b2.to_yaml
use_ui @ui do use_ui @ui do
@source_index.update @uri @source_index.update @uri, true
assert_equal @all_gem_names, @source_index.gems.keys.sort assert_equal @all_gem_names, @source_index.gems.keys.sort
end end
@ -398,22 +618,48 @@ class TestGemSourceIndex < RubyGemTestCase
Gem.configuration = old_gem_conf Gem.configuration = old_gem_conf
end end
def test_update_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"
use_ui @ui do
@source_index.update uri, true
assert_equal [@a1.full_name, @a2.full_name, @a_evil9.full_name,
@c1_2.full_name],
@source_index.gems.keys.sort
end
paths = @fetcher.paths
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
def test_update_with_missing def test_update_with_missing
marshal_uri = File.join @gem_repo, "quick", "Marshal.#{@marshal_version}", marshal_uri = File.join @gem_repo, "quick", "Marshal.#{@marshal_version}",
"#{@gem3.full_name}.gemspec.rz" "#{@c1_2.full_name}.gemspec.rz"
dumped = Marshal.dump @gem3 dumped = Marshal.dump @c1_2
@fetcher.data[marshal_uri] = util_zip(dumped) @fetcher.data[marshal_uri] = util_zip(dumped)
use_ui @ui do use_ui @ui do
@source_index.update_with_missing @uri, [@gem3.full_name] @source_index.update_with_missing @uri, [@c1_2.full_name]
end end
spec = @source_index.specification(@gem3.full_name) spec = @source_index.specification(@c1_2.full_name)
# We don't care about the equality of undumped attributes # We don't care about the equality of undumped attributes
@gem3.files = spec.files @c1_2.files = spec.files
@gem3.loaded_from = spec.loaded_from @c1_2.loaded_from = spec.loaded_from
assert_equal @gem3, spec assert_equal @c1_2, spec
end end
def util_setup_bulk_fetch(compressed) def util_setup_bulk_fetch(compressed)
@ -427,3 +673,4 @@ class TestGemSourceIndex < RubyGemTestCase
end end
end end

View file

@ -25,7 +25,12 @@ class TestGemSourceInfoCache < RubyGemTestCase
@sic = Gem::SourceInfoCache.new @sic = Gem::SourceInfoCache.new
@sic.instance_variable_set :@fetcher, @fetcher @sic.instance_variable_set :@fetcher, @fetcher
@si_new = Gem::SourceIndex.new
@sice_new = Gem::SourceInfoCacheEntry.new @si_new, 0
prep_cache_files @sic prep_cache_files @sic
@sic.reset_cache_data
end end
def teardown def teardown
@ -35,8 +40,10 @@ class TestGemSourceInfoCache < RubyGemTestCase
def test_self_cache_refreshes def test_self_cache_refreshes
Gem.configuration.update_sources = true #true by default Gem.configuration.update_sources = true #true by default
source_index = Gem::SourceIndex.new 'key' => 'sys' si = Gem::SourceIndex.new
@fetcher.data["#{@gem_repo}/Marshal.#{@marshal_version}"] = source_index.dump si.add_spec @a1
@fetcher.data["#{@gem_repo}/Marshal.#{@marshal_version}"] = si.dump
Gem.sources.replace %W[#{@gem_repo}] Gem.sources.replace %W[#{@gem_repo}]
@ -51,8 +58,10 @@ class TestGemSourceInfoCache < RubyGemTestCase
def test_self_cache_skips_refresh_based_on_configuration def test_self_cache_skips_refresh_based_on_configuration
Gem.configuration.update_sources = false Gem.configuration.update_sources = false
source_index = Gem::SourceIndex.new 'key' => 'sys' si = Gem::SourceIndex.new
@fetcher.data["#{@gem_repo}/Marshal.#{@marshal_version}"] = source_index.dump si.add_spec @a1
@fetcher.data["#{@gem_repo}/Marshal.#{@marshal_version}"] = si.dump
Gem.sources.replace %w[#{@gem_repo}] Gem.sources.replace %w[#{@gem_repo}]
@ -66,20 +75,24 @@ class TestGemSourceInfoCache < RubyGemTestCase
end end
def test_self_cache_data def test_self_cache_data
source_index = Gem::SourceIndex.new 'key' => 'sys' si = Gem::SourceIndex.new
@fetcher.data["#{@gem_repo}/Marshal.#{@marshal_version}"] = source_index.dump si.add_spec @a1
@fetcher.data["#{@gem_repo}/Marshal.#{@marshal_version}"] = si.dump
Gem::SourceInfoCache.instance_variable_set :@cache, nil Gem::SourceInfoCache.instance_variable_set :@cache, nil
sice = Gem::SourceInfoCacheEntry.new source_index, 0 sice = Gem::SourceInfoCacheEntry.new si, 0
use_ui @ui do use_ui @ui do
assert_equal source_index.gems, gems = Gem::SourceInfoCache.cache_data[@gem_repo].source_index.gems
Gem::SourceInfoCache.cache_data[@gem_repo].source_index.gems gem_names = gems.map { |_, spec| spec.full_name }
assert_equal si.gems.map { |_,spec| spec.full_name }, gem_names
end end
end end
def test_cache_data def test_cache_data
assert_equal [['key','sys']], @sic.cache_data.to_a.sort assert_equal [[@gem_repo, @usr_sice]], @sic.cache_data.to_a.sort
end end
def test_cache_data_dirty def test_cache_data_dirty
@ -97,7 +110,14 @@ class TestGemSourceInfoCache < RubyGemTestCase
data = { @gem_repo => { 'totally' => 'borked' } } data = { @gem_repo => { 'totally' => 'borked' } }
[@sic.system_cache_file, @sic.user_cache_file].each do |fn| cache_files = [
@sic.system_cache_file,
@sic.latest_system_cache_file,
@sic.user_cache_file,
@sic.latest_user_cache_file
]
cache_files.each do |fn|
FileUtils.mkdir_p File.dirname(fn) FileUtils.mkdir_p File.dirname(fn)
open(fn, "wb") { |f| f.write Marshal.dump(data) } open(fn, "wb") { |f| f.write Marshal.dump(data) }
end end
@ -113,7 +133,9 @@ class TestGemSourceInfoCache < RubyGemTestCase
def test_cache_data_none_readable def test_cache_data_none_readable
FileUtils.chmod 0222, @sic.system_cache_file FileUtils.chmod 0222, @sic.system_cache_file
FileUtils.chmod 0222, @sic.latest_system_cache_file
FileUtils.chmod 0222, @sic.user_cache_file FileUtils.chmod 0222, @sic.user_cache_file
FileUtils.chmod 0222, @sic.latest_user_cache_file
return if (File.stat(@sic.system_cache_file).mode & 0222) != 0222 return if (File.stat(@sic.system_cache_file).mode & 0222) != 0222
return if (File.stat(@sic.user_cache_file).mode & 0222) != 0222 return if (File.stat(@sic.user_cache_file).mode & 0222) != 0222
# HACK for systems that don't support chmod # HACK for systems that don't support chmod
@ -129,6 +151,16 @@ class TestGemSourceInfoCache < RubyGemTestCase
assert_equal 'unable to locate a writable cache file', e.message assert_equal 'unable to locate a writable cache file', e.message
end end
def test_cache_data_nonexistent
FileUtils.rm @sic.system_cache_file
FileUtils.rm @sic.latest_system_cache_file
FileUtils.rm @sic.user_cache_file
FileUtils.rm @sic.latest_user_cache_file
# TODO test verbose output
assert_equal [], @sic.cache_data.to_a.sort
end
def test_cache_data_repair def test_cache_data_repair
data = { data = {
@gem_repo => { @gem_repo => {
@ -152,7 +184,8 @@ class TestGemSourceInfoCache < RubyGemTestCase
def test_cache_data_user_fallback def test_cache_data_user_fallback
FileUtils.chmod 0444, @sic.system_cache_file FileUtils.chmod 0444, @sic.system_cache_file
assert_equal [['key','usr']], @sic.cache_data.to_a.sort
assert_equal [[@gem_repo, @usr_sice]], @sic.cache_data.to_a.sort
end end
def test_cache_file def test_cache_file
@ -174,60 +207,118 @@ class TestGemSourceInfoCache < RubyGemTestCase
end end
def test_flush def test_flush
@sic.cache_data['key'] = 'new' @sic.cache_data[@gem_repo] = @sice_new
@sic.update @sic.update
@sic.flush @sic.flush
assert_equal [['key','new']], read_cache(@sic.system_cache_file).to_a.sort assert_equal [[@gem_repo, @sice_new]],
read_cache(@sic.system_cache_file).to_a.sort
end
def test_latest_cache_data
util_make_gems
sice = Gem::SourceInfoCacheEntry.new @source_index, 0
@sic.set_cache_data @gem_repo => sice
latest = @sic.latest_cache_data
gems = latest[@gem_repo].source_index.search('a').map { |s| s.full_name }
assert_equal %w[a-2 a_evil-9], gems
end
def test_latest_cache_file
latest_cache_file = File.join File.dirname(@gemcache),
"latest_#{File.basename @gemcache}"
assert_equal latest_cache_file, @sic.latest_cache_file
end
def test_latest_system_cache_file
assert_equal File.join(Gem.dir, "latest_source_cache"),
@sic.latest_system_cache_file
end
def test_latest_user_cache_file
assert_equal @latest_usrcache, @sic.latest_user_cache_file
end end
def test_read_system_cache def test_read_system_cache
assert_equal [['key','sys']], @sic.cache_data.to_a.sort assert_equal [[@gem_repo, @sys_sice]], @sic.cache_data.to_a.sort
end end
def test_read_user_cache def test_read_user_cache
FileUtils.chmod 0444, @sic.system_cache_file FileUtils.chmod 0444, @sic.user_cache_file
FileUtils.chmod 0444, @sic.latest_user_cache_file
assert_equal [['key','usr']], @sic.cache_data.to_a.sort @si = Gem::SourceIndex.new
@si.add_specs @a1, @a2
@sice = Gem::SourceInfoCacheEntry.new @si, 0
@sic.set_cache_data({ @gem_repo => @sice })
@sic.update
@sic.write_cache
@sic.reset_cache_data
user_cache_data = @sic.cache_data.to_a.sort
assert_equal 1, user_cache_data.length
user_cache_data = user_cache_data.first
assert_equal @gem_repo, user_cache_data.first
gems = user_cache_data.last.source_index.map { |_,spec| spec.full_name }
assert_equal [@a2.full_name], gems
end end
def test_search def test_search
si = Gem::SourceIndex.new @gem1.full_name => @gem1 si = Gem::SourceIndex.new
cache_data = { si.add_spec @a1
@gem_repo => Gem::SourceInfoCacheEntry.new(si, nil) cache_data = { @gem_repo => Gem::SourceInfoCacheEntry.new(si, nil) }
}
@sic.instance_variable_set :@cache_data, cache_data @sic.instance_variable_set :@cache_data, cache_data
assert_equal [@gem1], @sic.search(//) assert_equal [@a1], @sic.search(//)
end
def test_search_all
util_make_gems
sice = Gem::SourceInfoCacheEntry.new @source_index, 0
@sic.set_cache_data @gem_repo => sice
@sic.update
@sic.write_cache
@sic.reset_cache_data
gem_names = @sic.search(//, false, true).map { |spec| spec.full_name }
assert_equal %w[a-1 a-2 a_evil-9 c-1.2], gem_names
end end
def test_search_dependency def test_search_dependency
si = Gem::SourceIndex.new @gem1.full_name => @gem1 si = Gem::SourceIndex.new
cache_data = { si.add_spec @a1
@gem_repo => Gem::SourceInfoCacheEntry.new(si, nil) cache_data = { @gem_repo => Gem::SourceInfoCacheEntry.new(si, nil) }
}
@sic.instance_variable_set :@cache_data, cache_data @sic.instance_variable_set :@cache_data, cache_data
dep = Gem::Dependency.new @gem1.name, @gem1.version dep = Gem::Dependency.new @a1.name, @a1.version
assert_equal [@gem1], @sic.search(dep) assert_equal [@a1], @sic.search(dep)
end end
def test_search_no_matches def test_search_no_matches
si = Gem::SourceIndex.new @gem1.full_name => @gem1 si = Gem::SourceIndex.new
cache_data = { si.add_spec @a1
@gem_repo => Gem::SourceInfoCacheEntry.new(si, nil) cache_data = { @gem_repo => Gem::SourceInfoCacheEntry.new(si, nil) }
}
@sic.instance_variable_set :@cache_data, cache_data @sic.instance_variable_set :@cache_data, cache_data
assert_equal [], @sic.search(/nonexistent/) assert_equal [], @sic.search(/nonexistent/)
end end
def test_search_no_matches_in_source def test_search_no_matches_in_source
si = Gem::SourceIndex.new @gem1.full_name => @gem1 si = Gem::SourceIndex.new
cache_data = { si.add_spec @a1
@gem_repo => Gem::SourceInfoCacheEntry.new(si, nil) cache_data = { @gem_repo => Gem::SourceInfoCacheEntry.new(si, nil) }
}
@sic.instance_variable_set :@cache_data, cache_data @sic.instance_variable_set :@cache_data, cache_data
Gem.sources.replace %w[more-gems.example.com] Gem.sources.replace %w[more-gems.example.com]
@ -235,13 +326,12 @@ class TestGemSourceInfoCache < RubyGemTestCase
end end
def test_search_with_source def test_search_with_source
si = Gem::SourceIndex.new @gem1.full_name => @gem1 si = Gem::SourceIndex.new
cache_data = { si.add_spec @a1
@gem_repo => Gem::SourceInfoCacheEntry.new(si, nil) cache_data = { @gem_repo => Gem::SourceInfoCacheEntry.new(si, nil) }
}
@sic.instance_variable_set :@cache_data, cache_data @sic.instance_variable_set :@cache_data, cache_data
assert_equal [[@gem1, @gem_repo]], assert_equal [[@a1, @gem_repo]],
@sic.search_with_source(//) @sic.search_with_source(//)
end end
@ -254,45 +344,81 @@ class TestGemSourceInfoCache < RubyGemTestCase
end end
def test_write_cache def test_write_cache
@sic.cache_data['key'] = 'new' @sic.cache_data[@gem_repo] = @sice_new
@sic.write_cache @sic.write_cache
assert_equal [['key', 'new']], assert_equal [[@gem_repo, @sice_new]],
read_cache(@sic.system_cache_file).to_a.sort read_cache(@sic.system_cache_file).to_a.sort
assert_equal [['key', 'usr']], assert_equal [[@gem_repo, @usr_sice]],
read_cache(@sic.user_cache_file).to_a.sort read_cache(@sic.user_cache_file).to_a.sort
end end
def test_write_cache_user def test_write_cache_user
FileUtils.chmod 0444, @sic.system_cache_file FileUtils.chmod 0444, @sic.system_cache_file
@sic.set_cache_data({'key' => 'new'}) @sic.set_cache_data({@gem_repo => @sice_new})
@sic.update @sic.update
@sic.write_cache @sic.write_cache
assert_equal [['key', 'sys']], read_cache(@sic.system_cache_file).to_a.sort assert File.exist?(@sic.user_cache_file), 'user_cache_file'
assert_equal [['key', 'new']], read_cache(@sic.user_cache_file).to_a.sort assert File.exist?(@sic.latest_user_cache_file),
'latest_user_cache_file exists'
assert_equal [[@gem_repo, @sys_sice]],
read_cache(@sic.system_cache_file).to_a.sort
assert_equal [[@gem_repo, @sice_new]],
read_cache(@sic.user_cache_file).to_a.sort
end end
def test_write_cache_user_from_scratch def test_write_cache_user_from_scratch
FileUtils.rm_rf @sic.user_cache_file FileUtils.rm_rf @sic.user_cache_file
FileUtils.rm_rf @sic.latest_user_cache_file
FileUtils.chmod 0444, @sic.system_cache_file FileUtils.chmod 0444, @sic.system_cache_file
@sic.set_cache_data({'key' => 'new'}) FileUtils.chmod 0444, @sic.latest_system_cache_file
@si = Gem::SourceIndex.new
@si.add_specs @a1, @a2
@sice = Gem::SourceInfoCacheEntry.new @si, 0
@sic.set_cache_data({ @gem_repo => @sice })
@sic.update @sic.update
@sic.write_cache @sic.write_cache
assert_equal [['key', 'sys']], read_cache(@sic.system_cache_file).to_a.sort assert File.exist?(@sic.user_cache_file), 'system_cache_file'
assert_equal [['key', 'new']], read_cache(@sic.user_cache_file).to_a.sort assert File.exist?(@sic.latest_user_cache_file),
'latest_system_cache_file'
user_cache_data = read_cache(@sic.user_cache_file).to_a.sort
assert_equal 1, user_cache_data.length
user_cache_data = user_cache_data.first
assert_equal @gem_repo, user_cache_data.first
gems = user_cache_data.last.source_index.map { |_,spec| spec.full_name }
assert_equal [@a1.full_name, @a2.full_name], gems
user_cache_data = read_cache(@sic.latest_user_cache_file).to_a.sort
assert_equal 1, user_cache_data.length
user_cache_data = user_cache_data.first
assert_equal @gem_repo, user_cache_data.first
gems = user_cache_data.last.source_index.map { |_,spec| spec.full_name }
assert_equal [@a2.full_name], gems
end end
def test_write_cache_user_no_directory def test_write_cache_user_no_directory
FileUtils.rm_rf File.dirname(@sic.user_cache_file) FileUtils.rm_rf File.dirname(@sic.user_cache_file)
FileUtils.chmod 0444, @sic.system_cache_file FileUtils.chmod 0444, @sic.system_cache_file
@sic.set_cache_data({'key' => 'new'}) @sic.set_cache_data({ @gem_repo => @sice_new })
@sic.update @sic.update
@sic.write_cache @sic.write_cache
assert_equal [['key','sys']], read_cache(@sic.system_cache_file).to_a.sort assert_equal [[@gem_repo, @sys_sice]],
assert_equal [['key','new']], read_cache(@sic.user_cache_file).to_a.sort read_cache(@sic.system_cache_file).to_a.sort
assert_equal [[@gem_repo, @sice_new]],
read_cache(@sic.user_cache_file).to_a.sort
end end
end end

View file

@ -9,37 +9,68 @@ class TestGemSourceInfoCacheEntry < RubyGemTestCase
util_setup_fake_fetcher util_setup_fake_fetcher
@si = Gem::SourceIndex.new @gem1.full_name => @gem1.name @si = Gem::SourceIndex.new
@si.add_spec @a1
@sic_e = Gem::SourceInfoCacheEntry.new @si, @si.dump.size @sic_e = Gem::SourceInfoCacheEntry.new @si, @si.dump.size
end end
def test_refresh def test_refresh
@fetcher.data["#{@gem_repo}/Marshal.#{@marshal_version}.Z"] = @fetcher.data["#{@gem_repo}/Marshal.#{@marshal_version}.Z"] =
proc { raise Exception } proc { raise }
@fetcher.data["#{@gem_repo}/Marshal.#{@marshal_version}"] = @si.dump @fetcher.data["#{@gem_repo}/Marshal.#{@marshal_version}"] = @si.dump
assert_nothing_raised do use_ui @ui do
@sic_e.refresh @gem_repo @sic_e.refresh @gem_repo, true
end end
end end
def test_refresh_all
@si.add_spec @a2
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
sic_e = Gem::SourceInfoCacheEntry.new Gem::SourceIndex.new, 0
use_ui @ui do
sic_e.refresh @gem_repo, false
end
assert_equal [a2_name], sic_e.source_index.map { |n,| n }.sort
use_ui @ui do
sic_e.refresh @gem_repo, true
end
assert_equal [a1_name, a2_name], sic_e.source_index.map { |n,| n }.sort
end
def test_refresh_bad_uri def test_refresh_bad_uri
assert_raise URI::BadURIError do assert_raise URI::BadURIError do
@sic_e.refresh 'gems.example.com' @sic_e.refresh 'gems.example.com', true
end end
end end
def test_refresh_update def test_refresh_update
si = Gem::SourceIndex.new @gem1.full_name => @gem1, si = Gem::SourceIndex.new
@gem2.full_name => @gem2 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 use_ui @ui do
@sic_e.refresh @gem_repo @sic_e.refresh @gem_repo, true
end end
new_gem = @sic_e.source_index.specification(@gem2.full_name) new_gem = @sic_e.source_index.specification(@b2.full_name)
assert_equal @gem2.full_name, new_gem.full_name assert_equal @b2.full_name, new_gem.full_name
end end
end end

View file

@ -0,0 +1,43 @@
require File.join(File.expand_path(File.dirname(__FILE__)),
'gem_installer_test_case')
require 'rubygems/uninstaller'
class TestGemUninstaller < GemInstallerTestCase
def setup
super
ui = MockGemUi.new
util_setup_gem ui
use_ui ui do
@installer.install
end
end
def test_remove_executables_force_keep
uninstaller = Gem::Uninstaller.new nil, :executables => false
use_ui @ui do
uninstaller.remove_executables @spec
end
assert_equal true, File.exist?(File.join(@gemhome, 'bin', 'executable'))
assert_equal "Executables and scripts will remain installed.\n", @ui.output
end
def test_remove_executables_force_remove
uninstaller = Gem::Uninstaller.new nil, :executables => true
use_ui @ui do
uninstaller.remove_executables @spec
end
assert_equal "Removing executable\n", @ui.output
assert_equal false, File.exist?(File.join(@gemhome, 'bin', 'executable'))
end
end