toga icon indicating copy to clipboard operation
toga copied to clipboard

Testbed presentation mode test failure on macOS

Open HalfWhitt opened this issue 9 months ago • 4 comments

Describe the bug

I'm consistently getting the same reported failure for tests/app/test_desktop.py::test_presentation_mode, though it doesn't always look the same, visually. Sometimes the testbed app exits normally... but sometimes both my monitors turn all black and stay that way. Unplugging one and plugging it back in puts things back to normal, but nothing short of that seems to. I've confirmed none of these work:

  • Command + tab
  • Escape
  • Command + Option + Escape to try and open Force Quit
  • Fn + F or Cmd + Ctrl + F to try and exit full screen
  • Frantic mouse-clicking

Other things are still running normally on my computer, despite the black screens — music and sounds still play, and if I run the whole testbed, the rest of the tests run and pass as usual; I just can't see the results until I unplug/plug a monitor.

Steps to reproduce

briefcase run --test -- tests/app/test_desktop.py::test_presentation_mode (or dev)

Expected behavior

I mean ideally the test would pass, but even if it fails, hopefully it shouldn't lock up both my monitors.

Screenshots

No response

Environment

  • Operating System: macOS Sonoma 14.7.3
  • Python version: 3.13.0
  • Software versions:
    • Briefcase: 0.3.22
    • Toga: Main branch

Logs

