# frozen_string_literal: true
require 'rubygems/installer_test_case'

class TestGemInstaller < Gem::InstallerTestCase
  @@symlink_supported = nil

  def symlink_supported?
    if @@symlink_supported.nil?
      begin
        File.symlink("", "")
      rescue Errno::ENOENT, Errno::EEXIST
        @@symlink_supported = true
      rescue NotImplementedError, SystemCallError
        @@symlink_supported = false
      end
    end
    @@symlink_supported
  end

  def setup
    super
    common_installer_setup

    if (self.class.method_defined?(:__name__) ? __name__ : name) =~ /\Atest_install(_|\Z)/
      FileUtils.rm_r @spec.gem_dir
      FileUtils.rm_r @user_spec.gem_dir
    end

    @config = Gem.configuration
  end

  def teardown
    common_installer_teardown

    super

    Gem.configuration = instance_variable_defined?(:@config) ? @config : nil
  end

  def test_app_script_text
    util_make_exec @spec, ''

    expected = <<-EOF
#!#{Gem.ruby}
#
# This file was generated by RubyGems.
#
# The application 'a' is installed as part of a gem, and
# this file is here to facilitate running it.
#

require 'rubygems'

version = \">= 0.a\"

if ARGV.first
  str = ARGV.first
  str = str.dup.force_encoding("BINARY")
  if str =~ /\\A_(.*)_\\z/ and Gem::Version.correct?($1) then
    version = $1
    ARGV.shift
  end
end

if Gem.respond_to?(:activate_bin_path)
load Gem.activate_bin_path('a', 'executable', version)
else
gem "a", version
load Gem.bin_path("a", "executable", version)
end
    EOF

    wrapper = @installer.app_script_text 'executable'
    assert_equal expected, wrapper
  end

  def test_check_executable_overwrite
    @installer.generate_bin

    @spec = Gem::Specification.new do |s|
      s.files = ['lib/code.rb']
      s.name = "a"
      s.version = "3"
      s.summary = "summary"
      s.description = "desc"
      s.require_path = 'lib'
    end

    util_make_exec
    @installer.gem_dir = @spec.gem_dir
    @installer.wrappers = true
    @installer.generate_bin

    installed_exec = File.join util_inst_bindir, 'executable'
    assert_path_exists installed_exec

    wrapper = File.read installed_exec
    assert_match %r|generated by RubyGems|, wrapper
  end

  def test_check_executable_overwrite_default_bin_dir
    if defined?(RUBY_FRAMEWORK_VERSION)
      orig_RUBY_FRAMEWORK_VERSION = RUBY_FRAMEWORK_VERSION
      Object.send :remove_const, :RUBY_FRAMEWORK_VERSION
    end
    orig_bindir = RbConfig::CONFIG['bindir']
    RbConfig::CONFIG['bindir'] = Gem.bindir

    util_conflict_executable false

    ui = Gem::MockGemUi.new "n\n"
    use_ui ui do
      e = assert_raises Gem::InstallError do
        @installer.generate_bin
      end

      conflicted = File.join @gemhome, 'bin', 'executable'
      assert_match %r%\A"executable" from a conflicts with (?:#{Regexp.quote(conflicted)}|installed executable from conflict)\z%,
                   e.message
    end
  ensure
    Object.const_set :RUBY_FRAMEWORK_VERSION, orig_RUBY_FRAMEWORK_VERSION if
      orig_RUBY_FRAMEWORK_VERSION
    if orig_bindir then
      RbConfig::CONFIG['bindir'] = orig_bindir
    else
      RbConfig::CONFIG.delete 'bindir'
    end
  end

  def test_check_executable_overwrite_format_executable
    @installer.generate_bin

    @spec = Gem::Specification.new do |s|
      s.files = ['lib/code.rb']
      s.name = "a"
      s.version = "3"
      s.summary = "summary"
      s.description = "desc"
      s.require_path = 'lib'
    end

    File.open File.join(util_inst_bindir, 'executable'), 'w' do |io|
     io.write <<-EXEC
#!/usr/local/bin/ruby
#
# This file was generated by RubyGems

gem 'other', version
     EXEC
    end

    util_make_exec
    Gem::Installer.exec_format = 'foo-%s-bar'
    @installer.gem_dir = @spec.gem_dir
    @installer.wrappers = true
    @installer.format_executable = true

    @installer.generate_bin # should not raise

    installed_exec = File.join util_inst_bindir, 'foo-executable-bar'
    assert_path_exists installed_exec

    wrapper = File.read installed_exec
    assert_match %r|generated by RubyGems|, wrapper
  end

  def test_check_executable_overwrite_other_gem
    util_conflict_executable true

    ui = Gem::MockGemUi.new "n\n"

    use_ui ui do
      e = assert_raises Gem::InstallError do
        @installer.generate_bin
      end

      assert_equal '"executable" from a conflicts with installed executable from conflict',
                   e.message
    end
  end

  def test_check_executable_overwrite_other_gem_force
    util_conflict_executable true
    @installer.wrappers = true
    @installer.force = true

    @installer.generate_bin

    installed_exec = File.join util_inst_bindir, 'executable'
    assert_path_exists installed_exec

    wrapper = File.read installed_exec
    assert_match %r|generated by RubyGems|, wrapper
  end

  def test_check_executable_overwrite_other_non_gem
    util_conflict_executable false
    @installer.wrappers = true

    @installer.generate_bin

    installed_exec = File.join util_inst_bindir, 'executable'
    assert_path_exists installed_exec

    wrapper = File.read installed_exec
    assert_match %r|generated by RubyGems|, wrapper
  end unless Gem.win_platform?

  def test_check_that_user_bin_dir_is_in_path
    bin_dir = @installer.bin_dir

    if Gem.win_platform?
      bin_dir = bin_dir.downcase.gsub(File::SEPARATOR, File::ALT_SEPARATOR)
    end

    orig_PATH, ENV['PATH'] =
      ENV['PATH'], [ENV['PATH'], bin_dir].join(File::PATH_SEPARATOR)

    use_ui @ui do
      @installer.check_that_user_bin_dir_is_in_path
    end

    assert_empty @ui.error
  ensure
    ENV['PATH'] = orig_PATH
  end

  def test_check_that_user_bin_dir_is_in_path_tilde
    skip "Tilde is PATH is not supported under MS Windows" if win_platform?

    orig_PATH, ENV['PATH'] =
      ENV['PATH'], [ENV['PATH'], '~/bin'].join(File::PATH_SEPARATOR)

    @installer.bin_dir.replace File.join @userhome, 'bin'

    use_ui @ui do
      @installer.check_that_user_bin_dir_is_in_path
    end

    assert_empty @ui.error
  ensure
    ENV['PATH'] = orig_PATH unless win_platform?
  end

  def test_check_that_user_bin_dir_is_in_path_not_in_path
    use_ui @ui do
      @installer.check_that_user_bin_dir_is_in_path
    end

    expected = @installer.bin_dir

    if Gem.win_platform? then
      expected = expected.downcase.gsub(File::SEPARATOR, File::ALT_SEPARATOR)
    end

    assert_match expected, @ui.error
  end

  def test_ensure_dependency
    util_spec 'a'

    dep = Gem::Dependency.new 'a', '>= 2'
    assert @installer.ensure_dependency(@spec, dep)

    dep = Gem::Dependency.new 'b', '> 2'
    e = assert_raises Gem::InstallError do
      @installer.ensure_dependency @spec, dep
    end

    assert_equal 'a requires b (> 2)', e.message
  end

  def test_ensure_loadable_spec
    a, a_gem = util_gem 'a', 2 do |s|
      s.add_dependency 'garbage ~> 5'
    end

    installer = Gem::Installer.at a_gem

    e = assert_raises Gem::InstallError do
      installer.ensure_loadable_spec
    end

    assert_equal "The specification for #{a.full_name} is corrupt " +
                 "(SyntaxError)", e.message
  end

  def test_ensure_loadable_spec_security_policy
    skip 'openssl is missing' unless defined?(OpenSSL::SSL)

    _, a_gem = util_gem 'a', 2 do |s|
      s.add_dependency 'garbage ~> 5'
    end

    policy = Gem::Security::HighSecurity
    installer = Gem::Installer.at a_gem, :security_policy => policy

    assert_raises Gem::Security::Exception do
      installer.ensure_loadable_spec
    end
  end

  def test_extract_files
    @installer.extract_files

    assert_path_exists File.join @spec.gem_dir, 'bin/executable'
  end

  def test_generate_bin_bindir
    @installer.wrappers = true

    @spec.executables = %w[executable]
    @spec.bindir = '.'

    exec_file = @installer.formatted_program_filename 'executable'
    exec_path = File.join @spec.gem_dir, exec_file
    File.open exec_path, 'w' do |f|
      f.puts '#!/usr/bin/ruby'
    end

    @installer.gem_dir = @spec.gem_dir

    @installer.generate_bin

    assert_equal true, File.directory?(util_inst_bindir)
    installed_exec = File.join(util_inst_bindir, 'executable')
    assert_path_exists installed_exec
    assert_equal mask, File.stat(installed_exec).mode unless win_platform?

    wrapper = File.read installed_exec
    assert_match %r|generated by RubyGems|, wrapper
  end

  def test_generate_bin_bindir_with_user_install_warning
    bin_dir = Gem.win_platform? ? File.expand_path(ENV["WINDIR"]).upcase :
                                  "/usr/bin"

    options = {
      :bin_dir => bin_dir,
      :install_dir => "/non/existent"
    }

    inst = Gem::Installer.at '', options

    Gem::Installer.path_warning = false

    use_ui @ui do
      inst.check_that_user_bin_dir_is_in_path
    end

    assert_equal "", @ui.error
  end

  def test_generate_bin_script
    @installer.wrappers = true
    util_make_exec
    @installer.gem_dir = @spec.gem_dir

    @installer.generate_bin
    assert File.directory? util_inst_bindir
    installed_exec = File.join util_inst_bindir, 'executable'
    assert_path_exists installed_exec
    assert_equal mask, File.stat(installed_exec).mode unless win_platform?

    wrapper = File.read installed_exec
    assert_match %r|generated by RubyGems|, wrapper
  end

  def test_generate_bin_script_format
    @installer.format_executable = true
    @installer.wrappers = true
    util_make_exec
    @installer.gem_dir = @spec.gem_dir

    Gem::Installer.exec_format = 'foo-%s-bar'
    @installer.generate_bin
    assert_equal true, File.directory?(util_inst_bindir)
    installed_exec = File.join util_inst_bindir, 'foo-executable-bar'
    assert_path_exists installed_exec
  ensure
    Gem::Installer.exec_format = nil
  end

  def test_generate_bin_script_format_disabled
    @installer.wrappers = true
    util_make_exec
    @installer.gem_dir = @spec.gem_dir

    Gem::Installer.exec_format = 'foo-%s-bar'
    @installer.generate_bin
    assert_equal true, File.directory?(util_inst_bindir)
    installed_exec = File.join util_inst_bindir, 'executable'
    assert_path_exists installed_exec
  ensure
    Gem::Installer.exec_format = nil
  end

  def test_generate_bin_script_install_dir
    @installer.wrappers = true

    gem_dir = File.join("#{@gemhome}2", "gems", @spec.full_name)
    gem_bindir = File.join gem_dir, 'bin'
    FileUtils.mkdir_p gem_bindir
    File.open File.join(gem_bindir, 'executable'), 'w' do |f|
      f.puts "#!/bin/ruby"
    end

    @installer.gem_home = "#{@gemhome}2"
    @installer.gem_dir = gem_dir
    @installer.bin_dir = File.join "#{@gemhome}2", 'bin'

    @installer.generate_bin

    installed_exec = File.join("#{@gemhome}2", "bin", 'executable')
    assert_path_exists installed_exec
    assert_equal mask, File.stat(installed_exec).mode unless win_platform?

    wrapper = File.read installed_exec
    assert_match %r|generated by RubyGems|, wrapper
  end

  def test_generate_bin_script_no_execs
    util_execless

    @installer.wrappers = true
    @installer.generate_bin

    refute_path_exists util_inst_bindir, 'bin dir was created when not needed'
  end

  def test_generate_bin_script_no_perms
    @installer.wrappers = true
    util_make_exec

    Dir.mkdir util_inst_bindir

    if win_platform?
      skip('test_generate_bin_script_no_perms skipped on MS Windows')
    elsif Process.uid.zero?
      skip('test_generate_bin_script_no_perms skipped in root privilege')
    else
      FileUtils.chmod 0000, util_inst_bindir

      assert_raises Gem::FilePermissionError do
        @installer.generate_bin
      end
    end
  ensure
    FileUtils.chmod 0755, util_inst_bindir unless ($DEBUG or win_platform?)
  end

  def test_generate_bin_script_no_shebang
    @installer.wrappers = true
    @spec.executables = %w[executable]

    gem_dir = File.join @gemhome, 'gems', @spec.full_name
    gem_bindir = File.join gem_dir, 'bin'
    FileUtils.mkdir_p gem_bindir
    File.open File.join(gem_bindir, 'executable'), 'w' do |f|
      f.puts "blah blah blah"
    end

    @installer.generate_bin

    installed_exec = File.join @gemhome, 'bin', 'executable'
    assert_path_exists installed_exec
    assert_equal mask, File.stat(installed_exec).mode unless win_platform?

    wrapper = File.read installed_exec
    assert_match %r|generated by RubyGems|, wrapper
    # HACK some gems don't have #! in their executables, restore 2008/06
    #assert_no_match %r|generated by RubyGems|, wrapper
  end

  def test_generate_bin_script_wrappers
    @installer.wrappers = true
    util_make_exec
    @installer.gem_dir = @spec.gem_dir
    installed_exec = File.join(util_inst_bindir, 'executable')

    real_exec = File.join @spec.gem_dir, 'bin', 'executable'

    # fake --no-wrappers for previous install
    unless Gem.win_platform? then
      FileUtils.mkdir_p File.dirname(installed_exec)
      FileUtils.ln_s real_exec, installed_exec
    end

    @installer.generate_bin
    assert_equal true, File.directory?(util_inst_bindir)
    assert_path_exists installed_exec
    assert_equal mask, File.stat(installed_exec).mode unless win_platform?

    assert_match %r|generated by RubyGems|, File.read(installed_exec)

    refute_match %r|generated by RubyGems|, File.read(real_exec),
                 'real executable overwritten'
  end

  def test_generate_bin_symlink
    return if win_platform? #Windows FS do not support symlinks

    @installer.wrappers = false
    util_make_exec
    @installer.gem_dir = @spec.gem_dir

    @installer.generate_bin
    assert_equal true, File.directory?(util_inst_bindir)
    installed_exec = File.join util_inst_bindir, 'executable'
    assert_equal true, File.symlink?(installed_exec)
    assert_equal(File.join(@spec.gem_dir, 'bin', 'executable'),
                 File.readlink(installed_exec))
  end

  def test_generate_bin_symlink_no_execs
    util_execless

    @installer.wrappers = false
    @installer.generate_bin

    refute_path_exists util_inst_bindir
  end

  def test_generate_bin_symlink_no_perms
    @installer.wrappers = false
    util_make_exec
    @installer.gem_dir = @spec.gem_dir

    Dir.mkdir util_inst_bindir

    if win_platform?
      skip('test_generate_bin_symlink_no_perms skipped on MS Windows')
    elsif Process.uid.zero?
      skip('test_user_install_disabled_read_only test skipped in root privilege')
    else
      FileUtils.chmod 0000, util_inst_bindir

      assert_raises Gem::FilePermissionError do
        @installer.generate_bin
      end
    end
  ensure
    FileUtils.chmod 0755, util_inst_bindir unless ($DEBUG or win_platform?)
  end

  def test_generate_bin_symlink_update_newer
    return if win_platform? #Windows FS do not support symlinks

    @installer.wrappers = false
    util_make_exec
    @installer.gem_dir = @spec.gem_dir

    @installer.generate_bin
    installed_exec = File.join(util_inst_bindir, 'executable')
    assert_equal(File.join(@spec.gem_dir, 'bin', 'executable'),
                 File.readlink(installed_exec))

    @spec = Gem::Specification.new do |s|
      s.files = ['lib/code.rb']
      s.name = "a"
      s.version = "3"
      s.summary = "summary"
      s.description = "desc"
      s.require_path = 'lib'
    end

    util_make_exec
    @installer.gem_dir = @spec.gem_dir
    @installer.generate_bin
    installed_exec = File.join(util_inst_bindir, 'executable')
    assert_equal(@spec.bin_file('executable'),
                 File.readlink(installed_exec),
                 "Ensure symlink moved to latest version")
  end

  def test_generate_bin_symlink_update_older
    return if !symlink_supported?

    @installer.wrappers = false
    util_make_exec
    @installer.gem_dir = @spec.gem_dir

    @installer.generate_bin
    installed_exec = File.join(util_inst_bindir, 'executable')
    assert_equal(File.join(@spec.gem_dir, 'bin', 'executable'),
                 File.readlink(installed_exec))

    spec = Gem::Specification.new do |s|
      s.files = ['lib/code.rb']
      s.name = "a"
      s.version = "1"
      s.summary = "summary"
      s.description = "desc"
      s.require_path = 'lib'
    end

    util_make_exec
    one = @spec.dup
    one.version = 1
    @installer = Gem::Installer.for_spec spec
    @installer.gem_dir = one.gem_dir

    @installer.generate_bin

    installed_exec = File.join util_inst_bindir, 'executable'
    expected = File.join @spec.gem_dir, 'bin', 'executable'
    assert_equal(expected,
                 File.readlink(installed_exec),
                 "Ensure symlink not moved")
  end

  def test_generate_bin_symlink_update_remove_wrapper
    return if !symlink_supported?

    @installer.wrappers = true
    util_make_exec
    @installer.gem_dir = @spec.gem_dir

    @installer.generate_bin

    installed_exec = File.join util_inst_bindir, 'executable'
    assert_path_exists installed_exec

    @spec = Gem::Specification.new do |s|
      s.files = ['lib/code.rb']
      s.name = "a"
      s.version = "3"
      s.summary = "summary"
      s.description = "desc"
      s.require_path = 'lib'
    end
    util_make_exec

    util_installer @spec, @gemhome
    @installer.wrappers = false
    @installer.gem_dir = @spec.gem_dir

    @installer.generate_bin

    installed_exec = File.join util_inst_bindir, 'executable'
    assert_equal(@spec.bin_file('executable'),
                 File.readlink(installed_exec),
                 "Ensure symlink moved to latest version")
  end

  def test_generate_bin_symlink_win32
    old_win_platform = Gem.win_platform?
    Gem.win_platform = true
    old_alt_separator = File::ALT_SEPARATOR
    File.__send__(:remove_const, :ALT_SEPARATOR)
    File.const_set(:ALT_SEPARATOR, '\\')
    @installer.wrappers = false
    util_make_exec
    @installer.gem_dir = @spec.gem_dir

    use_ui @ui do
      @installer.generate_bin
    end

    assert_equal true, File.directory?(util_inst_bindir)
    installed_exec = File.join(util_inst_bindir, 'executable')
    assert_path_exists installed_exec

    if symlink_supported?
      assert File.symlink?(installed_exec)
      return
    end

    assert_match(/Unable to use symlinks, installing wrapper/i,
                 @ui.error)

    wrapper = File.read installed_exec
    assert_match(/generated by RubyGems/, wrapper)
  ensure
    File.__send__(:remove_const, :ALT_SEPARATOR)
    File.const_set(:ALT_SEPARATOR, old_alt_separator)
    Gem.win_platform = old_win_platform
  end

  def test_generate_bin_uses_default_shebang
    return if !symlink_supported?

    @installer.wrappers = true
    util_make_exec

    @installer.generate_bin

    default_shebang = Gem.ruby
    shebang_line = open("#{@gemhome}/bin/executable") { |f| f.readlines.first }
    assert_match(/\A#!/, shebang_line)
    assert_match(/#{default_shebang}/, shebang_line)
  end

  def test_initialize
    spec = util_spec 'a' do |s| s.platform = Gem::Platform.new 'mswin32' end
    gem = File.join @tempdir, spec.file_name

    Dir.mkdir util_inst_bindir
    util_build_gem spec
    FileUtils.mv spec.cache_file, @tempdir

    installer = Gem::Installer.at gem

    assert_equal File.join(@gemhome, 'gems', spec.full_name), installer.gem_dir
    assert_equal File.join(@gemhome, 'bin'), installer.bin_dir
  end

  def test_initialize_user_install
    installer = Gem::Installer.at @gem, :user_install => true

    assert_equal File.join(Gem.user_dir, 'gems', @spec.full_name),
                 installer.gem_dir
    assert_equal Gem.bindir(Gem.user_dir), installer.bin_dir
  end

  def test_initialize_user_install_bin_dir
    installer =
      Gem::Installer.at @gem, :user_install => true, :bin_dir => @tempdir

    assert_equal File.join(Gem.user_dir, 'gems', @spec.full_name),
                 installer.gem_dir
    assert_equal @tempdir, installer.bin_dir
  end

  def test_install
    Dir.mkdir util_inst_bindir
    util_setup_gem
    util_clear_gems

    gemdir     = File.join @gemhome, 'gems', @spec.full_name
    cache_file = File.join @gemhome, 'cache', @spec.file_name
    stub_exe   = File.join @gemhome, 'bin', 'executable'
    rakefile   = File.join gemdir, 'ext', 'a', 'Rakefile'
    spec_file  = File.join @gemhome, 'specifications', @spec.spec_name

    Gem.pre_install do |installer|
      refute_path_exists cache_file, 'cache file must not exist yet'
      refute_path_exists spec_file,  'spec file must not exist yet'
      true
    end

    Gem.post_build do |installer|
      assert_path_exists gemdir, 'gem install dir must exist'
      assert_path_exists rakefile, 'gem executable must exist'
      refute_path_exists stub_exe, 'gem executable must not exist'
      refute_path_exists spec_file, 'spec file must not exist yet'
      true
    end

    Gem.post_install do |installer|
      assert_path_exists cache_file, 'cache file must exist'
      assert_path_exists spec_file,  'spec file must exist'
    end

    @newspec = nil
    build_rake_in do
      use_ui @ui do
        @newspec = @installer.install
      end
    end

    assert_equal @spec, @newspec
    assert_path_exists gemdir
    assert_path_exists stub_exe, 'gem executable must exist'

    exe = File.join gemdir, 'bin', 'executable'
    assert_path_exists exe

    exe_mode = File.stat(exe).mode & 0111
    assert_equal 0111, exe_mode, "0%o" % exe_mode unless win_platform?

    assert_path_exists File.join gemdir, 'lib', 'code.rb'

    assert_path_exists rakefile

    spec_file = File.join(@gemhome, 'specifications', @spec.spec_name)

    assert_equal spec_file, @newspec.loaded_from
    assert_path_exists spec_file

    assert_same @installer, @post_build_hook_arg
    assert_same @installer, @post_install_hook_arg
    assert_same @installer, @pre_install_hook_arg
  end

  def test_install_creates_working_binstub
    Dir.mkdir util_inst_bindir
    util_setup_gem
    util_clear_gems

    @installer.wrappers = true

    gemdir = File.join @gemhome, 'gems', @spec.full_name

    @newspec = nil
    build_rake_in do
      use_ui @ui do
        @newspec = @installer.install
      end
    end

    exe = File.join gemdir, 'bin', 'executable'

    e = assert_raises RuntimeError do
      instance_eval File.read(exe)
    end

    assert_match(/ran executable/, e.message)
  end

  def test_conflicting_binstubs
    Dir.mkdir util_inst_bindir
    util_clear_gems

    # build old version that has a bin file
    util_setup_gem do |spec|
      File.open File.join('bin', 'executable'), 'w' do |f|
        f.puts "require 'code'"
      end
      File.open File.join('lib', 'code.rb'), 'w' do |f|
        f.puts 'raise "I have an executable"'
      end
    end

    @installer.wrappers = true
    build_rake_in do
      use_ui @ui do
        @newspec = @installer.install
      end
    end

    old_bin_file = File.join @installer.bin_dir, 'executable'

    # build new version that doesn't have a bin file
    util_setup_gem do |spec|
      FileUtils.rm File.join('bin', 'executable')
      spec.files.delete File.join('bin', 'executable')
      spec.executables.delete 'executable'
      spec.version = @spec.version.bump
      File.open File.join('lib', 'code.rb'), 'w' do |f|
        f.puts 'raise "I do not have an executable"'
      end
    end

    build_rake_in do
      use_ui @ui do
        @newspec = @installer.install
      end
    end

    e = assert_raises RuntimeError do
      instance_eval File.read(old_bin_file)
    end

    # We expect the bin stub to activate the version that actually contains
    # the binstub.
    assert_match('I have an executable', e.message)
  end

  def test_install_creates_binstub_that_understand_version
    Dir.mkdir util_inst_bindir
    util_setup_gem
    util_clear_gems

    @installer.wrappers = true

    @newspec = nil
    build_rake_in do
      use_ui @ui do
        @newspec = @installer.install
      end
    end

    exe = File.join @gemhome, 'bin', 'executable'

    ARGV.unshift "_3.0_"

    begin
      Gem::Specification.reset

      e = assert_raises Gem::GemNotFoundException do
        instance_eval File.read(exe)
      end
    ensure
      ARGV.shift if ARGV.first == "_3.0_"
    end

    assert_includes(e.message, "can't find gem a (= 3.0)")
  end

  def test_install_creates_binstub_that_dont_trust_encoding
    Dir.mkdir util_inst_bindir
    util_setup_gem
    util_clear_gems

    @installer.wrappers = true

    @newspec = nil
    build_rake_in do
      use_ui @ui do
        @newspec = @installer.install
      end
    end

    exe = File.join @gemhome, 'bin', 'executable'

    extra_arg = "\xE4pfel".dup.force_encoding("UTF-8")
    ARGV.unshift extra_arg

    begin
      Gem::Specification.reset

      e = assert_raises RuntimeError do
        instance_eval File.read(exe)
      end
    ensure
      ARGV.shift if ARGV.first == extra_arg
    end

    assert_match(/ran executable/, e.message)
  end

  def test_install_with_no_prior_files
    Dir.mkdir util_inst_bindir
    util_clear_gems

    util_setup_gem
    build_rake_in do
      use_ui @ui do
        assert_equal @spec, @installer.install
      end
    end

    gemdir = File.join(@gemhome, 'gems', @spec.full_name)
    assert_path_exists File.join gemdir, 'lib', 'code.rb'

    util_setup_gem
    # Morph spec to have lib/other.rb instead of code.rb and recreate
    @spec.files = File.join('lib', 'other.rb')
    Dir.chdir @tempdir do
      File.open File.join('lib', 'other.rb'), 'w' do |f| f.puts '1' end
      use_ui ui do
        FileUtils.rm @gem
        Gem::Package.build @spec
      end
    end
    @installer = Gem::Installer.at @gem
    build_rake_in do
      use_ui @ui do
        assert_equal @spec, @installer.install
      end
    end

    assert_path_exists File.join gemdir, 'lib', 'other.rb'
    refute_path_exists File.join gemdir, 'lib', 'code.rb',
           "code.rb from prior install of same gem shouldn't remain here"
  end

  def test_install_force
    use_ui @ui do
      installer = Gem::Installer.at old_ruby_required, :force => true
      installer.install
    end

    gem_dir = File.join(@gemhome, 'gems', 'old_ruby_required-1')
    assert_path_exists gem_dir
  end

  def test_install_missing_dirs
    FileUtils.rm_f File.join(Gem.dir, 'cache')
    FileUtils.rm_f File.join(Gem.dir, 'docs')
    FileUtils.rm_f File.join(Gem.dir, 'specifications')

    use_ui @ui do
      @installer.install
    end

    File.directory? File.join(Gem.dir, 'cache')
    File.directory? File.join(Gem.dir, 'docs')
    File.directory? File.join(Gem.dir, 'specifications')

    assert_path_exists File.join @gemhome, 'cache', @spec.file_name
    assert_path_exists File.join @gemhome, 'specifications', @spec.spec_name
  end

  def test_install_post_build_false
    util_clear_gems

    Gem.post_build do
      false
    end

    use_ui @ui do
      e = assert_raises Gem::InstallError do
        @installer.install
      end

      location = "#{__FILE__}:#{__LINE__ - 9}"

      assert_equal "post-build hook at #{location} failed for a-2", e.message
    end

    spec_file = File.join @gemhome, 'specifications', @spec.spec_name
    refute_path_exists spec_file

    gem_dir = File.join @gemhome, 'gems', @spec.full_name
    refute_path_exists gem_dir
  end

  def test_install_post_build_nil
    util_clear_gems

    Gem.post_build do
      nil
    end

    use_ui @ui do
      @installer.install
    end

    spec_file = File.join @gemhome, 'specifications', @spec.spec_name
    assert_path_exists spec_file

    gem_dir = File.join @gemhome, 'gems', @spec.full_name
    assert_path_exists gem_dir
  end

  def test_install_pre_install_false
    util_clear_gems

    Gem.pre_install do
      false
    end

    use_ui @ui do
      e = assert_raises Gem::InstallError do
        @installer.install
      end

      location = "#{__FILE__}:#{__LINE__ - 9}"

      assert_equal "pre-install hook at #{location} failed for a-2", e.message
    end

    spec_file = File.join @gemhome, 'specifications', @spec.spec_name
    refute_path_exists spec_file
  end

  def test_install_pre_install_nil
    util_clear_gems

    Gem.pre_install do
      nil
    end

    use_ui @ui do
      @installer.install
    end

    spec_file = File.join @gemhome, 'specifications', @spec.spec_name
    assert_path_exists spec_file
  end

  def test_install_with_message
    @spec.post_install_message = 'I am a shiny gem!'

    use_ui @ui do
      path = Gem::Package.build @spec

      @installer = Gem::Installer.at path
      @installer.install
    end

    assert_match %r|I am a shiny gem!|, @ui.output
  end

  def test_install_with_skipped_message
    @spec.post_install_message = 'I am a shiny gem!'

    use_ui @ui do
      path = Gem::Package.build @spec

      @installer = Gem::Installer.at path, :post_install_message => false
      @installer.install
    end

    refute_match %r|I am a shiny gem!|, @ui.output
  end

  def test_install_extension_dir
    gemhome2 = "#{@gemhome}2"

    @spec.extensions << "extconf.rb"
    write_file File.join(@tempdir, "extconf.rb") do |io|
      io.write <<-RUBY
        require "mkmf"
        create_makefile("#{@spec.name}")
      RUBY
    end

    @spec.files += %w[extconf.rb]

    use_ui @ui do
      path = Gem::Package.build @spec

      installer = Gem::Installer.at path, :install_dir => gemhome2
      installer.install
    end

    expected_makefile = File.join gemhome2, 'gems', @spec.full_name, 'Makefile'

    assert_path_exists expected_makefile
  end

  def test_install_extension_dir_is_removed_on_reinstall
    @spec.extensions << "extconf.rb"
    write_file File.join(@tempdir, "extconf.rb") do |io|
      io.write <<-RUBY
        require "mkmf"
        create_makefile("#{@spec.name}")
      RUBY
    end

    @spec.files += %w[extconf.rb]

    path = Gem::Package.build @spec

    # Install a gem with an extension
    use_ui @ui do
      installer = Gem::Installer.at path
      installer.install
    end

    # pretend that a binary file was created as part of the build
    should_be_removed = File.join(@spec.extension_dir, "#{@spec.name}.so")
    write_file should_be_removed do |io|
      io.write "DELETE ME ON REINSTALL"
    end
    assert_path_exists should_be_removed

    # reinstall the gem, this is also the same as pristine
    use_ui @ui do
      installer = Gem::Installer.at path
      installer.install
    end

    refute_path_exists should_be_removed
  end

  def test_install_user_extension_dir
    @spec.extensions << "extconf.rb"
    write_file File.join(@tempdir, "extconf.rb") do |io|
      io.write <<-RUBY
        require "mkmf"
        create_makefile("#{@spec.name}")
      RUBY
    end

    @spec.files += %w[extconf.rb]

    # Create the non-user ext dir
    expected_extension_dir = @spec.extension_dir.dup
    FileUtils.mkdir_p expected_extension_dir

    use_ui @ui do
      path = Gem::Package.build @spec

      installer = Gem::Installer.at path, :user_install => true
      installer.install
    end

    expected_makefile = File.join Gem.user_dir, 'gems', @spec.full_name, 'Makefile'

    assert_path_exists expected_makefile
    assert_path_exists expected_extension_dir
    refute_path_exists File.join expected_extension_dir, 'gem_make.out'
  end

  def test_find_lib_file_after_install
    @spec.extensions << "extconf.rb"
    write_file File.join(@tempdir, "extconf.rb") do |io|
      io.write <<-RUBY
        require "mkmf"

        CONFIG['CC'] = '$(TOUCH) $@ ||'
        CONFIG['LDSHARED'] = '$(TOUCH) $@ ||'
        $ruby = '#{Gem.ruby}'

        create_makefile("#{@spec.name}")
      RUBY
    end

    write_file File.join(@tempdir, "depend")

    write_file File.join(@tempdir, "a.c") do |io|
      io.write <<-C
        #include <ruby.h>
        void Init_a() { }
      C
    end

    Dir.mkdir File.join(@tempdir, "lib")
    write_file File.join(@tempdir, 'lib', "b.rb") do |io|
      io.write "# b.rb"
    end

    @spec.files += %w[extconf.rb lib/b.rb depend a.c]

    use_ui @ui do
      path = Gem::Package.build @spec

      installer = Gem::Installer.at path
      installer.install
    end

    expected = File.join @spec.full_require_paths.find { |path|
      File.exist? File.join path, 'b.rb'
    }, 'b.rb'
    assert_equal expected, @spec.matches_for_glob('b.rb').first
  end

  def test_install_extension_and_script
    @spec.extensions << "extconf.rb"
    write_file File.join(@tempdir, "extconf.rb") do |io|
      io.write <<-RUBY
        require "mkmf"
        create_makefile("#{@spec.name}")
      RUBY
    end

    rb = File.join("lib", "#{@spec.name}.rb")
    @spec.files += [rb]
    write_file File.join(@tempdir, rb) do |io|
      io.write <<-RUBY
        # #{@spec.name}.rb
      RUBY
    end

    Dir.mkdir(File.join("lib", @spec.name))
    rb2 = File.join("lib", @spec.name, "#{@spec.name}.rb")
    @spec.files << rb2
    write_file File.join(@tempdir, rb2) do |io|
      io.write <<-RUBY
        # #{@spec.name}/#{@spec.name}.rb
      RUBY
    end

    refute_path_exists File.join @spec.gem_dir, rb
    refute_path_exists File.join @spec.gem_dir, rb2
    use_ui @ui do
      path = Gem::Package.build @spec

      @installer = Gem::Installer.at path
      @installer.install
    end
    assert_path_exists File.join @spec.gem_dir, rb
    assert_path_exists File.join @spec.gem_dir, rb2
  end

  def test_install_extension_flat
    @spec.require_paths = ["."]

    @spec.extensions << "extconf.rb"

    write_file File.join(@tempdir, "extconf.rb") do |io|
      io.write <<-RUBY
        require "mkmf"

        CONFIG['CC'] = '$(TOUCH) $@ ||'
        CONFIG['LDSHARED'] = '$(TOUCH) $@ ||'
        $ruby = '#{Gem.ruby}'

        create_makefile("#{@spec.name}")
      RUBY
    end

    # empty depend file for no auto dependencies
    @spec.files += %W"depend #{@spec.name}.c".each {|file|
      write_file File.join(@tempdir, file)
    }

    so = File.join(@spec.gem_dir, "#{@spec.name}.#{RbConfig::CONFIG["DLEXT"]}")
    refute_path_exists so
    use_ui @ui do
      path = Gem::Package.build @spec

      @installer = Gem::Installer.at path
      @installer.install
    end
    assert_path_exists so
  rescue
    puts '-' * 78
    puts File.read File.join(@gemhome, 'gems', 'a-2', 'Makefile')
    puts '-' * 78

    path = File.join(@gemhome, 'gems', 'a-2', 'gem_make.out')

    if File.exist?(path)
      puts File.read(path)
      puts '-' * 78
    end

    raise
  end

  def test_installation_satisfies_dependency_eh
    util_spec 'a'

    dep = Gem::Dependency.new 'a', '>= 2'
    assert @installer.installation_satisfies_dependency?(dep)

    dep = Gem::Dependency.new 'a', '> 2'
    refute @installer.installation_satisfies_dependency?(dep)
  end

  def test_installation_satisfies_dependency_eh_development
    @installer.options[:development] = true
    @installer.options[:dev_shallow] = true

    util_spec 'a'

    dep = Gem::Dependency.new 'a', :development
    assert @installer.installation_satisfies_dependency?(dep)
  end

  def test_pre_install_checks_dependencies
    @spec.add_dependency 'b', '> 5'
    util_setup_gem

    use_ui @ui do
      assert_raises Gem::InstallError do
        @installer.install
      end
    end
  end

  def test_pre_install_checks_dependencies_ignore
    @spec.add_dependency 'b', '> 5'
    @installer.ignore_dependencies = true

    build_rake_in do
      use_ui @ui do
        assert @installer.pre_install_checks
      end
    end
  end

  def test_pre_install_checks_dependencies_install_dir
    gemhome2 = "#{@gemhome}2"
    @spec.add_dependency 'd'

    quick_gem 'd', 2

    gem = File.join @gemhome, @spec.file_name

    FileUtils.mv @gemhome, gemhome2
    FileUtils.mkdir @gemhome

    FileUtils.mv File.join(gemhome2, 'cache', @spec.file_name), gem

    # Don't leak any already activated gems into the installer, require
    # that it work everything out on it's own.
    Gem::Specification.reset

    installer = Gem::Installer.at gem, :install_dir => gemhome2

    build_rake_in do
      use_ui @ui do
        assert installer.pre_install_checks
      end
    end
  end

  def test_pre_install_checks_ruby_version
    use_ui @ui do
      installer = Gem::Installer.at old_ruby_required
      e = assert_raises Gem::RuntimeRequirementNotMetError do
        installer.pre_install_checks
      end
      rv = Gem.ruby_api_version
      assert_equal "old_ruby_required requires Ruby version = 1.4.6. The current ruby version is #{rv}.",
                   e.message
    end
  end

  def test_pre_install_checks_wrong_rubygems_version
    spec = util_spec 'old_rubygems_required', '1' do |s|
      s.required_rubygems_version = '< 0'
    end

    util_build_gem spec

    gem = File.join(@gemhome, 'cache', spec.file_name)

    use_ui @ui do
      @installer = Gem::Installer.at gem
      e = assert_raises Gem::RuntimeRequirementNotMetError do
        @installer.pre_install_checks
      end
      rgv = Gem::VERSION
      assert_equal "old_rubygems_required requires RubyGems version < 0. The current RubyGems version is #{rgv}. " +
        "Try 'gem update --system' to update RubyGems itself.", e.message
    end
  end

  def test_pre_install_checks_malicious_name
    spec = util_spec '../malicious', '1'
    def spec.full_name # so the spec is buildable
      "malicious-1"
    end
    def spec.validate; end

    util_build_gem spec

    gem = File.join(@gemhome, 'cache', spec.file_name)

    use_ui @ui do
      @installer = Gem::Installer.at gem
      e = assert_raises Gem::InstallError do
        @installer.pre_install_checks
      end
      assert_equal '#<Gem::Specification name=../malicious version=1> has an invalid name', e.message
    end
  end

  def test_shebang
    util_make_exec @spec, "#!/usr/bin/ruby"

    shebang = @installer.shebang 'executable'

    assert_equal "#!#{Gem.ruby}", shebang
  end

  def test_process_options
    assert_nil @installer.build_root
    assert_equal File.join(@gemhome, 'bin'), @installer.bin_dir
    assert_equal @gemhome, @installer.gem_home
  end

  def test_process_options_build_root
    build_root = File.join @tempdir, 'build_root'

    @installer = Gem::Installer.at @gem, :build_root => build_root

    assert_equal Pathname(build_root), @installer.build_root
    assert_equal File.join(build_root, @gemhome, 'bin'), @installer.bin_dir
    assert_equal File.join(build_root, @gemhome), @installer.gem_home
  end

  def test_shebang_arguments
    util_make_exec @spec, "#!/usr/bin/ruby -ws"

    shebang = @installer.shebang 'executable'

    assert_equal "#!#{Gem.ruby} -ws", shebang
  end

  def test_shebang_empty
    util_make_exec @spec, ''

    shebang = @installer.shebang 'executable'
    assert_equal "#!#{Gem.ruby}", shebang
  end

  def test_shebang_env
    util_make_exec @spec, "#!/usr/bin/env ruby"

    shebang = @installer.shebang 'executable'

    assert_equal "#!#{Gem.ruby}", shebang
  end

  def test_shebang_env_arguments
    util_make_exec @spec, "#!/usr/bin/env ruby -ws"

    shebang = @installer.shebang 'executable'

    assert_equal "#!#{Gem.ruby} -ws", shebang
  end

  def test_shebang_env_shebang
    util_make_exec @spec, ''
    @installer.env_shebang = true

    shebang = @installer.shebang 'executable'

    env_shebang = "/usr/bin/env" unless Gem.win_platform?

    assert_equal("#!#{env_shebang} #{RbConfig::CONFIG['ruby_install_name']}",
                 shebang)
  end

  def test_shebang_nested
    util_make_exec @spec, "#!/opt/local/ruby/bin/ruby"

    shebang = @installer.shebang 'executable'

    assert_equal "#!#{Gem.ruby}", shebang
  end

  def test_shebang_nested_arguments
    util_make_exec @spec, "#!/opt/local/ruby/bin/ruby -ws"

    shebang = @installer.shebang 'executable'

    assert_equal "#!#{Gem.ruby} -ws", shebang
  end

  def test_shebang_version
    util_make_exec @spec, "#!/usr/bin/ruby18"

    shebang = @installer.shebang 'executable'

    assert_equal "#!#{Gem.ruby}", shebang
  end

  def test_shebang_version_arguments
    util_make_exec @spec, "#!/usr/bin/ruby18 -ws"

    shebang = @installer.shebang 'executable'

    assert_equal "#!#{Gem.ruby} -ws", shebang
  end

  def test_shebang_version_env
    util_make_exec @spec, "#!/usr/bin/env ruby18"

    shebang = @installer.shebang 'executable'

    assert_equal "#!#{Gem.ruby}", shebang
  end

  def test_shebang_version_env_arguments
    util_make_exec @spec, "#!/usr/bin/env ruby18 -ws"

    shebang = @installer.shebang 'executable'

    assert_equal "#!#{Gem.ruby} -ws", shebang
  end

  def test_shebang_custom
    conf = Gem::ConfigFile.new []
    conf[:custom_shebang] = 'test'

    Gem.configuration = conf

    util_make_exec @spec, "#!/usr/bin/ruby"

    shebang = @installer.shebang 'executable'

    assert_equal "#!test", shebang
  end

  def test_shebang_custom_with_expands
    bin_env = win_platform? ? '' : '/usr/bin/env'
    conf = Gem::ConfigFile.new []
    conf[:custom_shebang] = '1 $env 2 $ruby 3 $exec 4 $name'

    Gem.configuration = conf

    util_make_exec @spec, "#!/usr/bin/ruby"

    shebang = @installer.shebang 'executable'

    assert_equal "#!1 #{bin_env} 2 #{Gem.ruby} 3 executable 4 a", shebang
  end

  def test_shebang_custom_with_expands_and_arguments
    bin_env = win_platform? ? '' : '/usr/bin/env'
    conf = Gem::ConfigFile.new []
    conf[:custom_shebang] = '1 $env 2 $ruby 3 $exec'

    Gem.configuration = conf

    util_make_exec @spec, "#!/usr/bin/ruby -ws"

    shebang = @installer.shebang 'executable'

    assert_equal "#!1 #{bin_env} 2 #{Gem.ruby} -ws 3 executable", shebang
  end

  def test_unpack
    util_setup_gem

    dest = File.join @gemhome, 'gems', @spec.full_name

    @installer.unpack dest

    assert_path_exists File.join dest, 'lib', 'code.rb'
    assert_path_exists File.join dest, 'bin', 'executable'
  end

  def test_write_build_info_file
    refute_path_exists @spec.build_info_file

    @installer.build_args = %w[
      --with-libyaml-dir /usr/local/Cellar/libyaml/0.1.4
    ]

    @installer.write_build_info_file

    assert_path_exists @spec.build_info_file

    expected = "--with-libyaml-dir\n/usr/local/Cellar/libyaml/0.1.4\n"

    assert_equal expected, File.read(@spec.build_info_file)
  end

  def test_write_build_info_file_empty
    refute_path_exists @spec.build_info_file

    @installer.write_build_info_file

    refute_path_exists @spec.build_info_file
  end

  def test_write_build_info_file_install_dir
    installer = Gem::Installer.at @gem, :install_dir => "#{@gemhome}2"

    installer.build_args = %w[
      --with-libyaml-dir /usr/local/Cellar/libyaml/0.1.4
    ]

    installer.write_build_info_file

    refute_path_exists @spec.build_info_file
    assert_path_exists \
      File.join("#{@gemhome}2", 'build_info', "#{@spec.full_name}.info")
  end

  def test_write_cache_file
    cache_file = File.join @gemhome, 'cache', @spec.file_name
    gem = File.join @gemhome, @spec.file_name

    FileUtils.mv cache_file, gem
    refute_path_exists cache_file

    installer = Gem::Installer.at gem
    installer.gem_home = @gemhome

    installer.write_cache_file

    assert_path_exists cache_file
  end

  def test_write_spec
    FileUtils.rm @spec.spec_file
    refute_path_exists @spec.spec_file

    @installer = Gem::Installer.for_spec @spec
    @installer.gem_home = @gemhome

    @installer.write_spec

    assert_path_exists @spec.spec_file

    loaded = Gem::Specification.load @spec.spec_file

    assert_equal @spec, loaded

    assert_equal Gem.rubygems_version, @spec.installed_by_version
  end

  def test_write_spec_writes_cached_spec
    FileUtils.rm @spec.spec_file
    refute_path_exists @spec.spec_file

    @spec.files = %w[a.rb b.rb c.rb]

    @installer = Gem::Installer.for_spec @spec
    @installer.gem_home = @gemhome

    @installer.write_spec

    # cached specs have no file manifest:
    @spec.files = []

    assert_equal @spec, eval(File.read(@spec.spec_file))
  end

  def test_dir
    assert_match %r!/gemhome/gems/a-2$!, @installer.dir
  end

  def test_default_gem_loaded_from
    spec = util_spec 'a'
    installer = Gem::Installer.for_spec spec, :install_as_default => true
    installer.install
    assert_predicate spec, :default_gem?
  end

  def test_default_gem
    FileUtils.rm_f File.join(Gem.dir, 'specifications')

    @installer.wrappers = true
    @installer.options[:install_as_default] = true
    @installer.gem_dir = @spec.gem_dir
    @installer.generate_bin

    use_ui @ui do
      @installer.install
    end

    assert File.directory? util_inst_bindir
    installed_exec = File.join util_inst_bindir, 'executable'
    assert_path_exists installed_exec

    assert File.directory? File.join(Gem.default_dir, 'specifications')
    assert File.directory? File.join(Gem.default_dir, 'specifications', 'default')

    default_spec = eval File.read File.join(Gem.default_dir, 'specifications', 'default', 'a-2.gemspec')
    assert_equal Gem::Version.new("2"), default_spec.version
    assert_equal ['bin/executable'], default_spec.files
  end

  def old_ruby_required
    spec = util_spec 'old_ruby_required', '1' do |s|
      s.required_ruby_version = '= 1.4.6'
    end

    util_build_gem spec

    spec.cache_file
  end

  def util_execless
    @spec = util_spec 'z'
    util_build_gem @spec

    @installer = util_installer @spec, @gemhome
  end

  def util_conflict_executable wrappers
    conflict = quick_gem 'conflict' do |spec|
      util_make_exec spec
    end

    util_build_gem conflict

    installer = util_installer conflict, @gemhome
    installer.wrappers = wrappers
    installer.generate_bin
  end

  def mask
    0100755 & (~File.umask)
  end
end
