Finishing testing promises.
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
This commit is contained in:
parent
ef465b34e7
commit
a182c703f4
8 changed files with 114 additions and 36 deletions
|
@ -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.
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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")
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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')")
|
|
@ -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():
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue