Permalink
Browse files

Resolve #202 and add a unit test and acceptance test.

  • Loading branch information...
1 parent 9de44d4 commit b7e33acbc2e1ed54ba16a2792d5176abff585e4f @dnephin dnephin committed Feb 20, 2013
Showing with 88 additions and 42 deletions.
  1. +45 −34 tests/core/jobrun_test.py
  2. +36 −0 tests/trond_test.py
  3. +7 −8 tron/core/jobrun.py
View
79 tests/core/jobrun_test.py
@@ -1,11 +1,12 @@
import datetime
+import mock
import pytz
from testify import TestCase, setup, assert_equal
from testify.assertions import assert_in
from tests.assertions import assert_length, assert_raises, assert_call
from tests.mocks import MockNode
from tron.core import jobrun, actionrun
-from tests.testingutils import Turtle
+from tests.testingutils import Turtle, autospec_method
from tests import testingutils
@@ -38,11 +39,10 @@ def setup_jobrun(self):
self.job_run = jobrun.JobRun('jobname', 7, self.run_time, node,
action_runs=Turtle(
action_runs_with_cleanup=[],
- get_startable_action_runs=lambda: [],
- is_active=False
- ))
+ get_startable_action_runs=lambda: []))
self.job_run.watch = Turtle()
self.job_run.notify = Turtle()
+ self.action_run = mock.create_autospec(actionrun.ActionRun)
def test__init__(self):
assert_equal(self.job_run.job_name, 'jobname')
@@ -184,50 +184,61 @@ def failing():
assert_equal(started_runs, [])
def test_handler_not_end_state_event(self):
- self.job_run.finalize = Turtle()
- self.job_run.handler(None, actionrun.ActionRun.STATE_STARTING)
- assert_length(self.job_run.finalize.calls, 0)
+ autospec_method(self.job_run.finalize)
+ autospec_method(self.job_run._start_action_runs)
+ self.action_run.is_done = False
+ self.job_run.handler(self.action_run, mock.Mock())
+ assert not self.job_run.finalize.mock_calls
+ assert not self.job_run._start_action_runs.mock_calls
def test_handler_with_startable(self):
- self.job_run.action_runs.get_startable_action_runs = lambda: True
- startable_run = Turtle()
+ startable_run = mock.create_autospec(actionrun.ActionRun)
self.job_run.action_runs.get_startable_action_runs = lambda: [startable_run]
- self.job_run.finalize = Turtle()
+ autospec_method(self.job_run.finalize)
+ self.action_run.is_broken = False
- self.job_run.handler(None, actionrun.ActionRun.STATE_SUCCEEDED)
+ self.job_run.handler(self.action_run, mock.Mock())
assert_call(self.job_run.notify, 0, self.job_run.NOTIFY_STATE_CHANGED)
- assert_call(startable_run.start, 0)
- assert_length(self.job_run.finalize.calls, 0)
+ startable_run.start.assert_called_with()
+ assert not self.job_run.finalize.mock_calls
- def test_handler_not_done(self):
+ def test_handler_is_active(self):
self.job_run.action_runs.is_active = True
- self.job_run._start_action_runs = lambda: []
- self.job_run.finalize = Turtle()
-
- self.job_run.handler(None, actionrun.ActionRun.STATE_SUCCEEDED)
- assert_length(self.job_run.finalize.calls, 0)
+ autospec_method(self.job_run._start_action_runs, return_value=[])
+ autospec_method(self.job_run.finalize)
+ self.job_run.handler(self.action_run, mock.Mock())
+ assert not self.job_run.finalize.mock_calls
def test_handler_finished_without_cleanup(self):
+ self.job_run.action_runs.is_active = False
+ self.job_run.action_runs.is_scheduled = False
self.job_run.action_runs.cleanup_action_run = None
- self.job_run.finalize = Turtle()
-
- self.job_run.handler(None, actionrun.ActionRun.STATE_SUCCEEDED)
- assert_call(self.job_run.finalize, 0)
+ autospec_method(self.job_run.finalize)
+ self.job_run.handler(self.action_run, mock.Mock())
+ self.job_run.finalize.assert_called_with()
def test_handler_finished_with_cleanup_done(self):
- self.job_run.action_runs.cleanup_action_run = Turtle(is_done=True)
- self.job_run.finalize = Turtle()
-
- self.job_run.handler(None, actionrun.ActionRun.STATE_SUCCEEDED)
- assert_call(self.job_run.finalize, 0)
+ self.job_run.action_runs.is_active = False
+ self.job_run.action_runs.is_scheduled = False
+ self.job_run.action_runs.cleanup_action_run = mock.Mock(is_done=True)
+ autospec_method(self.job_run.finalize)
+ self.job_run.handler(self.action_run, mock.Mock())
+ self.job_run.finalize.assert_called_with()
def test_handler_finished_with_cleanup(self):
- self.job_run.action_runs.cleanup_action_run = Turtle(is_done=False)
- self.job_run.finalize = Turtle()
-
- self.job_run.handler(None, actionrun.ActionRun.STATE_SUCCEEDED)
- assert_length(self.job_run.finalize.calls, 0)
- assert_call(self.job_run.action_runs.cleanup_action_run.start, 0)
+ self.job_run.action_runs.is_active = False
+ self.job_run.action_runs.is_scheduled = False
+ self.job_run.action_runs.cleanup_action_run = mock.Mock(is_done=False)
+ autospec_method(self.job_run.finalize)
+ self.job_run.handler(self.action_run, mock.Mock())
+ assert not self.job_run.finalize.mock_calls
+ self.job_run.action_runs.cleanup_action_run.start.assert_called_with()
+
+ def test_handler_action_run_cancelled(self):
+ self.action_run.is_broken = True
+ autospec_method(self.job_run._start_action_runs)
+ self.job_run.handler(self.action_run, mock.Mock())
+ assert not self.job_run._start_action_runs.mock_calls
def test_state(self):
assert_equal(self.job_run.state, actionrun.ActionRun.STATE_SUCCEEDED)
View
36 tests/trond_test.py
@@ -339,3 +339,39 @@ def wait_on_service_start():
def wait_on_service_stop():
return client.service(service_url)['state'] == 'DOWN'
sandbox.wait_on_sandbox(wait_on_service_stop)
+
+ def test_job_queueing_false_with_overlap(self):
+ """Test that a job that has queueing false properly cancels an
+ overlapping job run.
+ """
+ config = BASIC_CONFIG + dedent("""
+ jobs:
+ - name: "cancel_overlap"
+ schedule: "interval 1s"
+ queueing: False
+ node: local
+ actions:
+ - name: "do_something"
+ command: "sleep 3s"
+ - name: "do_other"
+ command: "sleep 3s"
+ cleanup_action:
+ command: "echo done"
+ """)
+ client = self.sandbox.client
+ self.sandbox.save_config(config)
+ self.sandbox.trond()
+ job_run = client.get_url('MASTER.cancel_overlap')
+ job_run_url = client.get_url('MASTER.cancel_overlap.1')
+
+ def wait_on_job_schedule():
+ return len(client.job(job_run)['runs']) == 2
+ sandbox.wait_on_sandbox(wait_on_job_schedule)
+
+ def wait_on_job_cancel():
+ return client.job(job_run_url)['state'] == 'CANC'
+ sandbox.wait_on_sandbox(wait_on_job_cancel)
+
+ action_run_states = [action_run['state'] for action_run in
+ client.job(job_run_url)['runs']]
+ assert_equal(action_run_states, ['CANC'] * len(action_run_states))
View
15 tron/core/jobrun.py
@@ -184,8 +184,7 @@ def _do_start(self):
log.info("Starting JobRun %s", self.id)
self.action_runs.ready()
- started_runs = self._start_action_runs()
- if any(started_runs):
+ if any(self._start_action_runs()):
self.notify(self.EVENT_STARTED)
return True
@@ -203,28 +202,28 @@ def _start_action_runs(self):
return started_actions
- def handle_action_run_state_change(self, _action_run, event):
+ def handle_action_run_state_change(self, action_run, _):
"""Handle events triggered by JobRuns."""
# propagate all state changes (from action runs) up to state serializer
self.notify(self.NOTIFY_STATE_CHANGED)
- if event not in ActionRun.END_STATES:
+ if not action_run.is_done:
return
- started_actions = self._start_action_runs()
- if any(started_actions):
+ if not action_run.is_broken and any(self._start_action_runs()):
log.info("Action runs started for %s." % self)
return
- if self.action_runs.is_active:
- log.info("%s still has running actions." % self)
+ if self.action_runs.is_active or self.action_runs.is_scheduled:
+ log.info("%s still has running or scheduled actions." % self)
return
# If we can't make any progress, we're done
cleanup_run = self.action_runs.cleanup_action_run
if not cleanup_run or cleanup_run.is_done:
return self.finalize()
+ # TODO: remove in (0.6), start() no longer raises an exception
# When a job is being disabled, or the daemon is being shut down a bunch
# of ActionRuns will be cancelled/failed. This would cause cleanup
# action to be triggered more then once. Guard against that.

0 comments on commit b7e33ac

Please sign in to comment.