AnonymouX47 / term-image

Display images in the terminal with python
https://term-image.readthedocs.io
MIT License
219 stars 10 forks source link

Test (tests/renderable/test_renderable.py) Fails on macOS 14.5 (23F79) #110

Closed AbeEstrada closed 3 months ago

AbeEstrada commented 5 months ago

It fails using:

It displays images fine using the tutorial from the site:

from PIL import Image
from term_image.image import KittyImage

img = Image.open("python.png")
image = KittyImage(img)
print(image)

I only noticed this when running explictly the tests, all the other tests pass.

Here is the traceback:

❯ pytest -vv -r a --full-trace tests/renderable/test_renderable.py
========================================================================================= test session starts ==========================================================================================
platform darwin -- Python 3.12.3, pytest-8.2.1, pluggy-1.5.0 -- /Code/term-image/venv/bin/python
cachedir: .pytest_cache
rootdir: /Code/term-image
configfile: pyproject.toml
plugins: cov-5.0.0
collected 238 items

tests/renderable/test_renderable.py::TestMeta::test_not_a_subclass PASSED                                                                                                                        [  0%]
tests/renderable/test_renderable.py::TestMeta::TestRenderArgs::test_base PASSED                                                                                                                  [  0%]
tests/renderable/test_renderable.py::TestMeta::TestRenderArgs::test_default PASSED                                                                                                               [  1%]
tests/renderable/test_renderable.py::TestMeta::TestRenderArgs::TestInheritance::test_parent_with_no_args PASSED                                                                                  [  1%]
tests/renderable/test_renderable.py::TestMeta::TestRenderArgs::TestInheritance::test_parent_with_args PASSED                                                                                     [  2%]
tests/renderable/test_renderable.py::TestMeta::TestRenderArgs::TestInheritance::test_multi_level PASSED                                                                                          [  2%]
tests/renderable/test_renderable.py::TestMeta::TestRenderArgs::TestInheritance::test_multiple PASSED                                                                                             [  2%]
tests/renderable/test_renderable.py::TestMeta::TestRenderArgs::TestInheritance::test_complex PASSED                                                                                              [  3%]
tests/renderable/test_renderable.py::TestMeta::TestRenderArgs::test_optimization_default_namespaces_interned PASSED                                                                              [  3%]
tests/renderable/test_renderable.py::TestMeta::TestRenderData::test_base PASSED                                                                                                                  [  4%]
tests/renderable/test_renderable.py::TestMeta::TestRenderData::test_default PASSED                                                                                                               [  4%]
tests/renderable/test_renderable.py::TestMeta::TestRenderData::TestInheritance::test_parent_with_no_data PASSED                                                                                  [  5%]
tests/renderable/test_renderable.py::TestMeta::TestRenderData::TestInheritance::test_parent_with_data PASSED                                                                                     [  5%]
tests/renderable/test_renderable.py::TestMeta::TestRenderData::TestInheritance::test_multi_level PASSED                                                                                          [  5%]
tests/renderable/test_renderable.py::TestMeta::TestRenderData::TestInheritance::test_multiple PASSED                                                                                             [  6%]
tests/renderable/test_renderable.py::TestMeta::TestRenderData::TestInheritance::test_complex PASSED                                                                                              [  6%]
tests/renderable/test_renderable.py::TestMeta::TestExportedAttrs::test_base PASSED                                                                                                               [  7%]
tests/renderable/test_renderable.py::TestMeta::TestExportedAttrs::test_cls PASSED                                                                                                                [  7%]
tests/renderable/test_renderable.py::TestMeta::TestExportedAttrs::test_inheritance PASSED                                                                                                        [  7%]
tests/renderable/test_renderable.py::TestMeta::TestExportedAttrs::test_multiple_inheritance PASSED                                                                                               [  8%]
tests/renderable/test_renderable.py::TestMeta::TestExportedAttrs::TestConflict::test_cls_vs_base PASSED                                                                                          [  8%]
tests/renderable/test_renderable.py::TestMeta::TestExportedAttrs::TestConflict::test_cls_vs_base_of_base PASSED                                                                                  [  9%]
tests/renderable/test_renderable.py::TestMeta::TestExportedAttrs::TestConflict::test_base_vs_base PASSED                                                                                         [  9%]
tests/renderable/test_renderable.py::TestMeta::TestExportedAttrs::TestConflict::test_specific_vs_descendant PASSED                                                                               [ 10%]
tests/renderable/test_renderable.py::TestInit::test_invalid_frame_count[0] PASSED                                                                                                                [ 10%]
tests/renderable/test_renderable.py::TestInit::test_invalid_frame_count[-1] PASSED                                                                                                               [ 10%]
tests/renderable/test_renderable.py::TestInit::test_invalid_frame_count[-100] PASSED                                                                                                             [ 11%]
tests/renderable/test_renderable.py::TestInit::TestFrameDuration::test_invalid[0] PASSED                                                                                                         [ 11%]
tests/renderable/test_renderable.py::TestInit::TestFrameDuration::test_invalid[-1] PASSED                                                                                                        [ 12%]
tests/renderable/test_renderable.py::TestInit::TestFrameDuration::test_invalid[-100] PASSED                                                                                                      [ 12%]
tests/renderable/test_renderable.py::TestInit::TestFrameDuration::test_ignored_for_non_animated PASSED                                                                                           [ 13%]
tests/renderable/test_renderable.py::TestAnimated::test_non_animated PASSED                                                                                                                      [ 13%]
tests/renderable/test_renderable.py::TestAnimated::test_animated[2] PASSED                                                                                                                       [ 13%]
tests/renderable/test_renderable.py::TestAnimated::test_animated[FrameCount.INDEFINITE] PASSED                                                                                                   [ 14%]
tests/renderable/test_renderable.py::TestAnimated::test_animated[FrameCount.POSTPONED] PASSED                                                                                                    [ 14%]
tests/renderable/test_renderable.py::TestFrameCount::test_non_postponed[1] PASSED                                                                                                                [ 15%]
tests/renderable/test_renderable.py::TestFrameCount::test_non_postponed[2] PASSED                                                                                                                [ 15%]
tests/renderable/test_renderable.py::TestFrameCount::test_non_postponed[FrameCount.INDEFINITE] PASSED                                                                                            [ 15%]
tests/renderable/test_renderable.py::TestFrameCount::TestPostponed::test_not_implemented PASSED                                                                                                  [ 16%]
tests/renderable/test_renderable.py::TestFrameCount::TestPostponed::test_implemented[2] PASSED                                                                                                   [ 16%]
tests/renderable/test_renderable.py::TestFrameCount::TestPostponed::test_implemented[FrameCount.INDEFINITE] PASSED                                                                               [ 17%]
tests/renderable/test_renderable.py::TestFrameDuration::TestGet::test_non_animated[1] PASSED                                                                                                     [ 17%]
tests/renderable/test_renderable.py::TestFrameDuration::TestGet::test_non_animated[100] PASSED                                                                                                   [ 18%]
tests/renderable/test_renderable.py::TestFrameDuration::TestGet::test_non_animated[FrameDuration.DYNAMIC] PASSED                                                                                 [ 18%]
tests/renderable/test_renderable.py::TestFrameDuration::TestGet::TestAnimated::test_normal[1] PASSED                                                                                             [ 18%]
tests/renderable/test_renderable.py::TestFrameDuration::TestGet::TestAnimated::test_normal[100] PASSED                                                                                           [ 19%]
tests/renderable/test_renderable.py::TestFrameDuration::TestGet::TestAnimated::test_normal[FrameDuration.DYNAMIC] PASSED                                                                         [ 19%]
tests/renderable/test_renderable.py::TestFrameDuration::TestGet::TestAnimated::test_deleted_attribute PASSED                                                                                     [ 20%]
tests/renderable/test_renderable.py::TestFrameDuration::TestSet::test_non_animated[100] PASSED                                                                                                   [ 20%]
tests/renderable/test_renderable.py::TestFrameDuration::TestSet::test_non_animated[FrameDuration.DYNAMIC] PASSED                                                                                 [ 21%]
tests/renderable/test_renderable.py::TestFrameDuration::TestSet::test_non_animated[0] PASSED                                                                                                     [ 21%]
tests/renderable/test_renderable.py::TestFrameDuration::TestSet::test_non_animated[-100] PASSED                                                                                                  [ 21%]
tests/renderable/test_renderable.py::TestFrameDuration::TestSet::TestAnimated::test_valid[2] PASSED                                                                                              [ 22%]
tests/renderable/test_renderable.py::TestFrameDuration::TestSet::TestAnimated::test_valid[100] PASSED                                                                                            [ 22%]
tests/renderable/test_renderable.py::TestFrameDuration::TestSet::TestAnimated::test_valid[FrameDuration.DYNAMIC] PASSED                                                                          [ 23%]
tests/renderable/test_renderable.py::TestFrameDuration::TestSet::TestAnimated::test_invalid[0] PASSED                                                                                            [ 23%]
tests/renderable/test_renderable.py::TestFrameDuration::TestSet::TestAnimated::test_invalid[-1] PASSED                                                                                           [ 23%]
tests/renderable/test_renderable.py::TestFrameDuration::TestSet::TestAnimated::test_invalid[-100] PASSED                                                                                         [ 24%]
tests/renderable/test_renderable.py::test_render_size PASSED                                                                                                                                     [ 24%]
tests/renderable/test_renderable.py::TestIter::test_non_animated PASSED                                                                                                                          [ 25%]
tests/renderable/test_renderable.py::TestIter::test_animated PASSED                                                                                                                              [ 25%]
tests/renderable/test_renderable.py::TestStr::test_default_render_args PASSED                                                                                                                    [ 26%]
tests/renderable/test_renderable.py::TestStr::test_frame_number[0] PASSED                                                                                                                        [ 26%]
tests/renderable/test_renderable.py::TestStr::test_frame_number[5] PASSED                                                                                                                        [ 26%]
tests/renderable/test_renderable.py::TestStr::test_frame_number[9] PASSED                                                                                                                        [ 27%]
tests/renderable/test_renderable.py::TestDraw::TestNonAnimation::test_args_ignored_for_non_animated PASSED                                                                                       [ 27%]
tests/renderable/test_renderable.py::TestDraw::TestNonAnimation::test_args_ignored_for_animated_when_animate_is_false PASSED                                                                     [ 28%]
tests/renderable/test_renderable.py::TestDraw::TestNonAnimation::test_default PASSED                                                                                                             [ 28%]
tests/renderable/test_renderable.py::TestDraw::TestNonAnimation::TestRenderArgs::test_incompatible PASSED                                                                                        [ 28%]
tests/renderable/test_renderable.py::TestDraw::TestNonAnimation::TestRenderArgs::test_compatible PASSED                                                                                          [ 29%]
tests/renderable/test_renderable.py::TestDraw::TestNonAnimation::test_padding PASSED                                                                                                             [ 29%]
tests/renderable/test_renderable.py::TestDraw::TestNonAnimation::test_animate PASSED                                                                                                             [ 30%]
tests/renderable/test_renderable.py::TestDraw::TestNonAnimation::TestCheckSize::test_default PASSED                                                                                              [ 30%]
tests/renderable/test_renderable.py::TestDraw::TestNonAnimation::TestCheckSize::test_true PASSED                                                                                                 [ 31%]
tests/renderable/test_renderable.py::TestDraw::TestNonAnimation::TestCheckSize::test_false PASSED                                                                                                [ 31%]
tests/renderable/test_renderable.py::TestDraw::TestNonAnimation::TestAllowScroll::test_default PASSED                                                                                            [ 31%]
tests/renderable/test_renderable.py::TestDraw::TestNonAnimation::TestAllowScroll::test_false PASSED                                                                                              [ 32%]
tests/renderable/test_renderable.py::TestDraw::TestNonAnimation::TestAllowScroll::test_true PASSED                                                                                               [ 32%]
tests/renderable/test_renderable.py::TestDraw::TestNonAnimation::TestEchoInput::test_default ^C^C^C^C^C

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! KeyboardInterrupt !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

