Skip to content

Commit 149b5d9

Browse files
committed
Merge pull request #34 from pytest-dev/pyqt5
Adding PyQt5 support Fixes #33
2 parents 2ab6d4b + 84c22a6 commit 149b5d9

File tree

8 files changed

+133
-66
lines changed

8 files changed

+133
-66
lines changed

.travis.yml

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,26 @@
11
language: python
22

33
python:
4-
- 2.7
5-
- 3.2
4+
- "2.7"
5+
- "3.2"
66

77
virtualenv:
8-
system_site_packages: true
8+
system_site_packages: true
9+
10+
matrix:
11+
allow_failures:
12+
# unfortunately couldn't find a reliable way to test pyqt5 on travis yet
13+
- env: PYTEST_VERSION=2.7.0 PYTEST_QT_API=pyqt5
914

1015
env:
11-
- PYTEST_VERSION=2.6.4 PYTEST_QT_FORCE_PYQT=true
12-
- PYTEST_VERSION=2.6.4 PYTEST_QT_FORCE_PYQT=false
13-
- PYTEST_VERSION=2.7.0 PYTEST_QT_FORCE_PYQT=true
14-
- PYTEST_VERSION=2.7.0 PYTEST_QT_FORCE_PYQT=false
16+
- PYTEST_VERSION=2.6.4 PYTEST_QT_API=pyqt4
17+
- PYTEST_VERSION=2.6.4 PYTEST_QT_API=pyside
18+
- PYTEST_VERSION=2.7.0 PYTEST_QT_API=pyqt4
19+
- PYTEST_VERSION=2.7.0 PYTEST_QT_API=pyside
20+
- PYTEST_VERSION=2.7.0 PYTEST_QT_API=pyqt5
1521

1622
install:
23+
- sudo add-apt-repository --yes ppa:ubuntu-sdk-team/ppa
1724
- sudo apt-get update
1825

1926
# Qt

README.rst

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -56,10 +56,16 @@ Requirements
5656

5757
Python 2.6 or later, including Python 3+.
5858

59-
Works with either PySide_ or
60-
PyQt_ picking whichever is available on the system, giving
61-
preference to ``PySide`` if both are installed (to force it to use ``PyQt``, set
62-
the environment variable ``PYTEST_QT_FORCE_PYQT=true``).
59+
Works with either PySide_, PyQt_ (``PyQt4`` and ``PyQt5``) picking whichever
60+
is available on the system, giving preference to the first one installed in
61+
this order:
62+
63+
- ``PySide``
64+
- ``PyQt4``
65+
- ``PyQt5``
66+
67+
To force a particular API, set the environment variable ``PYTEST_QT_API`` to
68+
``pyside``, ``pyqt4`` or ``pyqt5``.
6369

6470
Documentation
6571
=============

docs/index.rst

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,16 @@ Python 2.6 or later, including Python 3+.
2626

2727
Tested with pytest version 2.5.2.
2828

29-
Works with either `PySide` or
30-
`PyQt`, picking one that is available giving
31-
preference to `PySide` if both are installed (to force it to use `PyQt`, set
32-
the environment variable `PYTEST_QT_FORCE_PYQT=true`).
29+
Works with either ``PySide``, ``PyQt4`` or ``PyQt5``, picking whichever
30+
is available on the system giving preference to the first one installed in
31+
this order:
32+
33+
- ``PySide``
34+
- ``PyQt4``
35+
- ``PyQt5``
36+
37+
To force a particular API, set the environment variable ``PYTEST_QT_API`` to
38+
``pyside``, ``pyqt4`` or ``pyqt5``.
3339

3440
Installation
3541
============

install-qt.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,12 @@ def run(cmd):
1212
sys.exit('command %s failed with status %s' % (cmd, r))
1313

1414
py3k = sys.version_info[0] == 3
15-
if os.environ['PYTEST_QT_FORCE_PYQT'] == "true":
15+
if os.environ['PYTEST_QT_API'] in ('pyqt4', 'pyqt5'):
16+
pyqt_ver = os.environ['PYTEST_QT_API'][-1]
1617
if py3k:
17-
run('sudo apt-get install -qq python3-pyqt4')
18+
run('sudo apt-get install -qq python3-pyqt%s' % pyqt_ver)
1819
else:
19-
run('sudo apt-get install -qq python-qt4')
20+
run('sudo apt-get install -qq python-qt%s' % pyqt_ver)
2021
else:
2122
if py3k:
2223
run('sudo apt-get install -qq python3-pyside')

pytestqt/_tests/test_basics.py

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,16 @@
11
import weakref
22
import pytest
3-
from pytestqt.qt_compat import QtGui, Qt, QEvent, QtCore
3+
from pytestqt.qt_compat import QtGui, Qt, QEvent, QtCore, QApplication, \
4+
QWidget
45

