test_task.py 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870
  1. from celery.tests.utils import unittest
  2. from celery.tests.utils import StringIO
  3. from datetime import datetime, timedelta
  4. from pyparsing import ParseException
  5. from celery import task
  6. from celery.app import app_or_default
  7. from celery.task import task as task_dec
  8. from celery.exceptions import RetryTaskError
  9. from celery.execute import send_task
  10. from celery.result import EagerResult
  11. from celery.task.schedules import crontab, crontab_parser
  12. from celery.utils import timeutils
  13. from celery.utils import gen_unique_id
  14. from celery.utils.functional import wraps
  15. from celery.utils.timeutils import parse_iso8601
  16. from celery.tests.utils import with_eager_tasks
  17. def return_True(*args, **kwargs):
  18. # Task run functions can't be closures/lambdas, as they're pickled.
  19. return True
  20. return_True_task = task_dec()(return_True)
  21. def raise_exception(self, **kwargs):
  22. raise Exception("%s error" % self.__class__)
  23. class MockApplyTask(task.Task):
  24. def run(self, x, y):
  25. return x * y
  26. @classmethod
  27. def apply_async(self, *args, **kwargs):
  28. pass
  29. class IncrementCounterTask(task.Task):
  30. name = "c.unittest.increment_counter_task"
  31. count = 0
  32. def run(self, increment_by=1, **kwargs):
  33. increment_by = increment_by or 1
  34. self.__class__.count += increment_by
  35. return self.__class__.count
  36. class RaisingTask(task.Task):
  37. name = "c.unittest.raising_task"
  38. def run(self, **kwargs):
  39. raise KeyError("foo")
  40. class RetryTask(task.Task):
  41. max_retries = 3
  42. iterations = 0
  43. def run(self, arg1, arg2, kwarg=1, **kwargs):
  44. self.__class__.iterations += 1
  45. retries = kwargs["task_retries"]
  46. if retries >= 3:
  47. return arg1
  48. else:
  49. kwargs.update({"kwarg": kwarg})
  50. return self.retry(args=[arg1, arg2], kwargs=kwargs, countdown=0)
  51. class RetryTaskNoArgs(task.Task):
  52. max_retries = 3
  53. iterations = 0
  54. def run(self, **kwargs):
  55. self.__class__.iterations += 1
  56. retries = kwargs["task_retries"]
  57. if retries >= 3:
  58. return 42
  59. else:
  60. return self.retry(kwargs=kwargs, countdown=0)
  61. class RetryTaskMockApply(task.Task):
  62. max_retries = 3
  63. iterations = 0
  64. applied = 0
  65. def run(self, arg1, arg2, kwarg=1, **kwargs):
  66. self.__class__.iterations += 1
  67. retries = kwargs["task_retries"]
  68. if retries >= 3:
  69. return arg1
  70. else:
  71. kwargs.update({"kwarg": kwarg})
  72. return self.retry(args=[arg1, arg2], kwargs=kwargs, countdown=0)
  73. @classmethod
  74. def apply_async(self, *args, **kwargs):
  75. self.applied = 1
  76. class MyCustomException(Exception):
  77. """Random custom exception."""
  78. class RetryTaskCustomExc(task.Task):
  79. max_retries = 3
  80. iterations = 0
  81. def run(self, arg1, arg2, kwarg=1, **kwargs):
  82. self.__class__.iterations += 1
  83. retries = kwargs["task_retries"]
  84. if retries >= 3:
  85. return arg1 + kwarg
  86. else:
  87. try:
  88. raise MyCustomException("Elaine Marie Benes")
  89. except MyCustomException, exc:
  90. kwargs.update({"kwarg": kwarg})
  91. return self.retry(args=[arg1, arg2], kwargs=kwargs,
  92. countdown=0, exc=exc)
  93. class TestTaskRetries(unittest.TestCase):
  94. def test_retry(self):
  95. RetryTask.max_retries = 3
  96. RetryTask.iterations = 0
  97. result = RetryTask.apply([0xFF, 0xFFFF])
  98. self.assertEqual(result.get(), 0xFF)
  99. self.assertEqual(RetryTask.iterations, 4)
  100. def test_retry_no_args(self):
  101. RetryTaskNoArgs.max_retries = 3
  102. RetryTaskNoArgs.iterations = 0
  103. result = RetryTaskNoArgs.apply()
  104. self.assertEqual(result.get(), 42)
  105. self.assertEqual(RetryTaskNoArgs.iterations, 4)
  106. def test_retry_kwargs_can_be_empty(self):
  107. self.assertRaises(RetryTaskError, RetryTaskMockApply.retry,
  108. args=[4, 4], kwargs=None)
  109. def test_retry_not_eager(self):
  110. exc = Exception("baz")
  111. try:
  112. RetryTaskMockApply.retry(args=[4, 4], kwargs={"task_retries": 0},
  113. exc=exc, throw=False)
  114. self.assertTrue(RetryTaskMockApply.applied)
  115. finally:
  116. RetryTaskMockApply.applied = 0
  117. try:
  118. self.assertRaises(RetryTaskError, RetryTaskMockApply.retry,
  119. args=[4, 4], kwargs={"task_retries": 0},
  120. exc=exc, throw=True)
  121. self.assertTrue(RetryTaskMockApply.applied)
  122. finally:
  123. RetryTaskMockApply.applied = 0
  124. def test_retry_with_kwargs(self):
  125. RetryTaskCustomExc.max_retries = 3
  126. RetryTaskCustomExc.iterations = 0
  127. result = RetryTaskCustomExc.apply([0xFF, 0xFFFF], {"kwarg": 0xF})
  128. self.assertEqual(result.get(), 0xFF + 0xF)
  129. self.assertEqual(RetryTaskCustomExc.iterations, 4)
  130. def test_retry_with_custom_exception(self):
  131. RetryTaskCustomExc.max_retries = 2
  132. RetryTaskCustomExc.iterations = 0
  133. result = RetryTaskCustomExc.apply([0xFF, 0xFFFF], {"kwarg": 0xF})
  134. self.assertRaises(MyCustomException,
  135. result.get)
  136. self.assertEqual(RetryTaskCustomExc.iterations, 3)
  137. def test_max_retries_exceeded(self):
  138. RetryTask.max_retries = 2
  139. RetryTask.iterations = 0
  140. result = RetryTask.apply([0xFF, 0xFFFF])
  141. self.assertRaises(RetryTask.MaxRetriesExceededError,
  142. result.get)
  143. self.assertEqual(RetryTask.iterations, 3)
  144. RetryTask.max_retries = 1
  145. RetryTask.iterations = 0
  146. result = RetryTask.apply([0xFF, 0xFFFF])
  147. self.assertRaises(RetryTask.MaxRetriesExceededError,
  148. result.get)
  149. self.assertEqual(RetryTask.iterations, 2)
  150. class MockPublisher(object):
  151. _declared = False
  152. def __init__(self, *args, **kwargs):
  153. self.kwargs = kwargs
  154. self.connection = app_or_default().broker_connection()
  155. def declare(self):
  156. self._declared = True
  157. class TestCeleryTasks(unittest.TestCase):
  158. def test_unpickle_task(self):
  159. import pickle
  160. @task_dec
  161. def xxx():
  162. pass
  163. self.assertIs(pickle.loads(pickle.dumps(xxx)), xxx)
  164. def createTaskCls(self, cls_name, task_name=None):
  165. attrs = {"__module__": self.__module__}
  166. if task_name:
  167. attrs["name"] = task_name
  168. cls = type(cls_name, (task.Task, ), attrs)
  169. cls.run = return_True
  170. return cls
  171. def test_AsyncResult(self):
  172. task_id = gen_unique_id()
  173. result = RetryTask.AsyncResult(task_id)
  174. self.assertEqual(result.backend, RetryTask.backend)
  175. self.assertEqual(result.task_id, task_id)
  176. @with_eager_tasks
  177. def test_ping(self):
  178. self.assertEqual(task.ping(), 'pong')
  179. def assertNextTaskDataEqual(self, consumer, presult, task_name,
  180. test_eta=False, test_expires=False, **kwargs):
  181. next_task = consumer.fetch()
  182. task_data = next_task.decode()
  183. self.assertEqual(task_data["id"], presult.task_id)
  184. self.assertEqual(task_data["task"], task_name)
  185. task_kwargs = task_data.get("kwargs", {})
  186. if test_eta:
  187. self.assertIsInstance(task_data.get("eta"), basestring)
  188. to_datetime = parse_iso8601(task_data.get("eta"))
  189. self.assertIsInstance(to_datetime, datetime)
  190. if test_expires:
  191. self.assertIsInstance(task_data.get("expires"), basestring)
  192. to_datetime = parse_iso8601(task_data.get("expires"))
  193. self.assertIsInstance(to_datetime, datetime)
  194. for arg_name, arg_value in kwargs.items():
  195. self.assertEqual(task_kwargs.get(arg_name), arg_value)
  196. def test_incomplete_task_cls(self):
  197. class IncompleteTask(task.Task):
  198. name = "c.unittest.t.itask"
  199. self.assertRaises(NotImplementedError, IncompleteTask().run)
  200. def test_task_kwargs_must_be_dictionary(self):
  201. self.assertRaises(ValueError, IncrementCounterTask.apply_async,
  202. [], "str")
  203. def test_task_args_must_be_list(self):
  204. self.assertRaises(ValueError, IncrementCounterTask.apply_async,
  205. "str", {})
  206. def test_regular_task(self):
  207. T1 = self.createTaskCls("T1", "c.unittest.t.t1")
  208. self.assertIsInstance(T1(), T1)
  209. self.assertTrue(T1().run())
  210. self.assertTrue(callable(T1()),
  211. "Task class is callable()")
  212. self.assertTrue(T1()(),
  213. "Task class runs run() when called")
  214. # task name generated out of class module + name.
  215. T2 = self.createTaskCls("T2")
  216. self.assertTrue(T2().name.endswith("test_task.T2"))
  217. t1 = T1()
  218. consumer = t1.get_consumer()
  219. self.assertRaises(NotImplementedError, consumer.receive, "foo", "foo")
  220. consumer.discard_all()
  221. self.assertIsNone(consumer.fetch())
  222. # Without arguments.
  223. presult = t1.delay()
  224. self.assertNextTaskDataEqual(consumer, presult, t1.name)
  225. # With arguments.
  226. presult2 = t1.apply_async(kwargs=dict(name="George Costanza"))
  227. self.assertNextTaskDataEqual(consumer, presult2, t1.name,
  228. name="George Costanza")
  229. # send_task
  230. sresult = send_task(t1.name, kwargs=dict(name="Elaine M. Benes"))
  231. self.assertNextTaskDataEqual(consumer, sresult, t1.name,
  232. name="Elaine M. Benes")
  233. # With eta.
  234. presult2 = t1.apply_async(kwargs=dict(name="George Costanza"),
  235. eta=datetime.now() + timedelta(days=1),
  236. expires=datetime.now() + timedelta(days=2))
  237. self.assertNextTaskDataEqual(consumer, presult2, t1.name,
  238. name="George Costanza", test_eta=True, test_expires=True)
  239. # With countdown.
  240. presult2 = t1.apply_async(kwargs=dict(name="George Costanza"),
  241. countdown=10, expires=12)
  242. self.assertNextTaskDataEqual(consumer, presult2, t1.name,
  243. name="George Costanza", test_eta=True, test_expires=True)
  244. # Discarding all tasks.
  245. consumer.discard_all()
  246. t1.apply_async()
  247. self.assertEqual(consumer.discard_all(), 1)
  248. self.assertIsNone(consumer.fetch())
  249. self.assertFalse(presult.successful())
  250. t1.backend.mark_as_done(presult.task_id, result=None)
  251. self.assertTrue(presult.successful())
  252. publisher = t1.get_publisher()
  253. self.assertTrue(publisher.exchange)
  254. def test_send_task_sent_event(self):
  255. T1 = self.createTaskCls("T1", "c.unittest.t.t1")
  256. conn = T1.app.broker_connection()
  257. chan = conn.channel()
  258. prev = T1.app.conf.CELERY_SEND_TASK_SENT_EVENT
  259. T1.app.conf.CELERY_SEND_TASK_SENT_EVENT = True
  260. dispatcher = [None]
  261. class Pub(object):
  262. channel = chan
  263. def delay_task(self, *args, **kwargs):
  264. dispatcher[0] = kwargs.get("event_dispatcher")
  265. try:
  266. T1.apply_async(publisher=Pub())
  267. finally:
  268. T1.app.conf.CELERY_SEND_TASK_SENT_EVENT = False
  269. chan.close()
  270. conn.close()
  271. self.assertTrue(dispatcher[0])
  272. def test_get_publisher(self):
  273. from celery.app import amqp
  274. old_pub = amqp.TaskPublisher
  275. amqp.TaskPublisher = MockPublisher
  276. try:
  277. p = IncrementCounterTask.get_publisher(exchange="foo",
  278. connection="bar")
  279. self.assertEqual(p.kwargs["exchange"], "foo")
  280. self.assertTrue(p._declared)
  281. p = IncrementCounterTask.get_publisher(exchange_type="fanout",
  282. connection="bar")
  283. self.assertEqual(p.kwargs["exchange_type"], "fanout")
  284. finally:
  285. amqp.TaskPublisher = old_pub
  286. def test_update_state(self):
  287. @task_dec
  288. def yyy():
  289. pass
  290. tid = gen_unique_id()
  291. yyy.update_state(tid, "FROBULATING", {"fooz": "baaz"})
  292. self.assertEqual(yyy.AsyncResult(tid).status, "FROBULATING")
  293. self.assertDictEqual(yyy.AsyncResult(tid).result, {"fooz": "baaz"})
  294. yyy.request.id = tid
  295. yyy.update_state(state="FROBUZATING", meta={"fooz": "baaz"})
  296. self.assertEqual(yyy.AsyncResult(tid).status, "FROBUZATING")
  297. self.assertDictEqual(yyy.AsyncResult(tid).result, {"fooz": "baaz"})
  298. def test_repr(self):
  299. @task_dec
  300. def task_test_repr():
  301. pass
  302. self.assertIn("task_test_repr", repr(task_test_repr))
  303. def test_has___name__(self):
  304. @task_dec
  305. def yyy2():
  306. pass
  307. self.assertTrue(yyy2.__name__)
  308. def test_get_logger(self):
  309. T1 = self.createTaskCls("T1", "c.unittest.t.t1")
  310. t1 = T1()
  311. logfh = StringIO()
  312. logger = t1.get_logger(logfile=logfh, loglevel=0)
  313. self.assertTrue(logger)
  314. T1.request.loglevel = 3
  315. logger = t1.get_logger(logfile=logfh, loglevel=None)
  316. self.assertTrue(logger)
  317. class TestTaskSet(unittest.TestCase):
  318. @with_eager_tasks
  319. def test_function_taskset(self):
  320. subtasks = [return_True_task.subtask([i]) for i in range(1, 6)]
  321. ts = task.TaskSet(subtasks)
  322. res = ts.apply_async()
  323. self.assertListEqual(res.join(), [True, True, True, True, True])
  324. def test_counter_taskset(self):
  325. IncrementCounterTask.count = 0
  326. ts = task.TaskSet(tasks=[
  327. IncrementCounterTask.subtask((), {}),
  328. IncrementCounterTask.subtask((), {"increment_by": 2}),
  329. IncrementCounterTask.subtask((), {"increment_by": 3}),
  330. IncrementCounterTask.subtask((), {"increment_by": 4}),
  331. IncrementCounterTask.subtask((), {"increment_by": 5}),
  332. IncrementCounterTask.subtask((), {"increment_by": 6}),
  333. IncrementCounterTask.subtask((), {"increment_by": 7}),
  334. IncrementCounterTask.subtask((), {"increment_by": 8}),
  335. IncrementCounterTask.subtask((), {"increment_by": 9}),
  336. ])
  337. self.assertEqual(ts.total, 9)
  338. consumer = IncrementCounterTask().get_consumer()
  339. consumer.purge()
  340. consumer.close()
  341. taskset_res = ts.apply_async()
  342. subtasks = taskset_res.subtasks
  343. taskset_id = taskset_res.taskset_id
  344. consumer = IncrementCounterTask().get_consumer()
  345. for subtask in subtasks:
  346. m = consumer.fetch().payload
  347. self.assertDictContainsSubset({"taskset": taskset_id,
  348. "task": IncrementCounterTask.name,
  349. "id": subtask.task_id}, m)
  350. IncrementCounterTask().run(
  351. increment_by=m.get("kwargs", {}).get("increment_by"))
  352. self.assertEqual(IncrementCounterTask.count, sum(xrange(1, 10)))
  353. class TestTaskApply(unittest.TestCase):
  354. def test_apply_throw(self):
  355. self.assertRaises(KeyError, RaisingTask.apply, throw=True)
  356. def test_apply_with_CELERY_EAGER_PROPAGATES_EXCEPTIONS(self):
  357. RaisingTask.app.conf.CELERY_EAGER_PROPAGATES_EXCEPTIONS = True
  358. try:
  359. self.assertRaises(KeyError, RaisingTask.apply)
  360. finally:
  361. RaisingTask.app.conf.CELERY_EAGER_PROPAGATES_EXCEPTIONS = False
  362. def test_apply(self):
  363. IncrementCounterTask.count = 0
  364. e = IncrementCounterTask.apply()
  365. self.assertIsInstance(e, EagerResult)
  366. self.assertEqual(e.get(), 1)
  367. e = IncrementCounterTask.apply(args=[1])
  368. self.assertEqual(e.get(), 2)
  369. e = IncrementCounterTask.apply(kwargs={"increment_by": 4})
  370. self.assertEqual(e.get(), 6)
  371. self.assertTrue(e.successful())
  372. self.assertTrue(e.ready())
  373. self.assertTrue(repr(e).startswith("<EagerResult:"))
  374. f = RaisingTask.apply()
  375. self.assertTrue(f.ready())
  376. self.assertFalse(f.successful())
  377. self.assertTrue(f.traceback)
  378. self.assertRaises(KeyError, f.get)
  379. class MyPeriodic(task.PeriodicTask):
  380. run_every = timedelta(hours=1)
  381. class TestPeriodicTask(unittest.TestCase):
  382. def test_must_have_run_every(self):
  383. self.assertRaises(NotImplementedError, type, "Foo",
  384. (task.PeriodicTask, ), {"__module__": __name__})
  385. def test_remaining_estimate(self):
  386. self.assertIsInstance(
  387. MyPeriodic().remaining_estimate(datetime.now()),
  388. timedelta)
  389. def test_timedelta_seconds_returns_0_on_negative_time(self):
  390. delta = timedelta(days=-2)
  391. self.assertEqual(MyPeriodic().timedelta_seconds(delta), 0)
  392. def test_timedelta_seconds(self):
  393. deltamap = ((timedelta(seconds=1), 1),
  394. (timedelta(seconds=27), 27),
  395. (timedelta(minutes=3), 3 * 60),
  396. (timedelta(hours=4), 4 * 60 * 60),
  397. (timedelta(days=3), 3 * 86400))
  398. for delta, seconds in deltamap:
  399. self.assertEqual(MyPeriodic().timedelta_seconds(delta), seconds)
  400. def test_delta_resolution(self):
  401. D = timeutils.delta_resolution
  402. dt = datetime(2010, 3, 30, 11, 50, 58, 41065)
  403. deltamap = ((timedelta(days=2), datetime(2010, 3, 30, 0, 0)),
  404. (timedelta(hours=2), datetime(2010, 3, 30, 11, 0)),
  405. (timedelta(minutes=2), datetime(2010, 3, 30, 11, 50)),
  406. (timedelta(seconds=2), dt))
  407. for delta, shoulda in deltamap:
  408. self.assertEqual(D(dt, delta), shoulda)
  409. def test_is_due_not_due(self):
  410. due, remaining = MyPeriodic().is_due(datetime.now())
  411. self.assertFalse(due)
  412. # TODO This assertion may fail if executed in the
  413. # first minute of an hour
  414. self.assertGreater(remaining, 60)
  415. def test_is_due(self):
  416. p = MyPeriodic()
  417. due, remaining = p.is_due(datetime.now() - p.run_every.run_every)
  418. self.assertTrue(due)
  419. self.assertEqual(remaining,
  420. p.timedelta_seconds(p.run_every.run_every))
  421. class EveryMinutePeriodic(task.PeriodicTask):
  422. run_every = crontab()
  423. class QuarterlyPeriodic(task.PeriodicTask):
  424. run_every = crontab(minute="*/15")
  425. class HourlyPeriodic(task.PeriodicTask):
  426. run_every = crontab(minute=30)
  427. class DailyPeriodic(task.PeriodicTask):
  428. run_every = crontab(hour=7, minute=30)
  429. class WeeklyPeriodic(task.PeriodicTask):
  430. run_every = crontab(hour=7, minute=30, day_of_week="thursday")
  431. def patch_crontab_nowfun(cls, retval):
  432. def create_patcher(fun):
  433. @wraps(fun)
  434. def __inner(*args, **kwargs):
  435. prev_nowfun = cls.run_every.nowfun
  436. cls.run_every.nowfun = lambda: retval
  437. try:
  438. return fun(*args, **kwargs)
  439. finally:
  440. cls.run_every.nowfun = prev_nowfun
  441. return __inner
  442. return create_patcher
  443. class test_crontab_parser(unittest.TestCase):
  444. def test_parse_star(self):
  445. self.assertEquals(crontab_parser(24).parse('*'), set(range(24)))
  446. self.assertEquals(crontab_parser(60).parse('*'), set(range(60)))
  447. self.assertEquals(crontab_parser(7).parse('*'), set(range(7)))
  448. def test_parse_range(self):
  449. self.assertEquals(crontab_parser(60).parse('1-10'),
  450. set(range(1, 10 + 1)))
  451. self.assertEquals(crontab_parser(24).parse('0-20'),
  452. set(range(0, 20 + 1)))
  453. self.assertEquals(crontab_parser().parse('2-10'),
  454. set(range(2, 10 + 1)))
  455. def test_parse_groups(self):
  456. self.assertEquals(crontab_parser().parse('1,2,3,4'),
  457. set([1, 2, 3, 4]))
  458. self.assertEquals(crontab_parser().parse('0,15,30,45'),
  459. set([0, 15, 30, 45]))
  460. def test_parse_steps(self):
  461. self.assertEquals(crontab_parser(8).parse('*/2'),
  462. set([0, 2, 4, 6]))
  463. self.assertEquals(crontab_parser().parse('*/2'),
  464. set(i * 2 for i in xrange(30)))
  465. self.assertEquals(crontab_parser().parse('*/3'),
  466. set(i * 3 for i in xrange(20)))
  467. def test_parse_composite(self):
  468. self.assertEquals(crontab_parser(8).parse('*/2'), set([0, 2, 4, 6]))
  469. self.assertEquals(crontab_parser().parse('2-9/5'), set([5]))
  470. self.assertEquals(crontab_parser().parse('2-10/5'), set([5, 10]))
  471. self.assertEquals(crontab_parser().parse('2-11/5,3'), set([3, 5, 10]))
  472. self.assertEquals(crontab_parser().parse('2-4/3,*/5,0-21/4'),
  473. set([0, 3, 4, 5, 8, 10, 12, 15, 16,
  474. 20, 25, 30, 35, 40, 45, 50, 55]))
  475. def test_parse_errors_on_empty_string(self):
  476. self.assertRaises(ParseException, crontab_parser(60).parse, '')
  477. def test_parse_errors_on_empty_group(self):
  478. self.assertRaises(ParseException, crontab_parser(60).parse, '1,,2')
  479. def test_parse_errors_on_empty_steps(self):
  480. self.assertRaises(ParseException, crontab_parser(60).parse, '*/')
  481. def test_parse_errors_on_negative_number(self):
  482. self.assertRaises(ParseException, crontab_parser(60).parse, '-20')
  483. def test_expand_cronspec_eats_iterables(self):
  484. self.assertEqual(crontab._expand_cronspec(iter([1, 2, 3]), 100),
  485. set([1, 2, 3]))
  486. def test_expand_cronspec_invalid_type(self):
  487. self.assertRaises(TypeError, crontab._expand_cronspec, object(), 100)
  488. def test_repr(self):
  489. self.assertIn("*", repr(crontab("*")))
  490. def test_eq(self):
  491. self.assertEqual(crontab(day_of_week="1, 2"),
  492. crontab(day_of_week="1-2"))
  493. self.assertEqual(crontab(minute="1", hour="2", day_of_week="5"),
  494. crontab(minute="1", hour="2", day_of_week="5"))
  495. self.assertNotEqual(crontab(minute="1"), crontab(minute="2"))
  496. self.assertFalse(object() == crontab(minute="1"))
  497. self.assertFalse(crontab(minute="1") == object())
  498. class test_crontab_remaining_estimate(unittest.TestCase):
  499. def next_ocurrance(self, crontab, now):
  500. crontab.nowfun = lambda: now
  501. return now + crontab.remaining_estimate(now)
  502. def test_next_minute(self):
  503. next = self.next_ocurrance(crontab(),
  504. datetime(2010, 9, 11, 14, 30, 15))
  505. self.assertEquals(next, datetime(2010, 9, 11, 14, 31))
  506. def test_not_next_minute(self):
  507. next = self.next_ocurrance(crontab(),
  508. datetime(2010, 9, 11, 14, 59, 15))
  509. self.assertEquals(next, datetime(2010, 9, 11, 15, 0))
  510. def test_this_hour(self):
  511. next = self.next_ocurrance(crontab(minute=[5, 42]),
  512. datetime(2010, 9, 11, 14, 30, 15))
  513. self.assertEquals(next, datetime(2010, 9, 11, 14, 42))
  514. def test_not_this_hour(self):
  515. next = self.next_ocurrance(crontab(minute=[5, 10, 15]),
  516. datetime(2010, 9, 11, 14, 30, 15))
  517. self.assertEquals(next, datetime(2010, 9, 11, 15, 5))
  518. def test_today(self):
  519. next = self.next_ocurrance(crontab(minute=[5, 42], hour=[12, 17]),
  520. datetime(2010, 9, 11, 14, 30, 15))
  521. self.assertEquals(next, datetime(2010, 9, 11, 17, 5))
  522. def test_not_today(self):
  523. next = self.next_ocurrance(crontab(minute=[5, 42], hour=[12]),
  524. datetime(2010, 9, 11, 14, 30, 15))
  525. self.assertEquals(next, datetime(2010, 9, 12, 12, 5))
  526. def test_weekday(self):
  527. next = self.next_ocurrance(crontab(minute=30,
  528. hour=14,
  529. day_of_week="sat"),
  530. datetime(2010, 9, 11, 14, 30, 15))
  531. self.assertEquals(next, datetime(2010, 9, 18, 14, 30))
  532. def test_not_weekday(self):
  533. next = self.next_ocurrance(crontab(minute=[5, 42],
  534. day_of_week="mon-fri"),
  535. datetime(2010, 9, 11, 14, 30, 15))
  536. self.assertEquals(next, datetime(2010, 9, 13, 0, 5))
  537. class test_crontab_is_due(unittest.TestCase):
  538. def setUp(self):
  539. self.now = datetime.now()
  540. self.next_minute = 60 - self.now.second - 1e-6 * self.now.microsecond
  541. def test_default_crontab_spec(self):
  542. c = crontab()
  543. self.assertEquals(c.minute, set(range(60)))
  544. self.assertEquals(c.hour, set(range(24)))
  545. self.assertEquals(c.day_of_week, set(range(7)))
  546. def test_simple_crontab_spec(self):
  547. c = crontab(minute=30)
  548. self.assertEquals(c.minute, set([30]))
  549. self.assertEquals(c.hour, set(range(24)))
  550. self.assertEquals(c.day_of_week, set(range(7)))
  551. def test_crontab_spec_minute_formats(self):
  552. c = crontab(minute=30)
  553. self.assertEquals(c.minute, set([30]))
  554. c = crontab(minute='30')
  555. self.assertEquals(c.minute, set([30]))
  556. c = crontab(minute=(30, 40, 50))
  557. self.assertEquals(c.minute, set([30, 40, 50]))
  558. c = crontab(minute=set([30, 40, 50]))
  559. self.assertEquals(c.minute, set([30, 40, 50]))
  560. def test_crontab_spec_invalid_minute(self):
  561. self.assertRaises(ValueError, crontab, minute=60)
  562. self.assertRaises(ValueError, crontab, minute='0-100')
  563. def test_crontab_spec_hour_formats(self):
  564. c = crontab(hour=6)
  565. self.assertEquals(c.hour, set([6]))
  566. c = crontab(hour='5')
  567. self.assertEquals(c.hour, set([5]))
  568. c = crontab(hour=(4, 8, 12))
  569. self.assertEquals(c.hour, set([4, 8, 12]))
  570. def test_crontab_spec_invalid_hour(self):
  571. self.assertRaises(ValueError, crontab, hour=24)
  572. self.assertRaises(ValueError, crontab, hour='0-30')
  573. def test_crontab_spec_dow_formats(self):
  574. c = crontab(day_of_week=5)
  575. self.assertEquals(c.day_of_week, set([5]))
  576. c = crontab(day_of_week='5')
  577. self.assertEquals(c.day_of_week, set([5]))
  578. c = crontab(day_of_week='fri')
  579. self.assertEquals(c.day_of_week, set([5]))
  580. c = crontab(day_of_week='tuesday,sunday,fri')
  581. self.assertEquals(c.day_of_week, set([0, 2, 5]))
  582. c = crontab(day_of_week='mon-fri')
  583. self.assertEquals(c.day_of_week, set([1, 2, 3, 4, 5]))
  584. c = crontab(day_of_week='*/2')
  585. self.assertEquals(c.day_of_week, set([0, 2, 4, 6]))
  586. def test_crontab_spec_invalid_dow(self):
  587. self.assertRaises(ValueError, crontab, day_of_week='fooday-barday')
  588. self.assertRaises(ValueError, crontab, day_of_week='1,4,foo')
  589. self.assertRaises(ValueError, crontab, day_of_week='7')
  590. self.assertRaises(ValueError, crontab, day_of_week='12')
  591. def test_every_minute_execution_is_due(self):
  592. last_ran = self.now - timedelta(seconds=61)
  593. due, remaining = EveryMinutePeriodic().is_due(last_ran)
  594. self.assertTrue(due)
  595. self.assertAlmostEquals(remaining, self.next_minute, 1)
  596. def test_every_minute_execution_is_not_due(self):
  597. last_ran = self.now - timedelta(seconds=self.now.second)
  598. due, remaining = EveryMinutePeriodic().is_due(last_ran)
  599. self.assertFalse(due)
  600. self.assertAlmostEquals(remaining, self.next_minute, 1)
  601. # 29th of May 2010 is a saturday
  602. @patch_crontab_nowfun(HourlyPeriodic, datetime(2010, 5, 29, 10, 30))
  603. def test_execution_is_due_on_saturday(self):
  604. last_ran = self.now - timedelta(seconds=61)
  605. due, remaining = EveryMinutePeriodic().is_due(last_ran)
  606. self.assertTrue(due)
  607. self.assertAlmostEquals(remaining, self.next_minute, 1)
  608. # 30th of May 2010 is a sunday
  609. @patch_crontab_nowfun(HourlyPeriodic, datetime(2010, 5, 30, 10, 30))
  610. def test_execution_is_due_on_sunday(self):
  611. last_ran = self.now - timedelta(seconds=61)
  612. due, remaining = EveryMinutePeriodic().is_due(last_ran)
  613. self.assertTrue(due)
  614. self.assertAlmostEquals(remaining, self.next_minute, 1)
  615. # 31st of May 2010 is a monday
  616. @patch_crontab_nowfun(HourlyPeriodic, datetime(2010, 5, 31, 10, 30))
  617. def test_execution_is_due_on_monday(self):
  618. last_ran = self.now - timedelta(seconds=61)
  619. due, remaining = EveryMinutePeriodic().is_due(last_ran)
  620. self.assertTrue(due)
  621. self.assertAlmostEquals(remaining, self.next_minute, 1)
  622. @patch_crontab_nowfun(HourlyPeriodic, datetime(2010, 5, 10, 10, 30))
  623. def test_every_hour_execution_is_due(self):
  624. due, remaining = HourlyPeriodic().is_due(datetime(2010, 5, 10, 6, 30))
  625. self.assertTrue(due)
  626. self.assertEquals(remaining, 60 * 60)
  627. @patch_crontab_nowfun(HourlyPeriodic, datetime(2010, 5, 10, 10, 29))
  628. def test_every_hour_execution_is_not_due(self):
  629. due, remaining = HourlyPeriodic().is_due(datetime(2010, 5, 10, 9, 30))
  630. self.assertFalse(due)
  631. self.assertEquals(remaining, 60)
  632. @patch_crontab_nowfun(QuarterlyPeriodic, datetime(2010, 5, 10, 10, 15))
  633. def test_first_quarter_execution_is_due(self):
  634. due, remaining = QuarterlyPeriodic().is_due(
  635. datetime(2010, 5, 10, 6, 30))
  636. self.assertTrue(due)
  637. self.assertEquals(remaining, 15 * 60)
  638. @patch_crontab_nowfun(QuarterlyPeriodic, datetime(2010, 5, 10, 10, 30))
  639. def test_second_quarter_execution_is_due(self):
  640. due, remaining = QuarterlyPeriodic().is_due(
  641. datetime(2010, 5, 10, 6, 30))
  642. self.assertTrue(due)
  643. self.assertEquals(remaining, 15 * 60)
  644. @patch_crontab_nowfun(QuarterlyPeriodic, datetime(2010, 5, 10, 10, 14))
  645. def test_first_quarter_execution_is_not_due(self):
  646. due, remaining = QuarterlyPeriodic().is_due(
  647. datetime(2010, 5, 10, 10, 0))
  648. self.assertFalse(due)
  649. self.assertEquals(remaining, 60)
  650. @patch_crontab_nowfun(QuarterlyPeriodic, datetime(2010, 5, 10, 10, 29))
  651. def test_second_quarter_execution_is_not_due(self):
  652. due, remaining = QuarterlyPeriodic().is_due(
  653. datetime(2010, 5, 10, 10, 15))
  654. self.assertFalse(due)
  655. self.assertEquals(remaining, 60)
  656. @patch_crontab_nowfun(DailyPeriodic, datetime(2010, 5, 10, 7, 30))
  657. def test_daily_execution_is_due(self):
  658. due, remaining = DailyPeriodic().is_due(datetime(2010, 5, 9, 7, 30))
  659. self.assertTrue(due)
  660. self.assertEquals(remaining, 24 * 60 * 60)
  661. @patch_crontab_nowfun(DailyPeriodic, datetime(2010, 5, 10, 10, 30))
  662. def test_daily_execution_is_not_due(self):
  663. due, remaining = DailyPeriodic().is_due(datetime(2010, 5, 10, 7, 30))
  664. self.assertFalse(due)
  665. self.assertEquals(remaining, 21 * 60 * 60)
  666. @patch_crontab_nowfun(WeeklyPeriodic, datetime(2010, 5, 6, 7, 30))
  667. def test_weekly_execution_is_due(self):
  668. due, remaining = WeeklyPeriodic().is_due(datetime(2010, 4, 30, 7, 30))
  669. self.assertTrue(due)
  670. self.assertEquals(remaining, 7 * 24 * 60 * 60)
  671. @patch_crontab_nowfun(WeeklyPeriodic, datetime(2010, 5, 7, 10, 30))
  672. def test_weekly_execution_is_not_due(self):
  673. due, remaining = WeeklyPeriodic().is_due(datetime(2010, 5, 6, 7, 30))
  674. self.assertFalse(due)
  675. self.assertEquals(remaining, 6 * 24 * 60 * 60 - 3 * 60 * 60)