config = <_pytest.config.Config object at 0x104dd4d10>, doit = <function _main at 0x105e4e700>

    def wrap_session(
        config: Config, doit: Callable[[Config, "Session"], Optional[Union[int, ExitCode]]]
    ) -> Union[int, ExitCode]:
        """Skeleton command line program."""
        session = Session.from_config(config)
        session.exitstatus = ExitCode.OK
        initstate = 0
        try:
            try:
                config._do_configure()
                initstate = 1
                config.hook.pytest_sessionstart(session=session)
                initstate = 2
>               session.exitstatus = doit(config, session) or 0

venv/lib/python3.12/site-packages/_pytest/main.py:285:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

config = <_pytest.config.Config object at 0x104dd4d10>, session = <Session  exitstatus=<ExitCode.OK: 0> testsfailed=0 testscollected=238>

    def _main(config: Config, session: "Session") -> Optional[Union[int, ExitCode]]:
        """Default command line protocol for initialization, session,
        running tests and reporting."""
        config.hook.pytest_collection(session=session)
>       config.hook.pytest_runtestloop(session=session)

venv/lib/python3.12/site-packages/_pytest/main.py:339:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

self = <HookCaller 'pytest_runtestloop'>, kwargs = {'session': <Session  exitstatus=<ExitCode.OK: 0> testsfailed=0 testscollected=238>}, firstresult = True

    def __call__(self, **kwargs: object) -> Any:
        """Call the hook.

        Only accepts keyword arguments, which should match the hook
        specification.

        Returns the result(s) of calling all registered plugins, see
        :ref:`calling`.
        """
        assert (
            not self.is_historic()
        ), "Cannot directly call a historic hook - use call_historic instead."
        self._verify_all_args_are_provided(kwargs)
        firstresult = self.spec.opts.get("firstresult", False) if self.spec else False
        # Copy because plugins may register other plugins during iteration (#438).
