From 8fab9d8e529772a1dfcde801996d03002d3e829f Mon Sep 17 00:00:00 2001 From: Ad5001 Date: Wed, 16 Oct 2024 22:18:53 +0200 Subject: [PATCH] Starting natural language plugin. --- runtime-pyside6/tests/__init__.py | 0 runtime-pyside6/tests/plugins/__init__.py | 0 .../tests/plugins/natural/__init__.py | 21 ++ .../plugins/natural/interfaces/__init__.py | 0 .../plugins/natural/interfaces/assertion.py | 30 +++ .../tests/plugins/natural/interfaces/base.py | 101 ++++++++ .../tests/plugins/natural/interfaces/basic.py | 61 +++++ .../tests/plugins/natural/interfaces/int.py | 229 ++++++++++++++++++ .../tests/plugins/natural/interfaces/utils.py | 27 +++ .../tests/{ => plugins/natural}/spy.py | 128 +++++----- runtime-pyside6/tests/plugins/natural/that.py | 57 +++++ runtime-pyside6/tests/test_promise.py | 20 +- 12 files changed, 593 insertions(+), 81 deletions(-) create mode 100644 runtime-pyside6/tests/__init__.py create mode 100644 runtime-pyside6/tests/plugins/__init__.py create mode 100644 runtime-pyside6/tests/plugins/natural/__init__.py create mode 100644 runtime-pyside6/tests/plugins/natural/interfaces/__init__.py create mode 100644 runtime-pyside6/tests/plugins/natural/interfaces/assertion.py create mode 100644 runtime-pyside6/tests/plugins/natural/interfaces/base.py create mode 100644 runtime-pyside6/tests/plugins/natural/interfaces/basic.py create mode 100644 runtime-pyside6/tests/plugins/natural/interfaces/int.py create mode 100644 runtime-pyside6/tests/plugins/natural/interfaces/utils.py rename runtime-pyside6/tests/{ => plugins/natural}/spy.py (69%) create mode 100644 runtime-pyside6/tests/plugins/natural/that.py diff --git a/runtime-pyside6/tests/__init__.py b/runtime-pyside6/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/runtime-pyside6/tests/plugins/__init__.py b/runtime-pyside6/tests/plugins/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/runtime-pyside6/tests/plugins/natural/__init__.py b/runtime-pyside6/tests/plugins/natural/__init__.py new file mode 100644 index 0000000..7a67869 --- /dev/null +++ b/runtime-pyside6/tests/plugins/natural/__init__.py @@ -0,0 +1,21 @@ +""" + * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. + * Copyright (C) 2021-2024 Ad5001 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . +""" + +from .spy import Spy +from .that import that + diff --git a/runtime-pyside6/tests/plugins/natural/interfaces/__init__.py b/runtime-pyside6/tests/plugins/natural/interfaces/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/runtime-pyside6/tests/plugins/natural/interfaces/assertion.py b/runtime-pyside6/tests/plugins/natural/interfaces/assertion.py new file mode 100644 index 0000000..f2504a2 --- /dev/null +++ b/runtime-pyside6/tests/plugins/natural/interfaces/assertion.py @@ -0,0 +1,30 @@ +""" + * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. + * Copyright (C) 2021-2024 Ad5001 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . +""" + +class Assertion(Exception): + def __init__(self, assertion: bool, message: str): + self.assertion = assertion + self.message = message + + def __str__(self): + return self.message + + def __bool__(self): + if not self.assertion: + raise self + return self.assertion \ No newline at end of file diff --git a/runtime-pyside6/tests/plugins/natural/interfaces/base.py b/runtime-pyside6/tests/plugins/natural/interfaces/base.py new file mode 100644 index 0000000..88b9e06 --- /dev/null +++ b/runtime-pyside6/tests/plugins/natural/interfaces/base.py @@ -0,0 +1,101 @@ +""" + * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. + * Copyright (C) 2021-2024 Ad5001 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . +""" +from typing import Self + +from tests.plugins.natural.interfaces.assertion import Assertion +from tests.plugins.natural.interfaces.utils import repr_ + + +class AssertionInterface: + """ + Most basic assertion interface. + You probably want to use BaseAssertionInterface + """ + def __init__(self, value): + self._value = value + + @property + def was(self) -> Self: + return self + + @property + def be(self) -> Self: + return self + + @property + def been(self) -> Self: + return self + + @property + def have(self) -> Self: + return self + + @property + def has(self) -> Self: + return self + + @property + def a(self) -> Self: + return self + + @property + def an(self) -> Self: + return self + + def is_a(self, type_) -> Assertion: + """ + Checks if the current value is equal to the provided value + """ + value_type_name = type(self._value).__name__ + return Assertion(isinstance(type_, type_), f"The value ({value_type_name} {repr_(self._value)}) is not a {type_.__name__}.") + + + +class EqualAssertionInterface(AssertionInterface): + """ + Interface created for when its value should be checked for equality + """ + def __init__(self, value): + super().__init__(value) + + def __call__(self, value) -> Assertion: + return Assertion(value == self._value, f"The value ({repr_(self._value)}) is not equal to {repr(value)}.") + + def to(self, value) -> Self: + return self(value) + + def of(self, value) -> Self: + return self(value) + + + +class BaseAssertionInterface(AssertionInterface): + + @property + def equals(self) -> EqualAssertionInterface: + """ + Checks if the current value is equal to the provided value + """ + return EqualAssertionInterface(self._value) + + @property + def is_equal(self) -> EqualAssertionInterface: + """ + Checks if the current value is equal to the provided value + """ + return self.equals \ No newline at end of file diff --git a/runtime-pyside6/tests/plugins/natural/interfaces/basic.py b/runtime-pyside6/tests/plugins/natural/interfaces/basic.py new file mode 100644 index 0000000..1d5a878 --- /dev/null +++ b/runtime-pyside6/tests/plugins/natural/interfaces/basic.py @@ -0,0 +1,61 @@ +""" + * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. + * Copyright (C) 2021-2024 Ad5001 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . +""" +from tests.plugins.natural.interfaces.assertion import Assertion +from tests.plugins.natural.interfaces.base import BaseAssertionInterface +from tests.plugins.natural.interfaces.int import IntInterface +from tests.plugins.natural.interfaces.utils import repr_ + + +class FixedIteratorInterface(BaseAssertionInterface): + @property + def length(self) -> IntInterface: + return IntInterface(len(self._value)) + + def elements(self, *elements) -> Assertion: + tests = [elem for elem in elements if elem in self._value] + return Assertion( + len(tests) == 0, + f"This value ({repr_(self._value)}) does not have elements ({repr_(tests)})" + ) + + def element(self, element) -> Assertion: + return Assertion( + element in self._value, + f"This value ({repr_(self._value)}) does not have element ({repr_(element)})" + ) + + def contains(self, *elements) -> Assertion: + return self.elements(*elements) + +class BoolInterface(BaseAssertionInterface): + @property + def is_true(self): + return Assertion( + self._value == True, + f"The value ({repr_(self._value)}) is not True." + ) + + @property + def is_false(self): + return Assertion( + self._value == False, + f"The value ({repr_(self._value)}) is not False." + ) + +class StringInterface(LengthInterface): + pass \ No newline at end of file diff --git a/runtime-pyside6/tests/plugins/natural/interfaces/int.py b/runtime-pyside6/tests/plugins/natural/interfaces/int.py new file mode 100644 index 0000000..aaf0511 --- /dev/null +++ b/runtime-pyside6/tests/plugins/natural/interfaces/int.py @@ -0,0 +1,229 @@ +""" + * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. + * Copyright (C) 2021-2024 Ad5001 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . +""" +from typing import Self + +from tests.plugins.natural.interfaces.assertion import Assertion +from tests.plugins.natural.interfaces.base import BaseAssertionInterface, EqualAssertionInterface, AssertionInterface +from tests.plugins.natural.interfaces.utils import repr_ + + +class IntComparisonAssertionInterface(AssertionInterface): + def __init__(self, value): + super().__init__(value) + self._compare_to = None + + def _compare(self) -> Assertion: + raise RuntimeError(f"No comparison method defined in {type(self).__name__}.") + + def __bool__(self) -> bool: + return bool(self._compare()) + + def __call__(self, compare_to: int) -> Self: + if self._compare_to is None: + self._compare_to = int(compare_to) + else: + self._compare_to *= int(compare_to) + return self + + @property + def time(self) -> Self: + return self + + @property + def times(self) -> Self: + return self + + @property + def never(self) -> Self: + return self(0) + + @property + def once(self) -> Self: + return self(1) + + @property + def twice(self) -> Self: + return self(2) + + @property + def thrice(self) -> Self: + return self(3) + + @property + def zero(self) -> Self: + return self(0) + + @property + def one(self) -> Self: + return self(1) + + @property + def two(self) -> Self: + return self(2) + + @property + def three(self) -> Self: + return self(3) + + @property + def four(self) -> Self: + return self(4) + + @property + def five(self) -> Self: + return self(5) + + @property + def six(self) -> Self: + return self(6) + + @property + def seven(self) -> Self: + return self(7) + + @property + def eight(self) -> Self: + return self(8) + + @property + def nine(self) -> Self: + return self(9) + + @property + def ten(self) -> Self: + return self(10) + + @property + def twenty(self) -> Self: + return self(20) + + @property + def thirty(self) -> Self: + return self(30) + + @property + def forty(self) -> Self: + return self(40) + + @property + def fifty(self) -> Self: + return self(50) + + @property + def sixty(self) -> Self: + return self(60) + + @property + def seventy(self) -> Self: + return self(70) + + @property + def eighty(self) -> Self: + return self(70) + + @property + def ninety(self) -> Self: + return self(70) + + @property + def hundred(self) -> Self: + return self(100) + + @property + def thousand(self) -> Self: + return self(1_000) + + @property + def million(self) -> Self: + return self(1_000_000) + + @property + def billion(self) -> Self: + return self(1_000_000_000) + + +class LessThanComparisonInterface(IntComparisonAssertionInterface): + def _compare(self) -> Assertion: + return Assertion( + self._value < self._compare_to, + f"The value ({repr_(self._value)}) is not less than to {repr_(self._compare_to)}." + ) + +class MoreThanComparisonInterface(IntComparisonAssertionInterface): + def _compare(self) -> Assertion: + return Assertion( + self._value > self._compare_to, + f"The value ({repr_(self._value)}) is not more than to {repr_(self._compare_to)}." + ) + +class AtLeastComparisonInterface(IntComparisonAssertionInterface): + def _compare(self) -> Assertion: + return Assertion( + self._value >= self._compare_to, + f"The value ({repr_(self._value)}) is not at least to {repr_(self._compare_to)}." + ) + +class AtMostComparisonInterface(IntComparisonAssertionInterface): + def _compare(self) -> Assertion: + return Assertion( + self._value <= self._compare_to, + f"The value ({repr_(self._value)}) is not at least to {repr_(self._compare_to)}." + ) + +class EqualComparisonInterface(IntComparisonAssertionInterface): + def _compare(self) -> Assertion: + return Assertion( + self._value == self._compare_to, + f"The value ({repr_(self._value)}) is not equal to {repr_(self._compare_to)}." + ) + + def to(self) -> Self: + return self + + def of(self) -> Self: + return self + + +class IntInterface(AssertionInterface): + def less_than(self) -> LessThanComparisonInterface: + return LessThanComparisonInterface(self._value) + + @property + def more_than(self) -> MoreThanComparisonInterface: + return MoreThanComparisonInterface(self._value) + + @property + def at_least(self) -> AtLeastComparisonInterface: + return AtLeastComparisonInterface(self._value) + + @property + def at_most(self) -> AtMostComparisonInterface: + return AtMostComparisonInterface(self._value) + + @property + def equals(self) -> EqualComparisonInterface: + return EqualComparisonInterface(self._value) + + @property + def is_equal(self) -> EqualComparisonInterface: + return EqualComparisonInterface(self._value) + + @property + def exactly(self) -> EqualComparisonInterface: + return EqualComparisonInterface(self._value) + diff --git a/runtime-pyside6/tests/plugins/natural/interfaces/utils.py b/runtime-pyside6/tests/plugins/natural/interfaces/utils.py new file mode 100644 index 0000000..50e3777 --- /dev/null +++ b/runtime-pyside6/tests/plugins/natural/interfaces/utils.py @@ -0,0 +1,27 @@ +""" + * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. + * Copyright (C) 2021-2024 Ad5001 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . +""" + +from PySide6.QtQml import QJSValue + + +def repr_(data): + if isinstance(data, QJSValue): + variant = data.toVariant() + return f"QJSValue<{type(variant).__name__}>({repr(variant)})" + else: + return repr(data) \ No newline at end of file diff --git a/runtime-pyside6/tests/spy.py b/runtime-pyside6/tests/plugins/natural/spy.py similarity index 69% rename from runtime-pyside6/tests/spy.py rename to runtime-pyside6/tests/plugins/natural/spy.py index 01f9aa0..1d77629 100644 --- a/runtime-pyside6/tests/spy.py +++ b/runtime-pyside6/tests/plugins/natural/spy.py @@ -17,35 +17,29 @@ """ from typing import Callable, Self -from PySide6.QtQml import QJSValue +from tests.plugins.natural.interfaces.base import Assertion, repr_, AssertionInterface +from tests.plugins.natural.interfaces.int import IntComparisonAssertionInterface PRINT_PREFIX = (" " * 24) -class SpyAssertionFailed(Exception): - def __init__(self, message, calls): - self.message = message + "\n" + +class SpyAssertion(Assertion): + def __init__(self, assertion: bool, message: str, calls: list): + super().__init__(assertion, message + "\n") if len(calls) > 0: self.message += self.render_calls(calls) else: self.message += f"{PRINT_PREFIX}0 registered calls." - def repr(self, data): - if isinstance(data, QJSValue): - variant = data.toVariant() - return f"QJSValue<{type(variant).__name__}>({repr(variant)})" - else: - return repr(data) def render_calls(self, calls): lines = [f"{PRINT_PREFIX}{len(calls)} registered call(s):"] for call in calls: - repr_args = [self.repr(arg) for arg in call[0]] - repr_kwargs =[f"{key}={self.repr(arg)}" for key, arg in call[1].items()] + repr_args = [repr_(arg) for arg in call[0]] + repr_kwargs = [f"{key}={repr_(arg)}" for key, arg in call[1].items()] lines.append(f" - {', '.join([*repr_args, *repr_kwargs])}") return ("\n" + PRINT_PREFIX).join(lines) - def __str__(self): - return self.message class Methods: AT_LEAST_ONCE = "AT_LEAST_ONCE" @@ -55,18 +49,19 @@ class Methods: MORE_THAN = "AT_LEAST" LESS_THAN = "AT_MOST" -class CalledInterface: + +class CalledInterface(IntComparisonAssertionInterface): """ Internal class generated by Spy.was_called. """ def __init__(self, calls: list[tuple[list, dict]]): + super().__init__(len(calls)) self.__calls = calls self.__method = Methods.AT_LEAST_ONCE - self.__times = None def __apply_method(self, calls): - required = self.__times + required = self._compare_to calls_count = len(calls) match self.__method: case Methods.AT_LEAST_ONCE: @@ -91,61 +86,59 @@ class CalledInterface: raise RuntimeError(f"Unknown method {self.__method}.") return compare, error - def __bool__(self): + def __bool__(self) -> bool: """ Converts to boolean on assertion. """ compare, error = self.__apply_method(self.__calls) - if not compare: - raise SpyAssertionFailed(error+".") - return compare - + return bool(SpyAssertion(compare, error + ".", self.__calls)) """ Chaining methods """ + def __call__(self, *args, **kwargs) -> Self: if len(args) != 1: raise RuntimeError("Cannot call called interface with more than one argument.") - self.__times = int(args[0]) + self._compare_to = int(args[0]) if self.__method == Methods.AT_LEAST_ONCE: self.__method = Methods.EXACTLY return self - @property - def never(self) -> Self: - return self(0) - - @property - def once(self) -> Self: - return self(1) - - @property - def twice(self) -> Self: - return self(2) - - @property - def thrice(self) -> Self: - return self(3) - @property def at_least(self) -> Self: - self.__method = Methods.AT_LEAST + if self.__method == Methods.AT_LEAST_ONCE: + self.__method = Methods.AT_LEAST + else: + raise RuntimeError(f"Cannot redefine method from {self.__method} to {Methods.AT_MOST}") return self @property def at_most(self) -> Self: - self.__method = Methods.AT_MOST + if self.__method == Methods.AT_LEAST_ONCE: + self.__method = Methods.AT_MOST + else: + raise RuntimeError(f"Cannot redefine method from {self.__method} to {Methods.AT_MOST}") return self @property def more_than(self) -> Self: - self.__method = Methods.MORE_THAN + if self.__method == Methods.AT_LEAST_ONCE: + self.__method = Methods.MORE_THAN + else: + raise RuntimeError(f"Cannot redefine method from {self.__method} to {Methods.MORE_THAN}") return self @property def less_than(self) -> Self: - self.__method = Methods.LESS_THAN + if self.__method == Methods.AT_LEAST_ONCE: + self.__method = Methods.LESS_THAN + else: + raise RuntimeError(f"Cannot redefine method from {self.__method} to {Methods.LESS_THAN}") + return self + + @property + def time(self) -> Self: return self @property @@ -155,6 +148,7 @@ class CalledInterface: """ Class properties. """ + def __match_calls_for_condition(self, condition: Callable[[list, dict], bool]) -> tuple[bool, str]: calls = [] for call in self.__calls: @@ -163,12 +157,12 @@ class CalledInterface: compare, error = self.__apply_method(calls) return compare, error - - def with_arguments(self, *args, **kwargs): + def with_arguments(self, *args, **kwargs) -> SpyAssertion: """ Checks if the Spy has been called the given number of times with at least the given arguments. """ + def some_args_matched(a, kw): args_matched = all(( arg in a @@ -179,55 +173,49 @@ class CalledInterface: for key, arg in kwargs.items() )) return args_matched and kwargs_matched + compare, error = self.__match_calls_for_condition(some_args_matched) - if not compare: - repr_args = ', '.join([repr(arg) for arg in args]) - repr_kwargs = ', '.join([f"{key}={repr(arg)}" for key, arg in kwargs.items()]) - raise SpyAssertionFailed(f"{error} with arguments ({repr_args}) and keyword arguments ({repr_kwargs}).", self.__calls) - return compare + repr_args = ', '.join([repr(arg) for arg in args]) + repr_kwargs = ', '.join([f"{key}={repr(arg)}" for key, arg in kwargs.items()]) + msg = f"{error} with arguments ({repr_args}) and keyword arguments ({repr_kwargs})." + return SpyAssertion(compare, msg, self.__calls) - - def with_arguments_matching(self, test_condition: Callable[[list, dict], bool]): + def with_arguments_matching(self, test_condition: Callable[[list, dict], bool]) -> SpyAssertion: """ Checks if the Spy has been called the given number of times with arguments matching the given conditions. """ compare, error = self.__match_calls_for_condition(test_condition) - if not compare: - raise SpyAssertionFailed(f"{error} with arguments matching given conditions.", self.__calls) - return compare + msg = f"{error} with arguments matching given conditions." + return SpyAssertion(compare, msg, self.__calls) - - def with_exact_arguments(self, *args, **kwargs): + def with_exact_arguments(self, *args, **kwargs) -> SpyAssertion: """ Checks if the Spy has been called the given number of times with all the given arguments. """ compare, error = self.__match_calls_for_condition(lambda a, kw: a == args and kw == kwargs) - if not compare: - repr_args = ', '.join([repr(arg) for arg in args]) - repr_kwargs = ', '.join([f"{key}={repr(arg)}" for key, arg in kwargs.items()]) - raise SpyAssertionFailed(f"{error} with exact arguments ({repr_args}) and keyword arguments ({repr_kwargs}).", self.__calls) - return compare + repr_args = ', '.join([repr(arg) for arg in args]) + repr_kwargs = ', '.join([f"{key}={repr(arg)}" for key, arg in kwargs.items()]) + msg = f"{error} with exact arguments ({repr_args}) and keyword arguments ({repr_kwargs})." + return SpyAssertion(compare, msg, self.__calls) - def with_no_argument(self): + def with_no_argument(self) -> SpyAssertion: """ Checks if the Spy has been called the given number of times with all the given arguments. """ compare, error = self.__match_calls_for_condition(lambda a, kw: len(a) == 0 and len(kw) == 0) - if not compare: - raise SpyAssertionFailed(f"{error} with no arguments.", self.__calls) - return compare + return SpyAssertion(compare, f"{error} with no arguments.", self.__calls) - -class Spy: +class Spy(AssertionInterface): """ Class to spy into method calls with natural language expressions. """ def __init__(self, function: Callable = None): + super().__init__(function) self.function = function self.calls = [] @@ -237,7 +225,7 @@ class Spy: self.function(*args, **kwargs) @property - def was_called(self) -> CalledInterface: + def called(self) -> CalledInterface: """ Returns a boolean-able interface to check conditions for a given number of time the spy was called. @@ -245,7 +233,7 @@ class Spy: return CalledInterface(self.calls) @property - def was_not_called(self) -> CalledInterface: + def not_called(self) -> CalledInterface: """ Returns a boolean-able interface to check that conditions were never fulfilled in the times the spy was called. diff --git a/runtime-pyside6/tests/plugins/natural/that.py b/runtime-pyside6/tests/plugins/natural/that.py new file mode 100644 index 0000000..38ee54e --- /dev/null +++ b/runtime-pyside6/tests/plugins/natural/that.py @@ -0,0 +1,57 @@ +""" + * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. + * Copyright (C) 2021-2024 Ad5001 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . +""" +from typing import overload, Generic, TypeVar + +from .spy import Spy +from .interfaces.base import AssertionInterface, BaseAssertionInterface +from .interfaces.basic import StringInterface, BoolInterface +from .interfaces.int import IntInterface + +Interface = TypeVar("Interface", bound=AssertionInterface) + +MATCHES = [ + (str, StringInterface), + (int, IntInterface), + (bool, BoolInterface), +] + + +@overload +def that(value: str) -> StringInterface: ... + + +@overload +def that(value: int) -> IntInterface: ... + + +@overload +def that(value: bool) -> BoolInterface: ... + + +@overload +def that[Interface](value: Interface) -> Interface: ... + + +def that(value: any) -> AssertionInterface: + if not isinstance(value, AssertionInterface): + interface = next((i for t, i in MATCHES if isinstance(value, t)), None) + if interface is not None: + value = interface(value) + else: + value = BaseAssertionInterface(value) + return value diff --git a/runtime-pyside6/tests/test_promise.py b/runtime-pyside6/tests/test_promise.py index 06c0b8a..a97f864 100644 --- a/runtime-pyside6/tests/test_promise.py +++ b/runtime-pyside6/tests/test_promise.py @@ -17,11 +17,9 @@ """ from time import sleep -import pytest -from PySide6.QtCore import QObject from PySide6.QtQml import QJSValue -from spy import Spy +from tests.plugins.natural import that, Spy from LogarithmPlotter.util.js import PyJSValue from LogarithmPlotter.util.promise import PyPromise @@ -95,10 +93,10 @@ class TestPyPromise: # Check on our spy. with qtbot.waitSignal(promise.fulfilled, timeout=10000): pass - assert spy_fulfilled.was_called.once - assert spy_fulfilled.was_not_called.with_arguments(3) - assert spy_fulfilled.was_called.with_arguments_matching(check_promise_result(3)) - assert spy_rejected.was_not_called + assert that(spy_fulfilled).was.called.once + assert that(spy_fulfilled).was.not_called.with_arguments(3) + assert that(spy_fulfilled).was.called.with_arguments_matching(check_promise_result(3)) + assert spy_rejected.was.not_called def test_rejected(self, qtbot): spy_fulfilled = Spy() @@ -106,10 +104,10 @@ class TestPyPromise: promise = PyPromise(async_throw) then_res = promise.then(spy_fulfilled, spy_rejected) # Check if the return value is the same promise (so we can chain then) - assert then_res == promise + assert that(then_res).is_equal.to(promise) # Check on our spies. with qtbot.waitSignal(promise.rejected, timeout=10000): pass - assert spy_rejected.was_called.once - assert spy_rejected.was_called.with_arguments("Exception('aaaa')") - assert spy_fulfilled.was_not_called + assert that(spy_rejected).was.called.once + assert that(spy_rejected).was.called.with_arguments("Exception('aaaa')") + assert that(spy_fulfilled).was.not_called