Updated script that can be controled by Nodejs web app
This commit is contained in:
@@ -0,0 +1,374 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
import sys
|
||||
from types import TracebackType
|
||||
from typing import Any
|
||||
|
||||
import pytest
|
||||
|
||||
import trio
|
||||
from trio.testing import Matcher, RaisesGroup
|
||||
|
||||
if sys.version_info < (3, 11):
|
||||
from exceptiongroup import ExceptionGroup
|
||||
|
||||
|
||||
def wrap_escape(s: str) -> str:
|
||||
return "^" + re.escape(s) + "$"
|
||||
|
||||
|
||||
def test_raises_group() -> None:
|
||||
with pytest.raises(
|
||||
ValueError,
|
||||
match=wrap_escape(
|
||||
f'Invalid argument "{TypeError()!r}" must be exception type, Matcher, or RaisesGroup.',
|
||||
),
|
||||
):
|
||||
RaisesGroup(TypeError())
|
||||
|
||||
with RaisesGroup(ValueError):
|
||||
raise ExceptionGroup("foo", (ValueError(),))
|
||||
|
||||
with RaisesGroup(SyntaxError):
|
||||
with RaisesGroup(ValueError):
|
||||
raise ExceptionGroup("foo", (SyntaxError(),))
|
||||
|
||||
# multiple exceptions
|
||||
with RaisesGroup(ValueError, SyntaxError):
|
||||
raise ExceptionGroup("foo", (ValueError(), SyntaxError()))
|
||||
|
||||
# order doesn't matter
|
||||
with RaisesGroup(SyntaxError, ValueError):
|
||||
raise ExceptionGroup("foo", (ValueError(), SyntaxError()))
|
||||
|
||||
# nested exceptions
|
||||
with RaisesGroup(RaisesGroup(ValueError)):
|
||||
raise ExceptionGroup("foo", (ExceptionGroup("bar", (ValueError(),)),))
|
||||
|
||||
with RaisesGroup(
|
||||
SyntaxError,
|
||||
RaisesGroup(ValueError),
|
||||
RaisesGroup(RuntimeError),
|
||||
):
|
||||
raise ExceptionGroup(
|
||||
"foo",
|
||||
(
|
||||
SyntaxError(),
|
||||
ExceptionGroup("bar", (ValueError(),)),
|
||||
ExceptionGroup("", (RuntimeError(),)),
|
||||
),
|
||||
)
|
||||
|
||||
# will error if there's excess exceptions
|
||||
with pytest.raises(ExceptionGroup):
|
||||
with RaisesGroup(ValueError):
|
||||
raise ExceptionGroup("", (ValueError(), ValueError()))
|
||||
|
||||
with pytest.raises(ExceptionGroup):
|
||||
with RaisesGroup(ValueError):
|
||||
raise ExceptionGroup("", (RuntimeError(), ValueError()))
|
||||
|
||||
# will error if there's missing exceptions
|
||||
with pytest.raises(ExceptionGroup):
|
||||
with RaisesGroup(ValueError, ValueError):
|
||||
raise ExceptionGroup("", (ValueError(),))
|
||||
|
||||
with pytest.raises(ExceptionGroup):
|
||||
with RaisesGroup(ValueError, SyntaxError):
|
||||
raise ExceptionGroup("", (ValueError(),))
|
||||
|
||||
|
||||
def test_flatten_subgroups() -> None:
|
||||
# loose semantics, as with expect*
|
||||
with RaisesGroup(ValueError, flatten_subgroups=True):
|
||||
raise ExceptionGroup("", (ExceptionGroup("", (ValueError(),)),))
|
||||
|
||||
with RaisesGroup(ValueError, TypeError, flatten_subgroups=True):
|
||||
raise ExceptionGroup("", (ExceptionGroup("", (ValueError(), TypeError())),))
|
||||
with RaisesGroup(ValueError, TypeError, flatten_subgroups=True):
|
||||
raise ExceptionGroup("", [ExceptionGroup("", [ValueError()]), TypeError()])
|
||||
|
||||
# mixed loose is possible if you want it to be at least N deep
|
||||
with RaisesGroup(RaisesGroup(ValueError, flatten_subgroups=True)):
|
||||
raise ExceptionGroup("", (ExceptionGroup("", (ValueError(),)),))
|
||||
with RaisesGroup(RaisesGroup(ValueError, flatten_subgroups=True)):
|
||||
raise ExceptionGroup(
|
||||
"",
|
||||
(ExceptionGroup("", (ExceptionGroup("", (ValueError(),)),)),),
|
||||
)
|
||||
with pytest.raises(ExceptionGroup):
|
||||
with RaisesGroup(RaisesGroup(ValueError, flatten_subgroups=True)):
|
||||
raise ExceptionGroup("", (ValueError(),))
|
||||
|
||||
# but not the other way around
|
||||
with pytest.raises(
|
||||
ValueError,
|
||||
match="^You cannot specify a nested structure inside a RaisesGroup with",
|
||||
):
|
||||
RaisesGroup(RaisesGroup(ValueError), flatten_subgroups=True) # type: ignore[call-overload]
|
||||
|
||||
|
||||
def test_catch_unwrapped_exceptions() -> None:
|
||||
# Catches lone exceptions with strict=False
|
||||
# just as except* would
|
||||
with RaisesGroup(ValueError, allow_unwrapped=True):
|
||||
raise ValueError
|
||||
|
||||
# expecting multiple unwrapped exceptions is not possible
|
||||
with pytest.raises(
|
||||
ValueError,
|
||||
match="^You cannot specify multiple exceptions with",
|
||||
):
|
||||
RaisesGroup(SyntaxError, ValueError, allow_unwrapped=True) # type: ignore[call-overload]
|
||||
# if users want one of several exception types they need to use a Matcher
|
||||
# (which the error message suggests)
|
||||
with RaisesGroup(
|
||||
Matcher(check=lambda e: isinstance(e, (SyntaxError, ValueError))),
|
||||
allow_unwrapped=True,
|
||||
):
|
||||
raise ValueError
|
||||
|
||||
# Unwrapped nested `RaisesGroup` is likely a user error, so we raise an error.
|
||||
with pytest.raises(ValueError, match="has no effect when expecting"):
|
||||
RaisesGroup(RaisesGroup(ValueError), allow_unwrapped=True) # type: ignore[call-overload]
|
||||
|
||||
# But it *can* be used to check for nesting level +- 1 if they move it to
|
||||
# the nested RaisesGroup. Users should probably use `Matcher`s instead though.
|
||||
with RaisesGroup(RaisesGroup(ValueError, allow_unwrapped=True)):
|
||||
raise ExceptionGroup("", [ExceptionGroup("", [ValueError()])])
|
||||
with RaisesGroup(RaisesGroup(ValueError, allow_unwrapped=True)):
|
||||
raise ExceptionGroup("", [ValueError()])
|
||||
|
||||
# with allow_unwrapped=False (default) it will not be caught
|
||||
with pytest.raises(ValueError, match="^value error text$"):
|
||||
with RaisesGroup(ValueError):
|
||||
raise ValueError("value error text")
|
||||
|
||||
# allow_unwrapped on it's own won't match against nested groups
|
||||
with pytest.raises(ExceptionGroup):
|
||||
with RaisesGroup(ValueError, allow_unwrapped=True):
|
||||
raise ExceptionGroup("", [ExceptionGroup("", [ValueError()])])
|
||||
|
||||
# for that you need both allow_unwrapped and flatten_subgroups
|
||||
with RaisesGroup(ValueError, allow_unwrapped=True, flatten_subgroups=True):
|
||||
raise ExceptionGroup("", [ExceptionGroup("", [ValueError()])])
|
||||
|
||||
# code coverage
|
||||
with pytest.raises(TypeError):
|
||||
with RaisesGroup(ValueError, allow_unwrapped=True):
|
||||
raise TypeError
|
||||
|
||||
|
||||
def test_match() -> None:
|
||||
# supports match string
|
||||
with RaisesGroup(ValueError, match="bar"):
|
||||
raise ExceptionGroup("bar", (ValueError(),))
|
||||
|
||||
# now also works with ^$
|
||||
with RaisesGroup(ValueError, match="^bar$"):
|
||||
raise ExceptionGroup("bar", (ValueError(),))
|
||||
|
||||
# it also includes notes
|
||||
with RaisesGroup(ValueError, match="my note"):
|
||||
e = ExceptionGroup("bar", (ValueError(),))
|
||||
e.add_note("my note")
|
||||
raise e
|
||||
|
||||
# and technically you can match it all with ^$
|
||||
# but you're probably better off using a Matcher at that point
|
||||
with RaisesGroup(ValueError, match="^bar\nmy note$"):
|
||||
e = ExceptionGroup("bar", (ValueError(),))
|
||||
e.add_note("my note")
|
||||
raise e
|
||||
|
||||
with pytest.raises(ExceptionGroup):
|
||||
with RaisesGroup(ValueError, match="foo"):
|
||||
raise ExceptionGroup("bar", (ValueError(),))
|
||||
|
||||
|
||||
def test_check() -> None:
|
||||
exc = ExceptionGroup("", (ValueError(),))
|
||||
with RaisesGroup(ValueError, check=lambda x: x is exc):
|
||||
raise exc
|
||||
with pytest.raises(ExceptionGroup):
|
||||
with RaisesGroup(ValueError, check=lambda x: x is exc):
|
||||
raise ExceptionGroup("", (ValueError(),))
|
||||
|
||||
|
||||
def test_unwrapped_match_check() -> None:
|
||||
def my_check(e: object) -> bool: # pragma: no cover
|
||||
return True
|
||||
|
||||
msg = (
|
||||
"`allow_unwrapped=True` bypasses the `match` and `check` parameters"
|
||||
" if the exception is unwrapped. If you intended to match/check the"
|
||||
" exception you should use a `Matcher` object. If you want to match/check"
|
||||
" the exceptiongroup when the exception *is* wrapped you need to"
|
||||
" do e.g. `if isinstance(exc.value, ExceptionGroup):"
|
||||
" assert RaisesGroup(...).matches(exc.value)` afterwards."
|
||||
)
|
||||
with pytest.raises(ValueError, match=re.escape(msg)):
|
||||
RaisesGroup(ValueError, allow_unwrapped=True, match="foo") # type: ignore[call-overload]
|
||||
with pytest.raises(ValueError, match=re.escape(msg)):
|
||||
RaisesGroup(ValueError, allow_unwrapped=True, check=my_check) # type: ignore[call-overload]
|
||||
|
||||
# Users should instead use a Matcher
|
||||
rg = RaisesGroup(Matcher(ValueError, match="^foo$"), allow_unwrapped=True)
|
||||
with rg:
|
||||
raise ValueError("foo")
|
||||
with rg:
|
||||
raise ExceptionGroup("", [ValueError("foo")])
|
||||
|
||||
# or if they wanted to match/check the group, do a conditional `.matches()`
|
||||
with RaisesGroup(ValueError, allow_unwrapped=True) as exc:
|
||||
raise ExceptionGroup("bar", [ValueError("foo")])
|
||||
if isinstance(exc.value, ExceptionGroup): # pragma: no branch
|
||||
assert RaisesGroup(ValueError, match="bar").matches(exc.value)
|
||||
|
||||
|
||||
def test_RaisesGroup_matches() -> None:
|
||||
rg = RaisesGroup(ValueError)
|
||||
assert not rg.matches(None)
|
||||
assert not rg.matches(ValueError())
|
||||
assert rg.matches(ExceptionGroup("", (ValueError(),)))
|
||||
|
||||
|
||||
def test_message() -> None:
|
||||
def check_message(message: str, body: RaisesGroup[Any]) -> None:
|
||||
with pytest.raises(
|
||||
AssertionError,
|
||||
match=f"^DID NOT RAISE any exception, expected {re.escape(message)}$",
|
||||
):
|
||||
with body:
|
||||
...
|
||||
|
||||
# basic
|
||||
check_message("ExceptionGroup(ValueError)", RaisesGroup(ValueError))
|
||||
# multiple exceptions
|
||||
check_message(
|
||||
"ExceptionGroup(ValueError, ValueError)",
|
||||
RaisesGroup(ValueError, ValueError),
|
||||
)
|
||||
# nested
|
||||
check_message(
|
||||
"ExceptionGroup(ExceptionGroup(ValueError))",
|
||||
RaisesGroup(RaisesGroup(ValueError)),
|
||||
)
|
||||
|
||||
# Matcher
|
||||
check_message(
|
||||
"ExceptionGroup(Matcher(ValueError, match='my_str'))",
|
||||
RaisesGroup(Matcher(ValueError, "my_str")),
|
||||
)
|
||||
check_message(
|
||||
"ExceptionGroup(Matcher(match='my_str'))",
|
||||
RaisesGroup(Matcher(match="my_str")),
|
||||
)
|
||||
|
||||
# BaseExceptionGroup
|
||||
check_message(
|
||||
"BaseExceptionGroup(KeyboardInterrupt)",
|
||||
RaisesGroup(KeyboardInterrupt),
|
||||
)
|
||||
# BaseExceptionGroup with type inside Matcher
|
||||
check_message(
|
||||
"BaseExceptionGroup(Matcher(KeyboardInterrupt))",
|
||||
RaisesGroup(Matcher(KeyboardInterrupt)),
|
||||
)
|
||||
# Base-ness transfers to parent containers
|
||||
check_message(
|
||||
"BaseExceptionGroup(BaseExceptionGroup(KeyboardInterrupt))",
|
||||
RaisesGroup(RaisesGroup(KeyboardInterrupt)),
|
||||
)
|
||||
# but not to child containers
|
||||
check_message(
|
||||
"BaseExceptionGroup(BaseExceptionGroup(KeyboardInterrupt), ExceptionGroup(ValueError))",
|
||||
RaisesGroup(RaisesGroup(KeyboardInterrupt), RaisesGroup(ValueError)),
|
||||
)
|
||||
|
||||
|
||||
def test_matcher() -> None:
|
||||
with pytest.raises(
|
||||
ValueError,
|
||||
match="^You must specify at least one parameter to match on.$",
|
||||
):
|
||||
Matcher() # type: ignore[call-overload]
|
||||
with pytest.raises(
|
||||
ValueError,
|
||||
match=f"^exception_type {re.escape(repr(object))} must be a subclass of BaseException$",
|
||||
):
|
||||
Matcher(object) # type: ignore[type-var]
|
||||
|
||||
with RaisesGroup(Matcher(ValueError)):
|
||||
raise ExceptionGroup("", (ValueError(),))
|
||||
with pytest.raises(ExceptionGroup):
|
||||
with RaisesGroup(Matcher(TypeError)):
|
||||
raise ExceptionGroup("", (ValueError(),))
|
||||
|
||||
|
||||
def test_matcher_match() -> None:
|
||||
with RaisesGroup(Matcher(ValueError, "foo")):
|
||||
raise ExceptionGroup("", (ValueError("foo"),))
|
||||
with pytest.raises(ExceptionGroup):
|
||||
with RaisesGroup(Matcher(ValueError, "foo")):
|
||||
raise ExceptionGroup("", (ValueError("bar"),))
|
||||
|
||||
# Can be used without specifying the type
|
||||
with RaisesGroup(Matcher(match="foo")):
|
||||
raise ExceptionGroup("", (ValueError("foo"),))
|
||||
with pytest.raises(ExceptionGroup):
|
||||
with RaisesGroup(Matcher(match="foo")):
|
||||
raise ExceptionGroup("", (ValueError("bar"),))
|
||||
|
||||
# check ^$
|
||||
with RaisesGroup(Matcher(ValueError, match="^bar$")):
|
||||
raise ExceptionGroup("", [ValueError("bar")])
|
||||
with pytest.raises(ExceptionGroup):
|
||||
with RaisesGroup(Matcher(ValueError, match="^bar$")):
|
||||
raise ExceptionGroup("", [ValueError("barr")])
|
||||
|
||||
|
||||
def test_Matcher_check() -> None:
|
||||
def check_oserror_and_errno_is_5(e: BaseException) -> bool:
|
||||
return isinstance(e, OSError) and e.errno == 5
|
||||
|
||||
with RaisesGroup(Matcher(check=check_oserror_and_errno_is_5)):
|
||||
raise ExceptionGroup("", (OSError(5, ""),))
|
||||
|
||||
# specifying exception_type narrows the parameter type to the callable
|
||||
def check_errno_is_5(e: OSError) -> bool:
|
||||
return e.errno == 5
|
||||
|
||||
with RaisesGroup(Matcher(OSError, check=check_errno_is_5)):
|
||||
raise ExceptionGroup("", (OSError(5, ""),))
|
||||
|
||||
with pytest.raises(ExceptionGroup):
|
||||
with RaisesGroup(Matcher(OSError, check=check_errno_is_5)):
|
||||
raise ExceptionGroup("", (OSError(6, ""),))
|
||||
|
||||
|
||||
def test_matcher_tostring() -> None:
|
||||
assert str(Matcher(ValueError)) == "Matcher(ValueError)"
|
||||
assert str(Matcher(match="[a-z]")) == "Matcher(match='[a-z]')"
|
||||
pattern_no_flags = re.compile("noflag", 0)
|
||||
assert str(Matcher(match=pattern_no_flags)) == "Matcher(match='noflag')"
|
||||
pattern_flags = re.compile("noflag", re.IGNORECASE)
|
||||
assert str(Matcher(match=pattern_flags)) == f"Matcher(match={pattern_flags!r})"
|
||||
assert (
|
||||
str(Matcher(ValueError, match="re", check=bool))
|
||||
== f"Matcher(ValueError, match='re', check={bool!r})"
|
||||
)
|
||||
|
||||
|
||||
def test__ExceptionInfo(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
monkeypatch.setattr(
|
||||
trio.testing._raises_group,
|
||||
"ExceptionInfo",
|
||||
trio.testing._raises_group._ExceptionInfo,
|
||||
)
|
||||
with trio.testing.RaisesGroup(ValueError) as excinfo:
|
||||
raise ExceptionGroup("", (ValueError("hello"),))
|
||||
assert excinfo.type is ExceptionGroup
|
||||
assert excinfo.value.exceptions[0].args == ("hello",)
|
||||
assert isinstance(excinfo.tb, TracebackType)
|
Reference in New Issue
Block a user