>       return self._hookexec(self.name, self._hookimpls.copy(), kwargs, firstresult)

venv/lib/python3.12/site-packages/pluggy/_hooks.py:513:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

self = <_pytest.config.PytestPluginManager object at 0x10608d9d0>, hook_name = 'pytest_runtestloop'
methods = [<HookImpl plugin_name='main', plugin=<module '_pytest.main' from '/Code/term-image/venv/lib/python3.12/site...test/main.py'>>, <HookImpl plugin_name='logging-plugin', plugin=<_pytest.logging.LoggingPlugin object at 0x10615bfb0>>]
kwargs = {'session': <Session  exitstatus=<ExitCode.OK: 0> testsfailed=0 testscollected=238>}, firstresult = True

    def _hookexec(
        self,
        hook_name: str,
        methods: Sequence[HookImpl],
        kwargs: Mapping[str, object],
        firstresult: bool,
    ) -> object | list[object]:
        # called from all hookcaller instances.
        # enable_tracing will set its own wrapping function at self._inner_hookexec
>       return self._inner_hookexec(hook_name, methods, kwargs, firstresult)

venv/lib/python3.12/site-packages/pluggy/_manager.py:120:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

self = <_pytest.logging.LoggingPlugin object at 0x10615bfb0>, session = <Session  exitstatus=<ExitCode.OK: 0> testsfailed=0 testscollected=238>

    @hookimpl(wrapper=True)
    def pytest_runtestloop(self, session: Session) -> Generator[None, object, object]:
        if session.config.option.collectonly:
            return (yield)

        if self._log_cli_enabled() and self._config.getoption("verbose") < 1:
            # The verbose flag is needed to avoid messy test progress output.
            self._config.option.verbose = 1

        with catching_logs(self.log_cli_handler, level=self.log_cli_level):
            with catching_logs(self.log_file_handler, level=self.log_file_level):
>               return (yield)  # Run all the tests.

venv/lib/python3.12/site-packages/_pytest/logging.py:807:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

session = <Session  exitstatus=<ExitCode.OK: 0> testsfailed=0 testscollected=238>

    def pytest_runtestloop(session: "Session") -> bool:
        if session.testsfailed and not session.config.option.continue_on_collection_errors:
            raise session.Interrupted(
                "%d error%s during collection"
                % (session.testsfailed, "s" if session.testsfailed != 1 else "")
            )

        if session.config.option.collectonly:
            return True

        for i, item in enumerate(session.items):
            nextitem = session.items[i + 1] if i + 1 < len(session.items) else None
>           item.config.hook.pytest_runtest_protocol(item=item, nextitem=nextitem)

venv/lib/python3.12/site-packages/_pytest/main.py:364:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

self = <HookCaller 'pytest_runtest_protocol'>, kwargs = {'item': <Function test_default>, 'nextitem': <Function test_non_default[False]>}, firstresult = True

    def __call__(self, **kwargs: object) -> Any:
        """Call the hook.

        Only accepts keyword arguments, which should match the hook
        specification.

        Returns the result(s) of calling all registered plugins, see
        :ref:`calling`.
        """
        assert (
            not self.is_historic()
        ), "Cannot directly call a historic hook - use call_historic instead."
        self._verify_all_args_are_provided(kwargs)
        firstresult = self.spec.opts.get("firstresult", False) if self.spec else False
        # Copy because plugins may register other plugins during iteration (#438).
>       return self._hookexec(self.name, self._hookimpls.copy(), kwargs, firstresult)

venv/lib/python3.12/site-packages/pluggy/_hooks.py:513:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