=================================== FAILURES ===================================
____________________________ test_presentation_mode ____________________________
Traceback (most recent call last):
  File "/Users/charles/toga_dev/toga/testbed/build/testbed/macos/app/Toga Testbed.app/Contents/Resources/app_packages/_pytest/runner.py", line 341, in from_call
    result: TResult | None = func()
                             ~~~~^^
  File "/Users/charles/toga_dev/toga/testbed/build/testbed/macos/app/Toga Testbed.app/Contents/Resources/app_packages/_pytest/runner.py", line 242, in <lambda>
    lambda: runtest_hook(item=item, **kwds), when=when, reraise=reraise
            ~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^
  File "/Users/charles/toga_dev/toga/testbed/build/testbed/macos/app/Toga Testbed.app/Contents/Resources/app_packages/pluggy/_hooks.py", line 513, in __call__
    return self._hookexec(self.name, self._hookimpls.copy(), kwargs, firstresult)
           ~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/charles/toga_dev/toga/testbed/build/testbed/macos/app/Toga Testbed.app/Contents/Resources/app_packages/pluggy/_manager.py", line 120, in _hookexec
    return self._inner_hookexec(hook_name, methods, kwargs, firstresult)
           ~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/charles/toga_dev/toga/testbed/build/testbed/macos/app/Toga Testbed.app/Contents/Resources/app_packages/pluggy/_callers.py", line 139, in _multicall
    raise exception.with_traceback(exception.__traceback__)
  File "/Users/charles/toga_dev/toga/testbed/build/testbed/macos/app/Toga Testbed.app/Contents/Resources/app_packages/pluggy/_callers.py", line 122, in _multicall
    teardown.throw(exception)  # type: ignore[union-attr]
    ~~~~~~~~~~~~~~^^^^^^^^^^^
  File "/Users/charles/toga_dev/toga/testbed/build/testbed/macos/app/Toga Testbed.app/Contents/Resources/app_packages/_pytest/threadexception.py", line 92, in pytest_runtest_call
    yield from thread_exception_runtest_hook()
  File "/Users/charles/toga_dev/toga/testbed/build/testbed/macos/app/Toga Testbed.app/Contents/Resources/app_packages/_pytest/threadexception.py", line 68, in thread_exception_runtest_hook
    yield
  File "/Users/charles/toga_dev/toga/testbed/build/testbed/macos/app/Toga Testbed.app/Contents/Resources/app_packages/pluggy/_callers.py", line 122, in _multicall
    teardown.throw(exception)  # type: ignore[union-attr]
    ~~~~~~~~~~~~~~^^^^^^^^^^^
  File "/Users/charles/toga_dev/toga/testbed/build/testbed/macos/app/Toga Testbed.app/Contents/Resources/app_packages/_pytest/unraisableexception.py", line 95, in pytest_runtest_call
    yield from unraisable_exception_runtest_hook()
  File "/Users/charles/toga_dev/toga/testbed/build/testbed/macos/app/Toga Testbed.app/Contents/Resources/app_packages/_pytest/unraisableexception.py", line 70, in unraisable_exception_runtest_hook
    yield
  File "/Users/charles/toga_dev/toga/testbed/build/testbed/macos/app/Toga Testbed.app/Contents/Resources/app_packages/pluggy/_callers.py", line 122, in _multicall
    teardown.throw(exception)  # type: ignore[union-attr]
    ~~~~~~~~~~~~~~^^^^^^^^^^^
  File "/Users/charles/toga_dev/toga/testbed/build/testbed/macos/app/Toga Testbed.app/Contents/Resources/app_packages/_pytest/logging.py", line 846, in pytest_runtest_call
    yield from self._runtest_for(item, "call")
  File "/Users/charles/toga_dev/toga/testbed/build/testbed/macos/app/Toga Testbed.app/Contents/Resources/app_packages/_pytest/logging.py", line 829, in _runtest_for
    yield
  File "/Users/charles/toga_dev/toga/testbed/build/testbed/macos/app/Toga Testbed.app/Contents/Resources/app_packages/pluggy/_callers.py", line 122, in _multicall
    teardown.throw(exception)  # type: ignore[union-attr]
    ~~~~~~~~~~~~~~^^^^^^^^^^^
  File "/Users/charles/toga_dev/toga/testbed/build/testbed/macos/app/Toga Testbed.app/Contents/Resources/app_packages/_pytest/capture.py", line 898, in pytest_runtest_call
    return (yield)
            ^^^^^
  File "/Users/charles/toga_dev/toga/testbed/build/testbed/macos/app/Toga Testbed.app/Contents/Resources/app_packages/pluggy/_callers.py", line 122, in _multicall
    teardown.throw(exception)  # type: ignore[union-attr]
    ~~~~~~~~~~~~~~^^^^^^^^^^^
  File "/Users/charles/toga_dev/toga/testbed/build/testbed/macos/app/Toga Testbed.app/Contents/Resources/app_packages/_pytest/skipping.py", line 257, in pytest_runtest_call
    return (yield)
            ^^^^^
  File "/Users/charles/toga_dev/toga/testbed/build/testbed/macos/app/Toga Testbed.app/Contents/Resources/app_packages/pluggy/_callers.py", line 103, in _multicall
    res = hook_impl.function(*args)
  File "/Users/charles/toga_dev/toga/testbed/build/testbed/macos/app/Toga Testbed.app/Contents/Resources/app_packages/_pytest/runner.py", line 174, in pytest_runtest_call
    item.runtest()
    ~~~~~~~~~~~~^^
  File "/Users/charles/toga_dev/toga/testbed/build/testbed/macos/app/Toga Testbed.app/Contents/Resources/app_packages/pytest_asyncio/plugin.py", line 533, in runtest
    super().runtest()
    ~~~~~~~~~~~~~~~^^
  File "/Users/charles/toga_dev/toga/testbed/build/testbed/macos/app/Toga Testbed.app/Contents/Resources/app_packages/_pytest/python.py", line 1627, in runtest
    self.ihook.pytest_pyfunc_call(pyfuncitem=self)
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^
  File "/Users/charles/toga_dev/toga/testbed/build/testbed/macos/app/Toga Testbed.app/Contents/Resources/app_packages/pluggy/_hooks.py", line 513, in __call__
    return self._hookexec(self.name, self._hookimpls.copy(), kwargs, firstresult)
           ~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/charles/toga_dev/toga/testbed/build/testbed/macos/app/Toga Testbed.app/Contents/Resources/app_packages/pluggy/_manager.py", line 120, in _hookexec
    return self._inner_hookexec(hook_name, methods, kwargs, firstresult)
           ~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/charles/toga_dev/toga/testbed/build/testbed/macos/app/Toga Testbed.app/Contents/Resources/app_packages/pluggy/_callers.py", line 182, in _multicall
    return outcome.get_result()
           ~~~~~~~~~~~~~~~~~~^^
  File "/Users/charles/toga_dev/toga/testbed/build/testbed/macos/app/Toga Testbed.app/Contents/Resources/app_packages/pluggy/_result.py", line 100, in get_result
    raise exc.with_traceback(exc.__traceback__)
  File "/Users/charles/toga_dev/toga/testbed/build/testbed/macos/app/Toga Testbed.app/Contents/Resources/app_packages/pluggy/_callers.py", line 103, in _multicall
    res = hook_impl.function(*args)
  File "/Users/charles/toga_dev/toga/testbed/build/testbed/macos/app/Toga Testbed.app/Contents/Resources/app_packages/_pytest/python.py", line 159, in pytest_pyfunc_call
    result = testfunction(**testargs)
  File "/Users/charles/toga_dev/toga/testbed/build/testbed/macos/app/Toga Testbed.app/Contents/Resources/app_packages/pytest_asyncio/plugin.py", line 1052, in inner
    _loop.run_until_complete(task)
    ~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^
  File "/Users/charles/toga_dev/toga/testbed/build/testbed/macos/app/Toga Testbed.app/Contents/Resources/app/tests/conftest.py", line 167, in run_until_complete
    return asyncio.run_coroutine_threadsafe(coro, self.loop).result()
           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^
  File "/Users/charles/toga_dev/toga/testbed/build/testbed/macos/app/Toga Testbed.app/Contents/Frameworks/Python.framework/Versions/3.13/lib/python3.13/concurrent/futures/_base.py", line 456, in result
    return self.__get_result()
           ~~~~~~~~~~~~~~~~~^^
  File "/Users/charles/toga_dev/toga/testbed/build/testbed/macos/app/Toga Testbed.app/Contents/Frameworks/Python.framework/Versions/3.13/lib/python3.13/concurrent/futures/_base.py", line 401, in __get_result
    raise self._exception
  File "/Users/charles/toga_dev/toga/testbed/build/testbed/macos/app/Toga Testbed.app/Contents/Resources/app/tests/app/test_desktop.py", line 287, in test_presentation_mode
    assert (
    ...<2 lines>...
    ), f"{window_information['window'].title}:"
