diff --git a/.appveyor.yml b/.appveyor.yml deleted file mode 100644 index b6fd0d6..0000000 --- a/.appveyor.yml +++ /dev/null @@ -1,30 +0,0 @@ -skip_tags: true - -os: Visual Studio 2015 - -environment: - matrix: - - PYTHON: "C:\\Python35" - - PYTHON: "C:\\Python35-x64" - - PYTHON: "C:\\Python36" - - PYTHON: "C:\\Python36-x64" - - PYTHON: "C:\\Python37" - - PYTHON: "C:\\Python37-x64" - -build_script: - - "git --no-pager log -n2" - - "echo %APPVEYOR_REPO_COMMIT%" - - "SET PATH=%PYTHON%;%PYTHON%\\Scripts;;%PATH%" - - "python --version" - - "python -c \"import struct; print(struct.calcsize('P') * 8)\"" - - "pip install ." - - "pip install -Ur test-requirements.txt" - - "pip install codecov" - -test_script: - - "mkdir empty" - - "cd empty" - # Make sure it's being imported from where we expect - - "python -c \"import os, exceptiongroup; print(os.path.dirname(exceptiongroup.__file__))\"" - - "python -u -m pytest -W error -ra -v -s --pyargs exceptiongroup --cov=exceptiongroup --cov-config=../.coveragerc" - - "codecov" diff --git a/.travis.yml b/.travis.yml index 76179f0..982b956 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,5 @@ language: python -sudo: false -dist: trusty +dist: xenial matrix: include: @@ -11,30 +10,12 @@ matrix: env: CHECK_FORMATTING=1 # The pypy tests are slow, so list them early - python: pypy3.5 - # Uncomment if you want to test on pypy nightly: - # - language: generic - # env: USE_PYPY_NIGHTLY=1 - - python: 3.5.0 - python: 3.5.2 - python: 3.6 - # As of 2018-07-05, Travis's 3.7 and 3.8 builds only work if you - # use dist: xenial AND sudo: required - # See: https://github.com/python-trio/trio/pull/556#issuecomment-402879391 - python: 3.7 - dist: xenial - sudo: required - python: 3.8-dev - dist: xenial - sudo: required - - os: osx - language: generic - env: MACPYTHON=3.5.4 - - os: osx - language: generic - env: MACPYTHON=3.6.6 - - os: osx - language: generic - env: MACPYTHON=3.7.0 + allow_failures: + - python: 3.8-dev script: - ci/travis.sh diff --git a/README.rst b/README.rst index e9b7d46..2cabbdb 100644 --- a/README.rst +++ b/README.rst @@ -8,7 +8,7 @@ Python. This project is currently maintained by the `Trio project `__, but the goal is for some version of -it to be merged into Python 3.8, so it can be used in both Trio and +it to be merged into Python 3.9, so it can be used in both Trio and asyncio. For more information, see: https://github.com/python-trio/trio/issues/611 diff --git a/ci/travis.sh b/ci/travis.sh index 7098011..bff3dd3 100755 --- a/ci/travis.sh +++ b/ci/travis.sh @@ -2,55 +2,6 @@ set -ex -if [ "$TRAVIS_OS_NAME" = "osx" ]; then - curl -Lo macpython.pkg https://www.python.org/ftp/python/${MACPYTHON}/python-${MACPYTHON}-macosx10.6.pkg - sudo installer -pkg macpython.pkg -target / - ls /Library/Frameworks/Python.framework/Versions/*/bin/ - PYTHON_EXE=/Library/Frameworks/Python.framework/Versions/*/bin/python3 - # The pip in older MacPython releases doesn't support a new enough TLS - curl https://bootstrap.pypa.io/get-pip.py | sudo $PYTHON_EXE - sudo $PYTHON_EXE -m pip install virtualenv - $PYTHON_EXE -m virtualenv testenv - source testenv/bin/activate -fi - -if [ "$USE_PYPY_NIGHTLY" = "1" ]; then - curl -fLo pypy.tar.bz2 http://buildbot.pypy.org/nightly/py3.5/pypy-c-jit-latest-linux64.tar.bz2 - if [ ! -s pypy.tar.bz2 ]; then - # We know: - # - curl succeeded (200 response code; -f means "exit with error if - # server returns 4xx or 5xx") - # - nonetheless, pypy.tar.bz2 does not exist, or contains no data - # This isn't going to work, and the failure is not informative of - # anything involving this package. - ls -l - echo "PyPy3 nightly build failed to download – something is wrong on their end." - echo "Skipping testing against the nightly build for right now." - exit 0 - fi - tar xaf pypy.tar.bz2 - # something like "pypy-c-jit-89963-748aa3022295-linux64" - PYPY_DIR=$(echo pypy-c-jit-*) - PYTHON_EXE=$PYPY_DIR/bin/pypy3 - ($PYTHON_EXE -m ensurepip \ - && $PYTHON_EXE -m pip install virtualenv \ - && $PYTHON_EXE -m virtualenv testenv) \ - || (echo "pypy nightly is broken; skipping tests"; exit 0) - source testenv/bin/activate -fi - -if [ "$USE_PYPY_RELEASE_VERSION" != "" ]; then - curl -fLo pypy.tar.bz2 https://bitbucket.org/squeaky/portable-pypy/downloads/pypy3.5-${USE_PYPY_RELEASE_VERSION}-linux_x86_64-portable.tar.bz2 - tar xaf pypy.tar.bz2 - # something like "pypy3.5-5.7.1-beta-linux_x86_64-portable" - PYPY_DIR=$(echo pypy3.5-*) - PYTHON_EXE=$PYPY_DIR/bin/pypy3 - $PYTHON_EXE -m ensurepip - $PYTHON_EXE -m pip install virtualenv - $PYTHON_EXE -m virtualenv testenv - source testenv/bin/activate -fi - pip install -U pip setuptools wheel if [ "$CHECK_FORMATTING" = "1" ]; then diff --git a/exceptiongroup/_tests/test_exceptiongroup.py b/exceptiongroup/_tests/test_exceptiongroup.py deleted file mode 100644 index ce17e25..0000000 --- a/exceptiongroup/_tests/test_exceptiongroup.py +++ /dev/null @@ -1,84 +0,0 @@ -import copy -import pytest - -from exceptiongroup import ExceptionGroup - - -def raise_group(): - try: - 1 / 0 - except Exception as e: - raise ExceptionGroup("ManyError", [e], [str(e)]) from e - - -def test_exception_group_init(): - memberA = ValueError("A") - memberB = RuntimeError("B") - group = ExceptionGroup( - "many error.", [memberA, memberB], [str(memberA), str(memberB)] - ) - assert group.exceptions == [memberA, memberB] - assert group.message == "many error." - assert group.sources == [str(memberA), str(memberB)] - assert group.args == ( - "many error.", - [memberA, memberB], - [str(memberA), str(memberB)], - ) - - -def test_exception_group_when_members_are_not_exceptions(): - with pytest.raises(TypeError): - ExceptionGroup( - "error", - [RuntimeError("RuntimeError"), "error2"], - ["RuntimeError", "error2"], - ) - - -def test_exception_group_init_when_exceptions_messages_not_equal(): - with pytest.raises(ValueError): - ExceptionGroup( - "many error.", [ValueError("A"), RuntimeError("B")], ["A"] - ) - - -def test_exception_group_str(): - memberA = ValueError("memberA") - memberB = ValueError("memberB") - group = ExceptionGroup( - "many error.", [memberA, memberB], [str(memberA), str(memberB)] - ) - assert "memberA" in str(group) - assert "memberB" in str(group) - - assert "ExceptionGroup: " in repr(group) - assert "memberA" in repr(group) - assert "memberB" in repr(group) - - -def test_exception_group_copy(): - try: - raise_group() # the exception is raise by `raise...from..` - except ExceptionGroup as e: - group = e - - another_group = copy.copy(group) - assert another_group.message == group.message - assert another_group.exceptions == group.exceptions - assert another_group.sources == group.sources - assert another_group.__traceback__ is group.__traceback__ - assert another_group.__cause__ is group.__cause__ - assert another_group.__context__ is group.__context__ - assert another_group.__suppress_context__ is group.__suppress_context__ - assert another_group.__cause__ is not None - assert another_group.__context__ is not None - assert another_group.__suppress_context__ is True - - # doing copy when __suppress_context__ is False - group.__suppress_context__ = False - another_group = copy.copy(group) - assert another_group.__cause__ is group.__cause__ - assert another_group.__context__ is group.__context__ - assert another_group.__suppress_context__ is group.__suppress_context__ - assert another_group.__suppress_context__ is False diff --git a/exceptiongroup/_tests/__init__.py b/exceptiongroup/tests/__init__.py similarity index 100% rename from exceptiongroup/_tests/__init__.py rename to exceptiongroup/tests/__init__.py diff --git a/exceptiongroup/tests/conftest.py b/exceptiongroup/tests/conftest.py new file mode 100644 index 0000000..9dfa38a --- /dev/null +++ b/exceptiongroup/tests/conftest.py @@ -0,0 +1,20 @@ +import pytest + + +def pytest_addoption(parser): + parser.addoption("--run-slow", action="store_true", help="run slow tests") + + +def pytest_configure(config): + config.addinivalue_line("markers", "slow: mark a time consuming test") + + +def pytest_collection_modifyitems(config, items): + if config.getoption("--run-slow", True): + # --runslow given in cli: do not skip slow tests + return + + skip_slow = pytest.mark.skip(reason="need --run-slow option to run") + for item in items: + if "slow" in item.keywords: + item.add_marker(skip_slow) diff --git a/exceptiongroup/tests/test_exceptiongroup.py b/exceptiongroup/tests/test_exceptiongroup.py new file mode 100644 index 0000000..ae9a316 --- /dev/null +++ b/exceptiongroup/tests/test_exceptiongroup.py @@ -0,0 +1,374 @@ +import copy +import logging +import sys +from traceback import format_exception, _cause_message + +import pytest + +from .. import ExceptionGroup +from .tutil import assert_match_in_seq + + +class NotHashableException(Exception): + code = None + + def __init__(self, code): + super().__init__() + self.code = code + + def __eq__(self, other): + if not isinstance(other, NotHashableException): + return False + return self.code == other.code + + +def einfo(exc): + return type(exc), exc, exc.__traceback__ + + +def raiser1(): + raiser1_2() + + +def raiser1_2(): + raiser1_3() + + +def raiser1_3(): + raise ValueError("raiser1_string") + + +def raiser2(): + raiser2_2() + + +def raiser2_2(): + raise KeyError("raiser2_string") + + +def raiser3(): + raise NameError + + +def get_exc(raiser): + try: + raiser() + except Exception as exc: + return exc + + +def make_tree(): + # Returns an object like: + # ExceptionGroup([ + # ExceptionGroup([ + # ValueError, + # KeyError, + # ]), + # NameError, + # ]) + # where all exceptions except the root have a non-trivial traceback. + exc1 = get_exc(raiser1) + exc2 = get_exc(raiser2) + exc3 = get_exc(raiser3) + + # Give m12 a non-trivial traceback + try: + raise ExceptionGroup("message", [exc1, exc2], ["exc1", "exc2"]) + except BaseException as m12: + return ExceptionGroup("message", [m12, exc3], ["m12", "exc3"]) + + +def raise_group(): + try: + 1 / 0 + except Exception as e: + raise ExceptionGroup("ManyError", [e], [str(e)]) from e + + +def test_exception_group_init(): + memberA = ValueError("A") + memberB = RuntimeError("B") + group = ExceptionGroup( + "many error.", [memberA, memberB], [str(memberA), str(memberB)] + ) + assert group.exceptions == [memberA, memberB] + assert group.message == "many error." + assert group.sources == [str(memberA), str(memberB)] + assert group.args == ( + "many error.", + [memberA, memberB], + [str(memberA), str(memberB)], + ) + + +def test_exception_group_when_members_are_not_exceptions(): + with pytest.raises(TypeError): + ExceptionGroup( + "error", + [RuntimeError("RuntimeError"), "error2"], + ["RuntimeError", "error2"], + ) + + +def test_exception_group_init_when_exceptions_messages_not_equal(): + with pytest.raises(ValueError): + ExceptionGroup( + "many error.", [ValueError("A"), RuntimeError("B")], ["A"] + ) + + +def test_exception_group_str(): + memberA = ValueError("memberA") + memberB = ValueError("memberB") + group = ExceptionGroup( + "many error.", [memberA, memberB], [str(memberA), str(memberB)] + ) + assert "memberA" in str(group) + assert "memberB" in str(group) + + assert "ExceptionGroup: " in repr(group) + assert "memberA" in repr(group) + assert "memberB" in repr(group) + + +def test_exception_group_copy(): + try: + raise_group() # the exception is raise by `raise...from..` + except ExceptionGroup as e: + group = e + + another_group = copy.copy(group) + assert another_group.message == group.message + assert another_group.exceptions == group.exceptions + assert another_group.sources == group.sources + assert another_group.__traceback__ is group.__traceback__ + assert another_group.__cause__ is group.__cause__ + assert another_group.__context__ is group.__context__ + assert another_group.__suppress_context__ is group.__suppress_context__ + assert another_group.__cause__ is not None + assert another_group.__context__ is not None + assert another_group.__suppress_context__ is True + + # doing copy when __suppress_context__ is False + group.__suppress_context__ = False + another_group = copy.copy(group) + assert another_group.__cause__ is group.__cause__ + assert another_group.__context__ is group.__context__ + assert another_group.__suppress_context__ is group.__suppress_context__ + assert another_group.__suppress_context__ is False + + +def test_traceback_recursion(): + exc1 = RuntimeError() + exc2 = KeyError() + exc3 = NotHashableException(42) + # Note how this creates a loop, where exc1 refers to exc1 + # This could trigger an infinite recursion; the 'seen' set is supposed to prevent + # this. + exc1.__cause__ = ExceptionGroup( + "message", [exc1, exc2, exc3], ["exc1", "exc2", "exc3"] + ) + # python traceback.TracebackException < 3.6.4 does not support unhashable exceptions + # and raises a TypeError exception + if sys.version_info < (3, 6, 4): + with pytest.raises(TypeError): + format_exception(*einfo(exc1)) + else: + format_exception(*einfo(exc1)) + + +def test_format_exception(): + exc = get_exc(raiser1) + formatted = "".join(format_exception(*einfo(exc))) + assert "raiser1_string" in formatted + assert "in raiser1_3" in formatted + assert "raiser2_string" not in formatted + assert "in raiser2_2" not in formatted + assert "direct cause" not in formatted + assert "During handling" not in formatted + + exc = get_exc(raiser1) + exc.__cause__ = get_exc(raiser2) + formatted = "".join(format_exception(*einfo(exc))) + assert "raiser1_string" in formatted + assert "in raiser1_3" in formatted + assert "raiser2_string" in formatted + assert "in raiser2_2" in formatted + assert "direct cause" in formatted + assert "During handling" not in formatted + # ensure cause included + assert _cause_message in formatted + + exc = get_exc(raiser1) + exc.__context__ = get_exc(raiser2) + formatted = "".join(format_exception(*einfo(exc))) + assert "raiser1_string" in formatted + assert "in raiser1_3" in formatted + assert "raiser2_string" in formatted + assert "in raiser2_2" in formatted + assert "direct cause" not in formatted + assert "During handling" in formatted + + exc.__suppress_context__ = True + formatted = "".join(format_exception(*einfo(exc))) + assert "raiser1_string" in formatted + assert "in raiser1_3" in formatted + assert "raiser2_string" not in formatted + assert "in raiser2_2" not in formatted + assert "direct cause" not in formatted + assert "During handling" not in formatted + + # chain=False + exc = get_exc(raiser1) + exc.__context__ = get_exc(raiser2) + formatted = "".join(format_exception(*einfo(exc), chain=False)) + assert "raiser1_string" in formatted + assert "in raiser1_3" in formatted + assert "raiser2_string" not in formatted + assert "in raiser2_2" not in formatted + assert "direct cause" not in formatted + assert "During handling" not in formatted + + # limit + exc = get_exc(raiser1) + exc.__context__ = get_exc(raiser2) + # get_exc adds a frame that counts against the limit, so limit=2 means we + # get 1 deep into the raiser stack + formatted = "".join(format_exception(*einfo(exc), limit=2)) + print(formatted) + assert "raiser1_string" in formatted + assert "in raiser1" in formatted + assert "in raiser1_2" not in formatted + assert "raiser2_string" in formatted + assert "in raiser2" in formatted + assert "in raiser2_2" not in formatted + + exc = get_exc(raiser1) + exc.__context__ = get_exc(raiser2) + formatted = "".join(format_exception(*einfo(exc), limit=1)) + print(formatted) + assert "raiser1_string" in formatted + assert "in raiser1" not in formatted + assert "raiser2_string" in formatted + assert "in raiser2" not in formatted + + # handles loops + exc = get_exc(raiser1) + exc.__cause__ = exc + formatted = "".join(format_exception(*einfo(exc))) + assert "raiser1_string" in formatted + assert "in raiser1_3" in formatted + assert "raiser2_string" not in formatted + assert "in raiser2_2" not in formatted + # ensure duplicate exception is not included as cause + assert _cause_message not in formatted + + # ExceptionGroup + formatted = "".join(format_exception(*einfo(make_tree()))) + print(formatted) + + assert_match_in_seq( + [ + # Outer exception is ExceptionGroup + r"ExceptionGroup:", + # First embedded exception is the embedded ExceptionGroup + r"\n m12:", + # Which has a single stack frame from make_tree raising it + r"in make_tree", + # Then it has two embedded exceptions + r" exc1:", + r"in raiser1_2", + # for some reason ValueError has no quotes + r"ValueError: raiser1_string", + r" exc2:", + r"in raiser2_2", + # But KeyError does have quotes + r"KeyError: 'raiser2_string'", + # And finally the NameError, which is a sibling of the embedded + # ExceptionGroup + r"\n exc3:", + r"in raiser3", + r"NameError", + ], + formatted, + ) + + # Prints duplicate exceptions in sub-exceptions + exc1 = get_exc(raiser1) + + def raise1_raiser1(): + try: + raise exc1 + except: + raise ValueError("foo") + + def raise2_raiser1(): + try: + raise exc1 + except: + raise KeyError("bar") + + exc2 = get_exc(raise1_raiser1) + exc3 = get_exc(raise2_raiser1) + + try: + raise ExceptionGroup("message", [exc2, exc3], ["exc2", "exc3"]) + except ExceptionGroup as e: + exc = e + + formatted = "".join(format_exception(*einfo(exc))) + print(formatted) + + assert_match_in_seq( + [ + r"Traceback", + # Outer exception is ExceptionGroup + r"ExceptionGroup:", + # First embedded exception is the embedded ValueError with cause of raiser1 + r"\n exc2:", + # Print details of exc1 + r" Traceback", + r"in get_exc", + r"in raiser1", + r"ValueError: raiser1_string", + # Print details of exc2 + r"\n During handling of the above exception, another exception occurred:", + r" Traceback", + r"in get_exc", + r"in raise1_raiser1", + r" ValueError: foo", + # Second embedded exception is the embedded KeyError with cause of raiser1 + r"\n exc3:", + # Print details of exc1 again + r" Traceback", + r"in get_exc", + r"in raiser1", + r"ValueError: raiser1_string", + # Print details of exc3 + r"\n During handling of the above exception, another exception occurred:", + r" Traceback", + r"in get_exc", + r"in raise2_raiser1", + r" KeyError: 'bar'", + ], + formatted, + ) + + +def test_logging(caplog): + exc1 = get_exc(raiser1) + exc2 = get_exc(raiser2) + + m = ExceptionGroup("message", [exc1, exc2], ["exc1", "exc2"]) + + message = "test test test" + try: + raise m + except ExceptionGroup as exc: + logging.getLogger().exception(message) + # Join lines together + formatted = "".join( + format_exception(type(exc), exc, exc.__traceback__) + ) + assert message in caplog.text + assert formatted in caplog.text diff --git a/exceptiongroup/tests/test_ipython.py b/exceptiongroup/tests/test_ipython.py new file mode 100644 index 0000000..92d2b54 --- /dev/null +++ b/exceptiongroup/tests/test_ipython.py @@ -0,0 +1,58 @@ +import pytest + +from .tutil import run_script, assert_match_in_seq + +try: + import IPython +except ImportError: # pragma: no cover + pytestmark = pytest.mark.skip(reason="need IPython") + + +def check_simple_excepthook(completed): + assert_match_in_seq( + [ + "in ", + "ExceptionGroup", + "a:", + "in exc1_fn", + "ValueError", + "b:", + "in exc2_fn", + "KeyError", + ], + completed.stdout.decode("utf-8"), + ) + + +@pytest.mark.slow +def test_ipython_exc_handler(): + completed = run_script("simple_excepthook.py", use_ipython=True) + check_simple_excepthook(completed) + + +@pytest.mark.slow +def test_ipython_imported_but_unused(): + completed = run_script("simple_excepthook_IPython.py") + check_simple_excepthook(completed) + + +@pytest.mark.slow +def test_ipython_custom_exc_handler(): + # Check we get a nice warning (but only one!) if the user is using IPython + # and already has some other set_custom_exc handler installed. + completed = run_script("ipython_custom_exc.py", use_ipython=True) + assert_match_in_seq( + [ + # The warning + "RuntimeWarning", + "IPython detected", + "skip installing exceptiongroup", + # The ExceptionGroup + "ExceptionGroup", + "ValueError", + "KeyError", + ], + completed.stdout.decode("utf-8"), + ) + # Make sure our other warning doesn't show up + assert "custom sys.excepthook" not in completed.stdout.decode("utf-8") diff --git a/exceptiongroup/_tests/test_scripts/__init__.py b/exceptiongroup/tests/test_scripts/__init__.py similarity index 100% rename from exceptiongroup/_tests/test_scripts/__init__.py rename to exceptiongroup/tests/test_scripts/__init__.py diff --git a/exceptiongroup/_tests/test_scripts/_common.py b/exceptiongroup/tests/test_scripts/_common.py similarity index 100% rename from exceptiongroup/_tests/test_scripts/_common.py rename to exceptiongroup/tests/test_scripts/_common.py diff --git a/exceptiongroup/_tests/test_scripts/custom_excepthook.py b/exceptiongroup/tests/test_scripts/custom_excepthook.py similarity index 100% rename from exceptiongroup/_tests/test_scripts/custom_excepthook.py rename to exceptiongroup/tests/test_scripts/custom_excepthook.py diff --git a/exceptiongroup/_tests/test_scripts/ipython_custom_exc.py b/exceptiongroup/tests/test_scripts/ipython_custom_exc.py similarity index 100% rename from exceptiongroup/_tests/test_scripts/ipython_custom_exc.py rename to exceptiongroup/tests/test_scripts/ipython_custom_exc.py diff --git a/exceptiongroup/_tests/test_scripts/simple_excepthook.py b/exceptiongroup/tests/test_scripts/simple_excepthook.py similarity index 100% rename from exceptiongroup/_tests/test_scripts/simple_excepthook.py rename to exceptiongroup/tests/test_scripts/simple_excepthook.py diff --git a/exceptiongroup/_tests/test_scripts/simple_excepthook_IPython.py b/exceptiongroup/tests/test_scripts/simple_excepthook_IPython.py similarity index 100% rename from exceptiongroup/_tests/test_scripts/simple_excepthook_IPython.py rename to exceptiongroup/tests/test_scripts/simple_excepthook_IPython.py diff --git a/exceptiongroup/_tests/test_tools.py b/exceptiongroup/tests/test_tools.py similarity index 100% rename from exceptiongroup/_tests/test_tools.py rename to exceptiongroup/tests/test_tools.py diff --git a/exceptiongroup/tests/tutil.py b/exceptiongroup/tests/tutil.py new file mode 100644 index 0000000..963d7df --- /dev/null +++ b/exceptiongroup/tests/tutil.py @@ -0,0 +1,58 @@ +import os +import re +import subprocess +import sys +from pathlib import Path + +import pytest + +import exceptiongroup + + +def run_script(name, use_ipython=False): + project_path = Path(exceptiongroup.__file__).parent.parent + script_path = Path(__file__).parent / "test_scripts" / name + + env = dict(os.environ) + print("parent PYTHONPATH:", env.get("PYTHONPATH")) + if "PYTHONPATH" in env: # pragma: no cover + pp = env["PYTHONPATH"].split(os.pathsep) + else: + pp = [] + pp.insert(0, str(project_path)) + pp.insert(0, str(script_path.parent)) + env["PYTHONPATH"] = os.pathsep.join(pp) + print("subprocess PYTHONPATH:", env.get("PYTHONPATH")) + + if use_ipython: + lines = [script_path.open().read(), "exit()"] + + cmd = [ + sys.executable, + "-u", + "-m", + "IPython", + # no startup files + "--quick", + "--TerminalIPythonApp.code_to_run=" + "\n".join(lines), + ] + else: + cmd = [sys.executable, "-u", str(script_path)] + print("running:", cmd) + completed = subprocess.run( + cmd, env=env, stdout=subprocess.PIPE, stderr=subprocess.STDOUT + ) + print("process output:") + print(completed.stdout.decode("utf-8")) + return completed + + +def assert_match_in_seq(pattern_list, string): + offset = 0 + print("looking for pattern matches...") + for pattern in pattern_list: + print("checking pattern:", pattern) + reobj = re.compile(pattern) + match = reobj.search(string, offset) + assert match is not None + offset = match.end() diff --git a/setup.py b/setup.py index 9b1f6d6..bede7ca 100644 --- a/setup.py +++ b/setup.py @@ -14,17 +14,11 @@ author_email="njs@pobox.com", license="MIT -or- Apache License 2.0", packages=find_packages(), - install_requires=["trio"], keywords=["async", "exceptions", "error handling"], python_requires=">=3.5", classifiers=[ "License :: OSI Approved :: MIT License", "License :: OSI Approved :: Apache Software License", - "Framework :: Trio", - "Framework :: AsyncIO", - "Operating System :: POSIX :: Linux", - "Operating System :: MacOS :: MacOS X", - "Operating System :: Microsoft :: Windows", "Programming Language :: Python :: 3 :: Only", "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: PyPy", diff --git a/test-requirements.txt b/test-requirements.txt index 9955dec..5c2eeba 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -1,2 +1,3 @@ pytest pytest-cov +ipython