require File.expand_path('../../test_helper', __FILE__)
require File.expand_path('../../fixtures/test_forking_jobs', __FILE__)

describe "Backburner::Workers::Forking module" do

  before do
    Backburner.default_queues.clear
    @worker_class = Backburner::Workers::Forking
  end

  describe "for prepare method" do
    it "should make tube names array always unique to avoid duplication" do
      worker = @worker_class.new(["foo", "demo.test.foo"])
      capture_stdout { worker.prepare }
      assert_equal ["demo.test.foo"], worker.tube_names
    end

    it "should watch specified tubes" do
      worker = @worker_class.new(["foo", "bar"])
      out = capture_stdout { worker.prepare }
      assert_equal ["demo.test.foo", "demo.test.bar"], worker.tube_names
      assert_same_elements ["demo.test.foo", "demo.test.bar"], worker.connection.tubes.watched.map(&:name)
      assert_match(/demo\.test\.foo/, out)
    end # multiple

    it "should watch single tube" do
      worker = @worker_class.new("foo")
      out = capture_stdout { worker.prepare }
      assert_equal ["demo.test.foo"], worker.tube_names
      assert_same_elements ["demo.test.foo"], worker.connection.tubes.watched.map(&:name)
      assert_match(/demo\.test\.foo/, out)
    end # single

    it "should respect default_queues settings" do
      Backburner.default_queues.concat(["foo", "bar"])
      worker = @worker_class.new
      out = capture_stdout { worker.prepare }
      assert_equal ["demo.test.foo", "demo.test.bar"], worker.tube_names
      assert_same_elements ["demo.test.foo", "demo.test.bar"], worker.connection.tubes.watched.map(&:name)
      assert_match(/demo\.test\.foo/, out)
    end

    it "should assign based on all tubes" do
      @worker_class.any_instance.expects(:all_existing_queues).once.returns("bar")
      worker = @worker_class.new
      out = capture_stdout { worker.prepare }
      assert_equal ["demo.test.bar"], worker.tube_names
      assert_same_elements ["demo.test.bar"], worker.connection.tubes.watched.map(&:name)
      assert_match(/demo\.test\.bar/, out)
    end # all assign

    it "should properly retrieve all tubes" do
      worker = @worker_class.new
      out = capture_stdout { worker.prepare }
      assert_contains worker.tube_names, "demo.test.backburner-jobs"
      assert_contains worker.connection.tubes.watched.map(&:name), "demo.test.backburner-jobs"
      assert_match(/demo\.test\.backburner-jobs/, out)
    end # all read
  end # prepare

  describe "for fork_one_job method" do

    it "should fork, work job, and exit" do
      clear_jobs!("bar.foo")
      @worker_class.enqueue TestJobForking, [1, 2], :queue => "bar.foo"

      fake_pid = 45
      Process.expects(:fork).returns(fake_pid).yields
      @worker_class.any_instance.expects(:work_one_job)
      @worker_class.any_instance.expects(:coolest_exit)
      Process.expects(:wait).with(fake_pid)

      silenced(2) do
        worker = @worker_class.new('bar.foo')
        worker.prepare
        worker.fork_one_job
      end
    end

  end # fork_one_job

  describe "practical tests" do

    before do
      @templogger = Templogger.new('/tmp')
      Backburner.configure { |config| config.logger = @templogger.logger }
      $worker_test_count = 0
      $worker_success = false
      $worker_raise   = false
      clear_jobs!('response')
      clear_jobs!('bar.foo.1', 'bar.foo.2', 'bar.foo.3', 'bar.foo.4', 'bar.foo.5')
      silenced do
        @response_worker = @worker_class.new('response')
      end
    end

    after do
      @templogger.close
      Backburner.configuration.logger = nil
      clear_jobs!('response')
      clear_jobs!('bar.foo.1', 'bar.foo.2', 'bar.foo.3', 'bar.foo.4', 'bar.foo.5')
    end


    it "should work an enqueued job" do
      @worker = @worker_class.new('bar.foo.1')
      @worker_class.enqueue TestJobForking, [1, 2], :queue => "bar.foo.1"
      @worker.prepare
      silenced(2) do
        @worker.fork_one_job
        @templogger.wait_for_match(/Completed TestJobFork/m)
        @response_worker.prepare
        @response_worker.work_one_job
      end
      assert_equal 3, $worker_test_count
    end # enqueue

    it "should work for an async job" do
      @worker = @worker_class.new('bar.foo.2')
      TestAsyncJobForking.async(:queue => 'bar.foo.2').foo(3, 5)
      @worker.prepare
      silenced(4) do
        @worker.fork_one_job
        @templogger.wait_for_match(/Completed TestAsyncJobFork/m)
        @response_worker.prepare
        @response_worker.work_one_job
      end
      assert_equal 15, $worker_test_count
    end # async

    it "should fail quietly if there's an argument error" do
      Backburner.configure { |config| config.max_job_retries = 0 }
      @worker = @worker_class.new('bar.foo.3')
      @worker_class.enqueue TestJobForking, ["bam", "foo", "bar"], :queue => "bar.foo.3"
      @worker.prepare
      silenced(5) do
        @worker.fork_one_job
        @templogger.wait_for_match(/Finished TestJobFork.*attempt 1 of 1/m)
      end
      assert_match(/Exception ArgumentError/, @templogger.body)
      assert_equal 0, $worker_test_count
    end # fail, argument

    it "should support retrying jobs and burying" do
      Backburner.configure { |config| config.max_job_retries = 1; config.retry_delay = 0 }
      @worker = @worker_class.new('bar.foo.4')
      @worker_class.enqueue TestRetryJobForking, ["bam", "foo"], :queue => 'bar.foo.4'
      @worker.prepare
      silenced(4) do
        2.times do
          $worker_test_count += 1
          @worker.fork_one_job
        end
        @templogger.wait_for_match(/Finished TestRetryJobFork.*attempt 2 of 2/m)
        @response_worker.prepare
        2.times { @response_worker.work_one_job }
      end
      assert_equal 4, $worker_test_count
      assert_equal false, $worker_success
    end # retry, bury

    it "should support retrying jobs and succeeds" do
      Backburner.configure { |config| config.max_job_retries = 2; config.retry_delay = 0 }
      @worker = @worker_class.new('bar.foo.5')
      @worker_class.enqueue TestRetryJobForking, ["bam", "foo"], :queue => 'bar.foo.5'
      @worker.prepare
      silenced(4) do
        3.times do
          $worker_test_count += 1
          @worker.fork_one_job
        end
        @templogger.wait_for_match(/Completed TestRetryJobFork/m)
        @response_worker.prepare
        3.times { @response_worker.work_one_job }
      end
      assert_equal 6, $worker_test_count
      assert_equal true, $worker_success
    end # retrying, succeeds

  end # practical tests
end