self = <_pytest.config.PytestPluginManager object at 0x10608d9d0>, hook_name = 'pytest_runtest_protocol'
methods = [<HookImpl plugin_name='runner', plugin=<module '_pytest.runner' from '/Code/term-image/venv/lib/python3.12/...n=<module '_pytest.warnings' from '/Code/term-image/venv/lib/python3.12/site-packages/_pytest/warnings.py'>>]
kwargs = {'item': <Function test_default>, 'nextitem': <Function test_non_default[False]>}, firstresult = True

    def _hookexec(
        self,
        hook_name: str,
        methods: Sequence[HookImpl],
        kwargs: Mapping[str, object],
        firstresult: bool,
    ) -> object | list[object]:
        # called from all hookcaller instances.
        # enable_tracing will set its own wrapping function at self._inner_hookexec
>       return self._inner_hookexec(hook_name, methods, kwargs, firstresult)

venv/lib/python3.12/site-packages/pluggy/_manager.py:120:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

item = <Function test_default>

    @pytest.hookimpl(wrapper=True, tryfirst=True)
    def pytest_runtest_protocol(item: Item) -> Generator[None, object, object]:
        with catch_warnings_for_item(
            config=item.config, ihook=item.ihook, when="runtest", item=item
        ):
>           return (yield)

venv/lib/python3.12/site-packages/_pytest/warnings.py:111:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

item = <Function test_default>

    @hookimpl(wrapper=True, tryfirst=True)
    def pytest_runtest_protocol(item: Item) -> Generator[None, object, object]:
        """Setup the pytest_assertrepr_compare and pytest_assertion_pass hooks.

        The rewrite module will use util._reprcompare if it exists to use custom
        reporting via the pytest_assertrepr_compare hook.  This sets up this custom
        comparison for the test.
        """
        ihook = item.ihook

        def callbinrepr(op, left: object, right: object) -> Optional[str]:
            """Call the pytest_assertrepr_compare hook and prepare the result.

            This uses the first result from the hook and then ensures the
            following:
            * Overly verbose explanations are truncated unless configured otherwise
              (eg. if running in verbose mode).
            * Embedded newlines are escaped to help util.format_explanation()
              later.
            * If the rewrite mode is used embedded %-characters are replaced
              to protect later % formatting.

            The result can be formatted by util.format_explanation() for
            pretty printing.
            """
            hook_result = ihook.pytest_assertrepr_compare(
                config=item.config, op=op, left=left, right=right
            )
            for new_expl in hook_result:
                if new_expl:
                    new_expl = truncate.truncate_if_required(new_expl, item)
                    new_expl = [line.replace("\n", "\\n") for line in new_expl]
                    res = "\n~".join(new_expl)
                    if item.config.getvalue("assertmode") == "rewrite":
                        res = res.replace("%", "%%")
                    return res
            return None

        saved_assert_hooks = util._reprcompare, util._assertion_pass
        util._reprcompare = callbinrepr
        util._config = item.config

        if ihook.pytest_assertion_pass.get_hookimpls():

            def call_assertion_pass_hook(lineno: int, orig: str, expl: str) -> None:
                ihook.pytest_assertion_pass(item=item, lineno=lineno, orig=orig, expl=expl)

            util._assertion_pass = call_assertion_pass_hook

        try:
>           return (yield)

venv/lib/python3.12/site-packages/_pytest/assertion/__init__.py:176:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

item = <Function test_default>

    @hookimpl(wrapper=True)
    def pytest_runtest_protocol(item: Item) -> Generator[None, object, object]:
        if isinstance(item, TestCaseFunction) and "twisted.trial.unittest" in sys.modules:
            ut: Any = sys.modules["twisted.python.failure"]
            global classImplements_has_run
            Failure__init__ = ut.Failure.__init__
            if not classImplements_has_run:
                from twisted.trial.itrial import IReporter
                from zope.interface import classImplements

                classImplements(TestCaseFunction, IReporter)
                classImplements_has_run = True

            def excstore(
                self, exc_value=None, exc_type=None, exc_tb=None, captureVars=None
            ):
                if exc_value is None:
                    self._rawexcinfo = sys.exc_info()
                else:
                    if exc_type is None:
                        exc_type = type(exc_value)
                    self._rawexcinfo = (exc_type, exc_value, exc_tb)
                try:
                    Failure__init__(
                        self, exc_value, exc_type, exc_tb, captureVars=captureVars
                    )
                except TypeError:
                    Failure__init__(self, exc_value, exc_type, exc_tb)

            ut.Failure.__init__ = excstore
            try:
                res = yield
            finally:
                ut.Failure.__init__ = Failure__init__
        else:
>           res = yield

venv/lib/python3.12/site-packages/_pytest/unittest.py:421:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

item = <Function test_default>

    @pytest.hookimpl(wrapper=True, trylast=True)
    def pytest_runtest_protocol(item: Item) -> Generator[None, object, object]:
        timeout = get_timeout_config_value(item.config)
        if timeout > 0:
            import faulthandler

            stderr = item.config.stash[fault_handler_stderr_fd_key]
            faulthandler.dump_traceback_later(timeout, file=stderr)
            try:
                return (yield)
            finally:
                faulthandler.cancel_dump_traceback_later()
        else:
>           return (yield)

venv/lib/python3.12/site-packages/_pytest/faulthandler.py:85:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

item = <Function test_default>, nextitem = <Function test_non_default[False]>

    def pytest_runtest_protocol(item: Item, nextitem: Optional[Item]) -> bool:
        ihook = item.ihook
        ihook.pytest_runtest_logstart(nodeid=item.nodeid, location=item.location)
>       runtestprotocol(item, nextitem=nextitem)

venv/lib/python3.12/site-packages/_pytest/runner.py:116:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

item = <Function test_default>, log = True, nextitem = <Function test_non_default[False]>

    def runtestprotocol(
        item: Item, log: bool = True, nextitem: Optional[Item] = None
    ) -> List[TestReport]:
        hasrequest = hasattr(item, "_request")
        if hasrequest and not item._request:  # type: ignore[attr-defined]
            # This only happens if the item is re-run, as is done by
            # pytest-rerunfailures.
            item._initrequest()  # type: ignore[attr-defined]
        rep = call_and_report(item, "setup", log)
        reports = [rep]
        if rep.passed:
            if item.config.getoption("setupshow", False):
                show_test_item(item)
            if not item.config.getoption("setuponly", False):
>               reports.append(call_and_report(item, "call", log))

venv/lib/python3.12/site-packages/_pytest/runner.py:135:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

item = <Function test_default>, when = 'call', log = True, kwds = {}, ihook = <_pytest.config.compat.PathAwareHookProxy object at 0x105d1d190>
reraise = (<class '_pytest.outcomes.Exit'>, <class 'KeyboardInterrupt'>)

    def call_and_report(
        item: Item, when: Literal["setup", "call", "teardown"], log: bool = True, **kwds
    ) -> TestReport:
        ihook = item.ihook
        if when == "setup":
            runtest_hook: Callable[..., None] = ihook.pytest_runtest_setup
        elif when == "call":
            runtest_hook = ihook.pytest_runtest_call
        elif when == "teardown":
            runtest_hook = ihook.pytest_runtest_teardown
        else:
            assert False, f"Unhandled runtest hook case: {when}"
        reraise: Tuple[Type[BaseException], ...] = (Exit,)
        if not item.config.getoption("usepdb", False):
            reraise += (KeyboardInterrupt,)
>       call = CallInfo.from_call(
            lambda: runtest_hook(item=item, **kwds), when=when, reraise=reraise
        )

venv/lib/python3.12/site-packages/_pytest/runner.py:240:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

cls = <class '_pytest.runner.CallInfo'>, func = <function call_and_report.<locals>.<lambda> at 0x1074c2b60>, when = 'call', reraise = (<class '_pytest.outcomes.Exit'>, <class 'KeyboardInterrupt'>)

    @classmethod
    def from_call(
        cls,
        func: Callable[[], TResult],
        when: Literal["collect", "setup", "call", "teardown"],
        reraise: Optional[
            Union[Type[BaseException], Tuple[Type[BaseException], ...]]
        ] = None,
    ) -> "CallInfo[TResult]":
        """Call func, wrapping the result in a CallInfo.

        :param func:
            The function to call. Called without arguments.
        :param when:
            The phase in which the function is called.
        :param reraise:
            Exception or exceptions that shall propagate if raised by the
            function, instead of being wrapped in the CallInfo.
        """
        excinfo = None
        start = timing.time()
        precise_start = timing.perf_counter()
        try:
>           result: Optional[TResult] = func()

venv/lib/python3.12/site-packages/_pytest/runner.py:341:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

>       lambda: runtest_hook(item=item, **kwds), when=when, reraise=reraise
    )

