forked from platypush/platypush
Cron expressions should follow the machine local time, not UTC [closes #173]
This commit is contained in:
parent
71af6e87e0
commit
296458ece3
6 changed files with 86 additions and 12 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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')
|
||||||
|
|
||||||
|
|
1
tests/etc/scripts/__init__.py
Normal file
1
tests/etc/scripts/__init__.py
Normal file
|
@ -0,0 +1 @@
|
||||||
|
# Auto-generated __init__.py - do not remove
|
26
tests/etc/scripts/test_cron.py
Normal file
26
tests/etc/scripts/test_cron.py
Normal 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
46
tests/test_cron.py
Normal 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:
|
Loading…
Reference in a new issue