# frozen_string_literal: true

require_relative "installer_test_case"

class TestGemInstaller < Gem::InstallerTestCase
  def setup
    super
    common_installer_setup

    @config = Gem.configuration
  end

  def teardown
    common_installer_teardown

    super

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

  def test_app_script_text
    load_relative "no" do
      installer = setup_base_installer

      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'

Gem.use_gemdeps

version = \">= 0.a\"

str = ARGV.first
if str
  str = str.b[/\\A_(.*)_\\z/, 1]
  if str and Gem::Version.correct?(str)
    version = str
    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
  end

  def test_check_executable_overwrite
    installer = setup_base_installer

    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_exist installed_exec

    wrapper = File.read installed_exec
    assert_match(/generated by RubyGems/, wrapper)
  end

  def test_check_executable_overwrite_default_bin_dir
    installer = setup_base_installer(false)

    bindir(Gem.bindir) do
      util_conflict_executable false

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

        conflicted = File.join @gemhome, "bin", "executable"
        assert_match(/\A"executable" from a conflicts with (?:#{Regexp.quote(conflicted)}|installed executable from conflict)\z/,
                     e.message)
      end
    end
  end

  def test_check_executable_overwrite_format_executable
    installer = setup_base_installer

    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_exist installed_exec

    wrapper = File.read installed_exec
    assert_match(/generated by RubyGems/, wrapper)
  ensure
    Gem::Installer.exec_format = nil
  end

  def test_check_executable_overwrite_other_gem
    installer = setup_base_installer(false)

    util_conflict_executable true

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

    use_ui ui do
      e = assert_raise 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
    installer = setup_base_installer

    util_conflict_executable true
    installer.wrappers = true
    installer.force = true

    installer.generate_bin

    installed_exec = File.join util_inst_bindir, "executable"
    assert_path_exist installed_exec

    wrapper = File.read installed_exec
    assert_match(/generated by RubyGems/, wrapper)
  end

  def test_check_executable_overwrite_other_non_gem
    installer = setup_base_installer

    util_conflict_executable false
    installer.wrappers = true

    installer.generate_bin

    installed_exec = File.join util_inst_bindir, "executable"
    assert_path_exist installed_exec

    wrapper = File.read installed_exec
    assert_match(/generated by RubyGems/, wrapper)
  end unless Gem.win_platform?

  def test_check_that_user_bin_dir_is_in_path
    installer = setup_base_installer

    bin_dir = installer.bin_dir

    if Gem.win_platform?
      bin_dir = bin_dir.downcase
    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(["executable"])
    end

    assert_empty @ui.error

    return unless Gem.win_platform?

    ENV["PATH"] = [orig_path, bin_dir.tr(File::SEPARATOR, File::ALT_SEPARATOR)].join(File::PATH_SEPARATOR)

    use_ui @ui do
      installer.check_that_user_bin_dir_is_in_path(["executable"])
    end

    assert_empty @ui.error
  ensure
    ENV["PATH"] = orig_path
  end

  def test_check_that_user_bin_dir_is_in_path_tilde
    pend "Tilde is PATH is not supported under MS Windows" if Gem.win_platform?

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

    installer = setup_base_installer
    installer.bin_dir.replace File.join @userhome, "bin"

    use_ui @ui do
      installer.check_that_user_bin_dir_is_in_path(["executable"])
    end

    assert_empty @ui.error
  ensure
    ENV["PATH"] = orig_path unless Gem.win_platform?
  end

  def test_check_that_user_bin_dir_is_in_path_not_in_path
    installer = setup_base_installer

    use_ui @ui do
      installer.check_that_user_bin_dir_is_in_path(["executable"])
    end

    expected = installer.bin_dir

    if Gem.win_platform?
      expected = expected.downcase
    end

    assert_match expected, @ui.error
    assert_match "(executable)", @ui.error
  end

  def test_ensure_dependency
    installer = setup_base_installer

    util_spec "a"

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

    dep = Gem::Dependency.new "b", "> 2"
    e = assert_raise 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_raise 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
    pend "openssl is missing" unless Gem::HAVE_OPENSSL

    _, 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_raise Gem::Security::Exception do
      installer.ensure_loadable_spec
    end
  end

  def test_extract_files
    installer = setup_base_installer

    installer.extract_files

    assert_path_exist File.join @spec.gem_dir, "bin/executable"
  end

  def test_generate_bin_bindir
    installer = setup_base_installer

    installer.wrappers = true

    @spec.executables = %w[executable]
    @spec.bindir = "bin"

    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_directory_exists util_inst_bindir
    installed_exec = File.join(util_inst_bindir, "executable")
    assert_path_exist installed_exec
    assert_equal mask, File.stat(installed_exec).mode unless Gem.win_platform?

    wrapper = File.read installed_exec
    assert_match(/generated by RubyGems/, wrapper)
  end

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

    old_path = ENV["PATH"]
    ENV["PATH"] = [ENV["PATH"], bin_dir].compact.join(File::PATH_SEPARATOR)

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

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

    use_ui @ui do
      inst.check_that_user_bin_dir_is_in_path(["executable"])
    end

    assert_equal "", @ui.error
  ensure
    ENV["PATH"] = old_path
  end

  def test_generate_bin_script
    installer = setup_base_installer

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

    installer.generate_bin
    assert_directory_exists util_inst_bindir
    installed_exec = File.join util_inst_bindir, "executable"
    assert_path_exist installed_exec
    assert_equal mask, File.stat(installed_exec).mode unless Gem.win_platform?

    wrapper = File.read installed_exec
    assert_match(/generated by RubyGems/, wrapper)
  end

  def test_generate_bin_script_format
    installer = setup_base_installer

    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_directory_exists util_inst_bindir
    installed_exec = File.join util_inst_bindir, "foo-executable-bar"
    assert_path_exist installed_exec
  ensure
    Gem::Installer.exec_format = nil
  end

  def test_generate_bin_script_format_disabled
    installer = setup_base_installer

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

    Gem::Installer.exec_format = "foo-%s-bar"
    installer.generate_bin
    assert_directory_exists util_inst_bindir
    installed_exec = File.join util_inst_bindir, "executable"
    assert_path_exist installed_exec
  ensure
    Gem::Installer.exec_format = nil
  end

  def test_generate_bin_script_install_dir
    installer = setup_base_installer

    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_exist installed_exec
    assert_equal mask, File.stat(installed_exec).mode unless Gem.win_platform?

    wrapper = File.read installed_exec
    assert_match(/generated by RubyGems/, wrapper)
  end

  def test_generate_bin_script_no_execs
    installer = setup_base_installer

    installer = util_execless

    installer.wrappers = true
    installer.generate_bin

    assert_path_not_exist util_inst_bindir, "bin dir was created when not needed"
  end

  def test_generate_bin_script_no_perms
    installer = setup_base_installer

    installer.wrappers = true
    util_make_exec

    Dir.mkdir util_inst_bindir

    if Gem.win_platform?
      pend("test_generate_bin_script_no_perms skipped on MS Windows")
    elsif Process.uid.zero?
      pend("test_generate_bin_script_no_perms skipped in root privilege")
    else
      FileUtils.chmod 0o000, util_inst_bindir

      assert_raise Gem::FilePermissionError do
        installer.generate_bin
      end
    end
  ensure
    FileUtils.chmod 0o755, util_inst_bindir unless $DEBUG || Gem.win_platform?
  end

  def test_generate_bin_script_no_shebang
    installer = setup_base_installer

    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_exist installed_exec
    assert_equal mask, File.stat(installed_exec).mode unless Gem.win_platform?

    wrapper = File.read installed_exec
    assert_match(/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 = setup_base_installer

    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?
      FileUtils.mkdir_p File.dirname(installed_exec)
      FileUtils.ln_s real_exec, installed_exec
    end

    installer.generate_bin
    assert_directory_exists util_inst_bindir
    assert_path_exist installed_exec
    assert_equal mask, File.stat(installed_exec).mode unless Gem.win_platform?

    assert_match(/generated by RubyGems/, File.read(installed_exec))

    refute_match(/generated by RubyGems/, File.read(real_exec),
                 "real executable overwritten")
  end

  def test_generate_bin_symlink
    pend "Symlinks not supported or not enabled" unless symlink_supported?

    installer = setup_base_installer

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

    installer.generate_bin
    assert_directory_exists 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
    installer = setup_base_installer

    installer = util_execless

    installer.wrappers = false
    installer.generate_bin

    assert_path_not_exist util_inst_bindir
  end

  def test_generate_bin_symlink_no_perms
    installer = setup_base_installer

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

    Dir.mkdir util_inst_bindir

    if Gem.win_platform?
      pend("test_generate_bin_symlink_no_perms skipped on MS Windows")
    elsif Process.uid.zero?
      pend("test_user_install_disabled_read_only test skipped in root privilege")
    else
      FileUtils.chmod 0o000, util_inst_bindir

      assert_raise Gem::FilePermissionError do
        installer.generate_bin
      end
    end
  ensure
    FileUtils.chmod 0o755, util_inst_bindir unless $DEBUG || Gem.win_platform?
  end

  def test_generate_bin_symlink_update_newer
    pend "Symlinks not supported or not enabled" unless symlink_supported?

    installer = setup_base_installer

    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
    pend "Symlinks not supported or not enabled" unless symlink_supported?

    installer = setup_base_installer

    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
    pend "Symlinks not supported or not enabled" unless symlink_supported?

    installer = setup_base_installer

    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_exist 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 = setup_base_installer

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

    use_ui @ui do
      installer.generate_bin
    end

    assert_directory_exists util_inst_bindir
    installed_exec = File.join(util_inst_bindir, "executable")
    assert_path_exist 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
    pend "Symlinks not supported or not enabled" unless symlink_supported?

    load_relative "no" do
      installer = setup_base_installer

      installer.wrappers = true
      util_make_exec

      installer.generate_bin

      default_shebang = Gem.ruby
      shebang_line = File.open("#{@gemhome}/bin/executable", &:gets)
      assert_match(/\A#!/, shebang_line)
      assert_include(shebang_line, default_shebang)
    end
  end

  def test_generate_bin_with_dangling_symlink
    gem_with_dangling_symlink = File.expand_path("packages/ascii_binder-0.1.10.1.gem", __dir__)

    installer = Gem::Installer.at(
      gem_with_dangling_symlink,
      user_install: false,
      force: true
    )

    build_rake_in do
      use_ui @ui do
        installer.install
      end
    end

    errors = @ui.error.split("\n")
    assert_equal "WARNING:  ascii_binder-0.1.10.1 ships with a dangling symlink named bin/ascii_binder pointing to missing bin/asciibinder file. Ignoring", errors.shift
    assert_empty errors

    assert_empty @ui.output
  end

  def test_generate_plugins
    installer = util_setup_installer do |spec|
      write_file File.join(@tempdir, "lib", "rubygems_plugin.rb") do |io|
        io.write "# do nothing"
      end

      spec.files += %w[lib/rubygems_plugin.rb]
    end

    build_rake_in do
      installer.install
    end

    plugin_path = File.join Gem.plugindir, "a_plugin.rb"

    FileUtils.rm plugin_path

    installer.generate_plugins

    assert File.exist?(plugin_path), "plugin not written"
  end

  def test_generate_plugins_with_install_dir
    spec = quick_gem "a" do |s|
      write_file File.join(@tempdir, "lib", "rubygems_plugin.rb") do |io|
        io.write "puts __FILE__"
      end

      s.files += %w[lib/rubygems_plugin.rb]
    end

    util_build_gem spec

    plugin_path = File.join "#{@gemhome}2", "plugins", "a_plugin.rb"
    installer = util_installer spec, "#{@gemhome}2"

    assert_equal spec, installer.install

    assert File.exist?(plugin_path), "plugin not written to install_dir"
  end

  def test_generate_plugins_with_user_install
    spec = quick_gem "a" do |s|
      write_file File.join(@tempdir, "lib", "rubygems_plugin.rb") do |io|
        io.write "puts __FILE__"
      end

      s.files += %w[lib/rubygems_plugin.rb]
    end

    util_build_gem spec

    File.chmod(0o555, Gem.plugindir)
    system_path = File.join(Gem.plugindir, "a_plugin.rb")
    user_path = File.join(Gem.plugindir(Gem.user_dir), "a_plugin.rb")
    installer = Gem::Installer.at spec.cache_file, user_install: true, force: true

    assert_equal spec, installer.install

    assert !File.exist?(system_path), "plugin incorrectly written to system plugins_dir"
    assert File.exist?(user_path), "plugin not written to user plugins_dir"
  end

  def test_generate_plugins_with_build_root
    spec = quick_gem "a" do |s|
      write_file File.join(@tempdir, "lib", "rubygems_plugin.rb") do |io|
        io.write "puts __FILE__"
      end

      s.files += %w[lib/rubygems_plugin.rb]
    end

    util_build_gem spec

    File.chmod(0o555, Gem.plugindir)
    system_path = File.join(Gem.plugindir, "a_plugin.rb")

    build_root = File.join(@tempdir, "build_root")
    build_root_path = File.join(build_root, Gem.plugindir.gsub(/^[a-zA-Z]:/, ""), "a_plugin.rb")

    installer = Gem::Installer.at spec.cache_file, build_root: build_root

    assert_equal spec, installer.install

    assert !File.exist?(system_path), "plugin written incorrect written to system plugins_dir"
    assert File.exist?(build_root_path), "plugin not written to build_root"

    refute_includes File.read(build_root_path), build_root
  end

  class << self
    attr_accessor :plugin_loaded
    attr_accessor :post_install_is_called
  end

  def test_use_plugin_immediately
    self.class.plugin_loaded = false
    self.class.post_install_is_called = false
    spec_version = nil
    plugin_path = nil
    installer = util_setup_installer do |spec|
      spec_version = spec.version
      plugin_path = File.join("lib", "rubygems_plugin.rb")
      write_file File.join(@tempdir, plugin_path) do |io|
        io.write <<-PLUGIN
#{self.class}.plugin_loaded = true
Gem.post_install do
  #{self.class}.post_install_is_called = true
end
        PLUGIN
      end
      spec.files += [plugin_path]
      plugin_path = File.join(spec.gem_dir, plugin_path)
    end
    build_rake_in do
      installer.install
    end
    assert self.class.plugin_loaded, "plugin is not loaded"
    assert self.class.post_install_is_called,
           "post install hook registered by plugin is not called"

    self.class.plugin_loaded = false
    $LOADED_FEATURES.delete(plugin_path)
    installer_new = util_setup_installer do |spec_new|
      spec_new.version = spec_version.version.succ
      plugin_path = File.join("lib", "rubygems_plugin.rb")
      write_file File.join(@tempdir, plugin_path) do |io|
        io.write "#{self.class}.plugin_loaded = true"
      end
      spec_new.files += [plugin_path]
    end
    build_rake_in do
      installer_new.install
    end
    assert !self.class.plugin_loaded,
           "plugin is loaded even when old version is already loaded"
  end

  def test_keeps_plugins_up_to_date
    # NOTE: version a-2 is already installed by setup hooks

    write_file File.join(@tempdir, "lib", "rubygems_plugin.rb") do |io|
      io.write "# do nothing"
    end

    build_rake_in do
      util_setup_installer do |spec|
        spec.version = "1"
        spec.files += %w[lib/rubygems_plugin.rb]
      end.install

      plugin_path = File.join Gem.plugindir, "a_plugin.rb"
      refute File.exist?(plugin_path), "old version installed while newer version without plugin also installed, but plugin written"

      util_setup_installer do |spec|
        spec.version = "2"
        spec.files += %w[lib/rubygems_plugin.rb]
      end.install

      plugin_path = File.join Gem.plugindir, "a_plugin.rb"
      assert File.exist?(plugin_path), "latest version reinstalled, but plugin not written"
      assert_match %r{\Arequire.*a-2/lib/rubygems_plugin\.rb}, File.read(plugin_path), "written plugin has incorrect content"

      util_setup_installer do |spec|
        spec.version = "3"
        spec.files += %w[lib/rubygems_plugin.rb]
      end.install

      plugin_path = File.join Gem.plugindir, "a_plugin.rb"
      assert File.exist?(plugin_path), "latest version installed, but plugin removed"
      assert_match %r{\Arequire.*a-3/lib/rubygems_plugin\.rb}, File.read(plugin_path), "written plugin has incorrect content"

      util_setup_installer do |spec|
        spec.version = "4"
      end.install

      refute File.exist?(plugin_path), "new version installed without a plugin while older version with a plugin installed, but plugin not removed"
    end
  end

  def test_generates_plugins_dir_under_install_dir_if_not_there
    Gem.use_paths "#{@gemhome}2" # Set GEM_HOME to an uninitialized repo

    @spec = util_spec "a"

    path = Gem::Package.build @spec

    installer = Gem::Installer.at path, install_dir: "#{@gemhome}3"
    assert_equal @spec, installer.install
  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
    @gem = setup_base_gem

    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
    @gem = setup_base_gem

    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_takes_precedence_to_user_install
    gemhome2 = "#{@gemhome}2"

    @gem = setup_base_gem

    installer =
      Gem::Installer.at @gem, install_dir: gemhome2, user_install: true
    installer.install

    assert_path_exist File.join(gemhome2, "gems", @spec.full_name)
    assert_path_not_exist File.join(Gem.user_dir, "gems", @spec.full_name)
  end

  def test_install
    installer = util_setup_installer

    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
      assert_path_not_exist cache_file, "cache file must not exist yet"
      true
    end

    Gem.post_build do
      assert_path_exist gemdir, "gem install dir must exist"
      assert_path_exist rakefile, "gem executable must exist"
      assert_path_not_exist stub_exe, "gem executable must not exist"
      true
    end

    Gem.post_install do
      assert_path_exist cache_file, "cache 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_exist gemdir
    assert_path_exist stub_exe, "gem executable must exist"

    exe = File.join gemdir, "bin", "executable"
    assert_path_exist exe

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

    assert_path_exist File.join gemdir, "lib", "code.rb"

    assert_path_exist rakefile

    assert_equal spec_file, @newspec.loaded_from
    assert_path_exist 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
    installer = util_setup_installer

    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_raise RuntimeError do
      instance_eval File.read(exe)
    end

    assert_match(/ran executable/, e.message)

    assert_path_not_exist(File.join(installer.bin_dir, "executable.lock"))
  end

  def test_conflicting_binstubs
    @gem = setup_base_gem

    # build old version that has a bin file
    installer = 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
    installer = 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_raise 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)

    assert_path_not_exist(File.join(installer.bin_dir, "executable.lock"))
  end

  def test_install_creates_binstub_that_understand_version
    installer = util_setup_installer

    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_raise 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)")

    assert_path_not_exist(File.join(installer.bin_dir, "executable.lock"))
  end

  def test_install_creates_binstub_that_prefers_user_installed_gem_to_default
    default_spec = new_default_spec("default", "2", nil, "exe/executable")
    default_spec.executables = "executable"
    install_default_gems default_spec

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

    assert_path_exist exe, "default gem's executable not installed"

    installer = util_setup_installer do |spec|
      spec.name = "default"
      spec.version = "2"
    end

    util_clear_gems

    installer.wrappers = true

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

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

    assert_equal(e.message, "ran executable")

    assert_path_not_exist(File.join(installer.bin_dir, "executable.lock"))
  end

  def test_install_creates_binstub_that_dont_trust_encoding
    installer = util_setup_installer

    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_raise RuntimeError do
        instance_eval File.read(exe)
      end
    ensure
      ARGV.shift if ARGV.first == extra_arg
    end

    assert_match(/ran executable/, e.message)

    assert_path_not_exist(File.join(installer.bin_dir, "executable.lock"))
  end

  def test_install_does_not_leave_lockfile_for_binstub
    installer = util_setup_installer

    installer.wrappers = true

    File.class_eval do
      alias_method :original_chmod, :chmod
      define_method(:chmod) do |mode|
        original_chmod(mode)
        raise Gem::Ext::BuildError if path.end_with?("/executable")
      end
    end

    assert_raise(Gem::Ext::BuildError) do
      installer.install
    end

    assert_path_not_exist(File.join(installer.bin_dir, "executable.lock"))
    # TODO: remove already copied files at failures.
    # assert_path_not_exist(File.join(installer.bin_dir, "executable"))
  ensure
    File.class_eval do
      remove_method :chmod
      alias_method :chmod, :original_chmod
      remove_method :original_chmod
    end
  end

  def test_install_with_no_prior_files
    installer = util_setup_installer

    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_exist File.join gemdir, "lib", "code.rb"

    installer = util_setup_installer

    # 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, force: true
    build_rake_in do
      use_ui @ui do
        assert_equal @spec, installer.install
      end
    end

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

  def test_install_force
    _, missing_dep_gem = util_gem "missing_dep", "1" do |s|
      s.add_dependency "doesnt_exist", "1"
    end

    use_ui @ui do
      installer = Gem::Installer.at missing_dep_gem, force: true
      installer.install
    end

    gem_dir = File.join(@gemhome, "gems", "missing_dep-1")
    assert_path_exist gem_dir
  end

  def test_install_build_root
    build_root = File.join(@tempdir, "build_root")

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

    assert_equal @spec, installer.install
  end

  def test_install_build_root_when_gem_home_not_writable_does_not_fallback_to_user_install_inside_build_root
    build_root = File.join(@tempdir, "build_root")

    orig_gem_home = ENV.delete("GEM_HOME")

    @gem = setup_base_gem

    FileUtils.chmod "-w", @gemhome

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

    assert_equal @spec, installer.install

    build_root_path = File.join(build_root, @gemhome.gsub(/^[a-zA-Z]:/, ""))
    assert File.exist?(build_root_path), "gem not written to build_root"
  ensure
    FileUtils.chmod "+w", @gemhome
    ENV["GEM_HOME"] = orig_gem_home
  end

  def test_install_missing_dirs
    installer = setup_base_installer

    FileUtils.rm_rf File.join(Gem.dir, "doc")
    FileUtils.rm_rf File.join(Gem.dir, "specifications")

    use_ui @ui do
      installer.install
    end

    assert_directory_exists File.join(Gem.dir, "doc")
    assert_directory_exists File.join(Gem.dir, "specifications")

    assert_path_exist File.join @gemhome, "cache", @spec.file_name
    assert_path_exist File.join @gemhome, "specifications", @spec.spec_name
  end

  def test_install_post_build_false
    @spec = util_spec "a"

    util_build_gem @spec

    installer = util_installer @spec, @gemhome

    Gem.post_build do
      false
    end

    use_ui @ui do
      e = assert_raise 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
    assert_path_not_exist spec_file

    gem_dir = File.join @gemhome, "gems", @spec.full_name
    assert_path_not_exist gem_dir
  end

  def test_install_post_build_nil
    installer = setup_base_installer

    Gem.post_build do
      nil
    end

    use_ui @ui do
      installer.install
    end

    spec_file = File.join @gemhome, "specifications", @spec.spec_name
    assert_path_exist spec_file

    gem_dir = File.join @gemhome, "gems", @spec.full_name
    assert_path_exist gem_dir
  end

  def test_install_pre_install_false
    @spec = util_spec "a"

    util_build_gem @spec

    installer = util_installer @spec, @gemhome

    Gem.pre_install do
      false
    end

    use_ui @ui do
      e = assert_raise 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
    assert_path_not_exist spec_file
  end

  def test_install_pre_install_nil
    installer = setup_base_installer

    Gem.pre_install do
      nil
    end

    use_ui @ui do
      installer.install
    end

    spec_file = File.join @gemhome, "specifications", @spec.spec_name
    assert_path_exist spec_file
  end

  def test_install_with_message
    @spec = setup_base_spec
    @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(/I am a shiny gem!/, @ui.output)
  end

  def test_install_with_skipped_message
    @spec = setup_base_spec
    @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(/I am a shiny gem!/, @ui.output)
  end

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

    @spec = setup_base_spec
    @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_exist expected_makefile
  end

  def test_install_extension_dir_is_removed_on_reinstall
    @spec = setup_base_spec

    @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_exist should_be_removed

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

    assert_path_not_exist should_be_removed
  end

  def test_install_user_extension_dir
    @spec = setup_base_spec
    @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_exist expected_makefile
    assert_path_exist expected_extension_dir
    assert_path_not_exist File.join expected_extension_dir, "gem_make.out"
  end

  def test_find_lib_file_after_install
    pend "needs investigation" if Gem.java_platform?

    @spec = setup_base_spec
    @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
    pend "Makefile creation crashes on jruby" if Gem.java_platform?
    pend "terminates on mswin" if vc_windows? && ruby_repo?

    @spec = setup_base_spec
    @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

    assert_path_not_exist File.join @spec.gem_dir, rb
    assert_path_not_exist 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_exist File.join @spec.gem_dir, rb
    assert_path_exist File.join @spec.gem_dir, rb2
  end

  def test_install_extension_flat
    pend "needs investigation" if Gem.java_platform?

    begin
      @spec = setup_base_spec
      @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 do |file|
        write_file File.join(@tempdir, file)
      end

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

        installer = Gem::Installer.at path
        installer.install
      end
      assert_path_exist so
    rescue StandardError
      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
  end

  def test_install_extension_clean_intermediate_files
    pend "needs investigation" if Gem.java_platform?
    @spec = setup_base_spec
    @spec.require_paths = ["."]
    @spec.extensions << "extconf.rb"

    File.write File.join(@tempdir, "extconf.rb"), <<-RUBY
      require "mkmf"
      CONFIG['CC'] = '$(TOUCH) $@ ||'
      CONFIG['LDSHARED'] = '$(TOUCH) $@ ||'
      $ruby = '#{Gem.ruby}'
      create_makefile("#{@spec.name}")
    RUBY

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

    shared_object = "#{@spec.name}.#{RbConfig::CONFIG["DLEXT"]}"
    extension_file = File.join @spec.extension_dir, shared_object
    intermediate_file = File.join @spec.gem_dir, shared_object

    assert_path_not_exist extension_file, "no before installing"
    use_ui @ui do
      path = Gem::Package.build @spec

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

    assert_path_exist extension_file, "installed"
    assert_path_not_exist intermediate_file
  end

  def test_installation_satisfies_dependency_eh
    installer = setup_base_installer

    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 = setup_base_installer
    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
    installer = setup_base_installer
    @spec.add_dependency "b", "> 5"
    installer = util_setup_gem
    installer.force = false

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

  def test_pre_install_checks_dependencies_ignore
    installer = util_setup_installer
    @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"

    @gem = setup_base_gem
    @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_malicious_name
    spec = util_spec "../malicious", "1"
    def spec.full_name # so the spec is buildable
      "malicious-1"
    end

    def spec.validate(packaging, strict); end

    util_build_gem spec

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

    use_ui @ui do
      installer = Gem::Installer.at gem
      e = assert_raise 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_pre_install_checks_malicious_name_before_eval
    spec = util_spec "malicious\n::Object.const_set(:FROM_EVAL, true)#", "1"
    def spec.full_name # so the spec is buildable
      "malicious-1"
    end

    def spec.validate(*args); end

    util_build_gem spec

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

    use_ui @ui do
      installer = Gem::Installer.at gem
      e = assert_raise Gem::InstallError do
        installer.pre_install_checks
      end
      assert_equal "#<Gem::Specification name=malicious\n::Object.const_set(:FROM_EVAL, true)# version=1> has an invalid name", e.message
    end
    refute defined?(::Object::FROM_EVAL)
  end

  def test_pre_install_checks_malicious_require_paths_before_eval
    spec = util_spec "malicious", "1"
    def spec.full_name # so the spec is buildable
      "malicious-1"
    end

    def spec.validate(*args); end
    spec.require_paths = ["malicious\n``"]

    util_build_gem spec

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

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

  def test_pre_install_checks_malicious_extensions_before_eval
    pend "mswin environment disallow to create file contained the carriage return code." if Gem.win_platform?

    spec = util_spec "malicious", "1"
    def spec.full_name # so the spec is buildable
      "malicious-1"
    end

    def spec.validate(*args); end
    spec.extensions = ["malicious\n``"]

    util_build_gem spec

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

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

  def test_pre_install_checks_malicious_specification_version_before_eval
    spec = util_spec "malicious", "1"
    def spec.full_name # so the spec is buildable
      "malicious-1"
    end

    def spec.validate(*args); end
    spec.specification_version = "malicious\n``"

    util_build_gem spec

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

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

  def test_pre_install_checks_malicious_dependencies_before_eval
    spec = util_spec "malicious", "1"
    def spec.full_name # so the spec is buildable
      "malicious-1"
    end

    def spec.validate(*args); end
    spec.add_dependency "b\nfoo", "> 5"

    util_build_gem spec

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

    use_ui @ui do
      installer = Gem::Installer.at gem
      installer.ignore_dependencies = true
      e = assert_raise Gem::InstallError do
        installer.pre_install_checks
      end
      assert_equal "#<Gem::Specification name=malicious version=1> has an invalid dependencies", e.message
    end
  end

  def test_pre_install_checks_malicious_platform_before_eval
    gem_with_ill_formated_platform = File.expand_path("packages/ill-formatted-platform-1.0.0.10.gem", __dir__)

    installer = Gem::Installer.at(
      gem_with_ill_formated_platform,
      install_dir: @gemhome,
      user_install: false,
      force: true
    )

    use_ui @ui do
      e = assert_raise Gem::InstallError do
        installer.pre_install_checks
      end

      assert_equal "x86-mswin32\n system('id > /tmp/nyangawa')# is an invalid platform", e.message
      assert_empty @ui.output
    end
  end

  def test_shebang
    load_relative "no" do
      installer = setup_base_installer

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

      shebang = installer.shebang "executable"

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

  def test_process_options
    installer = setup_base_installer

    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"
    bin_dir = File.join(build_root, @gemhome.gsub(/^[a-zA-Z]:/, ""), "bin")
    gem_home = File.join(build_root, @gemhome.gsub(/^[a-zA-Z]:/, ""))
    plugins_dir = File.join(build_root, @gemhome.gsub(/^[a-zA-Z]:/, ""), "plugins")

    @gem = setup_base_gem
    installer = use_ui(@ui) { Gem::Installer.at @gem, build_root: build_root }

    assert_equal build_root, installer.build_root
    assert_equal bin_dir, installer.bin_dir
    assert_equal gem_home, installer.gem_home

    errors = @ui.error.split("\n")

    assert_equal "WARNING:  You build with buildroot.", errors.shift
    assert_equal "  Build root: #{build_root}", errors.shift
    assert_equal "  Bin dir: #{bin_dir}", errors.shift
    assert_equal "  Gem home: #{gem_home}", errors.shift
    assert_equal "  Plugins dir: #{plugins_dir}", errors.shift
  end

  def test_process_options_fallback_to_user_install_when_gem_home_not_writable
    if Process.uid.zero?
      pend("skipped in root privilege")
      return
    end

    orig_gem_home = ENV.delete("GEM_HOME")

    @gem = setup_base_gem

    FileUtils.chmod 0o000, @gemhome

    installer = use_ui(@ui) { Gem::Installer.at @gem }

    assert_equal Gem.user_dir, installer.gem_home
    assert_equal "Defaulting to user installation because default installation directory (#{@gemhome}) is not writable.", @ui.output.strip
  ensure
    FileUtils.chmod 0o755, @gemhome
    ENV["GEM_HOME"] = orig_gem_home
  end

  def test_process_options_does_not_fallback_to_user_install_when_gem_home_not_writable_and_no_user_install
    if Process.uid.zero?
      pend("skipped in root privilege")
      return
    end

    orig_gem_home = ENV.delete("GEM_HOME")

    @gem = setup_base_gem

    FileUtils.chmod 0o000, @gemhome

    installer = use_ui(@ui) { Gem::Installer.at @gem, user_install: false }

    assert_equal @gemhome, installer.gem_home
    assert_empty @ui.output.strip
  ensure
    FileUtils.chmod 0o755, @gemhome
    ENV["GEM_HOME"] = orig_gem_home
  end

  def test_shebang_arguments
    load_relative "no" do
      installer = setup_base_installer

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

      shebang = installer.shebang "executable"

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

  def test_shebang_arguments_with_load_relative
    load_relative "yes" do
      installer = setup_base_installer

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

      shebang = installer.shebang "executable"

      shebang_lines = shebang.split "\n"

      assert_equal "#!/bin/sh", shebang_lines.shift
      assert_includes shebang_lines, "#!#{Gem.ruby} -ws"
    end
  end

  def test_shebang_empty
    load_relative "no" do
      installer = setup_base_installer

      util_make_exec @spec, ""

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

  def test_shebang_env
    load_relative "no" do
      installer = setup_base_installer

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

      shebang = installer.shebang "executable"

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

  def test_shebang_env_arguments
    load_relative "no" do
      installer = setup_base_installer

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

      shebang = installer.shebang "executable"

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

  def test_shebang_env_arguments_with_load_relative
    load_relative "yes" do
      installer = setup_base_installer

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

      shebang = installer.shebang "executable"

      shebang_lines = shebang.split "\n"

      assert_equal "#!/bin/sh", shebang_lines.shift
      assert_includes shebang_lines, "#!#{Gem.ruby} -ws"
    end
  end

  def test_shebang_env_shebang
    installer = setup_base_installer

    util_make_exec @spec, ""
    installer.env_shebang = true

    shebang = installer.shebang "executable"

    bin_env = get_bin_env

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

  def test_shebang_nested
    load_relative "no" do
      installer = setup_base_installer

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

      shebang = installer.shebang "executable"

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

  def test_shebang_nested_arguments
    load_relative "no" do
      installer = setup_base_installer

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

      shebang = installer.shebang "executable"

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

  def test_shebang_nested_arguments_with_load_relative
    load_relative "yes" do
      installer = setup_base_installer

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

      shebang = installer.shebang "executable"

      shebang_lines = shebang.split "\n"

      assert_equal "#!/bin/sh", shebang_lines.shift
      assert_includes shebang_lines, "#!#{Gem.ruby} -ws"
    end
  end

  def test_shebang_version
    load_relative "no" do
      installer = setup_base_installer

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

      shebang = installer.shebang "executable"

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

  def test_shebang_version_arguments
    load_relative "no" do
      installer = setup_base_installer

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

      shebang = installer.shebang "executable"

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

  def test_shebang_version_arguments_with_load_relative
    load_relative "yes" do
      installer = setup_base_installer

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

      shebang = installer.shebang "executable"

      shebang_lines = shebang.split "\n"

      assert_equal "#!/bin/sh", shebang_lines.shift
      assert_includes shebang_lines, "#!#{Gem.ruby} -ws"
    end
  end

  def test_shebang_version_env
    load_relative "no" do
      installer = setup_base_installer

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

      shebang = installer.shebang "executable"

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

  def test_shebang_version_env_arguments
    load_relative "no" do
      installer = setup_base_installer

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

      shebang = installer.shebang "executable"

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

  def test_shebang_version_env_arguments_with_load_relative
    load_relative "yes" do
      installer = setup_base_installer

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

      shebang = installer.shebang "executable"

      shebang_lines = shebang.split "\n"

      assert_equal "#!/bin/sh", shebang_lines.shift
      assert_includes shebang_lines, "#!#{Gem.ruby} -ws"
    end
  end

  def test_shebang_custom
    installer = setup_base_installer

    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 get_bin_env
    if Gem.win_platform?
      ""
    else
      %w[/usr/bin/env /bin/env].find {|f| File.executable?(f) }
    end
  end

  def test_shebang_custom_with_expands
    installer = setup_base_installer

    bin_env = get_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
    installer = setup_base_installer

    bin_env = get_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
    installer = util_setup_installer

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

    Gem::Deprecate.skip_during do
      installer.unpack dest
    end

    assert_path_exist File.join dest, "lib", "code.rb"
    assert_path_exist File.join dest, "bin", "executable"
  end

  def test_write_build_info_file
    installer = setup_base_installer

    assert_path_not_exist @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_exist @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
    installer = setup_base_installer

    assert_path_not_exist @spec.build_info_file

    installer.write_build_info_file

    assert_path_not_exist @spec.build_info_file
  end

  def test_write_build_info_file_install_dir
    @gem = setup_base_gem
    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

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

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

    FileUtils.mv cache_file, gem
    assert_path_not_exist cache_file

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

    installer.write_cache_file

    assert_path_exist cache_file
  end

  def test_write_spec
    @spec = setup_base_spec
    FileUtils.rm @spec.spec_file
    assert_path_not_exist @spec.spec_file

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

    installer.write_spec

    assert_path_exist @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
    @spec = setup_base_spec
    FileUtils.rm @spec.spec_file
    assert_path_not_exist @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_leaves_no_empty_cached_spec_when_no_more_disk_space
    @spec = setup_base_spec
    FileUtils.rm @spec.spec_file
    assert_path_not_exist @spec.spec_file

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

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

    File.singleton_class.class_eval do
      alias_method :original_binwrite, :binwrite

      def binwrite(path, data)
        raise Errno::ENOSPC
      end
    end

    assert_raise Errno::ENOSPC do
      installer.write_spec
    end

    assert_path_not_exist @spec.spec_file
  ensure
    File.singleton_class.class_eval do
      remove_method :binwrite
      alias_method :binwrite, :original_binwrite
      remove_method :original_binwrite
    end
  end

  def test_dir
    installer = setup_base_installer

    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_without_wrappers
    installer = setup_base_installer

    FileUtils.rm_rf File.join(Gem.default_dir, "specifications")

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

    use_ui @ui do
      installer.install
    end

    assert_directory_exists File.join(@spec.gem_dir, "bin")
    installed_exec = File.join @spec.gem_dir, "bin", "executable"
    assert_path_exist installed_exec

    assert_directory_exists File.join(Gem.default_dir, "specifications")
    assert_directory_exists 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

    assert_directory_exists util_inst_bindir

    installed_exec = File.join util_inst_bindir, "executable"
    assert_path_exist installed_exec

    wrapper = File.read installed_exec

    if symlink_supported?
      refute_match(/generated by RubyGems/, wrapper)
    else # when symlink not supported, it warns and fallbacks back to installing wrapper
      assert_match(/Unable to use symlinks, installing wrapper/, @ui.error)
      assert_match(/generated by RubyGems/, wrapper)
    end
  end

  def test_default_gem_with_wrappers
    installer = setup_base_installer

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

    use_ui @ui do
      installer.install
    end

    assert_directory_exists util_inst_bindir

    installed_exec = File.join util_inst_bindir, "executable"
    assert_path_exist installed_exec

    wrapper = File.read installed_exec
    assert_match(/generated by RubyGems/, wrapper)
  end

  def test_default_gem_with_exe_as_bindir
    @spec = quick_gem "c" do |spec|
      util_make_exec spec, "#!/usr/bin/ruby", "exe"
    end

    util_build_gem @spec

    @spec.cache_file

    installer = util_installer @spec, @gemhome

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

    use_ui @ui do
      installer.install
    end

    assert_directory_exists File.join(@spec.gem_dir, "exe")
    installed_exec = File.join @spec.gem_dir, "exe", "executable"
    assert_path_exist installed_exec

    assert_directory_exists File.join(Gem.default_dir, "specifications")
    assert_directory_exists File.join(Gem.default_dir, "specifications", "default")

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

  def test_default_gem_to_specific_install_dir
    @gem = setup_base_gem
    installer = util_installer @spec, "#{@gemhome}2"
    installer.options[:install_as_default] = true

    use_ui @ui do
      installer.install
    end

    assert_directory_exists File.join("#{@gemhome}2", "specifications")
    assert_directory_exists File.join("#{@gemhome}2", "specifications", "default")

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

  def test_package_attribute
    gem = quick_gem "c" do |spec|
      util_make_exec spec, "#!/usr/bin/ruby", "exe"
    end

    installer = util_installer(gem, @gemhome)
    assert_respond_to(installer, :package)
    assert_kind_of(Gem::Package, installer.package)
  end

  def test_gem_attribute
    gem = quick_gem "c" do |spec|
      util_make_exec spec, "#!/usr/bin/ruby", "exe"
    end

    installer = util_installer(gem, @gemhome)
    assert_respond_to(installer, :gem)
    assert_kind_of(String, installer.gem)
  end

  private

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

    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
    0o100755
  end

  def load_relative(value)
    orig_libruby_relative = RbConfig::CONFIG["LIBRUBY_RELATIVE"]
    RbConfig::CONFIG["LIBRUBY_RELATIVE"] = value

    yield
  ensure
    RbConfig::CONFIG["LIBRUBY_RELATIVE"] = orig_libruby_relative
  end
end
