Merge branch 'master' of github.com:web2py/web2py

This commit is contained in:
mdipierro
2016-05-04 09:12:52 -05:00
10 changed files with 557 additions and 201 deletions
+2 -2
View File
@@ -676,8 +676,8 @@ def run_view_in(environment):
else: else:
filename = pjoin(folder, 'views', view) filename = pjoin(folder, 'views', view)
if os.path.exists(path): # compiled views if os.path.exists(path): # compiled views
x = view.replace('/', '.') x = view.replace('/', '_')
files = ['views.%s.pyc' % x] files = ['views_%s.pyc' % x]
is_compiled = os.path.exists(pjoin(path, files[0])) is_compiled = os.path.exists(pjoin(path, files[0]))
# Don't use a generic view if the non-compiled view exists. # Don't use a generic view if the non-compiled view exists.
if is_compiled or (not is_compiled and not os.path.exists(filename)): if is_compiled or (not is_compiled and not os.path.exists(filename)):
+7 -9
View File
@@ -317,7 +317,7 @@ def URL(a=None,
response = current.response response = current.response
if response.static_version and response.static_version_urls: if response.static_version and response.static_version_urls:
args = [function] + args args = [function] + args
function = '_'+str(response.static_version) function = '_' + str(response.static_version)
if '.' in function: if '.' in function:
function, extension = function.rsplit('.', 1) function, extension = function.rsplit('.', 1)
@@ -330,18 +330,16 @@ def URL(a=None,
if args: if args:
if url_encode: if url_encode:
if encode_embedded_slash: if encode_embedded_slash:
other = '/' + '/'.join([urllib.quote(str( other = '/' + '/'.join([urllib.quote(str(x), '') for x in args])
x), '') for x in args])
else: else:
other = args and urllib.quote( other = args and urllib.quote('/' + '/'.join([str(x) for x in args]))
'/' + '/'.join([str(x) for x in args]))
else: else:
other = args and ('/' + '/'.join([str(x) for x in args])) other = args and ('/' + '/'.join([str(x) for x in args]))
else: else:
other = '' other = ''
if other.endswith('/'): if other.endswith('/'):
other += '/' # add trailing slash to make last trailing empty arg explicit other += '/' # add trailing slash to make last trailing empty arg explicit
list_vars = [] list_vars = []
for (key, vals) in sorted(vars.items()): for (key, vals) in sorted(vars.items()):
@@ -364,11 +362,11 @@ def URL(a=None,
h_args = '/%s/%s/%s%s' % (application, controller, function2, other) h_args = '/%s/%s/%s%s' % (application, controller, function2, other)
# how many of the vars should we include in our hash? # how many of the vars should we include in our hash?
if hash_vars is True: # include them all if hash_vars is True: # include them all
h_vars = list_vars h_vars = list_vars
elif hash_vars is False: # include none of them elif hash_vars is False: # include none of them
h_vars = '' h_vars = ''
else: # include just those specified else: # include just those specified
if hash_vars and not isinstance(hash_vars, (list, tuple)): if hash_vars and not isinstance(hash_vars, (list, tuple)):
hash_vars = [hash_vars] hash_vars = [hash_vars]
h_vars = [(k, v) for (k, v) in list_vars if k in hash_vars] h_vars = [(k, v) for (k, v) in list_vars if k in hash_vars]
+4 -2
View File
@@ -1002,17 +1002,19 @@ def update_all_languages(application_path):
findT(application_path, language[:-3]) findT(application_path, language[:-3])
def update_from_langfile(target, source): def update_from_langfile(target, source, force_update=False):
"""this will update untranslated messages in target from source (where both are language files) """this will update untranslated messages in target from source (where both are language files)
this can be used as first step when creating language file for new but very similar language this can be used as first step when creating language file for new but very similar language
or if you want update your app from welcome app of newer web2py version or if you want update your app from welcome app of newer web2py version
or in non-standard scenarios when you work on target and from any reason you have partial translation in source or in non-standard scenarios when you work on target and from any reason you have partial translation in source
Args:
force_update: if False existing translations remain unchanged, if True existing translations will update from source
""" """
src = read_dict(source) src = read_dict(source)
sentences = read_dict(target) sentences = read_dict(target)
for key in sentences: for key in sentences:
val = sentences[key] val = sentences[key]
if not val or val == key: if not val or val == key or force_update:
new_val = src.get(key) new_val = src.get(key)
if new_val and new_val != val: if new_val and new_val != val:
sentences[key] = new_val sentences[key] = new_val
+93 -109
View File
@@ -9,6 +9,26 @@ Background processes made simple
--------------------------------- ---------------------------------
""" """
import os
import time
import multiprocessing
import sys
import threading
import traceback
import signal
import socket
import datetime
import logging
import optparse
import tempfile
import types
import Queue
from gluon import DAL, Field, IS_NOT_EMPTY, IS_IN_SET, IS_NOT_IN_DB
from gluon import IS_INT_IN_RANGE, IS_DATETIME, IS_IN_DB
from gluon.utils import web2py_uuid
from gluon.storage import Storage
USAGE = """ USAGE = """
## Example ## Example
@@ -67,20 +87,6 @@ sudo restart web2py-scheduler
sudo status web2py-scheduler sudo status web2py-scheduler
""" """
import os
import time
import multiprocessing
import sys
import threading
import traceback
import signal
import socket
import datetime
import logging
import optparse
import types
import Queue
path = os.getcwd() path = os.getcwd()
if 'WEB2PY_PATH' not in os.environ: if 'WEB2PY_PATH' not in os.environ:
@@ -101,12 +107,6 @@ IDENTIFIER = "%s#%s" % (socket.gethostname(), os.getpid())
logger = logging.getLogger('web2py.scheduler.%s' % IDENTIFIER) logger = logging.getLogger('web2py.scheduler.%s' % IDENTIFIER)
from gluon import DAL, Field, IS_NOT_EMPTY, IS_IN_SET, IS_NOT_IN_DB
from gluon import IS_INT_IN_RANGE, IS_DATETIME, IS_IN_DB
from gluon.utils import web2py_uuid
from gluon.storage import Storage
QUEUED = 'QUEUED' QUEUED = 'QUEUED'
ASSIGNED = 'ASSIGNED' ASSIGNED = 'ASSIGNED'
RUNNING = 'RUNNING' RUNNING = 'RUNNING'
@@ -168,24 +168,25 @@ class TaskReport(object):
class JobGraph(object): class JobGraph(object):
"""Experimental: with JobGraph you can specify """Experimental: dependencies amongs tasks"""
dependencies amongs tasks"""
def __init__(self, db, job_name): def __init__(self, db, job_name):
self.job_name = job_name or 'job_0' self.job_name = job_name or 'job_0'
self.db = db self.db = db
def add_deps(self, task_parent, task_child): def add_deps(self, task_parent, task_child):
"""Creates a dependency between task_parent and task_child""" """Create a dependency between task_parent and task_child."""
self.db.scheduler_task_deps.insert(task_parent=task_parent, self.db.scheduler_task_deps.insert(task_parent=task_parent,
task_child=task_child, task_child=task_child,
job_name=self.job_name) job_name=self.job_name)
def validate(self, job_name=None): def validate(self, job_name=None):
"""Validates if all tasks job_name can be completed, i.e. there """Validate if all tasks job_name can be completed.
are no mutual dependencies among tasks.
Checks if there are no mutual dependencies among tasks.
Commits at the end if successfull, or it rollbacks the entire Commits at the end if successfull, or it rollbacks the entire
transaction. Handle with care!""" transaction. Handle with care!
"""
db = self.db db = self.db
sd = db.scheduler_task_deps sd = db.scheduler_task_deps
if job_name: if job_name:
@@ -223,14 +224,6 @@ class JobGraph(object):
db.rollback() db.rollback()
return None return None
def demo_function(*argv, **kwargs):
""" test function """
for i in range(argv[0]):
print 'click', i
time.sleep(1)
return 'done'
# the two functions below deal with simplejson decoding as unicode, esp for the dict decode # the two functions below deal with simplejson decoding as unicode, esp for the dict decode
# and subsequent usage as function Keyword arguments unicode variable names won't work! # and subsequent usage as function Keyword arguments unicode variable names won't work!
# borrowed from http://stackoverflow.com/questions/956867/how-to-get-string-objects-instead-unicode-ones-from-json-in-python # borrowed from http://stackoverflow.com/questions/956867/how-to-get-string-objects-instead-unicode-ones-from-json-in-python
@@ -261,11 +254,12 @@ def _decode_dict(dct):
def executor(queue, task, out): def executor(queue, task, out):
"""The function used to execute tasks in the background process""" """The function used to execute tasks in the background process."""
logger.debug(' task started') logger.debug(' task started')
class LogOutput(object): class LogOutput(object):
"""Facility to log output at intervals""" """Facility to log output at intervals."""
def __init__(self, out_queue): def __init__(self, out_queue):
self.out_queue = out_queue self.out_queue = out_queue
self.stdout = sys.stdout self.stdout = sys.stdout
@@ -280,7 +274,11 @@ def executor(queue, task, out):
def write(self, data): def write(self, data):
self.out_queue.put(data) self.out_queue.put(data)
W2P_TASK = Storage({'id': task.task_id, 'uuid': task.uuid}) W2P_TASK = Storage({
'id': task.task_id,
'uuid': task.uuid,
'run_id': task.run_id
})
stdout = LogOutput(out) stdout = LogOutput(out)
try: try:
if task.app: if task.app:
@@ -318,6 +316,11 @@ def executor(queue, task, out):
result = eval(task.function)( result = eval(task.function)(
*loads(task.args, object_hook=_decode_dict), *loads(task.args, object_hook=_decode_dict),
**loads(task.vars, object_hook=_decode_dict)) **loads(task.vars, object_hook=_decode_dict))
if len(result) >= 1024:
fd, temp_path = tempfile.mkstemp(suffix='.w2p_sched')
with os.fdopen(fd, 'w') as f:
f.write(result)
result = 'w2p_special:%s' % temp_path
queue.put(TaskReport('COMPLETED', result=result)) queue.put(TaskReport('COMPLETED', result=result))
except BaseException, e: except BaseException, e:
tb = traceback.format_exc() tb = traceback.format_exc()
@@ -335,7 +338,7 @@ class MetaScheduler(threading.Thread):
self.empty_runs = 0 self.empty_runs = 0
def async(self, task): def async(self, task):
"""Starts the background process """Start the background process.
Args: Args:
task : a `Task` object task : a `Task` object
@@ -410,6 +413,12 @@ class MetaScheduler(threading.Thread):
else: else:
logger.debug(' task completed or failed') logger.debug(' task completed or failed')
tr = queue.get() tr = queue.get()
result = tr.result
if result and result.startswith('w2p_special'):
temp_path = result.replace('w2p_special:', '', 1)
with open(temp_path) as f:
tr.result = f.read()
os.unlink(temp_path)
tr.output = task_output tr.output = task_output
return tr return tr
@@ -444,50 +453,23 @@ class MetaScheduler(threading.Thread):
self.start() self.start()
def send_heartbeat(self, counter): def send_heartbeat(self, counter):
print 'thum' raise NotImplementedError
time.sleep(1)
def pop_task(self): def pop_task(self):
"""Fetches a task ready to be executed""" """Fetches a task ready to be executed"""
return Task( raise NotImplementedError
app=None,
function='demo_function',
timeout=7,
args='[2]',
vars='{}')
def report_task(self, task, task_report): def report_task(self, task, task_report):
"""Creates a task report""" """Creates a task report"""
print 'reporting task' raise NotImplementedError
pass
def sleep(self): def sleep(self):
pass raise NotImplementedError
def loop(self): def loop(self):
"""Main loop, fetching tasks and starting executor's background """Main loop, fetching tasks and starting executor's background
processes""" processes"""
try: raise NotImplementedError
self.start_heartbeats()
while True and self.have_heartbeat:
logger.debug('looping...')
task = self.pop_task()
if task:
self.empty_runs = 0
self.report_task(task, self.async(task))
else:
self.empty_runs += 1
logger.debug('sleeping...')
if self.max_empty_runs != 0:
logger.debug('empty runs %s/%s',
self.empty_runs, self.max_empty_runs)
if self.empty_runs >= self.max_empty_runs:
logger.info(
'empty runs limit reached, killing myself')
self.die()
self.sleep()
except KeyboardInterrupt:
self.die()
TASK_STATUS = (QUEUED, RUNNING, COMPLETED, FAILED, TIMEOUT, STOPPED, EXPIRED) TASK_STATUS = (QUEUED, RUNNING, COMPLETED, FAILED, TIMEOUT, STOPPED, EXPIRED)
@@ -594,11 +576,11 @@ class Scheduler(MetaScheduler):
return True return True
def now(self): def now(self):
"""Shortcut that fetches current time based on UTC preferences""" """Shortcut that fetches current time based on UTC preferences."""
return self.utc_time and datetime.datetime.utcnow() or datetime.datetime.now() return self.utc_time and datetime.datetime.utcnow() or datetime.datetime.now()
def set_requirements(self, scheduler_task): def set_requirements(self, scheduler_task):
"""Called to set defaults for lazy_tables connections""" """Called to set defaults for lazy_tables connections."""
from gluon import current from gluon import current
if hasattr(current, 'request'): if hasattr(current, 'request'):
scheduler_task.application_name.default = '%s/%s' % ( scheduler_task.application_name.default = '%s/%s' % (
@@ -606,7 +588,7 @@ class Scheduler(MetaScheduler):
) )
def define_tables(self, db, migrate): def define_tables(self, db, migrate):
"""Defines Scheduler tables structure""" """Define Scheduler tables structure."""
from pydal.base import DEFAULT from pydal.base import DEFAULT
logger.debug('defining tables (migrate=%s)', migrate) logger.debug('defining tables (migrate=%s)', migrate)
now = self.now now = self.now
@@ -693,14 +675,14 @@ class Scheduler(MetaScheduler):
@staticmethod @staticmethod
def total_seconds(td): def total_seconds(td):
# backport for py2.6 """Backport for py2.6."""
if hasattr(td, 'total_seconds'): if hasattr(td, 'total_seconds'):
return td.total_seconds() return td.total_seconds()
else: else:
return (td.microseconds + (td.seconds + td.days * 24 * 3600) * 10 ** 6) / 10.0 ** 6 return (td.microseconds + (td.seconds + td.days * 24 * 3600) * 10 ** 6) / 10.0 ** 6
def loop(self, worker_name=None): def loop(self, worker_name=None):
"""Main loop """Main loop.
This works basically as a neverending loop that: This works basically as a neverending loop that:
@@ -752,7 +734,8 @@ class Scheduler(MetaScheduler):
self.die() self.die()
def wrapped_assign_tasks(self, db): def wrapped_assign_tasks(self, db):
"""Commodity function to call `assign_tasks` and trap exceptions """Commodity function to call `assign_tasks` and trap exceptions.
If an exception is raised, assume it happened because of database If an exception is raised, assume it happened because of database
contention and retries `assign_task` after 0.5 seconds contention and retries `assign_task` after 0.5 seconds
""" """
@@ -773,7 +756,8 @@ class Scheduler(MetaScheduler):
time.sleep(0.5) time.sleep(0.5)
def wrapped_pop_task(self): def wrapped_pop_task(self):
"""Commodity function to call `pop_task` and trap exceptions """Commodity function to call `pop_task` and trap exceptions.
If an exception is raised, assume it happened because of database If an exception is raised, assume it happened because of database
contention and retries `pop_task` after 0.5 seconds contention and retries `pop_task` after 0.5 seconds
""" """
@@ -793,7 +777,7 @@ class Scheduler(MetaScheduler):
time.sleep(0.5) time.sleep(0.5)
def pop_task(self, db): def pop_task(self, db):
"""Grabs a task ready to be executed from the queue""" """Grab a task ready to be executed from the queue."""
now = self.now() now = self.now()
st = self.db.scheduler_task st = self.db.scheduler_task
if self.is_a_ticker and self.do_assign_tasks: if self.is_a_ticker and self.do_assign_tasks:
@@ -874,7 +858,8 @@ class Scheduler(MetaScheduler):
uuid=task.uuid) uuid=task.uuid)
def wrapped_report_task(self, task, task_report): def wrapped_report_task(self, task, task_report):
"""Commodity function to call `report_task` and trap exceptions """Commodity function to call `report_task` and trap exceptions.
If an exception is raised, assume it happened because of database If an exception is raised, assume it happened because of database
contention and retries `pop_task` after 0.5 seconds contention and retries `pop_task` after 0.5 seconds
""" """
@@ -891,8 +876,10 @@ class Scheduler(MetaScheduler):
time.sleep(0.5) time.sleep(0.5)
def report_task(self, task, task_report): def report_task(self, task, task_report):
"""Takes care of storing the result according to preferences """Take care of storing the result according to preferences.
and deals with logic for repeating tasks"""
Deals with logic for repeating tasks.
"""
db = self.db db = self.db
now = self.now() now = self.now()
st = db.scheduler_task st = db.scheduler_task
@@ -914,12 +901,12 @@ class Scheduler(MetaScheduler):
logger.debug(' deleting task report in db because of no result') logger.debug(' deleting task report in db because of no result')
db(sr.id == task.run_id).delete() db(sr.id == task.run_id).delete()
# if there is a stop_time and the following run would exceed it # if there is a stop_time and the following run would exceed it
is_expired = (task.stop_time is_expired = (task.stop_time and
and task.next_run_time > task.stop_time task.next_run_time > task.stop_time and
and True or False) True or False)
status = (task.run_again and is_expired and EXPIRED status = (task.run_again and is_expired and EXPIRED or
or task.run_again and not is_expired task.run_again and not is_expired and
and QUEUED or COMPLETED) QUEUED or COMPLETED)
if task_report.status == COMPLETED: if task_report.status == COMPLETED:
d = dict(status=status, d = dict(status=status,
next_run_time=task.next_run_time, next_run_time=task.next_run_time,
@@ -945,27 +932,26 @@ class Scheduler(MetaScheduler):
logger.info('task completed (%s)', task_report.status) logger.info('task completed (%s)', task_report.status)
def update_dependencies(self, db, task_id): def update_dependencies(self, db, task_id):
"""Unblock execution paths for Jobs."""
db(db.scheduler_task_deps.task_child == task_id).update(can_visit=True) db(db.scheduler_task_deps.task_child == task_id).update(can_visit=True)
def adj_hibernation(self): def adj_hibernation(self):
"""Used to increase the "sleep" interval for DISABLED workers""" """Used to increase the "sleep" interval for DISABLED workers."""
if self.w_stats.status == DISABLED: if self.w_stats.status == DISABLED:
wk_st = self.w_stats.sleep wk_st = self.w_stats.sleep
hibernation = wk_st + HEARTBEAT if wk_st < MAXHIBERNATION else MAXHIBERNATION hibernation = wk_st + HEARTBEAT if wk_st < MAXHIBERNATION else MAXHIBERNATION
self.w_stats.sleep = hibernation self.w_stats.sleep = hibernation
def send_heartbeat(self, counter): def send_heartbeat(self, counter):
"""This function is vital for proper coordination among available """Coordination among available workers.
workers.
It:
It:
- sends the heartbeat - sends the heartbeat
- elects a ticker among available workers (the only process that - elects a ticker among available workers (the only process that
effectively dispatch tasks to workers) effectively dispatch tasks to workers)
- deals with worker's statuses - deals with worker's statuses
- does "housecleaning" for dead workers - does "housecleaning" for dead workers
- triggers tasks assignment to workers - triggers tasks assignment to workers
""" """
if not self.db_thread: if not self.db_thread:
logger.debug('thread building own DAL object') logger.debug('thread building own DAL object')
@@ -1053,7 +1039,8 @@ class Scheduler(MetaScheduler):
self.sleep() self.sleep()
def being_a_ticker(self): def being_a_ticker(self):
"""Elects a TICKER process that assigns tasks to available workers. """Elect a TICKER process that assigns tasks to available workers.
Does its best to elect a worker that is not busy processing other tasks Does its best to elect a worker that is not busy processing other tasks
to allow a proper distribution of tasks among all active workers ASAP to allow a proper distribution of tasks among all active workers ASAP
""" """
@@ -1087,7 +1074,7 @@ class Scheduler(MetaScheduler):
return False return False
def assign_tasks(self, db): def assign_tasks(self, db):
"""Assigns task to workers, that can then pop them from the queue """Assign task to workers, that can then pop them from the queue.
Deals with group_name(s) logic, in order to assign linearly tasks Deals with group_name(s) logic, in order to assign linearly tasks
to available workers for those groups to available workers for those groups
@@ -1137,9 +1124,6 @@ class Scheduler(MetaScheduler):
all_available = db( all_available = db(
(st.status.belongs((QUEUED, ASSIGNED))) & (st.status.belongs((QUEUED, ASSIGNED))) &
((st.times_run < st.repeats) | (st.repeats == 0)) &
(st.start_time <= now) &
((st.stop_time == None) | (st.stop_time > now)) &
(st.next_run_time <= now) & (st.next_run_time <= now) &
(st.enabled == True) & (st.enabled == True) &
(st.id.belongs(no_deps)) (st.id.belongs(no_deps))
@@ -1151,8 +1135,8 @@ class Scheduler(MetaScheduler):
# intelligence (like esteeming how many tasks will a worker complete # intelligence (like esteeming how many tasks will a worker complete
# before the ticker reassign them around, but the gain is quite small # before the ticker reassign them around, but the gain is quite small
# 50 is a sweet spot also for fast tasks, with sane heartbeat values # 50 is a sweet spot also for fast tasks, with sane heartbeat values
# NB: ticker reassign tasks every 5 cycles, so if a worker completes its # NB: ticker reassign tasks every 5 cycles, so if a worker completes
# 50 tasks in less than heartbeat*5 seconds, # its 50 tasks in less than heartbeat*5 seconds,
# it won't pick new tasks until heartbeat*5 seconds pass. # it won't pick new tasks until heartbeat*5 seconds pass.
# If a worker is currently elaborating a long task, its tasks needs to # If a worker is currently elaborating a long task, its tasks needs to
@@ -1165,7 +1149,7 @@ class Scheduler(MetaScheduler):
x = 0 x = 0
for group in wkgroups.keys(): for group in wkgroups.keys():
tasks = all_available(st.group_name == group).select( tasks = all_available(st.group_name == group).select(
limitby=(0, limit), orderby = st.next_run_time) limitby=(0, limit), orderby=st.next_run_time)
# let's break up the queue evenly among workers # let's break up the queue evenly among workers
for task in tasks: for task in tasks:
x += 1 x += 1
@@ -1183,8 +1167,6 @@ class Scheduler(MetaScheduler):
status=ASSIGNED, status=ASSIGNED,
assigned_worker_name=assigned_wn assigned_worker_name=assigned_wn
) )
if not task.task_name:
d['task_name'] = task.function_name
db( db(
(st.id == task.id) & (st.id == task.id) &
(st.status.belongs((QUEUED, ASSIGNED))) (st.status.belongs((QUEUED, ASSIGNED)))
@@ -1204,14 +1186,13 @@ class Scheduler(MetaScheduler):
logger.info('TICKER: tasks are %s', x) logger.info('TICKER: tasks are %s', x)
def sleep(self): def sleep(self):
"""Calculates the number of seconds to sleep according to worker's """Calculate the number of seconds to sleep."""
status and `heartbeat` parameter"""
time.sleep(self.w_stats.sleep) time.sleep(self.w_stats.sleep)
# should only sleep until next available task # should only sleep until next available task
def set_worker_status(self, group_names=None, action=ACTIVE, def set_worker_status(self, group_names=None, action=ACTIVE,
exclude=None, limit=None, worker_name=None): exclude=None, limit=None, worker_name=None):
"""Internal function to set worker's status""" """Internal function to set worker's status."""
ws = self.db.scheduler_worker ws = self.db.scheduler_worker
if not group_names: if not group_names:
group_names = self.group_names group_names = self.group_names
@@ -1235,10 +1216,12 @@ class Scheduler(MetaScheduler):
self.db(ws.id.belongs(workers)).update(status=action) self.db(ws.id.belongs(workers)).update(status=action)
def disable(self, group_names=None, limit=None, worker_name=None): def disable(self, group_names=None, limit=None, worker_name=None):
"""Sets DISABLED on the workers processing `group_names` tasks. """Set DISABLED on the workers processing `group_names` tasks.
A DISABLED worker will be kept alive but it won't be able to process A DISABLED worker will be kept alive but it won't be able to process
any waiting tasks, essentially putting it to sleep. any waiting tasks, essentially putting it to sleep.
By default, all group_names of Scheduler's instantation are selected""" By default, all group_names of Scheduler's instantation are selected
"""
self.set_worker_status( self.set_worker_status(
group_names=group_names, group_names=group_names,
action=DISABLED, action=DISABLED,
@@ -1283,8 +1266,9 @@ class Scheduler(MetaScheduler):
pvars: "raw" kwargs to be passed to the function. Automatically pvars: "raw" kwargs to be passed to the function. Automatically
jsonified jsonified
kwargs: all the parameters available (basically, every kwargs: all the parameters available (basically, every
`scheduler_task` column). If args and vars are here, they should `scheduler_task` column). If args and vars are here, they
be jsonified already, and they will override pargs and pvars should be jsonified already, and they will override pargs
and pvars
Returns: Returns:
a dict just as a normal validate_and_insert(), plus a uuid key a dict just as a normal validate_and_insert(), plus a uuid key
+1
View File
@@ -21,6 +21,7 @@ from test_contribs import *
from test_web import * from test_web import *
from test_dal import * from test_dal import *
from test_tools import * from test_tools import *
from test_appadmin import *
from test_scheduler import * from test_scheduler import *
if sys.version[:3] == '2.7': if sys.version[:3] == '2.7':
+175
View File
@@ -0,0 +1,175 @@
#!/bin/python
# -*- coding: utf-8 -*-
"""
Unit tests for gluon.sqlhtml
"""
import os
import sys
if sys.version < "2.7":
import unittest2 as unittest
else:
import unittest
from fix_path import fix_sys_path
fix_sys_path(__file__)
from compileapp import run_controller_in, run_view_in
from languages import translator
from gluon.storage import Storage, List
import gluon.fileutils
from gluon.dal import DAL, Field, Table
from gluon.http import HTTP
DEFAULT_URI = os.getenv('DB', 'sqlite:memory')
try:
import json
except ImportError:
from gluon.contrib import simplejson as json
def fake_check_credentials(foo):
return True
class TestAppAdmin(unittest.TestCase):
def setUp(self):
from gluon.globals import Request, Response, Session, current
from gluon.html import A, DIV, FORM, MENU, TABLE, TR, INPUT, URL, XML
from gluon.validators import IS_NOT_EMPTY
from compileapp import LOAD
from gluon.http import HTTP, redirect
from gluon.tools import Auth
from gluon.sql import SQLDB
from gluon.sqlhtml import SQLTABLE, SQLFORM
self.original_check_credentials = gluon.fileutils.check_credentials
gluon.fileutils.check_credentials = fake_check_credentials
request = Request(env={})
request.application = 'welcome'
request.controller = 'appadmin'
request.function = self._testMethodName.split('_')[1]
request.folder = 'applications/welcome'
request.env.http_host = '127.0.0.1:8000'
request.env.remote_addr = '127.0.0.1'
response = Response()
session = Session()
T = translator('', 'en')
session.connect(request, response)
current.request = request
current.response = response
current.session = session
current.T = T
db = DAL(DEFAULT_URI, check_reserved=['all'])
auth = Auth(db)
auth.define_tables(username=True, signature=False)
db.define_table('t0', Field('tt'), auth.signature)
# Create a user
db.auth_user.insert(first_name='Bart',
last_name='Simpson',
username='user1',
email='user1@test.com',
password='password_123',
registration_key=None,
registration_id=None)
self.env = locals()
def tearDown(self):
gluon.fileutils.check_credentials = self.original_check_credentials
def run_function(self):
return run_controller_in(self.env['request'].controller, self.env['request'].function, self.env)
def run_view(self):
return run_view_in(self.env)
def test_index(self):
result = self.run_function()
self.assertTrue('db' in result['databases'])
self.env.update(result)
try:
self.run_view()
except Exception as e:
print e.message
self.fail('Could not make the view')
def test_select(self):
request = self.env['request']
request.args = List(['db'])
request.env.query_string = 'query=db.auth_user.id>0'
result = self.run_function()
self.assertTrue('table' in result and 'query' in result)
self.assertTrue(result['table'] == 'auth_user')
self.assertTrue(result['query'] == 'db.auth_user.id>0')
self.env.update(result)
try:
self.run_view()
except Exception as e:
print e.message
self.fail('Could not make the view')
def test_insert(self):
request = self.env['request']
request.args = List(['db', 'auth_user'])
result = self.run_function()
self.assertTrue('table' in result)
self.assertTrue('form' in result)
self.assertTrue(str(result['table']) is 'auth_user')
self.env.update(result)
try:
self.run_view()
except Exception as e:
print e.message
self.fail('Could not make the view')
def test_insert_submit(self):
request = self.env['request']
request.args = List(['db', 'auth_user'])
form = self.run_function()['form']
hidden_fields = form.hidden_fields()
data = {}
data['_formkey'] = hidden_fields.element('input', _name='_formkey')['_value']
data['_formname'] = hidden_fields.element('input', _name='_formname')['_value']
data['first_name'] = 'Lisa'
data['last_name'] = 'Simpson'
data['username'] = 'lisasimpson'
data['password'] = 'password_123'
data['email'] = 'lisa@example.com'
request._vars = data
result = self.run_function()
self.env.update(result)
try:
self.run_view()
except Exception as e:
print e.message
self.fail('Could not make the view')
db = self.env['db']
lisa_record = db(db.auth_user.username == 'lisasimpson').select().first()
self.assertIsNotNone(lisa_record)
del data['_formkey']
del data['_formname']
del data['password']
for key in data:
self.assertEqual(data[key], lisa_record[key])
def test_update_submit(self):
request = self.env['request']
request.args = List(['db', 'auth_user', '1'])
form = self.run_function()['form']
hidden_fields = form.hidden_fields()
data = {}
data['_formkey'] = hidden_fields.element('input', _name='_formkey')['_value']
data['_formname'] = hidden_fields.element('input', _name='_formname')['_value']
for element in form.elements('input'):
data[element['_name']] = element['_value']
data['email'] = 'user1@example.com'
data['id'] = '1'
request._vars = data
self.assertRaises(HTTP, self.run_function)
if __name__ == '__main__':
unittest.main()
+27 -44
View File
@@ -98,6 +98,11 @@ class TestBareHelpers(unittest.TestCase):
self.assertEqual(rtn, '/a/c/f/x/y/z?p=1&p=3&q=2&_signature=5d01b982fd72b39674b012e0288071034e156d7a') self.assertEqual(rtn, '/a/c/f/x/y/z?p=1&p=3&q=2&_signature=5d01b982fd72b39674b012e0288071034e156d7a')
rtn = URL('a', 'c', 'f', args=['x', 'y', 'z'], vars={'p': (1, 3), 'q': 2}, hmac_key='key', hash_vars='p') rtn = URL('a', 'c', 'f', args=['x', 'y', 'z'], vars={'p': (1, 3), 'q': 2}, hmac_key='key', hash_vars='p')
self.assertEqual(rtn, '/a/c/f/x/y/z?p=1&p=3&q=2&_signature=5d01b982fd72b39674b012e0288071034e156d7a') self.assertEqual(rtn, '/a/c/f/x/y/z?p=1&p=3&q=2&_signature=5d01b982fd72b39674b012e0288071034e156d7a')
# test url_encode
rtn = URL('a', 'c', 'f', args=['x', 'y', 'z'], vars={'maï': (1, 3), 'lié': 2}, url_encode=False)
self.assertEqual(rtn, '/a/c/f/x/y/z?li\xc3\xa9=2&ma\xc3\xaf=1&ma\xc3\xaf=3')
rtn = URL('a', 'c', 'f', args=['x', 'y', 'z'], vars={'maï': (1, 3), 'lié': 2}, url_encode=True)
self.assertEqual(rtn, '/a/c/f/x/y/z?li%C3%A9=2&ma%C3%AF=1&ma%C3%AF=3')
# test CRLF detection # test CRLF detection
self.assertRaises(SyntaxError, URL, *['a\n', 'c', 'f']) self.assertRaises(SyntaxError, URL, *['a\n', 'c', 'f'])
self.assertRaises(SyntaxError, URL, *['a\r', 'c', 'f']) self.assertRaises(SyntaxError, URL, *['a\r', 'c', 'f'])
@@ -394,18 +399,12 @@ class TestBareHelpers(unittest.TestCase):
self.assertEqual(HR(_a='1', _b='2').xml(), '<hr a="1" b="2" />') self.assertEqual(HR(_a='1', _b='2').xml(), '<hr a="1" b="2" />')
def test_A(self): def test_A(self):
self.assertEqual( self.assertEqual(A('<>', _a='1', _b='2').xml(),
A('<>', _a='1', _b='2').xml(), '<a a="1" b="2">&lt;&gt;</a>')
'<a a="1" b="2">&lt;&gt;</a>' self.assertEqual(A('a', cid='b').xml(),
) '<a data-w2p_disable_with="default" data-w2p_method="GET" data-w2p_target="b">a</a>')
self.assertEqual( self.assertEqual(A('a', callback='b', _id='c').xml(),
A('a', cid='b').xml(), '<a data-w2p_disable_with="default" data-w2p_method="POST" href="b" id="c">a</a>')
'<a data-w2p_disable_with="default" data-w2p_method="GET" data-w2p_target="b">a</a>'
)
self.assertEqual(
A('a', callback='b', _id='c').xml(),
'<a data-w2p_disable_with="default" data-w2p_method="POST" href="b" id="c">a</a>'
)
# Callback with no id trigger web2py_uuid() call # Callback with no id trigger web2py_uuid() call
from html import web2pyHTMLParser from html import web2pyHTMLParser
a = A('a', callback='b').xml() a = A('a', callback='b').xml()
@@ -413,38 +412,22 @@ class TestBareHelpers(unittest.TestCase):
uuid_generated = tag.attributes['_id'] uuid_generated = tag.attributes['_id']
self.assertEqual(a, self.assertEqual(a,
'<a data-w2p_disable_with="default" data-w2p_method="POST" href="b" id="{id}">a</a>'.format(id=uuid_generated)) '<a data-w2p_disable_with="default" data-w2p_method="POST" href="b" id="{id}">a</a>'.format(id=uuid_generated))
self.assertEqual( self.assertEqual(A('a', delete='tr').xml(),
A('a', delete='tr').xml(), '<a data-w2p_disable_with="default" data-w2p_remove="tr">a</a>')
'<a data-w2p_disable_with="default" data-w2p_remove="tr">a</a>' self.assertEqual(A('a', _id='b', target='<self>').xml(),
) '<a data-w2p_disable_with="default" data-w2p_target="b" id="b">a</a>')
self.assertEqual( self.assertEqual(A('a', component='b').xml(),
A('a', _id='b', target='<self>').xml(), '<a data-w2p_disable_with="default" data-w2p_method="GET" href="b">a</a>')
'<a data-w2p_disable_with="default" data-w2p_target="b" id="b">a</a>' self.assertEqual(A('a', _id='b', callback='c', noconfirm=True).xml(),
) '<a data-w2p_disable_with="default" data-w2p_method="POST" href="c" id="b">a</a>')
self.assertEqual( self.assertEqual(A('a', cid='b').xml(),
A('a', component='b').xml(), '<a data-w2p_disable_with="default" data-w2p_method="GET" data-w2p_target="b">a</a>')
'<a data-w2p_disable_with="default" data-w2p_method="GET" href="b">a</a>' self.assertEqual(A('a', cid='b', _disable_with='processing...').xml(),
) '<a data-w2p_disable_with="processing..." data-w2p_method="GET" data-w2p_target="b">a</a>')
self.assertEqual( self.assertEqual(A('a', callback='b', delete='tr', noconfirm=True, _id='c').xml(),
A('a', _id='b', callback='c', noconfirm=True).xml(), '<a data-w2p_disable_with="default" data-w2p_method="POST" data-w2p_remove="tr" href="b" id="c">a</a>')
'<a data-w2p_disable_with="default" data-w2p_method="POST" href="c" id="b">a</a>' self.assertEqual(A('a', callback='b', delete='tr', confirm='Are you sure?', _id='c').xml(),
) '<a data-w2p_confirm="Are you sure?" data-w2p_disable_with="default" data-w2p_method="POST" data-w2p_remove="tr" href="b" id="c">a</a>')
self.assertEqual(
A('a', cid='b').xml(),
'<a data-w2p_disable_with="default" data-w2p_method="GET" data-w2p_target="b">a</a>'
)
self.assertEqual(
A('a', cid='b', _disable_with='processing...').xml(),
'<a data-w2p_disable_with="processing..." data-w2p_method="GET" data-w2p_target="b">a</a>'
)
self.assertEqual(
A('a', callback='b', delete='tr', noconfirm=True, _id='c').xml(),
'<a data-w2p_disable_with="default" data-w2p_method="POST" data-w2p_remove="tr" href="b" id="c">a</a>'
)
self.assertEqual(
A('a', callback='b', delete='tr', confirm='Are you sure?', _id='c').xml(),
'<a data-w2p_confirm="Are you sure?" data-w2p_disable_with="default" data-w2p_method="POST" data-w2p_remove="tr" href="b" id="c">a</a>'
)
def test_BUTTON(self): def test_BUTTON(self):
self.assertEqual(BUTTON('test', _type='button').xml(), self.assertEqual(BUTTON('test', _type='button').xml(),
+36 -2
View File
@@ -23,8 +23,8 @@ class TestRecfile(unittest.TestCase):
def tearDown(self): def tearDown(self):
shutil.rmtree('tests') shutil.rmtree('tests')
def testgeneration(self): def test_generation(self):
for k in range(20): for k in range(10):
teststring = 'test%s' % k teststring = 'test%s' % k
filename = os.path.join('tests', str(uuid.uuid4()) + '.test') filename = os.path.join('tests', str(uuid.uuid4()) + '.test')
with recfile.open(filename, "w") as g: with recfile.open(filename, "w") as g:
@@ -35,6 +35,40 @@ class TestRecfile(unittest.TestCase):
recfile.remove(filename) recfile.remove(filename)
is_there = recfile.exists(filename) is_there = recfile.exists(filename)
self.assertFalse(is_there) self.assertFalse(is_there)
for k in range(10):
teststring = 'test%s' % k
filename = str(uuid.uuid4()) + '.test'
with recfile.open(filename, "w", path='tests') as g:
g.write(teststring)
self.assertEqual(recfile.open(filename, "r", path='tests').read(), teststring)
is_there = recfile.exists(filename, path='tests')
self.assertTrue(is_there)
recfile.remove(filename, path='tests')
is_there = recfile.exists(filename, path='tests')
self.assertFalse(is_there)
for k in range(10):
teststring = 'test%s' % k
filename = os.path.join('tests', str(uuid.uuid4()), str(uuid.uuid4()) + '.test')
with recfile.open(filename, "w") as g:
g.write(teststring)
self.assertEqual(recfile.open(filename, "r").read(), teststring)
is_there = recfile.exists(filename)
self.assertTrue(is_there)
recfile.remove(filename)
is_there = recfile.exists(filename)
self.assertFalse(is_there)
def test_existing(self):
filename = os.path.join('tests', str(uuid.uuid4()) + '.test')
with open(filename, 'w') as g:
g.write('this file exists')
self.assertTrue(recfile.exists(filename))
self.assertTrue(hasattr(recfile.open(filename, "r"), 'read'))
recfile.remove(filename, path='tests')
self.assertFalse(recfile.exists(filename))
self.assertRaises(IOError, recfile.remove, filename)
self.assertRaises(IOError, recfile.open, filename, "r")
if __name__ == '__main__': if __name__ == '__main__':
unittest.main() unittest.main()
+204 -32
View File
@@ -9,6 +9,7 @@ import unittest
import glob import glob
import datetime import datetime
import sys import sys
from fix_path import fix_sys_path from fix_path import fix_sys_path
@@ -255,6 +256,14 @@ class testForSchedulerRunnerBase(BaseTestScheduler):
from gluon import current from gluon import current
fdest = os.path.join(current.request.folder, 'models', 'scheduler.py') fdest = os.path.join(current.request.folder, 'models', 'scheduler.py')
os.unlink(fdest) os.unlink(fdest)
additional_files = [
os.path.join(current.request.folder, 'private', 'demo8.pholder')
]
for f in additional_files:
try:
os.unlink(f)
except:
pass
def writefunction(self, content, initlines=None): def writefunction(self, content, initlines=None):
from gluon import current from gluon import current
@@ -266,7 +275,7 @@ import time
from gluon.scheduler import Scheduler from gluon.scheduler import Scheduler
db_dal = os.path.abspath(os.path.join(request.folder, '..', '..', 'dummy2.db')) db_dal = os.path.abspath(os.path.join(request.folder, '..', '..', 'dummy2.db'))
sched_dal = DAL('sqlite://%s' % db_dal, folder=os.path.dirname(db_dal)) sched_dal = DAL('sqlite://%s' % db_dal, folder=os.path.dirname(db_dal))
sched = Scheduler(sched_dal, max_empty_runs=20, migrate=False, heartbeat=1) sched = Scheduler(sched_dal, max_empty_runs=15, migrate=False, heartbeat=1)
""" """
with open(fdest, 'w') as q: with open(fdest, 'w') as q:
q.write(initlines) q.write(initlines)
@@ -274,34 +283,179 @@ sched = Scheduler(sched_dal, max_empty_runs=20, migrate=False, heartbeat=1)
def exec_sched(self): def exec_sched(self):
import subprocess import subprocess
call_args = [sys.executable, 'web2py.py', '-K', 'welcome'] call_args = [sys.executable, 'web2py.py', '--no-banner', '-D', '20','-K', 'welcome']
ret = subprocess.call(call_args, env=dict(os.environ)) ret = subprocess.call(call_args, env=dict(os.environ))
return ret return ret
def fetch_results(self, sched, task):
info = sched.task_status(task.id)
task_runs = self.db(self.db.scheduler_run.task_id == task.id).select()
return info, task_runs
def exec_asserts(self, stmts, tag):
for stmt in stmts:
self.assertEqual(stmt[1], True, msg="%s - %s" % (tag, stmt[0]))
class TestsForSchedulerRunner(testForSchedulerRunnerBase): class TestsForSchedulerRunner(testForSchedulerRunnerBase):
def testBasic(self): def testRepeats_and_Expired_and_Prio(self):
s = Scheduler(self.db) s = Scheduler(self.db)
foo = s.queue_task('foo') repeats = s.queue_task('demo1', ['a', 'b'], dict(c=1, d=2), repeats=2, period=5)
a_while_ago = datetime.datetime.now() - datetime.timedelta(seconds=60)
expired = s.queue_task('demo4', stop_time=a_while_ago)
prio1 = s.queue_task('demo1', ['scheduled_first'])
prio2 = s.queue_task('demo1', ['scheduled_second'], next_run_time=a_while_ago)
self.db.commit() self.db.commit()
self.writefunction(r""" self.writefunction(r"""
def foo(): def demo1(*args,**vars):
return 'a' print 'you passed args=%s and vars=%s' % (args, vars)
return args[0]
def demo4():
time.sleep(15)
print "I'm printing something"
return dict(a=1, b=2)
""") """)
ret = self.exec_sched() ret = self.exec_sched()
# process finished just fine
self.assertEqual(ret, 0) self.assertEqual(ret, 0)
info = s.task_status(foo.id, output=True) # repeats check
self.assertEqual(info.result, 'a') task, task_run = self.fetch_results(s, repeats)
res = [
("task status completed", task.status == 'COMPLETED'),
("task times_run is 2", task.times_run == 2),
("task ran 2 times only", len(task_run) == 2),
("scheduler_run records are COMPLETED ", (task_run[0].status == task_run[1].status == 'COMPLETED')),
("period is respected", (task_run[1].start_time > task_run[0].start_time + datetime.timedelta(seconds=task.period)))
]
self.exec_asserts(res, 'REPEATS')
# expired check
task, task_run = self.fetch_results(s, expired)
res = [
("task status expired", task.status == 'EXPIRED'),
("task times_run is 0", task.times_run == 0),
("task didn't run at all", len(task_run) == 0)
]
self.exec_asserts(res, 'EXPIRATION')
# prio check
task1 = s.task_status(prio1.id, output=True)
task2 = s.task_status(prio2.id, output=True)
res = [
("tasks status completed", task1.scheduler_task.status == task2.scheduler_task.status == 'COMPLETED'),
("priority2 was executed before priority1" , task1.scheduler_run.id > task2.scheduler_run.id)
]
self.exec_asserts(res, 'PRIORITY')
def testNoReturn_and_Timeout_and_Progress(self):
s = Scheduler(self.db)
noret1 = s.queue_task('demo5')
noret2 = s.queue_task('demo3')
timeout1 = s.queue_task('demo4', timeout=5)
timeout2 = s.queue_task('demo4')
progress = s.queue_task('demo6', sync_output=2)
self.db.commit()
self.writefunction(r"""
def demo3():
time.sleep(15)
print 1/0
return None
def demo4():
time.sleep(15)
print "I'm printing something"
return dict(a=1, b=2)
def demo5():
time.sleep(15)
print "I'm printing something"
rtn = dict(a=1, b=2)
def demo6():
time.sleep(5)
print '50%'
time.sleep(5)
print '!clear!100%'
return 1
""")
ret = self.exec_sched()
self.assertEqual(ret, 0)
# noreturn check
task1, task_run1 = self.fetch_results(s, noret1)
task2, task_run2 = self.fetch_results(s, noret2)
res = [
("tasks no_returns1 completed", task1.status == 'COMPLETED'),
("tasks no_returns2 failed", task2.status == 'FAILED'),
("no_returns1 doesn't have a scheduler_run record", len(task_run1) == 0),
("no_returns2 has a scheduler_run record FAILED", (len(task_run2) == 1 and task_run2[0].status == 'FAILED')),
]
self.exec_asserts(res, 'NO_RETURN')
# timeout check
task1 = s.task_status(timeout1.id, output=True)
task2 = s.task_status(timeout2.id, output=True)
res = [
("tasks timeouts1 timeoutted", task1.scheduler_task.status == 'TIMEOUT'),
("tasks timeouts2 completed", task2.scheduler_task.status == 'COMPLETED')
]
self.exec_asserts(res, 'TIMEOUT')
# progress check
task1 = s.task_status(progress.id, output=True)
res = [
("tasks percentages completed", task1.scheduler_task.status == 'COMPLETED'),
("output contains only 100%", task1.scheduler_run.run_output.strip() == "100%")
]
self.exec_asserts(res, 'PROGRESS')
def testDrift_and_env_and_immediate(self):
s = Scheduler(self.db)
immediate = s.queue_task('demo1', ['a', 'b'], dict(c=1, d=2), immediate=True)
env = s.queue_task('demo7')
drift = s.queue_task('demo1', ['a', 'b'], dict(c=1, d=2), period=93, prevent_drift=True)
self.db.commit()
self.writefunction(r"""
def demo1(*args,**vars):
print 'you passed args=%s and vars=%s' % (args, vars)
return args[0]
import random
def demo7():
time.sleep(random.randint(1,5))
print W2P_TASK, request.now
return W2P_TASK.id, W2P_TASK.uuid, W2P_TASK.run_id
""")
ret = self.exec_sched()
self.assertEqual(ret, 0)
# immediate check, can only check that nothing breaks
task1 = s.task_status(immediate.id)
res = [
("tasks status completed", task1.status == 'COMPLETED'),
]
self.exec_asserts(res, 'IMMEDIATE')
# drift check
task, task_run = self.fetch_results(s, drift)
res = [
("task status completed", task.status == 'COMPLETED'),
("next_run_time is exactly start_time + period", (task.next_run_time == task.start_time + datetime.timedelta(seconds=task.period)))
]
self.exec_asserts(res, 'DRIFT')
# env check
task1 = s.task_status(env.id, output=True)
res = [
("task %s returned W2P_TASK correctly" % (task1.scheduler_task.id), task1.result == [task1.scheduler_task.id, task1.scheduler_task.uuid, task1.scheduler_run.id]),
]
self.exec_asserts(res, 'ENV')
def testRetryFailed(self): def testRetryFailed(self):
s = Scheduler(self.db) s = Scheduler(self.db)
failed = s.queue_task('demo2', retry_failed=1, period=5) failed = s.queue_task('demo2', retry_failed=1, period=1)
failed_consecutive = s.queue_task('demo8', retry_failed=2, repeats=2, period=5) failed_consecutive = s.queue_task('demo8', retry_failed=2, repeats=2, period=1)
self.db.commit() self.db.commit()
self.writefunction(r""" self.writefunction(r"""
def demo2(): def demo2():
1/0 1/0
@@ -323,32 +477,50 @@ def demo8():
# process finished just fine # process finished just fine
self.assertEqual(ret, 0) self.assertEqual(ret, 0)
# failed - checks # failed - checks
info = s.task_status(failed.id) task, task_run = self.fetch_results(s, failed)
task_runs = self.db(self.db.scheduler_run.task_id == info.id).select()
res = [ res = [
("task status failed", info.status == 'FAILED'), ("task status failed", task.status == 'FAILED'),
("task times_run is 0", info.times_run == 0), ("task times_run is 0", task.times_run == 0),
("task times_failed is 2", info.times_failed == 2), ("task times_failed is 2", task.times_failed == 2),
("task ran 2 times only", len(task_runs) == 2), ("task ran 2 times only", len(task_run) == 2),
("scheduler_run records are FAILED", (task_runs[0].status == task_runs[1].status == 'FAILED')), ("scheduler_run records are FAILED", (task_run[0].status == task_run[1].status == 'FAILED')),
("period is respected", (task_runs[1].start_time > task_runs[0].start_time + datetime.timedelta(seconds=info.period))) ("period is respected", (task_run[1].start_time > task_run[0].start_time + datetime.timedelta(seconds=task.period)))
] ]
for a in res: self.exec_asserts(res, 'FAILED')
self.assertEqual(a[1], True, msg=a[0])
# failed consecutive - checks # failed consecutive - checks
info = s.task_status(failed_consecutive.id) task, task_run = self.fetch_results(s, failed_consecutive)
task_runs = self.db(self.db.scheduler_run.task_id == info.id).select()
res = [ res = [
("task status completed", info.status == 'COMPLETED'), ("task status completed", task.status == 'COMPLETED'),
("task times_run is 2", info.times_run == 2), ("task times_run is 2", task.times_run == 2),
("task times_failed is 0", info.times_failed == 0), ("task times_failed is 0", task.times_failed == 0),
("task ran 6 times", len(task_runs) == 6), ("task ran 6 times", len(task_run) == 6),
("scheduler_run records for COMPLETED is 2", len([run.status for run in task_runs if run.status == 'COMPLETED']) == 2), ("scheduler_run records for COMPLETED is 2", len([run.status for run in task_run if run.status == 'COMPLETED']) == 2),
("scheduler_run records for FAILED is 4", len([run.status for run in task_runs if run.status == 'FAILED']) == 4), ("scheduler_run records for FAILED is 4", len([run.status for run in task_run if run.status == 'FAILED']) == 4),
] ]
for a in res: self.exec_asserts(res, 'FAILED_CONSECUTIVE')
self.assertEqual(a[1], True, msg=a[0])
def testHugeResult(self):
s = Scheduler(self.db)
huge_result = s.queue_task('demo10', retry_failed=1, period=1)
self.db.commit()
self.writefunction(r"""
def demo10():
res = 'a' * 99999
return dict(res=res)
""")
ret = self.exec_sched()
# process finished just fine
self.assertEqual(ret, 0)
# huge_result - checks
task = s.task_status(huge_result.id, output=True)
res = [
("task status completed", task.scheduler_task.status == 'COMPLETED'),
("task times_run is 1", task.scheduler_task.times_run == 1),
("result is the correct one", task.result == dict(res='a' * 99999))
]
self.exec_asserts(res, 'HUGE_RESULT')
if __name__ == '__main__': if __name__ == '__main__':
unittest.main() unittest.main()
+8 -1
View File
@@ -30,8 +30,15 @@ if __name__ == '__main__':
dest="source", dest="source",
help="Specify language file (ro) where seek for translations" help="Specify language file (ro) where seek for translations"
) )
parser.add_argument(
'-f', '--force-update',
dest="force_update",
action="store_true",
default=False,
help="without it: add new + translate untranslated, if used: in addition update items if translation differs"
)
args = parser.parse_args() args = parser.parse_args()
update_from_langfile(args.target, args.source) update_from_langfile(args.target, args.source, force_update=args.force_update)
print '%s was updated.' % args.target print '%s was updated.' % args.target