Finishing testing promises.
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
Adsooi 2024-10-17 03:38:36 +02:00
parent ef465b34e7
commit a182c703f4
Signed by: Ad5001
GPG key ID: EF45F9C6AFE20160
8 changed files with 114 additions and 36 deletions

View file

@ -73,10 +73,10 @@ class PyPromiseRunner(QRunnable):
class PyPromise(QObject): class PyPromise(QObject):
""" """
Asynchronous Promise-like object meant to interface between Python and Javascript easily. Threaded A+/Promise implementation meant to interface between Python and Javascript easily.
Runs to_run in another thread, and calls fulfilled (populated by then) with its return value. Runs to_run in another thread, and calls fulfilled (populated by then) with its return value.
""" """
fulfilled = Signal((QJSValue,), (QObject,)) fulfilled = Signal(QJSValue)
rejected = Signal(str) rejected = Signal(str)
def __init__(self, to_run: Callable|QJSValue, args=[], start_automatically=True): def __init__(self, to_run: Callable|QJSValue, args=[], start_automatically=True):
@ -115,10 +115,8 @@ class PyPromise(QObject):
""" """
on_fulfill = check_callable(on_fulfill) on_fulfill = check_callable(on_fulfill)
on_reject = check_callable(on_reject) on_reject = check_callable(on_reject)
if on_fulfill is not None: self._fulfills.append(on_fulfill)
self._fulfills.append(on_fulfill) self._rejects.append(on_reject)
if on_reject is not None:
self._rejects.append(on_reject)
return self return self
def calls_upon_fulfillment(self, function: Callable | QJSValue) -> bool: def calls_upon_fulfillment(self, function: Callable | QJSValue) -> bool:
@ -155,20 +153,22 @@ class PyPromise(QObject):
def _fulfill(self, data): def _fulfill(self, data):
self._state = "fulfilled" self._state = "fulfilled"
no_return = [None, QJSValue.SpecialValue.UndefinedValue] no_return = [None, QJSValue.SpecialValue.UndefinedValue]
for on_fulfill in self._fulfills: print("Fulfill")
for i in range(len(self._fulfills)):
try: try:
result = on_fulfill(data) result = self._fulfills[i](data)
result = result.qjs_value if isinstance(result, PyJSValue) else result result = result.qjs_value if isinstance(result, PyJSValue) else result
data = result if result not in no_return else data # Forward data. data = result if result not in no_return else data # Forward data.
except Exception as e: except Exception as e:
self._reject(repr(e)) self._reject(repr(e), start_at=i)
break break
@Slot(QJSValue) @Slot(QJSValue)
@Slot(str) @Slot(str)
def _reject(self, error): def _reject(self, error, start_at=0):
self._state = "rejected" self._state = "rejected"
no_return = [None, QJSValue.SpecialValue.UndefinedValue] no_return = [None, QJSValue.SpecialValue.UndefinedValue]
for on_reject in self._rejects: for i in range(start_at, len(self._rejects)):
result = on_reject(error) result = self._rejects[i](error)
result = result.qjs_value if isinstance(result, PyJSValue) else result
error = result if result not in no_return else error # Forward data. error = result if result not in no_return else error # Forward data.

View file

@ -21,7 +21,7 @@ from typing import Callable, Self
from .base import Assertion, repr_, AssertionInterface from .base import Assertion, repr_, AssertionInterface
from .int import NumberComparisonAssertionInterface from .int import NumberComparisonAssertionInterface
PRINT_PREFIX = (" " * 24) PRINT_PREFIX = (" " * 3)
class SpyAssertion(Assertion): class SpyAssertion(Assertion):

View file

@ -30,4 +30,4 @@ class Spy:
def __call__(self, *args, **kwargs): def __call__(self, *args, **kwargs):
self.calls.append((args, kwargs)) self.calls.append((args, kwargs))
if self.function is not None: if self.function is not None:
self.function(*args, **kwargs) return self.function(*args, **kwargs)

View file

@ -134,13 +134,13 @@ def test_add_natural_complex():
assert that(2).equals.one.minus.two assert that(2).equals.one.minus.two
def test_spy(): def test_spy():
spy = Spy() spy = Spy(lambda *args, **kw: 10)
assert that(spy).is_.an.instance_of(Spy) assert that(spy).is_.an.instance_of(Spy)
assert that(spy).is_(callable) assert that(spy).is_(callable)
# Check calls # Check calls
assert that(spy).was.never.called assert that(spy).was.never.called
assert that(spy).was.called.zero.times assert that(spy).was.called.zero.times
spy(30, arg="string") assert spy(30, arg="string") == 10
assert that(spy).was.called assert that(spy).was.called
assert that(spy).was.called.once assert that(spy).was.called.once
assert that(spy).was.called.one.time assert that(spy).was.called.one.time
@ -159,7 +159,7 @@ def test_spy():
assert that(spy).was.called.with_no_argument() assert that(spy).was.called.with_no_argument()
def test_spy_seral_calls(): def test_spy_seral_calls():
spy = Spy(lambda *args, **kw: None) spy = Spy()
obj = object() obj = object()
spy() spy()
spy(30, arg="string") spy(30, arg="string")

