summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--module/common/APIExerciser.py155
-rw-r--r--module/common/__init__.py2
-rw-r--r--module/database/DatabaseBackend.py9
-rw-r--r--module/remote/JSONClient.py52
-rw-r--r--module/remote/WSClient.py41
-rw-r--r--module/remote/WebSocketBackend.py6
-rw-r--r--module/remote/json_converter.py26
-rw-r--r--module/remote/wsbackend/AbstractHandler.py29
-rw-r--r--module/remote/wsbackend/ApiHandler.py22
-rw-r--r--module/remote/wsbackend/AsyncHandler.py112
-rw-r--r--module/remote/wsbackend/EventHandler.py41
-rw-r--r--module/web/api_app.py2
-rw-r--r--module/web/utils.py8
-rwxr-xr-xpyLoadCore.py2
-rw-r--r--tests/__init__.py0
-rw-r--r--tests/api/ApiProxy.py68
-rw-r--r--tests/api/ApiTester.py35
-rw-r--r--tests/api/__init__.py0
-rw-r--r--tests/api/test_JSONBackend.py27
-rw-r--r--tests/api/test_api.py39
-rw-r--r--tests/api/test_noargs.py29
-rw-r--r--tests/config/db.version1
-rw-r--r--tests/helper/Stubs.py3
-rw-r--r--tests/manager/__init__.py0
-rw-r--r--tests/manager/test_filemanager.py (renamed from tests/test_filemanager.py)4
-rw-r--r--tests/manager/test_interactionManager.py (renamed from tests/test_interactionManager.py)2
-rw-r--r--tests/other/__init__.py0
-rw-r--r--tests/other/test_configparser.py (renamed from tests/test_configparser.py)7
-rw-r--r--tests/other/test_database.py (renamed from tests/test_database.py)7
-rw-r--r--tests/other/test_syntax.py (renamed from tests/test_syntax.py)4
-rw-r--r--tests/test_api.py18
-rw-r--r--tests/test_backends.py59
32 files changed, 477 insertions, 333 deletions
diff --git a/module/common/APIExerciser.py b/module/common/APIExerciser.py
deleted file mode 100644
index 37ba82b47..000000000
--- a/module/common/APIExerciser.py
+++ /dev/null
@@ -1,155 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import string
-from threading import Thread
-from random import choice, random, sample, randint
-from time import time, sleep
-from math import floor
-import gc
-
-from traceback import print_exc, format_exc
-
-def createURLs():
- """ create some urls, some may fail """
- urls = []
- for x in range(0, randint(20, 100)):
- name = "DEBUG_API"
- if randint(0, 5) == 5:
- name = "" #this link will fail
-
- urls.append(name + "".join(sample(string.ascii_letters, randint(10, 20))))
-
- return urls
-
-AVOID = (0,3,8)
-
-idPool = 0
-sumCalled = 0
-
-
-def startApiExerciser(core, n):
- for i in range(n):
- APIExerciser(core).start()
-
-class APIExerciser(Thread):
-
-
- def __init__(self, core, thrift=False, user=None, pw=None):
- global idPool
-
- Thread.__init__(self)
- self.setDaemon(True)
- self.core = core
- self.count = 0 #number of methods
- self.time = time()
-
- if thrift:
- self.api = ThriftClient(user=user, password=pw)
- else:
- self.api = core.api
-
-
- self.id = idPool
-
- idPool += 1
-
- #self.start()
-
- def run(self):
-
- self.core.log.info("API Exerciser started %d" % self.id)
-
- out = open("error.log", "ab")
- #core errors are not logged of course
- out.write("\n" + "Starting\n")
- out.flush()
-
- while True:
- try:
- self.testAPI()
- except Exception:
- self.core.log.error("Exerciser %d throw an exception" % self.id)
- print_exc()
- out.write(format_exc() + 2 * "\n")
- out.flush()
-
- if not self.count % 100:
- self.core.log.info("Exerciser %d tested %d api calls" % (self.id, self.count))
- if not self.count % 1000:
- out.flush()
-
- if not sumCalled % 1000: #not thread safe
- self.core.log.info("Exercisers tested %d api calls" % sumCalled)
- persec = sumCalled / (time() - self.time)
- self.core.log.info("Approx. %.2f calls per second." % persec)
- self.core.log.info("Approx. %.2f ms per call." % (1000 / persec))
- self.core.log.info("Collected garbage: %d" % gc.collect())
-
-
- #sleep(random() / 500)
-
- def testAPI(self):
- global sumCalled
-
- m = ["statusDownloads", "statusServer", "addPackage", "getPackageData", "getFileData", "deleteFiles",
- "deletePackages", "getQueue", "getCollector", "getQueueData", "getCollectorData", "isCaptchaWaiting",
- "getCaptchaTask", "stopAllDownloads", "getAllInfo", "getServices" , "getAccounts", "getAllUserData"]
-
- method = choice(m)
- #print "Testing:", method
-
- if hasattr(self, method):
- res = getattr(self, method)()
- else:
- res = getattr(self.api, method)()
-
- self.count += 1
- sumCalled += 1
-
- #print res
-
- def addPackage(self):
- name = "".join(sample(string.ascii_letters, 10))
- urls = createURLs()
-
- self.api.addPackage(name, urls, choice([Destination.Queue, Destination.Collector]), "")
-
-
- def deleteFiles(self):
- info = self.api.getQueueData()
- if not info: return
-
- pack = choice(info)
- fids = pack.links
-
- if len(fids):
- fids = [f.fid for f in sample(fids, randint(1, max(len(fids) / 2, 1)))]
- self.api.deleteFiles(fids)
-
-
- def deletePackages(self):
- info = choice([self.api.getQueue(), self.api.getCollector()])
- if not info: return
-
- pids = [p.pid for p in info]
- if len(pids):
- pids = sample(pids, randint(1, max(floor(len(pids) / 2.5), 1)))
- self.api.deletePackages(pids)
-
- def getFileData(self):
- info = self.api.getQueueData()
- if info:
- p = choice(info)
- if p.links:
- self.api.getFileData(choice(p.links).fid)
-
- def getPackageData(self):
- info = self.api.getQueue()
- if info:
- self.api.getPackageData(choice(info).pid)
-
- def getAccounts(self):
- self.api.getAccounts(False)
-
- def getCaptchaTask(self):
- self.api.getCaptchaTask(False)
diff --git a/module/common/__init__.py b/module/common/__init__.py
index de6d13128..e69de29bb 100644
--- a/module/common/__init__.py
+++ b/module/common/__init__.py
@@ -1,2 +0,0 @@
-__author__ = 'christian'
- \ No newline at end of file
diff --git a/module/database/DatabaseBackend.py b/module/database/DatabaseBackend.py
index b22f8ffc5..6e67c799a 100644
--- a/module/database/DatabaseBackend.py
+++ b/module/database/DatabaseBackend.py
@@ -190,19 +190,18 @@ class DatabaseBackend(Thread):
j = self.jobs.get()
if j == "quit":
self.c.close()
+ self.conn.commit()
self.conn.close()
+ self.closing.set()
break
j.processJob()
def shutdown(self):
self.running.clear()
- self._shutdown()
-
- @queue
- def _shutdown(self):
- self.conn.commit()
+ self.closing = Event()
self.jobs.put("quit")
+ self.closing.wait(1)
def _checkVersion(self):
""" get db version"""
diff --git a/module/remote/JSONClient.py b/module/remote/JSONClient.py
new file mode 100644
index 000000000..52b712c81
--- /dev/null
+++ b/module/remote/JSONClient.py
@@ -0,0 +1,52 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+from json_converter import loads, dumps
+from urllib import urlopen, urlencode
+from httplib import UNAUTHORIZED, FORBIDDEN
+
+class JSONClient:
+ URL = "http://localhost:8001/api"
+
+ def __init__(self, url=None):
+ self.url = url or self.URL
+ self.session = None
+
+ def request(self, path, data):
+ ret = urlopen(self.url + path, urlencode(data))
+ if ret.code == 404:
+ raise AttributeError("Unknown Method")
+ if ret.code == 500:
+ raise Exception("Remote Exception")
+ if ret.code == UNAUTHORIZED:
+ raise Exception("Unauthorized")
+ if ret.code == FORBIDDEN:
+ raise Exception("Forbidden")
+ return ret.read()
+
+ def login(self, username, password):
+ self.session = loads(self.request("/login", {'username': username, 'password': password}))
+ return self.session
+
+ def logout(self):
+ self.call("logout")
+ self.session = None
+
+ def call(self, func, *args, **kwargs):
+ # Add the current session
+ kwargs["session"] = self.session
+ path = "/" + func + "/" + "/".join(dumps(x) for x in args)
+ data = dict((k, dumps(v)) for k, v in kwargs.iteritems())
+ rep = self.request(path, data)
+ return loads(rep)
+
+ def __getattr__(self, item):
+ def call(*args, **kwargs):
+ return self.call(item, *args, **kwargs)
+
+ return call
+
+if __name__ == "__main__":
+ api = JSONClient()
+ api.login("User", "test")
+ print api.getServerVersion() \ No newline at end of file
diff --git a/module/remote/WSClient.py b/module/remote/WSClient.py
new file mode 100644
index 000000000..c06bab661
--- /dev/null
+++ b/module/remote/WSClient.py
@@ -0,0 +1,41 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+from json_converter import loads, dumps
+from websocket import create_connection
+
+class WSClient:
+ URL = "ws://localhost:7227/api"
+
+ def __init__(self, url=None):
+ self.url = url or self.URL
+ self.ws = None
+
+ def login(self, username, password):
+ self.ws = create_connection(self.URL)
+ return self.call("login", username, password)
+
+ def logout(self):
+ self.call("logout")
+ self.ws.close()
+
+ def call(self, func, *args, **kwargs):
+ self.ws.send(dumps([func, args, kwargs]))
+ code, result = loads(self.ws.recv())
+ if code == 404:
+ raise AttributeError("Unknown Method")
+ elif code == 505:
+ raise Exception("Remote Exception")
+
+ return result
+
+ def __getattr__(self, item):
+ def call(*args, **kwargs):
+ return self.call(item, *args, **kwargs)
+
+ return call
+
+if __name__ == "__main__":
+ api = WSClient()
+ api.login("User", "test")
+ print api.getServerVersion() \ No newline at end of file
diff --git a/module/remote/WebSocketBackend.py b/module/remote/WebSocketBackend.py
index 6e01dad87..2d22664c6 100644
--- a/module/remote/WebSocketBackend.py
+++ b/module/remote/WebSocketBackend.py
@@ -33,14 +33,14 @@ class WebSocketBackend(BackendBase):
from wsbackend.Server import WebSocketServer, DefaultOptions
from wsbackend.Dispatcher import Dispatcher
from wsbackend.ApiHandler import ApiHandler
- from wsbackend.EventHandler import EventHandler
+ from wsbackend.AsyncHandler import AsyncHandler
options = DefaultOptions()
options.server_host = host
options.port = port
options.dispatcher = Dispatcher()
- options.dispatcher.addHandler('/api', ApiHandler(self.core.api))
- options.dispatcher.addHandler('/events', EventHandler(self.core.api))
+ options.dispatcher.addHandler(ApiHandler.PATH, ApiHandler(self.core.api))
+ options.dispatcher.addHandler(AsyncHandler.PATH, AsyncHandler(self.core.api))
self.server = WebSocketServer(options)
diff --git a/module/remote/json_converter.py b/module/remote/json_converter.py
index 57e85fd16..ea76842a6 100644
--- a/module/remote/json_converter.py
+++ b/module/remote/json_converter.py
@@ -1,7 +1,13 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
-from module.common.json_layer import json
+try:
+ from module.common.json_layer import json
+except ImportError:
+ import json
+
+
+import ttypes
from ttypes import BaseObject
# json encoder that accepts TBase objects
@@ -16,18 +22,12 @@ class BaseEncoder(json.JSONEncoder):
return json.JSONEncoder.default(self, o)
-class BaseDecoder(json.JSONDecoder):
-
- def __init__(self, *args, **kwargs):
- json.JSONDecoder.__init__(self, *args, **kwargs)
- self.object_hook = self.convertObject
-
- def convertObject(self, dct):
- if '@class' in dct:
- # TODO: convert
- pass
- return dct
+def convert_obj(dct):
+ if '@class' in dct:
+ cls = getattr(ttypes, dct['@class'])
+ del dct['@class']
+ return cls(**dct)
def dumps(*args, **kwargs):
kwargs['cls'] = BaseEncoder
@@ -35,5 +35,5 @@ def dumps(*args, **kwargs):
def loads(*args, **kwargs):
- kwargs['cls'] = BaseDecoder
+ kwargs['object_hook'] = convert_obj
return json.loads(*args, **kwargs) \ No newline at end of file
diff --git a/module/remote/wsbackend/AbstractHandler.py b/module/remote/wsbackend/AbstractHandler.py
index 291dbf100..276f6fa38 100644
--- a/module/remote/wsbackend/AbstractHandler.py
+++ b/module/remote/wsbackend/AbstractHandler.py
@@ -25,10 +25,18 @@ class AbstractHandler:
"""
Abstract Handler providing common methods shared across WebSocket handlers
"""
+ PATH = "/"
+
+ OK = 200
+ UNAUTHORIZED = 401
+ FORBIDDEN = 403
+ NOT_FOUND = 404
+ ERROR = 500
def __init__(self, api):
self.log = get_class_logger()
self.api = api
+ self.core = api.core
def do_extra_handshake(self, req):
self.log.debug("WS Connected: %s" % req)
@@ -58,22 +66,23 @@ class AbstractHandler:
o = loads(msg)
except ValueError, e: #invalid json object
self.log.debug("Invalid Request: %s" % e)
+ self.send_result(req, self.ERROR, "No JSON request")
return None, None, None
- if type(o) != list and len(o) > 2:
+ if type(o) != list and len(o) not in range(1,4):
self.log.debug("Invalid Api call: %s" % o)
- self.send_result(req, 500, "Invalid Api call")
+ self.send_result(req, self.ERROR, "Invalid Api call")
return None, None, None
if len(o) == 1: # arguments omitted
- o.append([])
-
- func, args = o
- if type(args) == list:
- kwargs = {}
+ return o[0], [], {}
+ elif len(o) == 2:
+ func, args = o
+ if type(args) == list:
+ return func, args, {}
+ else:
+ return func, [], args
else:
- args, kwargs = [], args
-
- return func, args, kwargs
+ return tuple(o)
def send_result(self, req, code, result):
return send_message(req, dumps([code, result])) \ No newline at end of file
diff --git a/module/remote/wsbackend/ApiHandler.py b/module/remote/wsbackend/ApiHandler.py
index 57d9ecd5b..e8ba80982 100644
--- a/module/remote/wsbackend/ApiHandler.py
+++ b/module/remote/wsbackend/ApiHandler.py
@@ -34,14 +34,15 @@ class ApiHandler(AbstractHandler):
Non json request will be ignored.
"""
+ PATH = "/api"
+
def transfer_data(self, req):
while True:
try:
line = receive_message(req)
except TypeError, e: # connection closed
self.log.debug("WS Error: %s" % e)
- self.passive_closing_handshake(req)
- return
+ return self.passive_closing_handshake(req)
self.handle_message(line, req)
@@ -55,30 +56,31 @@ class ApiHandler(AbstractHandler):
user = self.api.checkAuth(*args, **kwargs)
if user:
req.api = self.api.withUserContext(user.uid)
- return self.send_result(req, 200, True)
+ return self.send_result(req, self.OK, True)
else:
- return self.send_result(req, 403, "Forbidden")
+ return self.send_result(req, self.FORBIDDEN, "Forbidden")
elif func == 'logout':
req.api = None
- return self.send_result(req, 200, True)
+ return self.send_result(req, self.OK, True)
else:
if not req.api:
- return self.send_result(req, 403, "Forbidden")
+ return self.send_result(req, self.FORBIDDEN, "Forbidden")
if not self.api.isAuthorized(func, req.api.user):
- return self.send_result(req, 401, "Unauthorized")
+ return self.send_result(req, self.UNAUTHORIZED, "Unauthorized")
try:
result = getattr(req.api, func)(*args, **kwargs)
except AttributeError:
- return self.send_result(req, 404, "Not Found")
+ return self.send_result(req, self.NOT_FOUND, "Not Found")
except Exception, e:
- return self.send_result(req, 500, str(e))
+ self.core.print_exc()
+ return self.send_result(req, self.ERROR, str(e))
# None is invalid json type
if result is None: result = True
- return self.send_result(req, 200, result) \ No newline at end of file
+ return self.send_result(req, self.OK, result) \ No newline at end of file
diff --git a/module/remote/wsbackend/AsyncHandler.py b/module/remote/wsbackend/AsyncHandler.py
new file mode 100644
index 000000000..a8382a211
--- /dev/null
+++ b/module/remote/wsbackend/AsyncHandler.py
@@ -0,0 +1,112 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+###############################################################################
+# Copyright(c) 2008-2012 pyLoad Team
+# http://www.pyload.org
+#
+# This file is part of pyLoad.
+# pyLoad is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# Subjected to the terms and conditions in LICENSE
+#
+# @author: RaNaN
+###############################################################################
+
+from Queue import Queue
+from threading import Lock
+
+from mod_pywebsocket.msgutil import receive_message
+
+from module.utils import lock
+from AbstractHandler import AbstractHandler
+
+class Mode:
+ STANDBY = 1
+ RUNNING = 2
+
+class AsyncHandler(AbstractHandler):
+ """
+ Handler that provides asynchronous information about server status, running downloads, occurred events.
+
+ Progress information are continuous and will be pushed in a fixed interval when available.
+ After connect you have to login and can set the interval by sending the json command ["setInterval", xy].
+ To start receiving updates call "start", afterwards no more incoming messages will be accept!
+ """
+
+ PATH = "/async"
+ COMMAND = "start"
+
+ PROGRESS_INTERVAL = 1
+ STATUS_INTERVAL = 60
+
+ def __init__(self, api):
+ AbstractHandler.__init__(self, api)
+ self.clients = []
+ self.lock = Lock()
+
+ @lock
+ def on_open(self, req):
+ req.queue = Queue()
+ req.interval = self.PROGRESS_INTERVAL
+ req.mode = Mode.STANDBY
+ self.clients.append(req)
+
+ @lock
+ def on_close(self, req):
+ self.clients.remove(req)
+
+ @lock
+ def add_event(self, event):
+ for req in self.clients:
+ req.queue.put(event)
+
+ def transfer_data(self, req):
+ while True:
+
+ if req.mode == Mode.STANDBY:
+ try:
+ line = receive_message(req)
+ except TypeError, e: # connection closed
+ self.log.debug("WS Error: %s" % e)
+ return self.passive_closing_handshake(req)
+
+ self.mode_standby(line, req)
+ else:
+ if self.mode_running(req):
+ return self.passive_closing_handshake(req)
+
+ def mode_standby(self, msg, req):
+ """ accepts calls before pushing updates """
+ func, args, kwargs = self.handle_call(msg, req)
+ if not func:
+ return # Result was already sent
+
+ if func == 'login':
+ user = self.api.checkAuth(*args, **kwargs)
+ if user:
+ req.api = self.api.withUserContext(user.uid)
+ return self.send_result(req, self.OK, True)
+
+ else:
+ return self.send_result(req, self.FORBIDDEN, "Forbidden")
+
+ elif func == 'logout':
+ req.api = None
+ return self.send_result(req, self.OK, True)
+
+ else:
+ if not req.api:
+ return self.send_result(req, self.FORBIDDEN, "Forbidden")
+ if func == "setInterval":
+ req.interval = args[0]
+ elif func == self.COMMAND:
+ req.mode = Mode.RUNNING
+
+
+ def mode_running(self, req):
+ """ Listen for events, closes socket when returning True """
+ self.send_result(req, "update", "test") \ No newline at end of file
diff --git a/module/remote/wsbackend/EventHandler.py b/module/remote/wsbackend/EventHandler.py
deleted file mode 100644
index 2550ff2eb..000000000
--- a/module/remote/wsbackend/EventHandler.py
+++ /dev/null
@@ -1,41 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-
-###############################################################################
-# Copyright(c) 2008-2012 pyLoad Team
-# http://www.pyload.org
-#
-# This file is part of pyLoad.
-# pyLoad is free software: you can redistribute it and/or modify
-# it under the terms of the GNU Affero General Public License as
-# published by the Free Software Foundation, either version 3 of the
-# License, or (at your option) any later version.
-#
-# Subjected to the terms and conditions in LICENSE
-#
-# @author: RaNaN
-###############################################################################
-
-from threading import Lock
-
-
-from module.utils import lock
-from AbstractHandler import AbstractHandler
-
-class EventHandler(AbstractHandler):
-
- def __init__(self, api):
- AbstractHandler.__init__(self, api)
- self.clients = []
- self.lock = Lock()
-
- @lock
- def on_open(self, req):
- self.clients.append(req)
-
- @lock
- def on_close(self, req):
- self.clients.remove(req)
-
- def handle_message(self, line, req):
- pass \ No newline at end of file
diff --git a/module/web/api_app.py b/module/web/api_app.py
index c3237c743..4a84c85ca 100644
--- a/module/web/api_app.py
+++ b/module/web/api_app.py
@@ -98,3 +98,5 @@ def logout():
s = request.environ.get('beaker.session')
s.delete()
+
+ return dumps(True)
diff --git a/module/web/utils.py b/module/web/utils.py
index 1c0b81338..ac85d9f91 100644
--- a/module/web/utils.py
+++ b/module/web/utils.py
@@ -39,10 +39,10 @@ def set_session(request, user):
return s
def get_user_api(s):
- uid = s.get("uid", None)
- if uid is not None:
- api = PYLOAD.withUserContext(uid)
- return api
+ if s:
+ uid = s.get("uid", None)
+ if uid is not None:
+ return PYLOAD.withUserContext(uid)
return None
def is_mobile():
diff --git a/pyLoadCore.py b/pyLoadCore.py
index a0f682bcb..a44e9a2d5 100755
--- a/pyLoadCore.py
+++ b/pyLoadCore.py
@@ -48,7 +48,6 @@ from module.network.RequestFactory import RequestFactory
from module.web.ServerThread import WebServer
from module.Scheduler import Scheduler
from module.common.JsEngine import JsEngine
-from module import remote
from module.remote.RemoteManager import RemoteManager
import module.common.pylgettext as gettext
@@ -582,6 +581,7 @@ class Core(object):
finally:
self.files.syncSave()
+ self.db.shutdown()
self.shuttedDown = True
self.deletePidFile()
diff --git a/tests/__init__.py b/tests/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/tests/__init__.py
diff --git a/tests/api/ApiProxy.py b/tests/api/ApiProxy.py
new file mode 100644
index 000000000..74c938870
--- /dev/null
+++ b/tests/api/ApiProxy.py
@@ -0,0 +1,68 @@
+# -*- coding: utf-8 -*-
+
+
+from module.remote.ttypes_debug import classes, methods
+
+class ApiProxy:
+ """ Proxy that does type checking on the api """
+
+ def __init__(self, api, user="User", pw="test"):
+ self.api = api
+ self.user = user
+ self.pw = pw
+
+ if user and pw is not None:
+ self.api.login(user, pw)
+
+ def assert_type(self, result, type):
+ if not type: return # void
+ try:
+ # Complex attribute
+ if isinstance(type, tuple):
+ # Optional result
+ if type[0] is None:
+ # Only check if not None
+ if result is not None: self.assert_type(result, type[1])
+
+ # List
+ elif type[0] == list:
+ assert isinstance(result, list)
+ for item in result:
+ self.assert_type(item, type[1])
+ # Dict
+ elif type[0] == dict:
+ assert isinstance(result, dict)
+ for k, v in result.iteritems():
+ self.assert_type(k, type[1])
+ self.assert_type(v, type[2])
+
+ # Struct - Api class
+ elif hasattr(result, "__name__") and result.__name__ in classes:
+ for attr, atype in zip(result.__slots__, classes[result.__name__]):
+ self.assert_type(getattr(result, attr), atype)
+ else: # simple object
+ assert isinstance(result, type)
+ except AssertionError:
+ print "Assertion for %s as %s failed" % (result, type)
+ raise
+
+
+ def call(self, func, *args, **kwargs):
+ result = getattr(self.api, func)(*args, **kwargs)
+ self.assert_type(result, methods[func])
+
+ return result
+
+
+ def __getattr__(self, item):
+ def call(*args, **kwargs):
+ return self.call(item, *args, **kwargs)
+
+ return call
+
+if __name__ == "__main__":
+
+ from module.remote.JSONClient import JSONClient
+
+ api = ApiProxy(JSONClient(), "User", "test")
+ api.getServerVersion() \ No newline at end of file
diff --git a/tests/api/ApiTester.py b/tests/api/ApiTester.py
new file mode 100644
index 000000000..b17a7655e
--- /dev/null
+++ b/tests/api/ApiTester.py
@@ -0,0 +1,35 @@
+# -*- coding: utf-8 -*-
+
+from module.remote.JSONClient import JSONClient
+from module.remote.WSClient import WSClient
+
+from ApiProxy import ApiProxy
+
+class ApiTester:
+
+ tester= []
+
+ @classmethod
+ def register(cls, tester):
+ cls.tester.append(tester)
+
+ @classmethod
+ def get_methods(cls):
+ """ All available methods for testing """
+ methods = []
+ for t in cls.tester:
+ methods.extend(getattr(t, attr) for attr in dir(t) if attr.startswith("test_"))
+ return methods
+
+ def __init__(self):
+ ApiTester.register(self)
+ self.api = None
+
+ def setApi(self, api):
+ self.api = api
+
+ def enableJSON(self):
+ self.api = ApiProxy(JSONClient())
+
+ def enableWS(self):
+ self.api = ApiProxy(WSClient())
diff --git a/tests/api/__init__.py b/tests/api/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/tests/api/__init__.py
diff --git a/tests/api/test_JSONBackend.py b/tests/api/test_JSONBackend.py
new file mode 100644
index 000000000..a3805497b
--- /dev/null
+++ b/tests/api/test_JSONBackend.py
@@ -0,0 +1,27 @@
+# -*- coding: utf-8 -*-
+
+from nose.tools import raises
+
+from module.remote.JSONClient import JSONClient
+
+class TestJSONBackend:
+
+ def setUp(self):
+ self.client = JSONClient()
+
+ def test_login(self):
+ self.client.login("User", "test")
+ self.client.getServerVersion()
+ self.client.logout()
+
+ def test_wronglogin(self):
+ ret = self.client.login("WrongUser", "wrongpw")
+ assert ret == False
+
+ @raises(Exception)
+ def test_access(self):
+ self.client.getServerVersion()
+
+ @raises(Exception)
+ def test_unknown_method(self):
+ self.client.sdfdsg()
diff --git a/tests/api/test_api.py b/tests/api/test_api.py
new file mode 100644
index 000000000..68448b891
--- /dev/null
+++ b/tests/api/test_api.py
@@ -0,0 +1,39 @@
+
+from unittest import TestCase
+from random import choice
+
+from pyLoadCore import Core
+
+from ApiTester import ApiTester
+
+class TestAPI(TestCase):
+ """
+ Test all available testers randomly and on all backends
+ """
+ core = None
+
+ @classmethod
+ def setUpClass(cls):
+ from test_noargs import TestNoArgs
+
+ cls.core = Core()
+ cls.core.start(False, False, True)
+ for Test in (TestNoArgs,):
+ t = Test()
+ t.enableJSON()
+ t = Test()
+ t.enableWS()
+ t = Test()
+ t.setApi(cls.core.api)
+
+ cls.methods = ApiTester.get_methods()
+
+ @classmethod
+ def tearDownClass(cls):
+ cls.core.shutdown()
+
+ def test_random(self, n=10000):
+
+ for i in range(n):
+ func = choice(self.methods)
+ func()
diff --git a/tests/api/test_noargs.py b/tests/api/test_noargs.py
new file mode 100644
index 000000000..02e49cf13
--- /dev/null
+++ b/tests/api/test_noargs.py
@@ -0,0 +1,29 @@
+# -*- coding: utf-8 -*-
+
+import inspect
+
+from ApiTester import ApiTester
+
+from module.remote.ttypes import Iface
+
+IGNORE = ('kill', 'restart')
+
+class TestNoArgs(ApiTester):
+ def setUp(self):
+ self.enableJSON()
+
+# Setup test_methods dynamically, only these which require no arguments
+for name in dir(Iface):
+ if name.startswith("_") or name in IGNORE: continue
+
+ spec = inspect.getargspec(getattr(Iface, name))
+ if len(spec.args) == 1 and (not spec.varargs or len(spec.varargs) == 0):
+ def meta_test(name): #retain local scope
+ def test(self):
+ getattr(self.api, name)()
+ test.func_name = "test_%s" % name
+ return test
+
+ setattr(TestNoArgs, "test_%s" % name, meta_test(name))
+
+ del meta_test \ No newline at end of file
diff --git a/tests/config/db.version b/tests/config/db.version
deleted file mode 100644
index bf0d87ab1..000000000
--- a/tests/config/db.version
+++ /dev/null
@@ -1 +0,0 @@
-4 \ No newline at end of file
diff --git a/tests/helper/Stubs.py b/tests/helper/Stubs.py
index 5c44cfb58..4ebd12592 100644
--- a/tests/helper/Stubs.py
+++ b/tests/helper/Stubs.py
@@ -4,6 +4,7 @@ import sys
from os.path import abspath, dirname, join
from time import strftime
from traceback import format_exc
+from collections import defaultdict
sys.path.append(abspath(join(dirname(__file__), "..", "..", "module", "lib")))
sys.path.append(abspath(join(dirname(__file__), "..", "..")))
@@ -73,6 +74,8 @@ class Core:
self.cache = {}
self.packageCache = {}
+ self.statusMsg = defaultdict(lambda: "statusmsg")
+
self.log = LogStub()
def getServerVersion(self):
diff --git a/tests/manager/__init__.py b/tests/manager/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/tests/manager/__init__.py
diff --git a/tests/test_filemanager.py b/tests/manager/test_filemanager.py
index 7b82840b1..81acea4d0 100644
--- a/tests/test_filemanager.py
+++ b/tests/manager/test_filemanager.py
@@ -2,8 +2,8 @@
from random import choice
-from helper.Stubs import Core
-from helper.BenchmarkTest import BenchmarkTest
+from tests.helper.Stubs import Core
+from tests.helper.BenchmarkTest import BenchmarkTest
from module.database import DatabaseBackend
# disable asyncronous queries
diff --git a/tests/test_interactionManager.py b/tests/manager/test_interactionManager.py
index 920d84b9d..db233bb25 100644
--- a/tests/test_interactionManager.py
+++ b/tests/manager/test_interactionManager.py
@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
from unittest import TestCase
-from helper.Stubs import Core
+from tests.helper.Stubs import Core
from module.Api import Input, Output
from module.interaction.InteractionManager import InteractionManager
diff --git a/tests/other/__init__.py b/tests/other/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/tests/other/__init__.py
diff --git a/tests/test_configparser.py b/tests/other/test_configparser.py
index d797c7912..acb05c63e 100644
--- a/tests/test_configparser.py
+++ b/tests/other/test_configparser.py
@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
from collections import defaultdict
-from helper.Stubs import Core
+from tests.helper.Stubs import Core
from module.database import DatabaseBackend
from module.config.ConfigParser import ConfigParser
@@ -9,6 +9,8 @@ from module.config.ConfigParser import ConfigParser
# TODO
class TestConfigParser():
+ db = None
+
@classmethod
def setUpClass(cls):
cls.db = DatabaseBackend(Core())
@@ -18,6 +20,9 @@ class TestConfigParser():
cls.db.setup()
cls.db.clearAllConfigs()
+ @classmethod
+ def tearDownClass(cls):
+ cls.db.shutdown()
def test_db(self):
diff --git a/tests/test_database.py b/tests/other/test_database.py
index fb134ff41..dd733b4ff 100644
--- a/tests/test_database.py
+++ b/tests/other/test_database.py
@@ -1,9 +1,7 @@
# -*- coding: utf-8 -*-
-from collections import defaultdict
-
-from helper.Stubs import Core
-from helper.BenchmarkTest import BenchmarkTest
+from tests.helper.Stubs import Core
+from tests.helper.BenchmarkTest import BenchmarkTest
from module.Api import DownloadState
from module.database import DatabaseBackend
@@ -29,7 +27,6 @@ class TestDatabase(BenchmarkTest):
cls.db = DatabaseBackend(Core())
cls.db.manager = cls.db.core
- cls.db.manager.statusMsg = defaultdict(lambda: "statusmsg")
cls.db.setup()
cls.db.purgeAll()
diff --git a/tests/test_syntax.py b/tests/other/test_syntax.py
index a4cc53ee5..fbf7edf8f 100644
--- a/tests/test_syntax.py
+++ b/tests/other/test_syntax.py
@@ -5,10 +5,10 @@ from os.path import abspath, dirname, join
from unittest import TestCase
-PATH = abspath(join(dirname(abspath(__file__)), "..", ""))
+PATH = abspath(join(dirname(abspath(__file__)), "..", "..", ""))
# needed to register globals
-from helper import Stubs
+from tests.helper import Stubs
class TestSyntax(TestCase):
pass
diff --git a/tests/test_api.py b/tests/test_api.py
deleted file mode 100644
index 0171b46bb..000000000
--- a/tests/test_api.py
+++ /dev/null
@@ -1,18 +0,0 @@
-
-from unittest import TestCase
-
-from pyLoadCore import Core
-from module.common.APIExerciser import APIExerciser
-
-class TestApi(TestCase):
-
- @classmethod
- def setUpClass(cls):
- cls.core = Core()
- cls.core.start(False, False, True)
-
- def test_random(self):
- api = APIExerciser(self.core)
-
- for i in range(2000):
- api.testAPI()
diff --git a/tests/test_backends.py b/tests/test_backends.py
deleted file mode 100644
index 71ccedd2f..000000000
--- a/tests/test_backends.py
+++ /dev/null
@@ -1,59 +0,0 @@
-# -*- coding: utf-8 -*-
-
-
-from urllib import urlencode
-from urllib2 import urlopen, HTTPError
-from json import loads
-
-from logging import log
-
-from module.common import APIExerciser
-
-url = "http://localhost:8001/api/%s"
-
-class TestBackends():
-
- def setUp(self):
- u = urlopen(url % "login", data=urlencode({"username": "TestUser", "password": "sometestpw"}))
- self.key = loads(u.read())
- assert self.key is not False
-
- def test_random(self):
- api = APIExerciser.APIExerciser(None, True, "TestUser", "sometestpw")
-
- assert api.api.login("crapp", "wrong pw") is False
-
- for i in range(0, 1000):
- api.testAPI()
-
- def call(self, name, post=None):
- if not post: post = {}
- post["session"] = self.key
- u = urlopen(url % name, data=urlencode(post))
- return loads(u.read())
-
- def test_wronglogin(self):
- u = urlopen(url % "login", data=urlencode({"username": "crap", "password": "wrongpw"}))
- assert loads(u.read()) is False
-
- def test_access(self):
- try:
- urlopen(url % "getServerVersion")
- except HTTPError, e:
- assert e.code == 403
- else:
- assert False
-
- def test_status(self):
- ret = self.call("statusServer")
- log(1, str(ret))
- assert "pause" in ret
- assert "queue" in ret
-
- def test_unknown_method(self):
- try:
- self.call("notExisting")
- except HTTPError, e:
- assert e.code == 404
- else:
- assert False