56

67
def test_basics(qtbot):
78
"""
89
Basic test that works more like a sanity check to ensure we are setting up a QApplication
910
properly and are able to display a simple event_recorder.
1011
"""
11-
assert isinstance(QtGui.qApp, QtGui.QApplication)
12-
widget = QtGui.QWidget()
12+
assert QApplication.instance() is not None
13+
widget = QWidget()
1314
qtbot.addWidget(widget)
1415
widget.setWindowTitle('W1')
1516
widget.show()
@@ -62,7 +63,7 @@ def test_stop_for_interaction(qtbot):
6263
"""
6364
Test qtbot.stopForInteraction()
6465
"""
65-
widget = QtGui.QWidget()
66+
widget = QWidget()
6667
qtbot.addWidget(widget)
6768
qtbot.waitForWindowShown(widget)
6869
QtCore.QTimer.singleShot(0, widget.close)
@@ -73,13 +74,13 @@ def test_widget_kept_as_weakref(qtbot):
7374
"""
7475
Test if the widget is kept as a weak reference in QtBot
7576
"""
76-
widget = QtGui.QWidget()
77+
widget = QWidget()
7778
qtbot.add_widget(widget)
7879
widget = weakref.ref(widget)
7980
assert widget() is None
8081

8182

82-
class EventRecorder(QtGui.QWidget):
83+
class EventRecorder(QWidget):
8384

8485
"""
8586
Widget that records some kind of events sent to it.
@@ -90,7 +91,7 @@ class EventRecorder(QtGui.QWidget):
9091
"""
9192

9293
def __init__(self):
93-
QtGui.QWidget.__init__(self)
94+
QWidget.__init__(self)
9495
self._event_types = {}
9596
self.event_data = None
9697

pytestqt/_tests/test_exceptions.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import pytest
22
import sys
33
from pytestqt.plugin import capture_exceptions, format_captured_exceptions
4-
from pytestqt.qt_compat import QtGui, Qt, QtCore
4+
from pytestqt.qt_compat import QtGui, Qt, QtCore, QApplication
55

66

77
pytest_plugins = 'pytester'
@@ -31,7 +31,7 @@ def test_catch_exceptions_in_virtual_methods(qtbot, raise_error):
3131
tests fail if any.
3232
"""
3333
v = Receiver(raise_error)
34-
app = QtGui.QApplication.instance()
34+
app = QApplication.instance()
3535
app.sendEvent(v, QtCore.QEvent(QtCore.QEvent.User))
3636
app.sendEvent(v, QtCore.QEvent(QtCore.QEvent.User))
3737
app.processEvents()
@@ -67,9 +67,9 @@ def test_no_capture(testdir, no_capture_by_marker):
6767
''')
6868
testdir.makepyfile('''
6969
import pytest
70-
from pytestqt.qt_compat import QtGui, QtCore
70+
from pytestqt.qt_compat import QWidget, QtCore
7171
72-
class MyWidget(QtGui.QWidget):
72+
class MyWidget(QWidget):
7373
7474
def mouseReleaseEvent(self, ev):
7575
raise RuntimeError

pytestqt/plugin.py

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
import pytest
88

9-
from pytestqt.qt_compat import QtCore, QtGui, QtTest
9+
from pytestqt.qt_compat import QtCore, QtTest, QApplication, QT_API
1010

1111

1212
def _inject_qtest_methods(cls):
@@ -199,8 +199,16 @@ def waitForWindowShown(self, widget):
199199
200200
:param QWidget widget:
201201
Widget to wait on.
202+
203+
.. note:: In Qt5, the actual method called is qWaitForWindowExposed,
204+
but this name is kept for backward compatibility
202205
"""
203-
QtTest.QTest.qWaitForWindowShown(widget)
206+
if hasattr(QtTest.QTest, 'qWaitForWindowShown'): # pragma: no cover
207+
# PyQt4 and PySide
208+
QtTest.QTest.qWaitForWindowShown(widget)
209+
else: # pragma: no cover
210+
# PyQt5
211+
QtTest.QTest.qWaitForWindowExposed(widget)
204212

205213
wait_for_window_shown = waitForWindowShown # pep-8 alias
206214

