require "isolate/test"

class Gem::Specification
  attr_writer :gems_dir, :gem_dir
end

class TestIsolateSandbox < Isolate::Test
  WITH_HOE = "test/fixtures/with-hoe"
  SYSTEM   = "test/fixtures/system"
  SYSTEM2  = "test/fixtures/system_redundant"

  def setup
    @sandbox = sandbox
    super
  end

  def test_activate
    @sandbox = sandbox :path => WITH_HOE

    refute_loaded_spec "hoe"
    @sandbox.gem "hoe"

    result = @sandbox.activate
    assert_equal @sandbox, result
    assert_loaded_spec "hoe"
  end

  def test_activate_environment_explicit
    @sandbox = sandbox :path => WITH_HOE

    @sandbox.gem "rubyforge"
    @sandbox.environment(:borg) { gem "hoe" }
    @sandbox.activate :borg

    assert_loaded_spec "hoe"
    assert_loaded_spec "rubyforge"
  end

  def test_activate_environment_implicit
    s = sandbox :path => WITH_HOE

    s.gem "rubyforge"
    s.environment(:borg) { gem "hoe" }

    s.activate
    refute_loaded_spec "hoe"
    assert_loaded_spec "rubyforge"
  end

  def test_activate_install
    s = sandbox :path => WITH_HOE, :install => true
    s.gem "foo"

    # rescuing because activate, well, actually tries to activate
    begin; s.activate; rescue Gem::LoadError; end

    assert_equal ["foo", Gem::Requirement.default],
      Gem::DependencyInstaller.value.shift
  end

  def test_activate_install_with_different_environment
    s = sandbox :path => WITH_HOE, :install => true
    s.environment(:borg) { gem 'foo' }

    # This won't crash because it should not install or activate the env
    s.activate
    refute_equal 'foo', Gem::DependencyInstaller.value.shift[0]
  end

  # TODO: cleanup with 2 versions of same gem, 1 activated
  # TODO: install with 1 older version, 1 new gem to be installed

  def test_cleanup_all
    with_env_setup do
      s = sandbox :path => WITH_HOE, :install => true, :cleanup => true

      assert_silent do
        s.activate # no gems on purpose
      end

      expected = [["hoe",       "2.3.3", WITH_HOE],
                  ["rake",      "0.8.7", WITH_HOE],
                  ["rubyforge", "1.0.4", WITH_HOE]]

      assert_equal expected, Gem::Uninstaller.value
    end
  end

  def test_cleanup_all_system
    with_env_setup do
      s = sandbox(:path => WITH_HOE, :install => true, :cleanup => true,
                  :system => true)

      assert_silent do
        s.activate # no gems on purpose
      end

      expected = [["hoe",       "2.3.3", WITH_HOE],
                  ["rake",      "0.8.7", WITH_HOE],
                  ["rubyforge", "1.0.4", WITH_HOE]]

      assert_equal expected, Gem::Uninstaller.value.sort
    end
  end

  def test_cleanup_partial
    with_env_setup do
      s = sandbox :path => WITH_HOE, :install => true, :cleanup => true

      s.gem "rake", "0.8.7"

      assert_silent do
        s.activate
      end

      expected = [["hoe",       "2.3.3", WITH_HOE],
                  ["rubyforge", "1.0.4", WITH_HOE]]

      assert_equal expected, Gem::Uninstaller.value
    end
  end

  def test_cleanup_partial_system
    with_env_setup do
      s = sandbox(:path => WITH_HOE, :install => true, :cleanup => true,
                  :system => true)

      s.gem "rake", "0.8.7"

      assert_silent do
        s.activate
      end

      expected = [["hoe",       "2.3.3", WITH_HOE],
                  ["rubyforge", "1.0.4", WITH_HOE]]

      assert_equal expected, Gem::Uninstaller.value.sort
    end
  end

  def test_cleanup_redundant
    skip "This test is now invalid as rubygems de-dupes internally. Need to figure out what to do"

    with_env_setup SYSTEM2 do
      s = sandbox(:path => WITH_HOE, :install => true, :cleanup => true,
                  :system => true)

      s.gem "rake", "0.8.7"

      assert_silent do
        s.activate
      end

      expected = [["hoe",       "2.3.3", WITH_HOE],
                  ["rake",      "0.8.7", WITH_HOE],
                  ["rubyforge", "1.0.4", WITH_HOE]]

      assert_equal expected, Gem::Uninstaller.value.sort
    end
  end

  def test_disable
    home, path, bin = ENV.values_at "GEM_HOME", "GEM_PATH", "PATH"
    # load_path  = $LOAD_PATH.dup

    @sandbox.enable

    refute_equal home, ENV["GEM_HOME"]
    refute_equal path, ENV["GEM_PATH"]
    refute_equal bin,  ENV["PATH"]

    # HACK: I have no idea the original intent of this assertion, but
    # it is useless with my installed gems. Either this test needs to
    # use an actual SEPARATE sandbox, or it fails.

    # refute_equal load_path, $LOAD_PATH

    result = @sandbox.disable
    assert_same @sandbox, result

    assert_equal home, ENV["GEM_HOME"]
    assert_equal path, ENV["GEM_PATH"]
    assert_equal bin,  ENV["PATH"]
    # assert_equal load_path, $LOAD_PATH
  end

  def test_enable
    refute_empty Gem.find_files("rake.rb"),
      "There's a rake.rb somewhere in the current env."

    assert_same @sandbox, @sandbox.enable

    assert_equal @sandbox.path, ENV["GEM_PATH"]
    assert_equal @sandbox.path, ENV["GEM_HOME"]
    assert ENV["PATH"].include?(File.join(@sandbox.path, "bin")), "in path"

    assert_equal [], Gem.find_files("hoe.rb"),
      "Can't find hoe.rb now, 'cause we're activated!"

    assert_empty Gem.loaded_specs
    assert_equal [@sandbox.path], Gem.path
  end

  def test_enable_idempotent_path_env
    bin  = File.join @sandbox.path, "bin"
    path = ENV["PATH"] = [bin, ENV["PATH"]].join(File::PATH_SEPARATOR)

    @sandbox.enable
    assert_equal path, ENV["PATH"]
  end

  def test_idempotent_rubyopt_env
    assert_nil ENV["RUBYOPT"], "sanity check to make sure ENV isn't infecting"

    @sandbox.enable
    rubyopt = ENV["RUBYOPT"]
    @sandbox.disable

    refute_equal rubyopt, ENV["RUBYOPT"]

    ENV["RUBYOPT"] = rubyopt
    @sandbox.enable
    assert_equal rubyopt, ENV["RUBYOPT"]
  end

  def test_environment
    @sandbox.gem "none"

    @sandbox.environment "test", "ci" do
      gem "test-ci"

      environment "production" do
        gem "test-ci-production"
      end
    end

    none, test_ci, test_ci_production = @sandbox.entries

    assert_equal [], none.environments
    assert_equal %w(test ci), test_ci.environments
    assert_equal %w(test ci production), test_ci_production.environments
  end

  def test_gem
    g = @sandbox.gem "foo"
    assert_includes @sandbox.entries, g

    assert_equal "foo", g.name
    assert_equal Gem::Requirement.create(">= 0"), g.requirement
  end

  def test_gem_multi_calls
    g  = @sandbox.gem "foo"
    g2 = @sandbox.gem "foo", :foo => :bar

    @sandbox.gem "foo", :bar => :baz

    assert_same g, g2
    assert_equal :bar, g.options[:foo]
    assert_equal :baz, g.options[:bar]

    @sandbox.gem "foo", "> 1.7"
    assert_equal Gem::Requirement.new("> 1.7"), g.requirement

    @sandbox.environment :corge do
      gem "foo"
    end

    @sandbox.environment :plurgh do
      gem "foo"
    end

    assert_equal %w(corge plurgh), g.environments
  end

  def test_gem_multi_requirements
    g = @sandbox.gem "foo", "= 1.0", "< 2.0"
    assert_equal Gem::Requirement.create(["= 1.0", "< 2.0"]), g.requirement
  end

  def test_gem_options
    g = @sandbox.gem "foo", :source => "somewhere"
    assert_equal "somewhere", g.options[:source]
  end

  def test_initialize_defaults
    s = Isolate::Sandbox.new

    assert_equal [], s.entries
    assert_equal [], s.environments
    assert_match(/tmp\/test/, s.path)

    assert s.cleanup?
    assert s.install?
    assert s.system?
    assert s.verbose?
    refute s.multiruby?
  end

  def test_initialize_override_defaults
    s = Isolate::Sandbox.new(:path      => "x",
                             :cleanup   => false,
                             :install   => false,
                             :system    => false,
                             :verbose   => false,
                             :multiruby => false)

    assert_equal File.expand_path("x"), s.path

    refute s.cleanup?
    refute s.install?
    refute s.system?
    refute s.verbose?
    refute s.multiruby?
  end

  # First the specifically requested file, then the block (if given),
  # THEN the local override file (if it exists).

  def test_initialize_file_and_block
    s = sandbox :file => "test/fixtures/override.rb" do
      environment :foo do
        gem "monkey", "2.0", :args => "--panic"
      end
    end

    monkey = s.entries.first

    assert_equal %w(foo bar), monkey.environments
    assert_equal "--asplode", monkey.options[:args]
    assert_equal Gem::Requirement.new("2.0"), monkey.requirement
  end

  def test_initialize__name
    exp = File.expand_path "~/.gem/repos/project_name"

    outer = self

    s = sandbox :name => "project_name" do |inner|
      outer.assert_equal exp, inner.options[:path]
    end

    assert_equal exp, s.options[:path]
  end

  def test_options
    @sandbox.options :hello => :monkey
    assert_equal :monkey, @sandbox.options[:hello]
  end

  def test_path
    s = sandbox :multiruby => false, :path => "tmp/foo"
    assert_equal File.expand_path("tmp/foo"), s.path

    v = "%s-%s" % [Gem.ruby_engine, RbConfig::CONFIG["ruby_version"]]
    p = File.expand_path("tmp/test/#{v}")

    s = sandbox :multiruby => true
    assert_equal p, s.path

    s = sandbox :path => "tmp/test/#{v}", :multiruby => false
    assert_equal p, s.path
  end

  def assert_loaded_spec name
    assert Gem.loaded_specs[name],
      "#{name} is a loaded gemspec, and it shouldn't be!"
  end

  def refute_loaded_spec name
    refute Gem.loaded_specs[name],
      "#{name} is NOT a loaded gemspec, and it should be!"
  end

  def sandbox opts = {}, &block
    defaults = {
      :install   => false,
      :system    => false,
      :verbose   => false,
      :multiruby => false
    }

    Isolate::Sandbox.new defaults.merge(opts), &block
  end

  def with_env_setup path = SYSTEM
    old_gem_path = ENV["GEM_PATH"]
    old_gem_home = ENV["GEM_HOME"]
    ENV["GEM_HOME"] = "test/fixtures/with-hoe"
    ENV["GEM_PATH"] = path
    Gem.refresh

    yield
  ensure
    ENV["GEM_PATH"] = old_gem_path
    ENV["GEM_HOME"] = old_gem_home
  end
end
