Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Latest main branch breaks testbed test_app.py::test_current_window test on macOS #2860

Closed
proneon267 opened this issue Sep 19, 2024 · 4 comments · Fixed by #2866
Closed

Latest main branch breaks testbed test_app.py::test_current_window test on macOS #2860

proneon267 opened this issue Sep 19, 2024 · 4 comments · Fixed by #2866
Labels
bug A crash or error in behavior. macOS The issue relates to Apple macOS support.

Comments

@proneon267
Copy link
Contributor

Describe the bug

With the latest main branch of toga, at the beginning of every test of testbed, an "Open File Dialog" is being shown, which has input focus. Consequently the testbed test_app.py::test_current_window test fails as the input focus is on the dialog instead of on the main window.

Steps to reproduce

  1. Clone the latest toga main branch
git clone https://github.com/beeware/toga.git
cd toga/testbed
  1. Run the testbed test_app.py::test_current_window:
briefcase dev -r  --test -- tests/app/test_desktop.py::test_current_window -s
  1. Notice a "Open File dialog" being shown from the start of the test:
    image

  2. Get Error Message:

[testbed] Running test suite in dev environment...
===========================================================================
Waiting for app to be ready for testing... ready.
============================= test session starts ==============================
collecting ... collected 1 item

tests/app/test_desktop.py::test_current_window FAILED