@@ -370,9 +378,9 @@ def qapp():
370378
fixture that instantiates the QApplication instance that will be used by
371379
the tests.
372380
"""
373-
app = QtGui.QApplication.instance()
381+
app = QApplication.instance()
374382
if app is None:
375-
app = QtGui.QApplication([])
383+
app = QApplication([])
376384
yield app
377385
app.exit()
378386
else:
@@ -410,4 +418,8 @@ def pytest_configure(config):
410418
config.addinivalue_line(
411419
'markers',
412420
"qt_no_exception_capture: Disables pytest-qt's automatic exception "
413-
'capture for just one test item.')
421+
'capture for just one test item.')
422+
423+
424+
def pytest_report_header():
425+
return ['qt-api: %s' % QT_API]

pytestqt/qt_compat.py

Lines changed: 66 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -11,47 +11,78 @@
1111
on_rtd = os.environ.get('READTHEDOCS', None) == 'True'
1212

1313
if not on_rtd: # pragma: no cover
14-
try:
15-
import PySide.QtCore as _QtCore
16-
QtCore = _QtCore
17-
USING_PYSIDE = True
18-
except ImportError:
19-
USING_PYSIDE = False
20-
21-
FORCE_PYQT = os.environ.get('PYTEST_QT_FORCE_PYQT', 'false') == 'true'
22-
if not USING_PYSIDE or FORCE_PYQT:
14+
15+
def _try_import(name):
2316
try:
24-
import sip
17+
__import__(name)
18+
return True
2519
except ImportError:
26-
msg = 'pytest-qt requires either PyQt4 or PySide to be installed'
20+
return False
21+
22+
def _guess_qt_api():
23+
if _try_import('PySide'):
24+
return 'pyside'
25+
elif _try_import('PyQt4'):
26+
return 'pyqt4'
27+
elif _try_import('PyQt5'):
28+
return 'pyqt5'
29+
else:
30+
msg = 'pytest-qt requires either PySide, PyQt4 or PyQt5 to be installed'
2731
raise ImportError(msg)
28-
USING_PYSIDE = False
29-
import PyQt4.QtCore as _QtCore
30-
QtCore = _QtCore
31-
32-
if USING_PYSIDE:
33-
def _import_module(moduleName):
34-
pyside = __import__('PySide', globals(), locals(), [moduleName], 0)
35-
return getattr(pyside, moduleName)
36-
32+
33+
# backward compatibility support: PYTEST_QT_FORCE_PYQT
34+
if os.environ.get('PYTEST_QT_FORCE_PYQT', 'false') == 'true':
35+
QT_API = 'pyqt4'
36+
else:
37+
QT_API = os.environ.get('PYTEST_QT_API')
38+
if QT_API is not None:
39+
QT_API = QT_API.lower()
40+
if QT_API not in ('pyside', 'pyqt4', 'pyqt5'):
41+
msg = 'Invalid value for $PYTEST_QT_API: %s'
42+
raise RuntimeError(msg % QT_API)
43+
else:
44+
QT_API = _guess_qt_api()
45+
46+
# backward compatibility
47+
USING_PYSIDE = QT_API == 'pyside'
48+
49+
def _import_module(module_name):
50+
m = __import__(_root_module, globals(), locals(), [module_name], 0)
51+
return getattr(m, module_name)
52+
53+
_root_modules = {
54+
'pyside': 'PySide',
55+
'pyqt4': 'PyQt4',
56+
'pyqt5': 'PyQt5',
57+
}
58+
_root_module = _root_modules[QT_API]
59+
60+
QtCore = _import_module('QtCore')
61+
QtGui = _import_module('QtGui')
62+
QtTest = _import_module('QtTest')
63+
Qt = QtCore.Qt
64+
QEvent = QtCore.QEvent
65+
66+
if QT_API == 'pyside':
3767
Signal = QtCore.Signal
3868
Slot = QtCore.Slot
3969
Property = QtCore.Property
40-
else:
41-
def _import_module(moduleName):
42-
pyside = __import__('PyQt4', globals(), locals(), [moduleName], 0)
43-
return getattr(pyside, moduleName)
44-
70+
QApplication = QtGui.QApplication
71+
QWidget = QtGui.QWidget
72+
73+
elif QT_API in ('pyqt4', 'pyqt5'):
4574
Signal = QtCore.pyqtSignal
4675
Slot = QtCore.pyqtSlot
4776
Property = QtCore.pyqtProperty
48-
49-
50-
QtGui = _import_module('QtGui')
51-
QtTest = _import_module('QtTest')
52-
Qt = QtCore.Qt
53-
QEvent = QtCore.QEvent
54-
77+
78+
if QT_API == 'pyqt5':
79+
_QtWidgets = _import_module('QtWidgets')
80+
QApplication = _QtWidgets.QApplication
81+
QWidget = _QtWidgets.QWidget
82+
else:
83+
QApplication = QtGui.QApplication
84+
QWidget = QtGui.QWidget
85+
5586
else: # pragma: no cover
5687
USING_PYSIDE = True
5788

@@ -77,3 +108,6 @@ def __getattr__(cls, name):
77108
QtTest = Mock()
78109
Qt = Mock()
79110
QEvent = Mock()
111+
QApplication = Mock()
112+
QWidget = Mock()
113+
QT_API = '<none>'

0 commit comments

Comments
 (0)