View file

@ -22,12 +22,13 @@ from os.path import exists, join
from PySide6.QtGui import QIcon from PySide6.QtGui import QIcon
from tempfile import TemporaryDirectory from tempfile import TemporaryDirectory
from .globals import app
from LogarithmPlotter.logarithmplotter import get_linux_theme, LINUX_THEMES, get_platform_qt_style, \ from LogarithmPlotter.logarithmplotter import get_linux_theme, LINUX_THEMES, get_platform_qt_style, \
register_icon_directories, install_translation, create_engine register_icon_directories, install_translation, create_engine
from LogarithmPlotter.util import config from LogarithmPlotter.util import config
from LogarithmPlotter.util.helper import Helper from LogarithmPlotter.util.helper import Helper
from LogarithmPlotter.util.latex import Latex from LogarithmPlotter.util.latex import Latex
from globals import app
THEMES = [ THEMES = [
"Basic", "Basic",

View file

@ -17,9 +17,10 @@
""" """
from time import sleep from time import sleep
import pytest
from PySide6.QtQml import QJSValue from PySide6.QtQml import QJSValue
from tests.plugins.natural import that, Spy from .plugins.natural import that, Spy
from LogarithmPlotter.util.js import PyJSValue from LogarithmPlotter.util.js import PyJSValue
from LogarithmPlotter.util.promise import PyPromise from LogarithmPlotter.util.promise import PyPromise
@ -56,6 +57,10 @@ def async_throw():
class TestPyPromise: class TestPyPromise:
def test_invalid_function(self):
with pytest.raises(ValueError):
promise = PyPromise("not a function")
def test_fulfill_values(self, qtbot): def test_fulfill_values(self, qtbot):
qjsv = QJSValue(3) qjsv = QJSValue(3)
values = [ values = [
@ -70,7 +75,6 @@ class TestPyPromise:
for [value, test] in values: for [value, test] in values:
promise = PyPromise(create_async_func(value)) promise = PyPromise(create_async_func(value))
with qtbot.assertNotEmitted(promise.rejected, wait=1000): with qtbot.assertNotEmitted(promise.rejected, wait=1000):
print("Testing", value)
with qtbot.waitSignal(promise.fulfilled, check_params_cb=test, timeout=2000): with qtbot.waitSignal(promise.fulfilled, check_params_cb=test, timeout=2000):
assert promise.state == "pending" assert promise.state == "pending"
assert promise.state == "fulfilled" assert promise.state == "fulfilled"
@ -84,30 +88,90 @@ class TestPyPromise:
assert promise.state == "rejected" assert promise.state == "rejected"
def test_fulfill(self, qtbot): def test_fulfill(self, qtbot):
spy_fulfilled = Spy() fulfilled = Spy()
spy_rejected = Spy() rejected = Spy()
promise = PyPromise(create_async_func(3)) promise = PyPromise(create_async_func(3))
then_res = promise.then(spy_fulfilled, spy_rejected) then_res = promise.then(fulfilled, rejected)
# Check if the return value is the same promise (so we can chain then) # Check if the return value is the same promise (so we can chain then)
assert then_res == promise assert that(then_res).does.equal(promise)
# Check on our spy. # Check on our spy.
with qtbot.waitSignal(promise.fulfilled, timeout=10000): with qtbot.waitSignal(promise.fulfilled, timeout=10000):
pass pass
assert that(spy_fulfilled).was.called.once assert that(fulfilled).was.called.once
assert that(spy_fulfilled).was.not_called.with_arguments(3) assert that(fulfilled).was.NOT.called.with_arguments(3)
assert that(spy_fulfilled).was.called.with_arguments_matching(check_promise_result(3)) assert that(fulfilled).was.called.with_arguments_matching(check_promise_result(3))
assert spy_rejected.was.not_called assert that(rejected).was.never.called
def test_rejected(self, qtbot): def test_rejected(self, qtbot):
spy_fulfilled = Spy() fulfilled = Spy()
spy_rejected = Spy() rejected = Spy()
promise = PyPromise(async_throw) promise = PyPromise(async_throw)
then_res = promise.then(spy_fulfilled, spy_rejected) then_res = promise.then(fulfilled, rejected)
# Check if the return value is the same promise (so we can chain then) # Check if the return value is the same promise (so we can chain then)
assert that(then_res).is_equal.to(promise) assert that(then_res).does.equal(promise)
# Check on our spies. # Check on our spies.
with qtbot.waitSignal(promise.rejected, timeout=10000): with qtbot.waitSignal(promise.rejected, timeout=10000):
pass pass
assert that(spy_rejected).was.called.once assert that(rejected).was.called.once
assert that(spy_rejected).was.called.with_arguments("Exception('aaaa')") assert that(rejected).was.called.with_arguments("Exception('aaaa')")
assert that(spy_fulfilled).was.not_called assert that(fulfilled).has.never.been.called
def test_chain_fulfill(self, qtbot):
convert = Spy(lambda v: v.toVariant())
plus = Spy(lambda v: v + 1)
rejected = Spy()
promise = PyPromise(create_async_func(5))
then_res = promise.then(convert, rejected).then(plus, rejected).then(plus, rejected).then(plus, rejected)
# Check if the return value is the same promise (so we can chain then)
assert that(then_res).does.equal(promise)
with qtbot.waitSignal(promise.fulfilled, timeout=10000):
pass
assert that(convert).was.called.once.with_arguments_matching(check_promise_result(5))
assert that(rejected).was.never.called
assert that(plus).was.called.three.times
assert that(plus).was.called.once.with_exact_arguments(5)
assert that(plus).was.called.once.with_exact_arguments(6)
assert that(plus).was.called.once.with_exact_arguments(7)
def test_chain_reject(self, qtbot):
fulfilled = Spy()
convert = Spy(lambda v: len(v))
minus = Spy(lambda v: v - 1)
promise = PyPromise(async_throw)
then_res = promise.then(fulfilled, convert).then(fulfilled, minus).then(fulfilled, minus).then(fulfilled, minus)
# Check if the return value is the same promise (so we can chain then)
assert that(then_res).does.equal(promise)
with qtbot.waitSignal(promise.rejected, timeout=10000):
pass
assert that(fulfilled).was.never.called
assert that(convert).was.called.once.with_arguments_matching(check_promise_result("Exception('aaaa')"))
assert that(minus).was.called.three.times
assert that(minus).was.called.once.with_exact_arguments(17)
assert that(minus).was.called.once.with_exact_arguments(16)
assert that(minus).was.called.once.with_exact_arguments(15)
def test_check_calls_upon(self):
promise = PyPromise(async_throw)
fulfilled = Spy()
rejected = Spy()
promise.then(fulfilled, rejected)
assert promise.calls_upon_fulfillment(fulfilled)
assert promise.calls_upon_rejection(rejected)
assert not promise.calls_upon_fulfillment(rejected)
assert not promise.calls_upon_rejection(fulfilled)
def test_reject_in_fulfill(self, qtbot):
def fulfilled_throw(x):
raise Exception('noooo')
promise = PyPromise(create_async_func("3"))
fulfilled_throw = Spy(fulfilled_throw)
fulfilled = Spy()
rejected = Spy()
then_res = promise.then(fulfilled, rejected).then(fulfilled_throw, rejected).then(fulfilled, rejected).then(fulfilled, rejected)
# Check if the return value is the same promise (so we can chain then)
assert that(then_res).does.equal(promise)
with qtbot.waitSignal(promise.fulfilled, timeout=10000):
pass
assert that(fulfilled_throw).has.been.called.once
assert that(rejected).has.been.called.three.times
assert that(rejected).has.been.called.three.times.with_arguments("Exception('noooo')")

View file

@ -21,7 +21,6 @@ from re import Pattern
from PySide6.QtQml import QJSEngine, QJSValue from PySide6.QtQml import QJSEngine, QJSValue
from LogarithmPlotter.util.js import PyJSValue, InvalidAttributeValueException, NotAPrimitiveException from LogarithmPlotter.util.js import PyJSValue, InvalidAttributeValueException, NotAPrimitiveException
from globals import app
@pytest.fixture() @pytest.fixture()
def data(): def data():

View file

@ -1,8 +1,20 @@
#!/bin/bash #!/bin/bash
cd "$(dirname "$(readlink -f "$0" || realpath "$0")")/.." || exit 1 cd "$(dirname "$(readlink -f "$0" || realpath "$0")")/.." || exit 1
box() {
len=${#1}
echo "┌─$(printf '─%.0s' $(seq 1 "$len"))─┐"
echo "$1"
echo "└─$(printf '─%.0s' $(seq 1 "$len"))─┘"
}
rebuild=true rebuild=true
cd runtime-pyside6/tests/plugins || exit 1
box "Testing pytest natural plugins..."
PYTHONPATH="$PYTHONPATH:." pytest --cov=natural --cov-report term-missing .
cd ../../../
while [ $# -gt 0 ]; do while [ $# -gt 0 ]; do
case "$1" in case "$1" in
--no-rebuild) --no-rebuild)
@ -27,10 +39,12 @@ rm -rf build/runtime-pyside6/tests
cp -r runtime-pyside6/tests build/runtime-pyside6 cp -r runtime-pyside6/tests build/runtime-pyside6
cp -r ci CHANGELOG.md build/runtime-pyside6 cp -r ci CHANGELOG.md build/runtime-pyside6
cd build/runtime-pyside6 || exit 1 cd build/runtime-pyside6 || exit 1
box "Testing runtime-pyside6..."
PYTHONPATH="$PYTHONPATH:." pytest --cov=LogarithmPlotter --cov-report term-missing . PYTHONPATH="$PYTHONPATH:." pytest --cov=LogarithmPlotter --cov-report term-missing .
cd ../../ cd ../../
# Run js tests # Run js tests
cd common || exit 1 cd common || exit 1
box "Testing common..."
npm test npm test