venv/lib/python3.12/site-packages/_pytest/runner.py:241:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

self = <HookCaller 'pytest_runtest_call'>, kwargs = {'item': <Function test_default>}, firstresult = False

    def __call__(self, **kwargs: object) -> Any:
        """Call the hook.

        Only accepts keyword arguments, which should match the hook
        specification.

        Returns the result(s) of calling all registered plugins, see
        :ref:`calling`.
        """
        assert (
            not self.is_historic()
        ), "Cannot directly call a historic hook - use call_historic instead."
        self._verify_all_args_are_provided(kwargs)
        firstresult = self.spec.opts.get("firstresult", False) if self.spec else False
        # Copy because plugins may register other plugins during iteration (#438).
>       return self._hookexec(self.name, self._hookimpls.copy(), kwargs, firstresult)

venv/lib/python3.12/site-packages/pluggy/_hooks.py:513:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

self = <_pytest.config.PytestPluginManager object at 0x10608d9d0>, hook_name = 'pytest_runtest_call'
methods = [<HookImpl plugin_name='runner', plugin=<module '_pytest.runner' from '/Code/term-image/venv/lib/python3.12/...test.threadexception' from '/Code/term-image/venv/lib/python3.12/site-packages/_pytest/threadexception.py'>>]
kwargs = {'item': <Function test_default>}, firstresult = False

    def _hookexec(
        self,
        hook_name: str,
        methods: Sequence[HookImpl],
        kwargs: Mapping[str, object],
        firstresult: bool,
    ) -> object | list[object]:
        # called from all hookcaller instances.
        # enable_tracing will set its own wrapping function at self._inner_hookexec
>       return self._inner_hookexec(hook_name, methods, kwargs, firstresult)

venv/lib/python3.12/site-packages/pluggy/_manager.py:120:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

    @pytest.hookimpl(wrapper=True, tryfirst=True)
    def pytest_runtest_call() -> Generator[None, None, None]:
>       yield from thread_exception_runtest_hook()

venv/lib/python3.12/site-packages/_pytest/threadexception.py:87:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

    def thread_exception_runtest_hook() -> Generator[None, None, None]:
        with catch_threading_exception() as cm:
            try:
>               yield

venv/lib/python3.12/site-packages/_pytest/threadexception.py:63:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

    @pytest.hookimpl(wrapper=True, tryfirst=True)
    def pytest_runtest_call() -> Generator[None, None, None]:
>       yield from unraisable_exception_runtest_hook()

venv/lib/python3.12/site-packages/_pytest/unraisableexception.py:90:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

    def unraisable_exception_runtest_hook() -> Generator[None, None, None]:
        with catch_unraisable_exception() as cm:
            try:
>               yield

venv/lib/python3.12/site-packages/_pytest/unraisableexception.py:65:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

self = <_pytest.logging.LoggingPlugin object at 0x10615bfb0>, item = <Function test_default>

    @hookimpl(wrapper=True)
    def pytest_runtest_call(self, item: nodes.Item) -> Generator[None, None, None]:
        self.log_cli_handler.set_when("call")

>       yield from self._runtest_for(item, "call")

venv/lib/python3.12/site-packages/_pytest/logging.py:850:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

self = <_pytest.logging.LoggingPlugin object at 0x10615bfb0>, item = <Function test_default>, when = 'call'

    def _runtest_for(self, item: nodes.Item, when: str) -> Generator[None, None, None]:
        """Implement the internals of the pytest_runtest_xxx() hooks."""
        with catching_logs(
            self.caplog_handler,
            level=self.log_level,
        ) as caplog_handler, catching_logs(
            self.report_handler,
            level=self.log_level,
        ) as report_handler:
            caplog_handler.reset()
            report_handler.reset()
            item.stash[caplog_records_key][when] = caplog_handler.records
            item.stash[caplog_handler_key] = caplog_handler

            try:
>               yield

venv/lib/python3.12/site-packages/_pytest/logging.py:833:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

self = <CaptureManager _method='fd' _global_capturing=None _capture_fixture=None>, item = <Function test_default>

    @hookimpl(wrapper=True)
    def pytest_runtest_call(self, item: Item) -> Generator[None, None, None]:
        with self.item_capture("call", item):
>           return (yield)

venv/lib/python3.12/site-packages/_pytest/capture.py:878:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

item = <Function test_default>

    @hookimpl(wrapper=True)
    def pytest_runtest_call(item: Item) -> Generator[None, None, None]:
        xfailed = item.stash.get(xfailed_key, None)
        if xfailed is None:
            item.stash[xfailed_key] = xfailed = evaluate_xfail_marks(item)

        if xfailed and not item.config.option.runxfail and not xfailed.run:
            xfail("[NOTRUN] " + xfailed.reason)

        try:
>           return (yield)

venv/lib/python3.12/site-packages/_pytest/skipping.py:257:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

item = <Function test_default>

    def pytest_runtest_call(item: Item) -> None:
        _update_current_test_var(item, "call")
        try:
            del sys.last_type
            del sys.last_value
            del sys.last_traceback
            if sys.version_info >= (3, 12, 0):
                del sys.last_exc  # type: ignore[attr-defined]
        except AttributeError:
            pass
        try:
>           item.runtest()

venv/lib/python3.12/site-packages/_pytest/runner.py:173:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

self = <Function test_default>

    def runtest(self) -> None:
        """Execute the underlying test function."""
>       self.ihook.pytest_pyfunc_call(pyfuncitem=self)

venv/lib/python3.12/site-packages/_pytest/python.py:1632:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