=================================== FAILURES ===================================
_____________________________ test_current_window ______________________________
Traceback (most recent call last):
  File "/Users/proneon267/venv/lib/python3.12/site-packages/_pytest/runner.py", line 341, in from_call
    result: TResult | None = func()
                             ^^^^^^
  File "/Users/proneon267/venv/lib/python3.12/site-packages/_pytest/runner.py", line 242, in <lambda>
    lambda: runtest_hook(item=item, **kwds), when=when, reraise=reraise
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/proneon267/venv/lib/python3.12/site-packages/pluggy/_hooks.py", line 513, in __call__
    return self._hookexec(self.name, self._hookimpls.copy(), kwargs, firstresult)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/proneon267/venv/lib/python3.12/site-packages/pluggy/_manager.py", line 120, in _hookexec
    return self._inner_hookexec(hook_name, methods, kwargs, firstresult)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/proneon267/venv/lib/python3.12/site-packages/pluggy/_callers.py", line 139, in _multicall
    raise exception.with_traceback(exception.__traceback__)
  File "/Users/proneon267/venv/lib/python3.12/site-packages/pluggy/_callers.py", line 122, in _multicall
    teardown.throw(exception)  # type: ignore[union-attr]
    ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/proneon267/venv/lib/python3.12/site-packages/_pytest/threadexception.py", line 92, in pytest_runtest_call
    yield from thread_exception_runtest_hook()
  File "/Users/proneon267/venv/lib/python3.12/site-packages/_pytest/threadexception.py", line 68, in thread_exception_runtest_hook
    yield
  File "/Users/proneon267/venv/lib/python3.12/site-packages/pluggy/_callers.py", line 122, in _multicall
    teardown.throw(exception)  # type: ignore[union-attr]
    ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/proneon267/venv/lib/python3.12/site-packages/_pytest/unraisableexception.py", line 95, in pytest_runtest_call
    yield from unraisable_exception_runtest_hook()
  File "/Users/proneon267/venv/lib/python3.12/site-packages/_pytest/unraisableexception.py", line 70, in unraisable_exception_runtest_hook
    yield
  File "/Users/proneon267/venv/lib/python3.12/site-packages/pluggy/_callers.py", line 122, in _multicall
    teardown.throw(exception)  # type: ignore[union-attr]
    ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/proneon267/venv/lib/python3.12/site-packages/_pytest/logging.py", line 846, in pytest_runtest_call
    yield from self._runtest_for(item, "call")
  File "/Users/proneon267/venv/lib/python3.12/site-packages/_pytest/logging.py", line 829, in _runtest_for
    yield
  File "/Users/proneon267/venv/lib/python3.12/site-packages/pluggy/_callers.py", line 122, in _multicall
    teardown.throw(exception)  # type: ignore[union-attr]
    ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/proneon267/venv/lib/python3.12/site-packages/_pytest/capture.py", line 880, in pytest_runtest_call
    return (yield)
            ^^^^^
  File "/Users/proneon267/venv/lib/python3.12/site-packages/pluggy/_callers.py", line 122, in _multicall
    teardown.throw(exception)  # type: ignore[union-attr]
    ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/proneon267/venv/lib/python3.12/site-packages/_pytest/skipping.py", line 257, in pytest_runtest_call
    return (yield)
            ^^^^^
  File "/Users/proneon267/venv/lib/python3.12/site-packages/pluggy/_callers.py", line 103, in _multicall
    res = hook_impl.function(*args)
          ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/proneon267/venv/lib/python3.12/site-packages/_pytest/runner.py", line 174, in pytest_runtest_call
    item.runtest()
  File "/Users/proneon267/venv/lib/python3.12/site-packages/pytest_asyncio/plugin.py", line 457, in runtest
    super().runtest()
  File "/Users/proneon267/venv/lib/python3.12/site-packages/_pytest/python.py", line 1627, in runtest
    self.ihook.pytest_pyfunc_call(pyfuncitem=self)
  File "/Users/proneon267/venv/lib/python3.12/site-packages/pluggy/_hooks.py", line 513, in __call__
    return self._hookexec(self.name, self._hookimpls.copy(), kwargs, firstresult)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/proneon267/venv/lib/python3.12/site-packages/pluggy/_manager.py", line 120, in _hookexec
    return self._inner_hookexec(hook_name, methods, kwargs, firstresult)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/proneon267/venv/lib/python3.12/site-packages/pluggy/_callers.py", line 182, in _multicall
    return outcome.get_result()
           ^^^^^^^^^^^^^^^^^^^^
  File "/Users/proneon267/venv/lib/python3.12/site-packages/pluggy/_result.py", line 100, in get_result
    raise exc.with_traceback(exc.__traceback__)
  File "/Users/proneon267/venv/lib/python3.12/site-packages/pluggy/_callers.py", line 103, in _multicall
    res = hook_impl.function(*args)
          ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/proneon267/venv/lib/python3.12/site-packages/_pytest/python.py", line 159, in pytest_pyfunc_call
    result = testfunction(**testargs)
             ^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/proneon267/venv/lib/python3.12/site-packages/pytest_asyncio/plugin.py", line 929, in inner
    _loop.run_until_complete(task)
  File "/Users/proneon267/toga/testbed/tests/conftest.py", line 142, in run_until_complete
    return asyncio.run_coroutine_threadsafe(coro, self.loop).result()
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/proneon267/.pyenv/versions/3.12.4/lib/python3.12/concurrent/futures/_base.py", line 456, in result
    return self.__get_result()
           ^^^^^^^^^^^^^^^^^^^
  File "/Users/proneon267/.pyenv/versions/3.12.4/lib/python3.12/concurrent/futures/_base.py", line 401, in __get_result
    raise self._exception
  File "/Users/proneon267/toga/testbed/tests/app/test_desktop.py", line 353, in test_current_window
    assert app.current_window == main_window
  File "/Users/proneon267/venv/lib/python3.12/site-packages/toga/app.py", line 831, in current_window
    return window.interface
           ^^^^^^^^^^^^^^^^
  File "/Users/proneon267/venv/lib/python3.12/site-packages/rubicon/objc/api.py", line 1097, in __getattr__
    raise AttributeError(
AttributeError: rubicon.objc.api.ObjCInstance NSOpenPanel has no attribute interface
=========================== short test summary info ============================
FAILED tests/app/test_desktop.py::test_current_window - AttributeError: rubicon.objc.api.ObjCInstance NSOpenPanel has no attribute interface
============================== 1 failed in 0.63s ===============================

Expected behavior

The "Open File Dialog" should not be shown at the start of the test.

Screenshots

No response

Environment

  • Operating System: macOS 14.6
  • Python version: 3.12
  • Software versions:
    • Briefcase: 0.3.19
    • Toga: latest main branch
    • ...

Logs

No logs generated.

Additional context

The bugs seems to have been arisen out of the following addition of the document_types to the testbed:

def main():
return Testbed(
app_name="testbed",
document_types=[ExampleDoc, ReadonlyDoc],
)

As without specifying the document_types, the testbed test_app.py::test_current_window test passes and no "Open File Dialog" is shown at the beginning of the test.

This problem is also arising on the linux-wayland testbed, as evident by the CI failures of input focus related widget tests.

@proneon267 proneon267 added the bug A crash or error in behavior. label Sep 19, 2024
@freakboy3742
Copy link
Member

Huh... so it is. It looks like this is a side effect of the rework of Document-based apps; by adding document definitions to the testbed app (so that we can test document features), that has triggered the "file open" logic on a new app.

That shouldn't be happening, because the app isn't a full "document-based" app - so something definitely needs to be fixed here.

@freakboy3742 freakboy3742 added the macOS The issue relates to Apple macOS support. label Sep 19, 2024
@proneon267
Copy link
Contributor Author

proneon267 commented Sep 20, 2024

Taking a quick look at it, the problem is arising due to the addition of applicationOpenUntitledFile_ delegate event:

@objc_method
def applicationOpenUntitledFile_(self, sender) -> bool:
asyncio.create_task(self.interface.documents.request_open())
return True
@objc_method
def applicationShouldOpenUntitledFile_(self, sender) -> bool:
return bool(self.interface.documents.types)

As mentioned in the docs: https://developer.apple.com/documentation/appkit/nsapplicationdelegate/1428444-applicationshouldopenuntitledfil#discussion

Use this(applicationShouldOpenUntitledFile:) method to decide whether the application should open a new, untitled file. Note that applicationOpenUntitledFile: is invoked if this method returns YES.

Since applicationShouldOpenUntitledFile_ returns True when document_types is specified, hence applicationOpenUntitledFile_ is triggered during the startup.

But I haven't looked deeply into it and there might be more to it.

@freakboy3742
Copy link
Member

@proneon267 I think your analysis is completely correct - the error is that the implementation of applicationShouldOpenUntitledFile: should also be taking into account the value of self.interface.main_window. If there is a defined main window, we shouldn't be triggering default window opening behavior; that is only required if main_window is None.

This won't be affecting other platforms, because App._create_initial_windows does a main_window is None check before attempting any default window handling.

@proneon267
Copy link
Contributor Author

proneon267 commented Sep 21, 2024

It seems that when applicationShouldOpenUntitledFile_ event is triggered, the main_window(self.interface.main_window) always remains undefined i.e., App._UNDEFINED.

Hence, we cannot check if self.interface.main_window is defined inside the applicationShouldOpenUntitledFile_ and applicationOpenUntitledFile_ delegate events.

This must be due to timing of the delegate event being triggered and the calling of the startup() method, i.e., applicationShouldOpenUntitledFile_ is triggered earlier than startup() method . So, we need to do the checking somewhere else.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug A crash or error in behavior. macOS The issue relates to Apple macOS support.
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants