File: conftest.py

package info (click to toggle)
python-enaml 0.19.0-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 13,284 kB
  • sloc: python: 31,443; cpp: 4,499; makefile: 140; javascript: 68; lisp: 53; sh: 20
file content (190 lines) | stat: -rw-r--r-- 5,596 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
# ------------------------------------------------------------------------------
# Copyright (c) 2013-2025, Nucleic Development Team.
#
# Distributed under the terms of the Modified BSD License.
#
# The full license is in the file LICENSE, distributed with this software.
# ------------------------------------------------------------------------------
"""Pytest fixtures.

"""
import math
import os
import pathlib
from traceback import format_exc

from enaml.application import Application
from enaml.widgets.api import Window
from enaml.widgets.widget import Widget

from utils import wait_for_window_displayed, close_window_or_popup

# Make sure enaml already imported qt to avoid issues with pytest
try:
    from enaml.qt import QT_API, PYQT5_API, PYSIDE2_API, PYQT6_API, PYSIDE6_API

    if QT_API in PYQT5_API:
        os.environ.setdefault("PYTEST_QT_API", "pyqt5")
    elif QT_API in PYSIDE2_API:
        os.environ.setdefault("PYTEST_QT_API", "pyside2")
    elif QT_API in PYQT6_API:
        os.environ.setdefault("PYTEST_QT_API", "pyqt6")
    elif QT_API in PYSIDE6_API:
        os.environ.setdefault("PYTEST_QT_API", "pyside6")

    from enaml.qt import QtCore, QtGui

    pytest_plugins = (str("pytest-qt"),)
    QT_AVAILABLE = True

except Exception:
    QT_AVAILABLE = False


import pytest
from utils import close_all_windows, close_all_popups

#: Global variable linked to the --enaml-sleep cmd line option.
DIALOG_SLEEP = 0


def pytest_addoption(parser):
    """Add command line options."""
    parser.addoption(
        "--enaml-sleep",
        action="store",
        type=int,
        help="Time to sleep after handling a ui event",
    )


def pytest_configure(config):
    """Turn the --enaml-sleep command line into a global variable."""
    s = config.getoption("--enaml-sleep")
    if s is not None:
        global DIALOG_SLEEP
        DIALOG_SLEEP = s


@pytest.fixture(scope="session", autouse=True)
def validate_parser_is_up_to_date():
    """Check that the generated parser is up to date with its sources."""
    from enaml.core.parser import base_enaml_parser, base_python_parser, enaml_parser

    last_source_modif = max(
        os.path.getmtime(base_enaml_parser.__file__),
        os.path.getmtime(base_python_parser.__file__),
        os.path.getmtime(
            pathlib.Path(base_enaml_parser.__file__).parent / "enaml.gram"
        ),
    )

    # We round the times to avoid spurious failure because both files were
    # written at slightly different times.
    assert math.ceil(os.path.getmtime(enaml_parser.__file__)) >= math.floor(
        last_source_modif
    ), (
        "Generated parser appears outdated compared to its sources, "
        "re-generate it using `python -m enaml.core.parser.generate_enaml_parser`"
    )


@pytest.fixture
def enaml_sleep():
    """Return the time to sleep in s as set by the --enaml-sleep option."""
    return DIALOG_SLEEP


@pytest.fixture(scope="session")
def qapp():
    """Make sure a QtApplication is active."""
    if not QT_AVAILABLE:
        pytest.skip("Requires a Qt binding")
    try:
        from enaml.qt.qt_application import QtApplication
    except Exception:
        pytest.skip("No Qt binding found: %s" % format_exc())

    # Import it before starting the app as otherwise it can cause issues
    # in particular with PySide
    try:
        from enaml.qt import QtWebEngineWidgets
    except Exception:
        pass

    app = QtApplication("enaml-testing-app")
    yield app._qapp
    app.stop()


@pytest.fixture
def enaml_qtbot(qapp, qtbot):
    from enaml.qt.qt_application import QtApplication

    qtbot.enaml_app = QtApplication.instance()
    assert qtbot.enaml_app is not None
    pixel_ratio = QtGui.QGuiApplication.primaryScreen().devicePixelRatio()

    def get_global_pos(widget: Widget) -> QtCore.QPoint:
        assert widget.proxy
        qw = widget.proxy.widget
        # Take into account the pixel ratio so that PyQt and PyAutoGUI agree on
        # the coordinates
        return qw.mapToGlobal(qw.rect().center()) * pixel_ratio

    qtbot.get_global_pos = get_global_pos

    def post_event(widget, event):
        qapp.postEvent(widget, event)

    qtbot.post_event = post_event

    with close_all_windows(qtbot), close_all_popups(qtbot):
        yield qtbot


@pytest.fixture
def enaml_run(enaml_qtbot, monkeypatch):
    """Patches the QtApplication to allow using the qtbot when the
    enaml application is started. It also patches QApplication.exit as
    recommended in the pytest-qt docs.

    Yields
    -------
    handler: object
        an object with a `run` attribute that can be set to a callback that
        will be invoked with the application and first window shown.

    References
    ----------
    1. https://pytest-qt.readthedocs.io/en/latest/app_exit.html

    """
    from enaml.qt.qt_application import QtApplication, QApplication

    app = Application.instance()
    if app:
        Application._instance = None

    class Runner:
        # Set this to a callback
        run = None

    runner = Runner()

    def start(self):
        for window in Window.windows:
            wait_for_window_displayed(enaml_qtbot, window)
            if callable(runner.run):
                runner.run(self, window)
            else:
                close_window_or_popup(enaml_qtbot, window)
            break

    try:
        with monkeypatch.context() as m:
            m.setattr(QtApplication, "start", start)
            m.setattr(QApplication, "exit", lambda self: None)
            yield runner
    finally:
        Application._instance = app