import times from datetime import datetime from tests import RQTestCase from tests.fixtures import some_calculation, say_hello from tests.helpers import strip_milliseconds from cPickle import loads from rq.job import Job from rq.exceptions import NoSuchJobError, UnpickleError class TestJob(RQTestCase): def test_create_empty_job(self): """Creation of new empty jobs.""" job = Job() # Jobs have a random UUID and a creation date self.assertIsNotNone(job.id) self.assertIsNotNone(job.created_at) # ...and nothing else self.assertIsNone(job.func, None) self.assertIsNone(job.args, None) self.assertIsNone(job.kwargs, None) self.assertIsNone(job.origin, None) self.assertIsNone(job.enqueued_at, None) self.assertIsNone(job.ended_at, None) self.assertIsNone(job.return_value, None) self.assertIsNone(job.exc_info, None) def test_create_typical_job(self): """Creation of jobs for function calls.""" job = Job.create(some_calculation, 3, 4, z=2) # Jobs have a random UUID self.assertIsNotNone(job.id) self.assertIsNotNone(job.created_at) self.assertIsNotNone(job.description) # Job data is set... self.assertEquals(job.func, some_calculation) self.assertEquals(job.args, (3, 4)) self.assertEquals(job.kwargs, {'z': 2}) # ...but metadata is not self.assertIsNone(job.origin) self.assertIsNone(job.enqueued_at) self.assertIsNone(job.return_value) def test_save(self): # noqa """Storing jobs.""" job = Job.create(some_calculation, 3, 4, z=2) # Saving creates a Redis hash self.assertEquals(self.testconn.exists(job.key), False) job.save() self.assertEquals(self.testconn.type(job.key), 'hash') # Saving writes pickled job data unpickled_data = loads(self.testconn.hget(job.key, 'data')) self.assertEquals(unpickled_data[0], 'tests.fixtures.some_calculation') def test_fetch(self): """Fetching jobs.""" # Prepare test self.testconn.hset('rq:job:some_id', 'data', "(S'tests.fixtures.some_calculation'\np0\n(I3\nI4\ntp1\n(dp2\nS'z'\np3\nI2\nstp4\n.") # noqa self.testconn.hset('rq:job:some_id', 'created_at', "2012-02-07 22:13:24+0000") # Fetch returns a job job = Job.fetch('some_id') self.assertEquals(job.id, 'some_id') self.assertEquals(job.func_name, 'tests.fixtures.some_calculation') self.assertEquals(job.args, (3, 4)) self.assertEquals(job.kwargs, dict(z=2)) self.assertEquals(job.created_at, datetime(2012, 2, 7, 22, 13, 24)) def test_persistence_of_empty_jobs(self): # noqa """Storing empty jobs.""" job = Job() job.save() expected_date = strip_milliseconds(job.created_at) stored_date = self.testconn.hget(job.key, 'created_at') self.assertEquals( times.to_universal(stored_date), expected_date) # ... and no other keys are stored self.assertItemsEqual( self.testconn.hkeys(job.key), ['created_at']) def test_persistence_of_typical_jobs(self): """Storing typical jobs.""" job = Job.create(some_calculation, 3, 4, z=2) job.save() expected_date = strip_milliseconds(job.created_at) stored_date = self.testconn.hget(job.key, 'created_at') self.assertEquals( times.to_universal(stored_date), expected_date) # ... and no other keys are stored self.assertItemsEqual( self.testconn.hkeys(job.key), ['created_at', 'data', 'description']) def test_store_then_fetch(self): """Store, then fetch.""" job = Job.create(some_calculation, 3, 4, z=2) job.save() job2 = Job.fetch(job.id) self.assertEquals(job.func, job2.func) self.assertEquals(job.args, job2.args) self.assertEquals(job.kwargs, job2.kwargs) # Mathematical equation self.assertEquals(job, job2) def test_fetching_can_fail(self): """Fetching fails for non-existing jobs.""" with self.assertRaises(NoSuchJobError): Job.fetch('b4a44d44-da16-4620-90a6-798e8cd72ca0') def test_fetching_unreadable_data(self): """Fetching fails on unreadable data.""" # Set up job = Job.create(some_calculation, 3, 4, z=2) job.save() # Just replace the data hkey with some random noise self.testconn.hset(job.key, 'data', 'this is no pickle string') with self.assertRaises(UnpickleError): job.refresh() def test_job_is_unimportable(self): """Jobs that cannot be imported throw exception on access.""" job = Job.create(say_hello, 'Lionel') job.save() # Now slightly modify the job to make it unimportable (this is # equivalent to a worker not having the most up-to-date source code # and unable to import the function) data = self.testconn.hget(job.key, 'data') unimportable_data = data.replace('say_hello', 'shut_up') self.testconn.hset(job.key, 'data', unimportable_data) job.refresh() with self.assertRaises(AttributeError): job.func # accessing the func property should fail