require_relative "helper"

class TestBundlerGem < Gem::TestCase
  PROJECT_DIR = File.expand_path("../..", __dir__).tap(&Gem::UNTAINT)

  def test_self_use_gemdeps
    with_local_bundler_at(Gem.dir) do
      with_rubygems_gemdeps("-") do
        FileUtils.mkdir_p "detect/a/b"
        FileUtils.mkdir_p "detect/a/Isolate"

        FileUtils.touch "detect/Isolate"

        begin
          Dir.chdir "detect/a/b"

          Gem.use_gemdeps

          assert_equal add_bundler_full_name([]), loaded_spec_names
        ensure
          Dir.chdir @tempdir
        end
      end
    end
  end

  def test_self_find_files_with_gemfile
    with_local_bundler_at(Gem.dir) do
      cwd = File.expand_path("test/rubygems", PROJECT_DIR)
      actual_load_path = $LOAD_PATH.unshift(cwd).dup

      discover_path = File.join "lib", "sff", "discover.rb"

      foo1, _ = %w[1 2].map do |version|
        spec = quick_gem "sff", version do |s|
          s.files << discover_path
        end

        write_file(File.join "gems", spec.full_name, discover_path) do |fp|
          fp.puts "# #{spec.full_name}"
        end

        spec
      end
      Gem.refresh

      write_file(File.join Dir.pwd, "Gemfile") do |fp|
        fp.puts "source 'https://rubygems.org'"
        fp.puts "gem '#{foo1.name}', '#{foo1.version}'"
      end
      Gem.use_gemdeps(File.join Dir.pwd, "Gemfile")

      expected = [
        File.expand_path("test/rubygems/sff/discover.rb", PROJECT_DIR),
        File.join(foo1.full_gem_path, discover_path),
      ].sort

      assert_equal expected, Gem.find_files("sff/discover").sort
      assert_equal expected, Gem.find_files("sff/**.rb").sort, "[ruby-core:31730]"
      assert_equal cwd, actual_load_path.shift
    end
  end

  def test_auto_activation_of_specific_gemdeps_file
    with_local_bundler_at(Gem.dir) do
      a = util_spec "a", "1", nil, "lib/a.rb"
      b = util_spec "b", "1", nil, "lib/b.rb"
      c = util_spec "c", "1", nil, "lib/c.rb"

      install_specs a, b, c

      path = File.join @tempdir, "gem.deps.rb"

      File.open path, "w" do |f|
        f.puts "gem 'a'"
        f.puts "gem 'b'"
        f.puts "gem 'c'"
      end

      with_rubygems_gemdeps(path) do
        Gem.use_gemdeps

        assert_equal add_bundler_full_name(%W[a-1 b-1 c-1]), loaded_spec_names
      end
    end
  end

  def test_auto_activation_of_used_gemdeps_file
    with_local_bundler_at(Gem.dir) do
      a = util_spec "a", "1", nil, "lib/a.rb"
      b = util_spec "b", "1", nil, "lib/b.rb"
      c = util_spec "c", "1", nil, "lib/c.rb"

      install_specs a, b, c

      path = File.join @tempdir, "gem.deps.rb"

      File.open path, "w" do |f|
        f.puts "gem 'a'"
        f.puts "gem 'b'"
        f.puts "gem 'c'"
      end

      with_rubygems_gemdeps("-") do
        expected_specs = [a, b, util_spec("bundler", Bundler::VERSION), c].compact.map(&:full_name)

        Gem.use_gemdeps

        assert_equal expected_specs, loaded_spec_names
      end
    end
  end

  def test_looks_for_gemdeps_files_automatically_from_binstubs
    path = File.join(@tempdir, "gd-tmp")

    with_local_bundler_at(path) do
      a = util_spec "a", "1" do |s|
        s.executables = %w[foo]
        s.bindir = "exe"
      end

      write_file File.join(@tempdir, "exe", "foo") do |fp|
        fp.puts "puts Gem.loaded_specs.values.map(&:full_name).sort"
      end

      b = util_spec "b", "1", nil, "lib/b.rb"
      c = util_spec "c", "1", nil, "lib/c.rb"

      install_specs a, b, c

      install_gem a, :install_dir => path
      install_gem b, :install_dir => path
      install_gem c, :install_dir => path

      ENV["GEM_PATH"] = path

      with_rubygems_gemdeps("-") do
        new_PATH = [File.join(path, "bin"), ENV["PATH"]].join(File::PATH_SEPARATOR)
        new_RUBYOPT = "-I#{rubygems_path} -I#{bundler_path}"

        path = File.join @tempdir, "gem.deps.rb"

        File.open path, "w" do |f|
          f.puts "gem 'a'"
        end
        out0 = with_path_and_rubyopt(new_PATH, new_RUBYOPT) do
          IO.popen("foo", &:read).split(/\n/)
        end

        File.open path, "a" do |f|
          f.puts "gem 'b'"
          f.puts "gem 'c'"
        end
        out = with_path_and_rubyopt(new_PATH, new_RUBYOPT) do
          IO.popen("foo", &:read).split(/\n/)
        end

        assert_equal ["b-1", "c-1"], out - out0
      end
    end
  end

  def test_looks_for_gemdeps_files_automatically_from_binstubs_in_parent_dir
    path = File.join(@tempdir, "gd-tmp")

    with_local_bundler_at(path) do
      pend "IO.popen has issues on JRuby when passed :chdir" if Gem.java_platform?

      a = util_spec "a", "1" do |s|
        s.executables = %w[foo]
        s.bindir = "exe"
      end

      write_file File.join(@tempdir, "exe", "foo") do |fp|
        fp.puts "puts Gem.loaded_specs.values.map(&:full_name).sort"
      end

      b = util_spec "b", "1", nil, "lib/b.rb"
      c = util_spec "c", "1", nil, "lib/c.rb"

      install_specs a, b, c

      install_gem a, :install_dir => path
      install_gem b, :install_dir => path
      install_gem c, :install_dir => path

      ENV["GEM_PATH"] = path

      with_rubygems_gemdeps("-") do
        Dir.mkdir "sub1"

        new_PATH = [File.join(path, "bin"), ENV["PATH"]].join(File::PATH_SEPARATOR)
        new_RUBYOPT = "-I#{rubygems_path} -I#{bundler_path}"

        path = File.join @tempdir, "gem.deps.rb"

        File.open path, "w" do |f|
          f.puts "gem 'a'"
        end
        out0 = with_path_and_rubyopt(new_PATH, new_RUBYOPT) do
          IO.popen("foo", :chdir => "sub1", &:read).split(/\n/)
        end

        File.open path, "a" do |f|
          f.puts "gem 'b'"
          f.puts "gem 'c'"
        end
        out = with_path_and_rubyopt(new_PATH, new_RUBYOPT) do
          IO.popen("foo", :chdir => "sub1", &:read).split(/\n/)
        end

        Dir.rmdir "sub1"

        assert_equal ["b-1", "c-1"], out - out0
      end
    end
  end

  def test_use_gemdeps
    with_local_bundler_at(Gem.dir) do
      gem_deps_file = "gem.deps.rb".tap(&Gem::UNTAINT)
      spec = util_spec "a", 1
      install_specs spec

      spec = Gem::Specification.find {|s| s == spec }
      refute spec.activated?

      File.open gem_deps_file, "w" do |io|
        io.write 'gem "a"'
      end

      assert_nil Gem.gemdeps

      Gem.use_gemdeps gem_deps_file

      assert_equal add_bundler_full_name(%W[a-1]), loaded_spec_names
      refute_nil Gem.gemdeps
    end
  end

  def test_use_gemdeps_ENV
    with_local_bundler_at(Gem.dir) do
      with_rubygems_gemdeps(nil) do
        spec = util_spec "a", 1

        refute spec.activated?

        File.open "gem.deps.rb", "w" do |io|
          io.write 'gem "a"'
        end

        Gem.use_gemdeps

        refute spec.activated?
      end
    end
  end

  def test_use_gemdeps_argument_missing
    with_local_bundler_at(Gem.dir) do
      e = assert_raise ArgumentError do
        Gem.use_gemdeps "gem.deps.rb"
      end

      assert_equal "Unable to find gem dependencies file at gem.deps.rb",
                   e.message
    end
  end

  def test_use_gemdeps_argument_missing_match_ENV
    with_local_bundler_at(Gem.dir) do
      with_rubygems_gemdeps("gem.deps.rb") do
        e = assert_raise ArgumentError do
          Gem.use_gemdeps "gem.deps.rb"
        end

        assert_equal "Unable to find gem dependencies file at gem.deps.rb",
                     e.message
      end
    end
  end

  def test_use_gemdeps_automatic
    with_local_bundler_at(Gem.dir) do
      with_rubygems_gemdeps("-") do
        spec = util_spec "a", 1
        install_specs spec
        spec = Gem::Specification.find {|s| s == spec }

        refute spec.activated?

        File.open "Gemfile", "w" do |io|
          io.write 'gem "a"'
        end

        Gem.use_gemdeps

        assert_equal add_bundler_full_name(%W[a-1]), loaded_spec_names
      end
    end
  end

  def test_use_gemdeps_automatic_missing
    with_local_bundler_at(Gem.dir) do
      with_rubygems_gemdeps("-") do
        Gem.use_gemdeps

        assert true # count
      end
    end
  end

  def test_use_gemdeps_disabled
    with_local_bundler_at(Gem.dir) do
      with_rubygems_gemdeps("") do
        spec = util_spec "a", 1

        refute spec.activated?

        File.open "gem.deps.rb", "w" do |io|
          io.write 'gem "a"'
        end

        Gem.use_gemdeps

        refute spec.activated?
      end
    end
  end

  def test_use_gemdeps_missing_gem
    with_local_bundler_at(Gem.dir) do
      with_rubygems_gemdeps("x") do
        File.open "x", "w" do |io|
          io.write 'gem "a"'
        end

        expected = <<-EXPECTED
