Cron expressions should follow the machine local time, not UTC [closes #173]

This commit is contained in:
Fabio Manganiello 2021-03-09 00:18:33 +01:00
parent 71af6e87e0
commit 296458ece3
6 changed files with 86 additions and 12 deletions

1
.gitignore vendored
View file

@ -18,5 +18,4 @@ platypush/notebooks
platypush/requests platypush/requests
/http-client.env.json /http-client.env.json
/platypush/backend/http/static/css/dist /platypush/backend/http/static/css/dist
/tests/etc/scripts
/tests/etc/dashboards /tests/etc/dashboards

View file

@ -3,6 +3,13 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
Given the high speed of development in the first phase, changes are being reported only starting from v0.20.2. Given the high speed of development in the first phase, changes are being reported only starting from v0.20.2.
## [unreleased]
### Fixed
- Cron expressions should adhere to the UNIX cronjob standard and use the machine local time,
not UTC, as a reference (closes [#173](https://git.platypush.tech/platypush/platypush/-/issues/173)).
## [0.20.4] - 2021-03-08 ## [0.20.4] - 2021-03-08
### Added ### Added

View file

@ -1,9 +1,10 @@
import datetime
import enum import enum
import logging import logging
import threading import threading
import time
import croniter import croniter
from dateutil.tz import gettz
from platypush.procedure import Procedure from platypush.procedure import Procedure
from platypush.utils import is_functional_cron from platypush.utils import is_functional_cron
@ -56,16 +57,10 @@ class Cronjob(threading.Thread):
self.state = CronjobState.ERROR self.state = CronjobState.ERROR
def wait(self): def wait(self):
now = int(time.time()) now = datetime.datetime.now().replace(tzinfo=gettz())
cron = croniter.croniter(self.cron_expression, now) cron = croniter.croniter(self.cron_expression, now)
next_run = int(cron.get_next()) next_run = cron.get_next()
self._should_stop.wait(next_run - now) self._should_stop.wait(next_run - now.timestamp())
def should_run(self):
now = int(time.time())
cron = croniter.croniter(self.cron_expression, now)
next_run = int(cron.get_next())
return now == next_run
def stop(self): def stop(self):
self._should_stop.set() self._should_stop.set()
@ -117,7 +112,7 @@ class CronScheduler(threading.Thread):
if job.state == CronjobState.IDLE: if job.state == CronjobState.IDLE:
job.start() job.start()
time.sleep(0.5) self._should_stop.wait(timeout=0.5)
logger.info('Terminating cron scheduler') logger.info('Terminating cron scheduler')

View file

@ -0,0 +1 @@
# Auto-generated __init__.py - do not remove

View file

@ -0,0 +1,26 @@
import datetime
from platypush.cron import cron
from tests.test_cron import tmp_files, tmp_files_ready, \
test_timeout, expected_cron_file_content
# Prepare a cronjob that should start test_timeout/2 seconds from the application start
cron_time = datetime.datetime.now() + datetime.timedelta(seconds=test_timeout/2)
cron_expr = '{min} {hour} {day} {month} * {sec}'.format(
min=cron_time.minute, hour=cron_time.hour, day=cron_time.day,
month=cron_time.month, sec=cron_time.second)
@cron(cron_expr)
def cron_test(**_):
"""
Simple cronjob that awaits for ``../test_cron.py`` to be ready and writes the expected
content to the monitored temporary file.
"""
files_ready = tmp_files_ready.wait(timeout=test_timeout)
assert files_ready, \
'The test did not prepare the temporary files within {} seconds'.format(test_timeout)
with open(tmp_files[0], 'w') as f:
f.write(expected_cron_file_content)

46
tests/test_cron.py Normal file
View file

@ -0,0 +1,46 @@
import os
import pytest
import tempfile
import threading
import time
tmp_files = []
tmp_files_ready = threading.Event()
test_timeout = 10
expected_cron_file_content = 'The cronjob ran successfully!'
@pytest.fixture(scope='module', autouse=True)
def tmp_file(*_):
tmp_file = tempfile.NamedTemporaryFile(prefix='platypush-test-cron-',
suffix='.txt', delete=False)
tmp_files.append(tmp_file.name)
tmp_files_ready.set()
yield tmp_file.name
if os.path.isfile(tmp_files[0]):
os.unlink(tmp_files[0])
def test_cron_execution(tmp_file):
"""
Test that the cronjob in ``../etc/scripts/test_cron.py`` runs successfully.
"""
actual_cron_file_content = None
test_start = time.time()
while actual_cron_file_content != expected_cron_file_content and \
time.time() - test_start < test_timeout:
with open(tmp_file, 'r') as f:
actual_cron_file_content = f.read()
time.sleep(0.5)
assert actual_cron_file_content == expected_cron_file_content, \
'cron_test failed to run within {} seconds'.format(test_timeout)
if __name__ == '__main__':
pytest.main()
# vim:sw=4:ts=4:et: