__init__.py 31 KB

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