From c7ad1cc5b4a5d190a060e3ddd9274c3065da6708 Mon Sep 17 00:00:00 2001 From: RaNaN Date: Fri, 13 Jan 2012 23:24:21 +0100 Subject: plugin unit test, closed #499, #500 --- tests/helper/PluginTester.py | 52 +++++++++++++++++++ tests/helper/Stubs.py | 119 +++++++++++++++++++++++++++++++++++++++++++ tests/helper/__init__.py | 0 3 files changed, 171 insertions(+) create mode 100644 tests/helper/PluginTester.py create mode 100644 tests/helper/Stubs.py create mode 100644 tests/helper/__init__.py (limited to 'tests/helper') diff --git a/tests/helper/PluginTester.py b/tests/helper/PluginTester.py new file mode 100644 index 000000000..997a0923f --- /dev/null +++ b/tests/helper/PluginTester.py @@ -0,0 +1,52 @@ +# -*- coding: utf-8 -*- + +from unittest import TestCase +from os.path import abspath +from sys import exc_clear, exc_info +from logging import log, DEBUG +from time import sleep, time + +from Stubs import Thread, Core, noop + +from module.plugins.Hoster import Hoster, Abort, Fail + +def _wait(self): + """ waits the time previously set """ + self.waiting = True + + waittime = self.pyfile.waitUntil - time() + log(DEBUG, "waiting %ss" % waittime) + + if self.wantReconnect: + raise Fail("Would wait for reconnect %ss" % waittime ) + if self.wantReconnect or waittime > 300: + raise Fail("Would wait %ss" % waittime ) + + while self.pyfile.waitUntil > time(): + sleep(1) + if self.pyfile.abort: raise Abort + + self.waiting = False + self.pyfile.setStatus("starting") + +Hoster.wait = _wait + +Hoster.checkForSameFiles = noop + +class PluginTester(TestCase): + + @classmethod + def setUpClass(cls): + cls.core = Core() + + def setUp(self): + self.thread = Thread(self.core) + exc_clear() + + def tearDown(self): + exc = exc_info() + if exc != (None, None, None): + debug = self.thread.writeDebugReport() + log(DEBUG, debug) + # generate attachment + print "\n[[ATTACHMENT|%s]]\n" % abspath(debug) \ No newline at end of file diff --git a/tests/helper/Stubs.py b/tests/helper/Stubs.py new file mode 100644 index 000000000..eb3cc98c1 --- /dev/null +++ b/tests/helper/Stubs.py @@ -0,0 +1,119 @@ +# -*- coding: utf-8 -*- + +import sys +from os.path import abspath, dirname, join +from time import strftime + +sys.path.append(abspath(join(dirname(__file__), "..", "..", "module", "lib"))) +sys.path.append(abspath(join(dirname(__file__), "..", ".."))) + +import __builtin__ + +from module.PyPackage import PyPackage +from module.threads.BaseThread import BaseThread +from module.config.ConfigParser import ConfigParser +from module.network.RequestFactory import RequestFactory +from module.plugins.PluginManager import PluginManager +from module.common.JsEngine import JsEngine + +from logging import log, DEBUG, INFO, WARN, ERROR + + +# Do nothing +def noop(*args, **kwargs): + pass + +ConfigParser.save = noop + +class LogStub: + def debug(self, *args): + log(DEBUG, *args) + + def info(self, *args): + log(INFO, *args) + + def error(self, *args): + log(ERROR, *args) + + def warning(self, *args): + log(WARN, *args) + + +class NoLog: + def debug(self, *args): + pass + + def info(self, *args): + pass + + def error(self, *args): + log(ERROR, *args) + + def warning(self, *args): + log(WARN, *args) + + +class Core: + def __init__(self): + self.log = NoLog() + + self.api = self + self.core = self + self.debug = True + self.captcha = True + self.config = ConfigParser() + self.pluginManager = PluginManager(self) + self.requestFactory = RequestFactory(self) + __builtin__.pyreq = self.requestFactory + self.accountManager = AccountManager() + self.hookManager = self.eventManager = self.interActionManager = NopClass() + self.js = JsEngine() + self.cache = {} + self.packageCache = {} + + self.log = LogStub() + + def getServerVersion(self): + return "TEST_RUNNER on %s" % strftime("%d %h %Y") + + def path(self, path): + return path + + def updateLink(self, *args): + pass + + def updatePackage(self, *args): + pass + + def getPackage(self, id): + return PyPackage(self, 0, "tmp", "tmp", "", "", 0, 0) + + + +class NopClass: + def __getattr__(self, item): + return noop + +class AccountManager: + + def getAccountForPlugin(self, name): + return None + +class Thread(BaseThread): + def __init__(self, core): + BaseThread.__init__(self, core) + self.plugin = None + + + def writeDebugReport(self): + if hasattr(self, "pyfile"): + dump = BaseThread.writeDebugReport(self, self.plugin.__name__, pyfile=self.pyfile) + else: + dump = BaseThread.writeDebugReport(self, self.plugin.__name__, plugin=self.plugin) + + return dump + +__builtin__._ = lambda x: x +__builtin__.pypath = "" +__builtin__.hookManager = NopClass() +__builtin__.pyreq = None \ No newline at end of file diff --git a/tests/helper/__init__.py b/tests/helper/__init__.py new file mode 100644 index 000000000..e69de29bb -- cgit v1.2.3 From 069503da5106fd2fcf7fa2d3a8462ab109b44adb Mon Sep 17 00:00:00 2001 From: RaNaN Date: Fri, 13 Jan 2012 23:42:10 +0100 Subject: cosmetic fixes --- tests/helper/PluginTester.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'tests/helper') diff --git a/tests/helper/PluginTester.py b/tests/helper/PluginTester.py index 997a0923f..d0c1cdd3c 100644 --- a/tests/helper/PluginTester.py +++ b/tests/helper/PluginTester.py @@ -7,6 +7,7 @@ from logging import log, DEBUG from time import sleep, time from Stubs import Thread, Core, noop +from sys import stderr from module.plugins.Hoster import Hoster, Abort, Fail @@ -49,4 +50,4 @@ class PluginTester(TestCase): debug = self.thread.writeDebugReport() log(DEBUG, debug) # generate attachment - print "\n[[ATTACHMENT|%s]]\n" % abspath(debug) \ No newline at end of file + stderr.write("\n[[ATTACHMENT|%s]]\n" % abspath(debug)) \ No newline at end of file -- cgit v1.2.3 From 828cc89cc9b7a2ecacf98fc73928d988e15f0b98 Mon Sep 17 00:00:00 2001 From: RaNaN Date: Sat, 14 Jan 2012 15:49:08 +0100 Subject: captcha and attachments for plugin tester --- tests/helper/PluginTester.py | 118 +++++++++++++++++++++++++++++++++++++++---- 1 file changed, 108 insertions(+), 10 deletions(-) (limited to 'tests/helper') diff --git a/tests/helper/PluginTester.py b/tests/helper/PluginTester.py index d0c1cdd3c..e0ce8f354 100644 --- a/tests/helper/PluginTester.py +++ b/tests/helper/PluginTester.py @@ -1,14 +1,21 @@ # -*- coding: utf-8 -*- from unittest import TestCase -from os.path import abspath +from os import makedirs, remove +from os.path import exists, join, expanduser +from shutil import move from sys import exc_clear, exc_info from logging import log, DEBUG from time import sleep, time +from random import randint +from glob import glob + +from pycurl import LOW_SPEED_TIME, FORM_FILE +from json import loads from Stubs import Thread, Core, noop -from sys import stderr +from module.network.RequestFactory import getRequest, getURL from module.plugins.Hoster import Hoster, Abort, Fail def _wait(self): @@ -18,10 +25,10 @@ def _wait(self): waittime = self.pyfile.waitUntil - time() log(DEBUG, "waiting %ss" % waittime) - if self.wantReconnect: - raise Fail("Would wait for reconnect %ss" % waittime ) - if self.wantReconnect or waittime > 300: - raise Fail("Would wait %ss" % waittime ) + if self.wantReconnect and waittime > 300: + raise Fail("Would wait for reconnect %ss" % waittime) + elif waittime > 300: + raise Fail("Would wait %ss" % waittime) while self.pyfile.waitUntil > time(): sleep(1) @@ -32,13 +39,106 @@ def _wait(self): Hoster.wait = _wait + +def decryptCaptcha(self, url, get={}, post={}, cookies=False, forceUser=False, imgtype='jpg', + result_type='textual'): + img = self.load(url, get=get, post=post, cookies=cookies) + + id = ("%.2f" % time())[-6:].replace(".", "") + temp_file = open(join("tmp", "tmpCaptcha_%s_%s.%s" % (self.__name__, id, imgtype)), "wb") + temp_file.write(img) + temp_file.close() + + Ocr = self.core.pluginManager.loadClass("captcha", self.__name__) + + if Ocr: + log(DEBUG, "Using tesseract for captcha") + sleep(randint(3000, 5000) / 1000.0) + if self.pyfile.abort: raise Abort + + ocr = Ocr() + result = ocr.get_captcha(temp_file.name) + else: + log(DEBUG, "Using ct for captcha") + # put username and passkey into two lines in ct.conf + conf = join(expanduser("~"), "ct.conf") + if not exists(conf): raise Exception("CaptchaTrader config %s not found." % conf) + f = open(conf, "rb") + req = getRequest() + + #raise timeout threshold + req.c.setopt(LOW_SPEED_TIME, 80) + + try: + json = req.load("http://captchatrader.com/api/submit", + post={"api_key": "9f65e7f381c3af2b076ea680ae96b0b7", + "username": f.readline().strip(), + "password": f.readline().strip(), + "value": (FORM_FILE, temp_file.name), + "type": "file"}, multipart=True) + finally: + f.close() + req.close() + + response = loads(json) + log(DEBUG, str(response)) + result = response[1] + + self.cTask = response[0] + + return result + +Hoster.decryptCaptcha = decryptCaptcha + + +def respond(ticket, value): + conf = join(expanduser("~"), "ct.conf") + f = open(conf, "rb") + try: + getURL("http://captchatrader.com/api/respond", + post={"is_correct": value, + "username": f.readline().strip(), + "password": f.readline().strip(), + "ticket": ticket}) + except Exception, e : + print "CT Exception:", e + log(DEBUG, str(e)) + finally: + f.close() + + + +def invalidCaptcha(self): + log(DEBUG, "Captcha invalid") + if self.cTask: + respond(self.ticket, 0) + +Hoster.invalidCaptcha = invalidCaptcha + +def correctCaptcha(self): + log(DEBUG, "Captcha correct") + if self.cTask: + respond(self.ticket, 1) + +Hoster.correctCaptcha = correctCaptcha + Hoster.checkForSameFiles = noop class PluginTester(TestCase): - @classmethod def setUpClass(cls): cls.core = Core() + name = "tests.%s.%s" % (cls.__name__, cls.__name__) + for f in glob(join(name, "debug_*")): + remove(f) + + # Copy debug report to attachment dir for jenkins + @classmethod + def tearDownClass(cls): + name = "tests.%s.%s" % (cls.__name__, cls.__name__) + if not exists(name): makedirs(name) + for f in glob("debug_*"): + move(f, join(name, f)) def setUp(self): self.thread = Thread(self.core) @@ -48,6 +148,4 @@ class PluginTester(TestCase): exc = exc_info() if exc != (None, None, None): debug = self.thread.writeDebugReport() - log(DEBUG, debug) - # generate attachment - stderr.write("\n[[ATTACHMENT|%s]]\n" % abspath(debug)) \ No newline at end of file + log(DEBUG, debug) \ No newline at end of file -- cgit v1.2.3 From a0615c500c8bce03aaf89156a5f5e5e06873587c Mon Sep 17 00:00:00 2001 From: RaNaN Date: Sat, 14 Jan 2012 16:07:47 +0100 Subject: fixed attachment file path --- tests/helper/PluginTester.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'tests/helper') diff --git a/tests/helper/PluginTester.py b/tests/helper/PluginTester.py index e0ce8f354..db01a2d06 100644 --- a/tests/helper/PluginTester.py +++ b/tests/helper/PluginTester.py @@ -128,14 +128,14 @@ class PluginTester(TestCase): @classmethod def setUpClass(cls): cls.core = Core() - name = "tests.%s.%s" % (cls.__name__, cls.__name__) + name = "%s.%s" % (cls.__name__, cls.__name__) for f in glob(join(name, "debug_*")): remove(f) # Copy debug report to attachment dir for jenkins @classmethod def tearDownClass(cls): - name = "tests.%s.%s" % (cls.__name__, cls.__name__) + name = "%s.%s" % (cls.__name__, cls.__name__) if not exists(name): makedirs(name) for f in glob("debug_*"): move(f, join(name, f)) -- cgit v1.2.3 From 26227cfe53f8fd4bc1631d8e1b35031f589682dc Mon Sep 17 00:00:00 2001 From: RaNaN Date: Sun, 15 Jan 2012 15:55:19 +0100 Subject: backend + api test case, nicer format for plugin tester --- tests/helper/PluginTester.py | 4 ++-- tests/helper/Stubs.py | 9 ++++----- 2 files changed, 6 insertions(+), 7 deletions(-) (limited to 'tests/helper') diff --git a/tests/helper/PluginTester.py b/tests/helper/PluginTester.py index db01a2d06..b70c0d061 100644 --- a/tests/helper/PluginTester.py +++ b/tests/helper/PluginTester.py @@ -128,14 +128,14 @@ class PluginTester(TestCase): @classmethod def setUpClass(cls): cls.core = Core() - name = "%s.%s" % (cls.__name__, cls.__name__) + name = "%s.%s" % (cls.__module__, cls.__name__) for f in glob(join(name, "debug_*")): remove(f) # Copy debug report to attachment dir for jenkins @classmethod def tearDownClass(cls): - name = "%s.%s" % (cls.__name__, cls.__name__) + name = "%s.%s" % (cls.__module__, cls.__name__) if not exists(name): makedirs(name) for f in glob("debug_*"): move(f, join(name, f)) diff --git a/tests/helper/Stubs.py b/tests/helper/Stubs.py index eb3cc98c1..cfa5d6fdb 100644 --- a/tests/helper/Stubs.py +++ b/tests/helper/Stubs.py @@ -66,7 +66,7 @@ class Core: self.requestFactory = RequestFactory(self) __builtin__.pyreq = self.requestFactory self.accountManager = AccountManager() - self.hookManager = self.eventManager = self.interActionManager = NopClass() + self.hookManager = self.eventManager = self.interActionManager = NoopClass() self.js = JsEngine() self.cache = {} self.packageCache = {} @@ -89,8 +89,7 @@ class Core: return PyPackage(self, 0, "tmp", "tmp", "", "", 0, 0) - -class NopClass: +class NoopClass: def __getattr__(self, item): return noop @@ -114,6 +113,6 @@ class Thread(BaseThread): return dump __builtin__._ = lambda x: x -__builtin__.pypath = "" -__builtin__.hookManager = NopClass() +__builtin__.pypath = abspath(join(dirname(__file__), "..", "..")) +__builtin__.hookManager = NoopClass() __builtin__.pyreq = None \ No newline at end of file -- cgit v1.2.3 From aabf168c0ccee7d6d7eacfbadecdca3cfc3956d3 Mon Sep 17 00:00:00 2001 From: RaNaN Date: Mon, 16 Jan 2012 18:02:26 +0100 Subject: little fixes --- tests/helper/PluginTester.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'tests/helper') diff --git a/tests/helper/PluginTester.py b/tests/helper/PluginTester.py index b70c0d061..ef61385be 100644 --- a/tests/helper/PluginTester.py +++ b/tests/helper/PluginTester.py @@ -111,14 +111,14 @@ def respond(ticket, value): def invalidCaptcha(self): log(DEBUG, "Captcha invalid") if self.cTask: - respond(self.ticket, 0) + respond(self.cTask, 0) Hoster.invalidCaptcha = invalidCaptcha def correctCaptcha(self): log(DEBUG, "Captcha correct") if self.cTask: - respond(self.ticket, 1) + respond(self.cTask, 1) Hoster.correctCaptcha = correctCaptcha @@ -148,4 +148,4 @@ class PluginTester(TestCase): exc = exc_info() if exc != (None, None, None): debug = self.thread.writeDebugReport() - log(DEBUG, debug) \ No newline at end of file + log(DEBUG, debug) -- cgit v1.2.3 From b6753b215aae358d23d42fec736b3f865d923b1b Mon Sep 17 00:00:00 2001 From: RaNaN Date: Mon, 6 Feb 2012 10:42:32 +0100 Subject: fix in unit test --- tests/helper/Stubs.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'tests/helper') diff --git a/tests/helper/Stubs.py b/tests/helper/Stubs.py index cfa5d6fdb..ade15f0c4 100644 --- a/tests/helper/Stubs.py +++ b/tests/helper/Stubs.py @@ -3,6 +3,7 @@ import sys from os.path import abspath, dirname, join from time import strftime +from traceback import format_exc sys.path.append(abspath(join(dirname(__file__), "..", "..", "module", "lib"))) sys.path.append(abspath(join(dirname(__file__), "..", ".."))) @@ -87,6 +88,9 @@ class Core: def getPackage(self, id): return PyPackage(self, 0, "tmp", "tmp", "", "", 0, 0) + + def print_exc(self): + log(ERROR, format_exc()) class NoopClass: @@ -115,4 +119,4 @@ class Thread(BaseThread): __builtin__._ = lambda x: x __builtin__.pypath = abspath(join(dirname(__file__), "..", "..")) __builtin__.hookManager = NoopClass() -__builtin__.pyreq = None \ No newline at end of file +__builtin__.pyreq = None -- cgit v1.2.3 From 4df2b77fdf42046fe19bd371be7c7255986b5980 Mon Sep 17 00:00:00 2001 From: RaNaN Date: Tue, 6 Mar 2012 13:36:39 +0100 Subject: renamed hooks to addons, new filemanager and database, many new api methods you will loose ALL your LINKS, webinterface will NOT work --- tests/helper/BenchmarkTest.py | 66 +++++++++++++++++++++++++++++++++++++++++++ tests/helper/Stubs.py | 11 +++++--- 2 files changed, 73 insertions(+), 4 deletions(-) create mode 100644 tests/helper/BenchmarkTest.py (limited to 'tests/helper') diff --git a/tests/helper/BenchmarkTest.py b/tests/helper/BenchmarkTest.py new file mode 100644 index 000000000..d28c52959 --- /dev/null +++ b/tests/helper/BenchmarkTest.py @@ -0,0 +1,66 @@ +# -*- coding: utf-8 -*- + +from time import time + + +class BenchmarkTest: + + bench = [] + results = {} + + @classmethod + def timestamp(cls, name, a): + t = time() + r = cls.results.get(name, []) + r.append((t-a) * 1000) + cls.results[name] = r + + @classmethod + def benchmark(cls, n=1): + + print "Benchmarking %s" % cls.__name__ + print + + for i in range(n): + cls.collect_results() + + if "setUpClass" in cls.results: + cls.bench.insert(0, "setUpClass") + + if "tearDownClass" in cls.results: + cls.bench.append("tearDownClass") + + length = str(max([len(k) for k in cls.bench]) + 1) + total = 0 + + for k in cls.bench: + v = cls.results[k] + + if len(v) > 1: + print ("%" + length +"s: %s | average: %.2f ms") % (k, ", ".join(["%.2f" % x for x in v]), sum(v)/len(v)) + total += sum(v)/len(v) + else: + print ("%" + length +"s: %.2f ms") % (k, v[0]) + total += v[0] + + print "\ntotal: %.2f ms" % total + + + @classmethod + def collect_results(cls): + if hasattr(cls, "setUpClass"): + a = time() + cls.setUpClass() + cls.timestamp("setUpClass", a) + + obj = cls() + + for f in cls.bench: + a = time() + getattr(obj, "test_" + f)() + cls.timestamp(f, a) + + if hasattr(cls, "tearDownClass"): + a = time() + cls.tearDownClass() + cls.timestamp("tearDownClass", a) \ No newline at end of file diff --git a/tests/helper/Stubs.py b/tests/helper/Stubs.py index ade15f0c4..be2f5052b 100644 --- a/tests/helper/Stubs.py +++ b/tests/helper/Stubs.py @@ -58,8 +58,8 @@ class Core: def __init__(self): self.log = NoLog() - self.api = self - self.core = self + self.api = self.core = self + self.threadManager = self self.debug = True self.captcha = True self.config = ConfigParser() @@ -67,7 +67,7 @@ class Core: self.requestFactory = RequestFactory(self) __builtin__.pyreq = self.requestFactory self.accountManager = AccountManager() - self.hookManager = self.eventManager = self.interActionManager = NoopClass() + self.addonManager = self.eventManager = self.interActionManager = NoopClass() self.js = JsEngine() self.cache = {} self.packageCache = {} @@ -86,6 +86,9 @@ class Core: def updatePackage(self, *args): pass + def processingIds(self, *args): + return [] + def getPackage(self, id): return PyPackage(self, 0, "tmp", "tmp", "", "", 0, 0) @@ -118,5 +121,5 @@ class Thread(BaseThread): __builtin__._ = lambda x: x __builtin__.pypath = abspath(join(dirname(__file__), "..", "..")) -__builtin__.hookManager = NoopClass() +__builtin__.addonManager = NoopClass() __builtin__.pyreq = None -- cgit v1.2.3 From 50d4df8b4d48b855bd18e9922355b7f3f2b4da4e Mon Sep 17 00:00:00 2001 From: RaNaN Date: Tue, 20 Mar 2012 14:57:45 +0100 Subject: captcha decrypting for all plugin types, new interaction manager --- tests/helper/Stubs.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) (limited to 'tests/helper') diff --git a/tests/helper/Stubs.py b/tests/helper/Stubs.py index be2f5052b..963efd290 100644 --- a/tests/helper/Stubs.py +++ b/tests/helper/Stubs.py @@ -67,7 +67,8 @@ class Core: self.requestFactory = RequestFactory(self) __builtin__.pyreq = self.requestFactory self.accountManager = AccountManager() - self.addonManager = self.eventManager = self.interActionManager = NoopClass() + self.addonManager = AddonManager() + self.eventManager = self.interActionManager = NoopClass() self.js = JsEngine() self.cache = {} self.packageCache = {} @@ -100,6 +101,10 @@ class NoopClass: def __getattr__(self, item): return noop +class AddonManager(NoopClass): + def activePlugins(self): + return [] + class AccountManager: def getAccountForPlugin(self, name): @@ -121,5 +126,5 @@ class Thread(BaseThread): __builtin__._ = lambda x: x __builtin__.pypath = abspath(join(dirname(__file__), "..", "..")) -__builtin__.addonManager = NoopClass() +__builtin__.addonManager = AddonManager() __builtin__.pyreq = None -- cgit v1.2.3 From 0d2d6daef850ac6bcc7fafccd230e52d2a862c2c Mon Sep 17 00:00:00 2001 From: RaNaN Date: Sun, 3 Jun 2012 17:45:10 +0200 Subject: updates for database + api --- tests/helper/Stubs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'tests/helper') diff --git a/tests/helper/Stubs.py b/tests/helper/Stubs.py index 963efd290..ac4a41605 100644 --- a/tests/helper/Stubs.py +++ b/tests/helper/Stubs.py @@ -10,7 +10,7 @@ sys.path.append(abspath(join(dirname(__file__), "..", ".."))) import __builtin__ -from module.PyPackage import PyPackage +from module.datatypes.PyPackage import PyPackage from module.threads.BaseThread import BaseThread from module.config.ConfigParser import ConfigParser from module.network.RequestFactory import RequestFactory -- cgit v1.2.3 From 54bc92b4c5e0b3543a313f497cbc2276403c5980 Mon Sep 17 00:00:00 2001 From: RaNaN Date: Mon, 10 Sep 2012 11:49:35 +0200 Subject: changed config + progress api --- tests/helper/Stubs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'tests/helper') diff --git a/tests/helper/Stubs.py b/tests/helper/Stubs.py index ac4a41605..5c44cfb58 100644 --- a/tests/helper/Stubs.py +++ b/tests/helper/Stubs.py @@ -14,7 +14,7 @@ from module.datatypes.PyPackage import PyPackage from module.threads.BaseThread import BaseThread from module.config.ConfigParser import ConfigParser from module.network.RequestFactory import RequestFactory -from module.plugins.PluginManager import PluginManager +from module.PluginManager import PluginManager from module.common.JsEngine import JsEngine from logging import log, DEBUG, INFO, WARN, ERROR -- cgit v1.2.3