Package: assertion
assertionThis small package solves a few issues of the regularassertstatements in Python:Regular asserts are ignored if the code is run withpython -O.If an assertion fails, no additional information is given about the
parameters which caused the fail.Test parameters are fixed (e.g., a variable which isTrue).A timing-critical test might failnow, but would have passed a few seconds later
(e.g., after some action has been completed).In a set of tests, only tests until the first failed test are executed.Theassertiontests behave differently:The tests are always evaluated.If a test fails, a string representation of the provided arguments is included in the
exception description.Test parameters can beconstants,functions (sync and async),other mutable variables.Optionally, a test can be re-evaluated until a configurable timeout is reached. By
default, the timeout is zero and test fail instantly.Optionally, raising exceptions of failed tests can be deferred to allow for the
completion of a set of tests (works also withpytest).QuickstartHere is a simple example:>>>fromassertionimportAssertion>>>assertion=Assertion()>>>val=999>>>assertion.false(val)Traceback(mostrecentcalllast):...TestError:999!=FalseHere is a more complex example using coroutines:importasynciofromassertionimportAssertionassertion=Assertion(msg="D'oh!",timeout=10.0)classCountdown:def__init__(self,start:int):self.counter=startasyncdefcountdown(self)->bool:self.counter-=1returnself.counter==0asyncdefmain():print("Test A...")awaitassertion.true(Countdown(10).countdown)# should be successfulprint("ok")print("Test B...")awaitassertion.true(Countdown(50).countdown)# should failprint("ok")asyncio.run(main())NoteThe test functions act as both regular functions and as awaitables depending
on the test arguments.If the test arguments contain coroutines or awaitables, youmustawaitthe test.There are several pre-defined tests:FunctionDescriptiontruefalseExpects single parameter and determines its boolean value.equalnot_equallessless_or_equalgreatergreater_or_equalExpects two parameters and performs the corresponding comparison operators.in_not_inExpects two parameters and uses the built-ininoperator.is_is_notExpects two parameters and uses the built-inisoperator.In addition to the test parameters, each call accepts optional parameters:msg:str, default: ""This message is prefixed to the exception text.timeout:float, default: 0.0 secondsThe number of seconds until the test is finally considered as failed.raised_exc(Type(Exception), default:TestError):AnyExceptionsubclass which is raised if the test fails.exc_pass:Type(Exception)orTuple(Type(Exception)), default:[]If one of the provided tests raises an exception inexc_pass, the test is
considered to be passed, independent of the actual test criteria.exc_ignore:Type(Exception)orTuple(Type(Exception)), default:[]If one of the provided tests raises an exception inexc_ignore, the exception
is ignored, and the test evaluation proceeds.NoteIf you use a non-zero timeout, the test arguments might be evaluated/called
multiple time. Make sure that any given function call can handle this or set
the timeout to zero.Detailed DescriptionConfigurationYou can choose different defaults formsg,raised_exc,timeout,exc_pass,
andexc_ignorewhile creating an instance ofAssertion.In addition, you can specify three more parameters:msg_length_max:int, default: 100This limits the string representation of the provided test parameters to the given
number of characters. The optional message is not considered and will always be
included in full length).delay_init:float, default 0.125 secondsNon-zero timeout only: If the initial test fails, the arguments are re-evaluated
after this initial delay.delay_max:float, default: 5.0 secondsNon-zero timeout only: This limits the delay between two test parameter
evaluations.timeout_fraction_warning:float, default: 0.75 (equals 75%)Non-zero timeout only: If more than this fraction of the timeout passed before
the test was successful, a warning is logged. If you see this warning, you might
want to increase the timeout. Set toNoneto disable warning.In the default implementation, the delay between each evaluation is doubled
(until it reaches the given maximum). This behavior can be changed by
overloadingAssert._get_new_delay.DeferringIf you want to execute all tests in a set of tests even if early tests fail, you can
defer the raising of exceptions. You have three options:Decorator:assertion=Assertion(raised_exc=ZeroDivisionError)@assertion.deferring_decoratordefmultiple_fails():assertion.true(False)assertion.true("this will not fail")assertion.equal(5,99,raised_exc=ValueError)assertion.false(True,msg="D'oh!")When callingmultiple_fails(), all four tests will be executed (with three
failing), but only one exception will be raised when leaving the functions scope. Here
is the abbreviated output of the exception:ZeroDivisionError: 3 deferred exception(s):
    ZeroDivisionError[File "<input>", line 5, in multiple_fails, "<code unavailable>"]: False != True
    ValueError[File "<input>", line 7, in multiple_fails, "<code unavailable>"]: 5 != 99
    ZeroDivisionError[File "<input>", line 8, in multiple_fails, "<code unavailable>"]: D'oh!: True != FalseThe decorator works for both synchronous and asynchronous functions.Context manager:If you want to define a defer scope on a sub-function level, you can use contexts:assertion=Assertion(raised_exc=ZeroDivisionError)withassertion.deferring_context():foriinrange(3):assertion.equal(i,1)Explicit start/stop calls (only use as a last resort):assertion=Assertion(raised_exc=ZeroDivisionError)assertion.start_deferring()foriinrange(3):assertion.equal(i,1)assertion.stop_deferring()When an assertion instance is garbage-collected, it checks if there are open defer
scopes (which not always works because of the limitations during the teardown phase).
If so, a warning and any remaining fail messages are logged, but no exception
is raised to avoid disturbing any ongoing shutdown procedures.Defer scopes can be nested. The raising of exceptions is deferred untilallscopes
have been exited.Exception HandlingIf an exception is raised from any of the tests, this exception is not caught by default
and therefore terminates the test evaluation. This behavior can be changed with the
two parametersexc_passandexc_ignore:If one of the provided tests raises an exception inexc_pass, the test is
considered to be passed, independent of the actual test criteria.If one of the provided tests raises an exception inexc_ignore, the exception
is ignored, and the test evaluation proceeds. This only makes a difference if thetimeoutparameter is non-zero, and the tests are re-evaluated.Similar PackagesThis package was heavily inspired by the assert magic inpytest <https://pytest.org>_.assert-info <https://pypi.org/project/assert-info/>_ has a slightly different
focus, but might suit you better.