__init__.py 31 KB

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