Could not find gem 'a' in locally installed gems.
You may need to `bundle install` to install missing gems

      EXPECTED

        Gem::Deprecate.skip_during do
          actual_stdout, actual_stderr = capture_output do
            Gem.use_gemdeps
          end
          assert_empty actual_stdout
          assert_equal(expected, actual_stderr)
        end
      end
    end
  end

  def test_use_gemdeps_specific
    with_local_bundler_at(Gem.dir) do
      with_rubygems_gemdeps("x") do
        spec = util_spec "a", 1
        install_specs spec

        spec = Gem::Specification.find {|s| s == spec }
        refute spec.activated?

        File.open "x", "w" do |io|
          io.write 'gem "a"'
        end

        Gem.use_gemdeps

        assert_equal add_bundler_full_name(%W[a-1]), loaded_spec_names
      end
    end
  end

  private

  def add_bundler_full_name(names)
    names << "bundler-#{Bundler::VERSION}".freeze
    names.sort!
    names
  end

  def with_path_and_rubyopt(path_value, rubyopt_value)
    path, ENV["PATH"] = ENV["PATH"], path_value
    rubyopt, ENV["RUBYOPT"] = ENV["RUBYOPT"], rubyopt_value

    yield
  ensure
    ENV["PATH"] = path
    ENV["RUBYOPT"] = rubyopt
  end

  def with_rubygems_gemdeps(value)
    rubygems_gemdeps, ENV["RUBYGEMS_GEMDEPS"] = ENV["RUBYGEMS_GEMDEPS"], value

    yield
  ensure
    ENV["RUBYGEMS_GEMDEPS"] = rubygems_gemdeps
  end

  def with_local_bundler_at(path)
    require "bundler"

    # If bundler gemspec exists, pretend it's installed
    bundler_gemspec = File.expand_path("../../bundler/bundler.gemspec", __dir__)
    if File.exist?(bundler_gemspec)
      target_gemspec_location = "#{path}/specifications/bundler-#{Bundler::VERSION}.gemspec"

      FileUtils.mkdir_p File.dirname(target_gemspec_location)

      File.write target_gemspec_location, Gem::Specification.load(bundler_gemspec).to_ruby_for_cache
    end

    yield
  ensure
    Bundler.reset!
  end
end
