diff --git a/src/calibre/utils/rapydscript.py b/src/calibre/utils/rapydscript.py index 05bd9e3782..e90fb20673 100644 --- a/src/calibre/utils/rapydscript.py +++ b/src/calibre/utils/rapydscript.py @@ -329,6 +329,59 @@ def atomic_write(base, name, content): atomic_rename(tname, name) +def run_rapydscript_tests(): + from PyQt5.QtWebEngineWidgets import QWebEnginePage, QWebEngineScript + from PyQt5.Qt import QApplication, QEventLoop + from calibre.gui2.webengine import secure_webengine + from calibre.gui2 import must_use_qt + must_use_qt() + base = base_dir() + rapydscript_dir = os.path.join(base, 'src', 'pyj') + fname = os.path.join(rapydscript_dir, 'test.pyj') + with lopen(fname, 'rb') as f: + js = compile_fast(f.read(), fname) + + def create_script(src, name): + s = QWebEngineScript() + s.setName(name) + s.setInjectionPoint(QWebEngineScript.DocumentReady) + s.setWorldId(QWebEngineScript.ApplicationWorld) + s.setRunsOnSubFrames(False) + s.setSourceCode(src) + return s + + class Tester(QWebEnginePage): + + def __init__(self): + QWebEnginePage.__init__(self) + self.titleChanged.connect(self.title_changed) + secure_webengine(self) + self.scripts().insert(create_script(js, 'test-rapydscript.js')) + self.setHtml('

initialize') + self.working = True + + def title_changed(self, title): + if title == 'initialized': + self.titleChanged.disconnect() + self.runJavaScript('window.main()', QWebEngineScript.ApplicationWorld, self.callback) + + def spin_loop(self): + while self.working: + QApplication.instance().processEvents(QEventLoop.ExcludeUserInputEvents) + return self.result + + def callback(self, result): + self.result = result + self.working = False + + def javaScriptConsoleMessage(self, level, msg, line_num, source_id): + print(msg, file=sys.stderr if level > 0 else sys.stdout) + + tester = Tester() + result = tester.spin_loop() + raise SystemExit(int(result)) + + def compile_editor(): base = base_dir() rapydscript_dir = os.path.join(base, 'src', 'pyj') diff --git a/src/pyj/test.pyj b/src/pyj/test.pyj new file mode 100644 index 0000000000..aea7d9f841 --- /dev/null +++ b/src/pyj/test.pyj @@ -0,0 +1,47 @@ +# vim:fileencoding=utf-8 +# License: GPL v3 Copyright: 2020, Kovid Goyal +from __python__ import bound_methods, hash_literals + +import traceback + +from testing import registered_tests, reset_dom + + +def get_matching_tests_for_name(name): + ans = [] + for k in Object.keys(registered_tests): + q = k.split('.')[-1] + if not name or q is name: + ans.append(registered_tests[k]) + return ans + + +def run_tests(tests): + failed_tests = [] + count = 0 + for f in tests: + print(f.test_name, '...') + reset_dom() + try: + f() + count += 1 + except: + traceback.print_stack() + failed_tests.append((f.test_name, traceback.format_stack())) + return failed_tests, count + + +def main(): + tests = get_matching_tests_for_name() + st = window.performance.now() + failed_tests, total = run_tests(tests) + time = window.performance.now() - st + if failed_tests.length: + console.error(f'{failed_tests.length} out of {total} failed in {time:.1f} seconds') + else: + print(f'Ran {total} tests in {time:.1f} seconds') + return 1 if failed_tests.length else 0 + + +window.main = main +document.title = 'initialized' diff --git a/src/pyj/testing.pyj b/src/pyj/testing.pyj new file mode 100644 index 0000000000..ffdc037158 --- /dev/null +++ b/src/pyj/testing.pyj @@ -0,0 +1,78 @@ +# vim:fileencoding=utf-8 +# License: GPL v3 Copyright: 2020, Kovid Goyal +from __python__ import bound_methods, hash_literals + +from dom import clear + +def raise_fail(preamble, msg): + if msg: + msg = '. ' + msg + else: + msg = '' + raise AssertionError(preamble + msg) + + +def assert_equal(a, b, msg): + + def fail(): + raise_fail(f'{a} != {b}', msg) + + atype = jstype(a) + btype = jstype(b) + base_types = {'number': True, 'boolean': True, 'string': True, 'undefined': True} + if base_types[a] or base_types[b] or a is None or b is None: + if a is not b: + fail() + return + if a.__eq__: + if not a.__eq__(b): + fail() + return + if b.__eq__: + if not b.__eq__(a): + fail() + return + if a.length? or b.length?: + if a.length is not b.length: + fail() + for i in range(a.length): + assert_equal(a[i], b[i]) + return + if atype is 'object': + for key in Object.keys(a): + assert_equal(a[key], b[key]) + if btype is 'object': + for key in Object.keys(b): + assert_equal(a[key], b[key]) + + if a is not b: + fail() + + +def assert_true(x, msg): + if not x: + raise_fail(f'{x} is not truthy', msg) + + +def assert_fale(x, msg): + if x: + raise_fail(f'{x} is truthy', msg) + + +def reset_dom(): + html = document.documentElement + clear(html) + head = document.createElement('head') + body = document.createElement('body') + html.appendChild(head) + html.appendChild(body) + + +registered_tests = {} + + +def test(f): + mod = f.__module__ or 'unknown_test_module' + f.test_name = mod + '.' + f.name + registered_tests[f.test_name] = f + return f