self = <HookCaller 'pytest_pyfunc_call'>, kwargs = {'pyfuncitem': <Function test_default>}, firstresult = True

    def __call__(self, **kwargs: object) -> Any:
        """Call the hook.

        Only accepts keyword arguments, which should match the hook
        specification.

        Returns the result(s) of calling all registered plugins, see
        :ref:`calling`.
        """
        assert (
            not self.is_historic()
        ), "Cannot directly call a historic hook - use call_historic instead."
        self._verify_all_args_are_provided(kwargs)
        firstresult = self.spec.opts.get("firstresult", False) if self.spec else False
        # Copy because plugins may register other plugins during iteration (#438).
>       return self._hookexec(self.name, self._hookimpls.copy(), kwargs, firstresult)

venv/lib/python3.12/site-packages/pluggy/_hooks.py:513:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

self = <_pytest.config.PytestPluginManager object at 0x10608d9d0>, hook_name = 'pytest_pyfunc_call'
methods = [<HookImpl plugin_name='python', plugin=<module '_pytest.python' from '/Code/term-image/venv/lib/python3.12/site-packages/_pytest/python.py'>>]
kwargs = {'pyfuncitem': <Function test_default>}, firstresult = True

    def _hookexec(
        self,
        hook_name: str,
        methods: Sequence[HookImpl],
        kwargs: Mapping[str, object],
        firstresult: bool,
    ) -> object | list[object]:
        # called from all hookcaller instances.
        # enable_tracing will set its own wrapping function at self._inner_hookexec
>       return self._inner_hookexec(hook_name, methods, kwargs, firstresult)
E       KeyboardInterrupt

venv/lib/python3.12/site-packages/pluggy/_manager.py:120: KeyboardInterrupt
========================================================================================== 78 passed in 5.89s ==========================================================================================
AnonymouX47 commented 5 months ago

Hmm :thinking:... quite interesting.

From what I can see here, the test function has yet to be executed.

For some context on my inference, see the docstring of the function in the third to the last stack frame. It states:

Execute the underlying test function.

and looking through the last two frames, it [strongly] doesn't seem like the test function has been executed.

Hence, the issue seems to be within the pytest framework, which is rather strange.


Or did you hit Ctrl-C very early? If so, I'll appreciate if you can run it again, wait a considerable amount of time and:

  1. See if it raises a RecursionError (no idea why I suspect recursion here, just wanna be sure).
  2. If it's not recursive, hit Ctrl-C.

Try this multiple times (copying/saving the traceback each time) and check if the tracebacks all end at the same frame.

If you are chanced to try this, let me know the results, please. There's no rush, you can do this at your convinience.


