# frozen_string_literal: true

require_relative "abstract_unit"

class ForkTrackerTest < ActiveSupport::TestCase
  def test_object_fork
    read, write = IO.pipe
    called = false

    handler = ActiveSupport::ForkTracker.after_fork do
      called = true
      write.write "forked"
    end

    assert_not respond_to?(:fork)
    pid = fork do
      read.close
      write.close
      exit!
    end

    write.close

    Process.waitpid(pid)
    assert_equal "forked", read.read
    read.close

    assert_not called
  ensure
    ActiveSupport::ForkTracker.unregister(handler)
  end

  def test_object_fork_without_block
    read, write = IO.pipe
    called = false

    handler = ActiveSupport::ForkTracker.after_fork do
      called = true
      write.write "forked"
    end

    if pid = fork
      write.close
      Process.waitpid(pid)
      assert_equal "forked", read.read
      read.close
      assert_not called
    else
      read.close
      write.close
      exit!
    end
  ensure
    ActiveSupport::ForkTracker.unregister(handler)
  end

  def test_process_fork
    read, write = IO.pipe
    called = false

    handler = ActiveSupport::ForkTracker.after_fork do
      called = true
      write.write "forked"
    end

    pid = Process.fork do
      read.close
      write.close
      exit!
    end

    write.close

    Process.waitpid(pid)
    assert_equal "forked", read.read
    read.close
    assert_not called
  ensure
    ActiveSupport::ForkTracker.unregister(handler)
  end

  def test_process_fork_without_block
    read, write = IO.pipe
    called = false

    handler = ActiveSupport::ForkTracker.after_fork do
      called = true
      write.write "forked"
    end

    if pid = Process.fork
      write.close
      Process.waitpid(pid)
      assert_equal "forked", read.read
      read.close
      assert_not called
    else
      read.close
      write.close
      exit!
    end
  ensure
    ActiveSupport::ForkTracker.unregister(handler)
  end

  def test_kernel_fork
    read, write = IO.pipe
    called = false

    handler = ActiveSupport::ForkTracker.after_fork do
      called = true
      write.write "forked"
    end

    pid = Kernel.fork do
      read.close
      write.close
      exit!
    end

    write.close

    Process.waitpid(pid)
    assert_equal "forked", read.read
    read.close
    assert_not called
  ensure
    ActiveSupport::ForkTracker.unregister(handler)
  end

  def test_kernel_fork_without_block
    read, write = IO.pipe
    called = false

    handler = ActiveSupport::ForkTracker.after_fork do
      called = true
      write.write "forked"
    end

    if pid = Kernel.fork
      write.close
      Process.waitpid(pid)
      assert_equal "forked", read.read
      read.close
      assert_not called
    else
      read.close
      write.close
      exit!
    end
  ensure
    ActiveSupport::ForkTracker.unregister(handler)
  end

  def test_check
    count = 0
    handler = ActiveSupport::ForkTracker.after_fork { count += 1 }

    assert_no_difference -> { count } do
      3.times { ActiveSupport::ForkTracker.check! }
    end

    Process.stub(:pid, Process.pid + 1) do
      assert_difference -> { count }, +1 do
        3.times { ActiveSupport::ForkTracker.check! }
      end
    end

    assert_difference -> { count }, +1 do
      3.times { ActiveSupport::ForkTracker.check! }
    end
  ensure
    ActiveSupport::ForkTracker.unregister(handler)
  end

  def test_basic_object_with_kernel_fork
    read, write = IO.pipe
    called = false

    handler = ActiveSupport::ForkTracker.after_fork do
      called = true
      write.write "forked"
    end

    klass = Class.new(BasicObject) do
      include ::Kernel
      def fark(&block)
        fork(&block)
      end
    end

    object = klass.new
    assert_not object.respond_to?(:fork)
    pid = object.fark do
      read.close
      write.close
      exit!
    end

    write.close

    Process.waitpid(pid)
    assert_equal "forked", read.read
    read.close

    assert_not called
  ensure
    ActiveSupport::ForkTracker.unregister(handler)
  end
end if Process.respond_to?(:fork)