AssertionError: Test Window 0:
assert <WindowState.NORMAL: 0> == <WindowState.PRESENTATION: 4>
 +  where <WindowState.NORMAL: 0> = <tests_backend.window.WindowProbe object at 0x122ce7610>.instantaneous_state
 +  and   <WindowState.PRESENTATION: 4> = WindowState.PRESENTATION
---------------------------- Captured stdout setup -----------------------------
Resetting main_window
----------------------------- Captured stdout call -----------------------------
All Test Windows are visible
App is in presentation mode
App is in presentation mode
=========================== short test summary info ============================
FAILED tests/app/test_desktop.py::test_presentation_mode - AssertionError: Test Window 0:
assert <WindowState.NORMAL: 0> == <WindowState.PRESENTATION: 4>
 +  where <WindowState.NORMAL: 0> = <tests_backend.window.WindowProbe object at 0x122ce7610>.instantaneous_state
 +  and   <WindowState.PRESENTATION: 4> = WindowState.PRESENTATION
============================== 1 failed in 6.54s ===============================

Additional context

No response

HalfWhitt avatar Mar 26 '25 01:03 HalfWhitt

Thanks for the report - I can confirm I'm seeing the same failure on my (2 external + laptop) setup. It also fails with --slow enabled, which suggests it's not just a timing issue - there's something fundamental going wrong. Unfortunately, I'm not sure there's much of a fix but to add a load of print debugging to see what is and isn't happening at each step in the process.

The fact that the test isn't cleaning up is a little more concerning - I'm surprised it's possible for the test to actually complete, exit the executable, and still leave a window in presentation mode...

freakboy3742 avatar Mar 27 '25 04:03 freakboy3742

I'm surprised it's possible for the test to actually complete, exit the executable, and still leave a window in presentation mode...

Yeah, it's very weird, especially how it seems completely un-interactive — certainly makes it difficult to debug something when both your screens are solid black. I know you said you're seeing the same test failure — are you getting the intermittent cleanup issue too?

HalfWhitt avatar Mar 27 '25 22:03 HalfWhitt

I know you said you're seeing the same test failure — are you getting the intermittent cleanup issue too?

Not so far - but I've only run the test a couple of times.

freakboy3742 avatar Mar 27 '25 23:03 freakboy3742

Gotcha. For me, it seems to happen roughly half the time.

HalfWhitt avatar Mar 28 '25 00:03 HalfWhitt

It seems that the monitor soft-lock is indeed fixed. However, I still consistently get the same test failure locally.

HalfWhitt avatar Nov 21 '25 02:11 HalfWhitt