diff --git a/rq/job.py b/rq/job.py index 5238967e..11d45a4d 100644 --- a/rq/job.py +++ b/rq/job.py @@ -179,6 +179,20 @@ class Job(object): conn.hmset(key, obj) + def cancel(self): + """Cancels the given job, which will prevent the job from ever being + ran (or inspected). + + This method merely exists as a high-level API call to cancel jobs + without worrying about the internals required to implement job + cancellation. Technically, this call is (currently) the same as just + deleting the job hash. + """ + self.delete() + + def delete(self): + """Deletes the job hash from Redis.""" + conn.delete(self.key) # Job execution def perform(self): # noqa diff --git a/tests/test_worker.py b/tests/test_worker.py index c450631e..237ab726 100644 --- a/tests/test_worker.py +++ b/tests/test_worker.py @@ -1,3 +1,4 @@ +import os from tests import RQTestCase from tests import testjob, failing_job from tests.helpers import strip_milliseconds @@ -5,6 +6,15 @@ from rq import Queue, Worker from rq.job import Job +SENTINEL_FILE = '/tmp/rq-tests.txt' + + +def create_sentinel(): + # Create some evidence that the job ran + with open(SENTINEL_FILE, 'w') as f: + f.write('Just a sentinel.') + + class TestWorker(RQTestCase): def test_create_worker(self): """Worker creation.""" @@ -83,3 +93,28 @@ class TestWorker(RQTestCase): self.assertEquals(job.enqueued_at, enqueued_at_date) self.assertIsNotNone(job.exc_info) # should contain exc_info + + def test_cancelled_jobs_arent_executed(self): + """Cancelling jobs.""" + try: + # Remove the sentinel if it is leftover from a previous test run + os.remove(SENTINEL_FILE) + except OSError as e: + if e.errno != 2: + raise + + q = Queue() + result = q.enqueue(create_sentinel) + + # Here, we cancel the job, so the sentinel file may not be created + assert q.count == 1 + result.cancel() + assert q.count == 1 + + w = Worker([q]) + w.work(burst=True) + assert q.count == 0 + + # Should not have created evidence of execution + self.assertEquals(os.path.exists(SENTINEL_FILE), False) +