# -*- coding: utf-8 -*-
require 'spec_helper'

describe Tengine::Job::Stoppable do
  include TestCredentialFixture
  include TestServerFixture

  describe :stop do
    context "rjn0011" do
      before do
        builder = Rjn0011NestedForkJobnetBuilder.new
        @ctx = builder.context
        @root = builder.create_actual
        @ctx[:j1100].tap do |j|
          j.killing_signals = ["INT", "HUP", "QUIT", "KILL"]
          j.killing_signal_interval = 30
        end
        @execution = Tengine::Job::Execution.create!({
            :root_jobnet_id => @root.id,
          })
        @mock_event = mock(:event,
          :occurred_at => Time.utc(2011,10,28,0,50))
        @mock_event.stub!(:[]).with(:execution_id).and_return(@execution.id.to_s)
        @signal = Tengine::Job::Signal.new(@mock_event)
      end

      context "強制停止しても何も変更なし" do
        ([:dying, :success, :error, :stuck]).each do |phase_key|
          it "#{phase_key}の場合" do
            @ctx[:j1110].phase_key = phase_key
            expect{
              @ctx[:j1110].stop(@signal)
            }.to_not raise_error
            @ctx[:j1110].phase_key.should == phase_key
          end
        end
      end

      context ":readyならば:initializedに戻す" do
        # 特別ルール「starting直前stop」
        # initializedに戻されたジョブに対して、:readyになる際にtransmitで送信されたイベントを受け取って、
        # activateしようとすると状態は遷移しないが、後続のエッジを実行する。
        # (エッジを実行しようとした際、エッジがclosedならばそのジョブネットのEndに遷移する。)

        it "(ジョブネットに対するstopによって)後続のエッジをcloseしてある場合" do
          t = Time.now.utc
          @mock_event.should_receive(:occurred_at).and_return(t)
          @mock_event.should_receive(:[]).with(:stop_reason).and_return("test stopping")
          [:e6, :e7, :e8, :e9].each{|name| @ctx[name].phase_key = :closing}
          @ctx[:j1110].tap do |j|
            j.phase_key = :ready
            j.executing_pid = nil
            @ctx[:j1100].should_receive(:jobnet_fail).with(@signal)
            j.stop(@signal)
            j.phase_key.should == :initialized
            j.stop_reason.should == "test stopping"
            j.stopped_at.to_time.iso8601.should == t.utc.iso8601
          end
          [:e6, :e7, :e8, :e9].each{|name| @ctx[name].phase_key.should == :closed}
        end

        it "(ジョブを単体で停止する)エッジはcloseしていない場合" do
          t = Time.now.utc
          @mock_event.should_receive(:occurred_at).and_return(t)
          @mock_event.should_receive(:[]).with(:stop_reason).and_return("test stopping")
          @ctx[:j1110].tap do |j|
            j.phase_key = :ready
            j.executing_pid = nil
            @ctx[:j1120].should_receive(:transmit).with(@signal)
            j.stop(@signal)
            j.phase_key.should == :initialized
            j.stop_reason.should == "test stopping"
            j.stopped_at.to_time.iso8601.should == t.utc.iso8601
          end
        end
      end

      context ":startingならば:runningになるのを待って、stopする" do

        it "(ジョブを単体で停止する)エッジはcloseしていない場合" do
          t = Time.now.utc
          @mock_event.should_receive(:occurred_at).and_return(t)
          @mock_event.should_receive(:[]).with(:stop_reason).and_return("test stopping")
          @ctx[:j1110].tap do |j|
            j.phase_key = :starting
            j.executing_pid = nil
            @root.save!
            @pid = "111"
            @root.reload

            mock_ssh = mock(:ssh)
            mock_channel = mock(:channel)
            Net::SSH.should_receive(:start).
              with(test_server1.hostname_or_ipv4,
              an_instance_of(Tengine::Resource::Credential),
              an_instance_of(Hash)).and_yield(mock_ssh)
            mock_ssh.should_receive(:open_channel).and_yield(mock_channel)
            mock_channel.should_receive(:exec) do |*args|
              args.length.should == 1
              args.first.tap do |cmd|
                cmd.should =~ %r<source \/etc\/profile>
                cmd.should =~ /tengine_job_agent_kill #{@pid} 30 INT,HUP,QUIT,KILL/
              end
            end

            idx = 0
            @root.vertex(j.id).stop(@signal) do
              idx += 1
              if idx >= 3 # 3回目のリトライ後にデータが更新され、4回目でループを抜けて強制停止が始まります
                root_dup = @root.class.find(@root.id)
                job = root_dup.vertex(j.id)
                job.executing_pid = @pid
                job.phase_key = :running
                root_dup.save!
              end
            end
            @root.save!
            job = @root.vertex(j.id)
            job.phase_key.should == :dying
            job.stop_reason.should == "test stopping"
            job.stopped_at.to_time.iso8601.should == t.utc.iso8601
            @signal.callback.should_not be_nil
            @signal.callback.call
          end
        end
      end


      shared_examples_for "SSHでtengine_job_agent_killを実行する" do |name, interval, signals|
        it do
          @pid = "111"
          mock_ssh = mock(:ssh)
          mock_channel = mock(:channel)
          Net::SSH.should_receive(:start).
            with(test_server1.hostname_or_ipv4,
            an_instance_of(Tengine::Resource::Credential),
            an_instance_of(Hash)).and_yield(mock_ssh)
          mock_ssh.should_receive(:open_channel).and_yield(mock_channel)
          mock_channel.should_receive(:exec) do |*args|
            args.length.should == 1
            args.first.tap do |cmd|
              cmd.should =~ %r<source \/etc\/profile>
              cmd.should =~ /tengine_job_agent_kill #{@pid} #{interval} #{signals}/
            end
          end
          t = Time.now.utc
          @mock_event.should_receive(:occurred_at).and_return(t)
          @mock_event.should_receive(:[]).with(:stop_reason).and_return("test stopping")
          @ctx[name].tap do |j|
            j.phase_key = :running
            j.executing_pid = @pid
            j.stop(@signal)
            j.phase_key.should == :dying
            j.stop_reason.should == "test stopping"
            j.stopped_at.to_time.iso8601.should == t.utc.iso8601
          end
          @signal.callback.should_not be_nil
          @signal.callback.call
        end
      end

      default_interval = Tengine::Job::Killing::DEFAULT_KILLING_SIGNAL_INTERVAL
      [
        [:j1110, 30, "INT,HUP,QUIT,KILL"],
        [:j1121, 30, "INT,HUP,QUIT,KILL"],
        [:j1131, 30, "INT,HUP,QUIT,KILL"],
        [:j1140, 30, "INT,HUP,QUIT,KILL"],
        [:j1200, default_interval, "KILL"],
        [:j1310, default_interval, "KILL"],
      ].each do |args|
        it_should_behave_like "SSHでtengine_job_agent_killを実行する", *args
      end

    end
  end
end