kopia lustrzana https://github.com/OpenDroneMap/WebODM
150 wiersze
4.7 KiB
Python
150 wiersze
4.7 KiB
Python
import time, redis
|
|
|
|
import logging
|
|
|
|
import marshal
|
|
import types
|
|
|
|
import json
|
|
|
|
from webodm import settings
|
|
|
|
logger = logging.getLogger('app.logger')
|
|
|
|
class TestWatch:
|
|
def __init__(self):
|
|
self.clear()
|
|
|
|
def func_to_name(f):
|
|
return "{}.{}".format(f.__module__, f.__name__)
|
|
|
|
def clear(self):
|
|
self._calls = {}
|
|
self._intercept_list = {}
|
|
|
|
def intercept(self, fname, f = None):
|
|
self._intercept_list[fname] = f if f is not None else True
|
|
|
|
def intercept_list_has(self, fname):
|
|
return fname in self._intercept_list
|
|
|
|
def execute_intercept_function_replacement(self, fname, *args, **kwargs):
|
|
if self.intercept_list_has(fname) and callable(self._intercept_list[fname]):
|
|
(self._intercept_list[fname])(*args, **kwargs)
|
|
|
|
def get_calls(self, fname):
|
|
return self._calls[fname] if fname in self._calls else []
|
|
|
|
def set_calls(self, fname, value):
|
|
self._calls[fname] = value
|
|
|
|
def should_prevent_execution(self, func):
|
|
return self.intercept_list_has(TestWatch.func_to_name(func))
|
|
|
|
def get_calls_count(self, fname):
|
|
return len(self.get_calls(fname))
|
|
|
|
def wait_until_call(self, fname, count = 1, timeout = 30):
|
|
SLEEP_INTERVAL = 0.125
|
|
TIMEOUT_LIMIT = timeout / SLEEP_INTERVAL
|
|
c = 0
|
|
while self.get_calls_count(fname) < count and c < TIMEOUT_LIMIT:
|
|
time.sleep(SLEEP_INTERVAL)
|
|
c += 1
|
|
|
|
if c >= TIMEOUT_LIMIT:
|
|
raise TimeoutError("wait_until_call has timed out waiting for {}".format(fname))
|
|
|
|
return self.get_calls(fname)
|
|
|
|
def log_call(self, func, *args, **kwargs):
|
|
fname = TestWatch.func_to_name(func)
|
|
logger.info("{} called".format(fname))
|
|
list = self.get_calls(fname)
|
|
list.append({'f': fname, 'args': args, 'kwargs': kwargs})
|
|
self.set_calls(fname, list)
|
|
|
|
def hook_pre(self, func, *args, **kwargs):
|
|
if settings.TESTING and self.should_prevent_execution(func):
|
|
fname = TestWatch.func_to_name(func)
|
|
logger.info(fname + " intercepted")
|
|
self.execute_intercept_function_replacement(fname, *args, **kwargs)
|
|
self.log_call(func, *args, **kwargs)
|
|
return True # Intercept
|
|
return False # Do not intercept
|
|
|
|
def hook_post(self, func, *args, **kwargs):
|
|
if settings.TESTING:
|
|
self.log_call(func, *args, **kwargs)
|
|
|
|
def watch(**kwargs):
|
|
"""
|
|
Decorator that adds pre/post hook calls
|
|
"""
|
|
tw = kwargs.get('testWatch', testWatch)
|
|
def outer(func):
|
|
def wrapper(*args, **kwargs):
|
|
if tw.hook_pre(func, *args, **kwargs): return
|
|
ret = func(*args, **kwargs)
|
|
tw.hook_post(func, *args, **kwargs)
|
|
return ret
|
|
return wrapper
|
|
return outer
|
|
|
|
"""
|
|
Redis-backed test watch
|
|
suitable for cross-machine/cross-process
|
|
test watching
|
|
"""
|
|
class SharedTestWatch(TestWatch):
|
|
"""
|
|
:param redis_url same as celery broker URL, for ex. redis://localhost:1234
|
|
"""
|
|
def needs_redis(func):
|
|
# Lazy evaluator for redis instance
|
|
def wrapper(self, *args):
|
|
if not hasattr(self, 'r'):
|
|
self.r = redis.from_url(self.redis_url)
|
|
return func(self, *args)
|
|
return wrapper
|
|
|
|
def __init__(self, redis_url):
|
|
self.redis_url = redis_url
|
|
super().__init__()
|
|
|
|
@needs_redis
|
|
def clear(self):
|
|
self.r.delete('testwatch:calls', 'testwatch:intercept_list')
|
|
|
|
@needs_redis
|
|
def intercept(self, fname, f = None):
|
|
self.r.hmset('testwatch:intercept_list', {fname: marshal.dumps(f.__code__) if f is not None else 1})
|
|
|
|
@needs_redis
|
|
def intercept_list_has(self, fname):
|
|
return self.r.hget('testwatch:intercept_list', fname) is not None
|
|
|
|
@needs_redis
|
|
def execute_intercept_function_replacement(self, fname, *args, **kwargs):
|
|
if self.intercept_list_has(fname) and self.r.hget('testwatch:intercept_list', fname) != b'1':
|
|
# Rebuild function
|
|
fcode = self.r.hget('testwatch:intercept_list', fname)
|
|
f = types.FunctionType(marshal.loads(fcode), globals())
|
|
f(*args, **kwargs)
|
|
|
|
@needs_redis
|
|
def get_calls(self, fname):
|
|
value = self.r.hget('testwatch:calls', fname)
|
|
if value is None: return []
|
|
else:
|
|
return json.loads(value.decode('utf-8'))
|
|
|
|
@needs_redis
|
|
def set_calls(self, fname, value):
|
|
self.r.hmset('testwatch:calls', {fname: json.dumps(value)})
|
|
|
|
def watch(**kwargs):
|
|
return TestWatch.watch(tw=sharedTestWatch, **kwargs)
|
|
|
|
testWatch = TestWatch()
|
|
sharedTestWatch = SharedTestWatch(settings.CELERY_BROKER_URL) |