I'm really sorry to bother you. :pray: The issue is, I can't seem to reproduce it on my end (with a similar environment, except that I'm on Linux) and I don't have immediate/easy access to a Mac.

Thank you very much. :smiley:

AbeEstrada commented 5 months ago

Don't worry, I know this is going to be hard going back and forth. I hope this helps. I had to press Ctrl+C five times to be able to stop it. I waited and I couldn't get the RecursionError but I was able to get a different traceback.

Traceback (most recent call last):
  File "/Code/term-image/src/term_image/renderable/_renderable.py", line 577, in draw
    output.write(render)
  File "/Code/term-image/tests/renderable/test_renderable.py", line 215, in write
    type(slave).write(slave, string)
KeyboardInterrupt

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/Code/term-image/tests/renderable/test_renderable.py", line 224, in capture_stdout_pty
    yield master, buffer
  File "/opt/homebrew/Cellar/python@3.12/3.12.3/Frameworks/Python.framework/Versions/3.12/lib/python3.12/contextlib.py", line 81, in inner
    return func(*args, **kwds)
           ^^^^^^^^^^^^^^^^^^^
  File "/Code/term-image/tests/renderable/test_renderable.py", line 923, in test_default
    echo_space.draw()
  File "/Code/term-image/src/term_image/renderable/_renderable.py", line 585, in draw
    output.write("\n")
  File "/Code/term-image/tests/renderable/test_renderable.py", line 215, in write
    type(slave).write(slave, string)
KeyboardInterrupt

During handling of the above exception, another exception occurred:

KeyboardInterrupt

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/Code/term-image/venv/lib/python3.12/site-packages/_pytest/main.py", line 285, in wrap_session
    session.exitstatus = doit(config, session) or 0
                         ^^^^^^^^^^^^^^^^^^^^^
  File "/Code/term-image/venv/lib/python3.12/site-packages/_pytest/main.py", line 339, in _main
    config.hook.pytest_runtestloop(session=session)
  File "/Code/term-image/venv/lib/python3.12/site-packages/pluggy/_hooks.py", line 513, in __call__
    return self._hookexec(self.name, self._hookimpls.copy(), kwargs, firstresult)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Code/term-image/venv/lib/python3.12/site-packages/pluggy/_manager.py", line 120, in _hookexec
    return self._inner_hookexec(hook_name, methods, kwargs, firstresult)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Code/term-image/venv/lib/python3.12/site-packages/pluggy/_callers.py", line 139, in _multicall
    raise exception.with_traceback(exception.__traceback__)
  File "/Code/term-image/venv/lib/python3.12/site-packages/pluggy/_callers.py", line 122, in _multicall
    teardown.throw(exception)  # type: ignore[union-attr]
    ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Code/term-image/venv/lib/python3.12/site-packages/_pytest/logging.py", line 807, in pytest_runtestloop
    return (yield)  # Run all the tests.
            ^^^^^
  File "/Code/term-image/venv/lib/python3.12/site-packages/pluggy/_callers.py", line 103, in _multicall
    res = hook_impl.function(*args)
          ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Code/term-image/venv/lib/python3.12/site-packages/_pytest/main.py", line 364, in pytest_runtestloop
    item.config.hook.pytest_runtest_protocol(item=item, nextitem=nextitem)
  File "/Code/term-image/venv/lib/python3.12/site-packages/pluggy/_hooks.py", line 513, in __call__
    return self._hookexec(self.name, self._hookimpls.copy(), kwargs, firstresult)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Code/term-image/venv/lib/python3.12/site-packages/pluggy/_manager.py", line 120, in _hookexec
    return self._inner_hookexec(hook_name, methods, kwargs, firstresult)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Code/term-image/venv/lib/python3.12/site-packages/pluggy/_callers.py", line 139, in _multicall
    raise exception.with_traceback(exception.__traceback__)
  File "/Code/term-image/venv/lib/python3.12/site-packages/pluggy/_callers.py", line 122, in _multicall
    teardown.throw(exception)  # type: ignore[union-attr]
    ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Code/term-image/venv/lib/python3.12/site-packages/_pytest/warnings.py", line 111, in pytest_runtest_protocol
    return (yield)
            ^^^^^
  File "/Code/term-image/venv/lib/python3.12/site-packages/pluggy/_callers.py", line 122, in _multicall
    teardown.throw(exception)  # type: ignore[union-attr]
    ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Code/term-image/venv/lib/python3.12/site-packages/_pytest/assertion/__init__.py", line 176, in pytest_runtest_protocol
    return (yield)
            ^^^^^
  File "/Code/term-image/venv/lib/python3.12/site-packages/pluggy/_callers.py", line 122, in _multicall
    teardown.throw(exception)  # type: ignore[union-attr]
    ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Code/term-image/venv/lib/python3.12/site-packages/_pytest/unittest.py", line 421, in pytest_runtest_protocol
    res = yield
          ^^^^^
  File "/Code/term-image/venv/lib/python3.12/site-packages/pluggy/_callers.py", line 122, in _multicall
    teardown.throw(exception)  # type: ignore[union-attr]
    ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Code/term-image/venv/lib/python3.12/site-packages/_pytest/faulthandler.py", line 85, in pytest_runtest_protocol
    return (yield)
            ^^^^^
  File "/Code/term-image/venv/lib/python3.12/site-packages/pluggy/_callers.py", line 103, in _multicall
    res = hook_impl.function(*args)
          ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Code/term-image/venv/lib/python3.12/site-packages/_pytest/runner.py", line 116, in pytest_runtest_protocol
    runtestprotocol(item, nextitem=nextitem)
  File "/Code/term-image/venv/lib/python3.12/site-packages/_pytest/runner.py", line 135, in runtestprotocol
    reports.append(call_and_report(item, "call", log))
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Code/term-image/venv/lib/python3.12/site-packages/_pytest/runner.py", line 240, in call_and_report
    call = CallInfo.from_call(
           ^^^^^^^^^^^^^^^^^^^
  File "/Code/term-image/venv/lib/python3.12/site-packages/_pytest/runner.py", line 341, in from_call
    result: Optional[TResult] = func()
                                ^^^^^^
  File "/Code/term-image/venv/lib/python3.12/site-packages/_pytest/runner.py", line 241, in <lambda>
    lambda: runtest_hook(item=item, **kwds), when=when, reraise=reraise
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Code/term-image/venv/lib/python3.12/site-packages/pluggy/_hooks.py", line 513, in __call__
    return self._hookexec(self.name, self._hookimpls.copy(), kwargs, firstresult)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Code/term-image/venv/lib/python3.12/site-packages/pluggy/_manager.py", line 120, in _hookexec
    return self._inner_hookexec(hook_name, methods, kwargs, firstresult)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Code/term-image/venv/lib/python3.12/site-packages/pluggy/_callers.py", line 139, in _multicall
    raise exception.with_traceback(exception.__traceback__)
  File "/Code/term-image/venv/lib/python3.12/site-packages/pluggy/_callers.py", line 122, in _multicall
    teardown.throw(exception)  # type: ignore[union-attr]
    ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Code/term-image/venv/lib/python3.12/site-packages/_pytest/threadexception.py", line 87, in pytest_runtest_call
    yield from thread_exception_runtest_hook()
  File "/Code/term-image/venv/lib/python3.12/site-packages/_pytest/threadexception.py", line 63, in thread_exception_runtest_hook
    yield
  File "/Code/term-image/venv/lib/python3.12/site-packages/pluggy/_callers.py", line 122, in _multicall
    teardown.throw(exception)  # type: ignore[union-attr]
    ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Code/term-image/venv/lib/python3.12/site-packages/_pytest/unraisableexception.py", line 90, in pytest_runtest_call
    yield from unraisable_exception_runtest_hook()
  File "/Code/term-image/venv/lib/python3.12/site-packages/_pytest/unraisableexception.py", line 65, in unraisable_exception_runtest_hook
    yield
  File "/Code/term-image/venv/lib/python3.12/site-packages/pluggy/_callers.py", line 122, in _multicall
    teardown.throw(exception)  # type: ignore[union-attr]
    ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Code/term-image/venv/lib/python3.12/site-packages/_pytest/logging.py", line 850, in pytest_runtest_call
    yield from self._runtest_for(item, "call")
  File "/Code/term-image/venv/lib/python3.12/site-packages/_pytest/logging.py", line 833, in _runtest_for
    yield
  File "/Code/term-image/venv/lib/python3.12/site-packages/pluggy/_callers.py", line 122, in _multicall
    teardown.throw(exception)  # type: ignore[union-attr]
    ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Code/term-image/venv/lib/python3.12/site-packages/_pytest/capture.py", line 878, in pytest_runtest_call
    return (yield)
            ^^^^^
  File "/Code/term-image/venv/lib/python3.12/site-packages/pluggy/_callers.py", line 122, in _multicall
    teardown.throw(exception)  # type: ignore[union-attr]
    ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Code/term-image/venv/lib/python3.12/site-packages/_pytest/skipping.py", line 257, in pytest_runtest_call
    return (yield)
            ^^^^^
  File "/Code/term-image/venv/lib/python3.12/site-packages/pluggy/_callers.py", line 103, in _multicall
    res = hook_impl.function(*args)
          ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Code/term-image/venv/lib/python3.12/site-packages/_pytest/runner.py", line 173, in pytest_runtest_call
    item.runtest()
  File "/Code/term-image/venv/lib/python3.12/site-packages/_pytest/python.py", line 1632, in runtest
    self.ihook.pytest_pyfunc_call(pyfuncitem=self)
  File "/Code/term-image/venv/lib/python3.12/site-packages/pluggy/_hooks.py", line 513, in __call__
    return self._hookexec(self.name, self._hookimpls.copy(), kwargs, firstresult)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Code/term-image/venv/lib/python3.12/site-packages/pluggy/_manager.py", line 120, in _hookexec
    return self._inner_hookexec(hook_name, methods, kwargs, firstresult)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Code/term-image/venv/lib/python3.12/site-packages/pluggy/_callers.py", line 139, in _multicall
    raise exception.with_traceback(exception.__traceback__)
  File "/Code/term-image/venv/lib/python3.12/site-packages/pluggy/_callers.py", line 103, in _multicall
    res = hook_impl.function(*args)
          ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Code/term-image/venv/lib/python3.12/site-packages/_pytest/python.py", line 162, in pytest_pyfunc_call
    result = testfunction(**testargs)
             ^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/homebrew/Cellar/python@3.12/3.12.3/Frameworks/Python.framework/Versions/3.12/lib/python3.12/contextlib.py", line 80, in inner
    with self._recreate_cm():
  File "/opt/homebrew/Cellar/python@3.12/3.12.3/Frameworks/Python.framework/Versions/3.12/lib/python3.12/contextlib.py", line 158, in __exit__
    self.gen.throw(value)
  File "/Code/term-image/tests/renderable/test_renderable.py", line 223, in capture_stdout_pty
    with master, slave:
KeyboardInterrupt

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/Code/term-image/venv/bin/pytest", line 8, in <module>
    sys.exit(console_main())
             ^^^^^^^^^^^^^^
  File "/Code/term-image/venv/lib/python3.12/site-packages/_pytest/config/__init__.py", line 206, in console_main
    code = main()
           ^^^^^^
  File "/Code/term-image/venv/lib/python3.12/site-packages/_pytest/config/__init__.py", line 178, in main
    ret: Union[ExitCode, int] = config.hook.pytest_cmdline_main(
                                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Code/term-image/venv/lib/python3.12/site-packages/pluggy/_hooks.py", line 513, in __call__
    return self._hookexec(self.name, self._hookimpls.copy(), kwargs, firstresult)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Code/term-image/venv/lib/python3.12/site-packages/pluggy/_manager.py", line 120, in _hookexec
    return self._inner_hookexec(hook_name, methods, kwargs, firstresult)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Code/term-image/venv/lib/python3.12/site-packages/pluggy/_callers.py", line 139, in _multicall
    raise exception.with_traceback(exception.__traceback__)
  File "/Code/term-image/venv/lib/python3.12/site-packages/pluggy/_callers.py", line 103, in _multicall
    res = hook_impl.function(*args)
          ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Code/term-image/venv/lib/python3.12/site-packages/_pytest/main.py", line 332, in pytest_cmdline_main
    return wrap_session(config, _main)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Code/term-image/venv/lib/python3.12/site-packages/_pytest/main.py", line 299, in wrap_session
    config.hook.pytest_keyboard_interrupt(excinfo=excinfo)
  File "/Code/term-image/venv/lib/python3.12/site-packages/pluggy/_hooks.py", line 513, in __call__
    return self._hookexec(self.name, self._hookimpls.copy(), kwargs, firstresult)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Code/term-image/venv/lib/python3.12/site-packages/pluggy/_manager.py", line 120, in _hookexec
    return self._inner_hookexec(hook_name, methods, kwargs, firstresult)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Code/term-image/venv/lib/python3.12/site-packages/pluggy/_callers.py", line 139, in _multicall
    raise exception.with_traceback(exception.__traceback__)
  File "/Code/term-image/venv/lib/python3.12/site-packages/pluggy/_callers.py", line 103, in _multicall
    res = hook_impl.function(*args)
          ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Code/term-image/venv/lib/python3.12/site-packages/_pytest/terminal.py", line 906, in pytest_keyboard_interrupt
    self._keyboardinterrupt_memo = excinfo.getrepr(funcargs=True)
                                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Code/term-image/venv/lib/python3.12/site-packages/_pytest/_code/code.py", line 696, in getrepr
    return fmt.repr_excinfo(self)
           ^^^^^^^^^^^^^^^^^^^^^^
  File "/Code/term-image/venv/lib/python3.12/site-packages/_pytest/_code/code.py", line 1063, in repr_excinfo
    reprtraceback = self.repr_traceback(excinfo_)
                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Code/term-image/venv/lib/python3.12/site-packages/_pytest/_code/code.py", line 993, in repr_traceback
    self.repr_traceback_entry(entry, excinfo if last == entry else None)
  File "/Code/term-image/venv/lib/python3.12/site-packages/_pytest/_code/code.py", line 931, in repr_traceback_entry
    source = self._getentrysource(entry)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Code/term-image/venv/lib/python3.12/site-packages/_pytest/_code/code.py", line 833, in _getentrysource
    source = entry.getsource(self.astcache)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Code/term-image/venv/lib/python3.12/site-packages/_pytest/_code/code.py", line 251, in getsource
    source = self.frame.code.fullsource
             ^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Code/term-image/venv/lib/python3.12/site-packages/_pytest/_code/code.py", line 107, in fullsource
    full, _ = findsource(self.raw)
              ^^^^^^^^^^^^^^^^^^^^
  File "/Code/term-image/venv/lib/python3.12/site-packages/_pytest/_code/source.py", line 120, in findsource
    sourcelines, lineno = inspect.findsource(obj)
                          ^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/homebrew/Cellar/python@3.12/3.12.3/Frameworks/Python.framework/Versions/3.12/lib/python3.12/inspect.py", line 1083, in findsource
    module = getmodule(object, file)
             ^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/homebrew/Cellar/python@3.12/3.12.3/Frameworks/Python.framework/Versions/3.12/lib/python3.12/inspect.py", line 1002, in getmodule
    if f == _filesbymodname.get(modname, None):
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
KeyboardInterrupt
AnonymouX47 commented 5 months ago

It does help, thank you.

From what I can infer, the hang seems to occur when performing an operation on an open file object with an underlying pty slave file descriptor, particularly write operations (it seems).

Anyways, there isn't enough info to tell why this is happening. For that, I guess I'll have to wait till I can get access to a Mac and investigate further.

As soon as I have more info, I'll get back to you.

On another note, this has also further emphasized the need to test across platforms. I'll make necessary changes to the CI as soon as possible, to also run the tests on Mac and Windows, if possible.

Thank you very much for your time and effort. :pray:

AnonymouX47 commented 5 months ago

I tried setting up CI jobs to build the package and run tests on Windows and macOS. Everything worked fine (not without a hitch though) except the tests on macOS just as you've pointed out. The tests halted at the same point, I had to cancel the jobs. See here.

Hence, I'll still have to wait until I get my hands on a Mac and investigate. In the meantime, I've excluded macOS from the list of OSes to run the tests on.

I'll keep you updated.

Thank you very much. :smiley: