Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add qgis.testing module for generic qgis test helpers
- Loading branch information
Showing
79 changed files
with
871 additions
and
620 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
# See ../CMakeLists.txt for info on staged-plugins* and clean-staged-plugins targets | ||
|
||
SET (QGIS_PYTHON_DIR ${QGIS_DATA_DIR}/python) | ||
SET (PYTHON_OUTPUT_DIRECTORY ${QGIS_OUTPUT_DIRECTORY}/python) | ||
|
||
SET(PY_FILES | ||
__init__.py | ||
mocked.py | ||
) | ||
|
||
FILE (MAKE_DIRECTORY ${QGIS_PYTHON_OUTPUT_DIRECTORY}/testing) | ||
INSTALL(FILES ${PY_FILES} DESTINATION "${QGIS_PYTHON_DIR}/testing") | ||
|
||
ADD_CUSTOM_TARGET(pytesting ALL) | ||
# stage to output to make available when QGIS is run from build directory | ||
FOREACH(pyfile ${PY_FILES}) | ||
ADD_CUSTOM_COMMAND(TARGET pytesting | ||
POST_BUILD | ||
COMMAND ${CMAKE_COMMAND} -E copy ${pyfile} "${QGIS_PYTHON_OUTPUT_DIRECTORY}/testing" | ||
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} | ||
DEPENDS ${pyfile} | ||
) | ||
PY_COMPILE(pyutils "${QGIS_PYTHON_OUTPUT_DIRECTORY}/testing/${pyfile}") | ||
ENDFOREACH(pyfile) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,197 @@ | ||
# -*- coding: utf-8 -*- | ||
|
||
""" | ||
*************************************************************************** | ||
__init__.py | ||
--------------------- | ||
Date : January 2016 | ||
Copyright : (C) 2016 by Matthias Kuhn | ||
Email : matthias@opengis.ch | ||
*************************************************************************** | ||
* * | ||
* This program is free software; you can redistribute it and/or modify * | ||
* it under the terms of the GNU General Public License as published by * | ||
* the Free Software Foundation; either version 2 of the License, or * | ||
* (at your option) any later version. * | ||
* * | ||
*************************************************************************** | ||
""" | ||
|
||
__author__ = 'Matthias Kuhn' | ||
__date__ = 'January 2016' | ||
__copyright__ = '(C) 2016, Matthias Kuhn' | ||
|
||
# This will get replaced with a git SHA1 when you do a git archive | ||
|
||
__revision__ = ':%H$' | ||
|
||
import os | ||
import sys | ||
|
||
from PyQt4.QtCore import QVariant | ||
from qgis.core import QgsApplication, QgsFeatureRequest, QgsVectorLayer | ||
from nose2.compat import unittest | ||
|
||
# Get a backup, we will patch this one later | ||
_TestCase = unittest.TestCase | ||
|
||
|
||
class TestCase(_TestCase): | ||
|
||
def assertLayersEqual(self, layer1, layer2, **kwargs): | ||
""" | ||
:param layer1: The first layer to compare | ||
:param layer2: The second layer to compare | ||
:param request: Optional, A feature request. This can be used to specify | ||
an order by clause to make sure features are compared in | ||
a given sequence if they don't match by default. | ||
""" | ||
|
||
try: | ||
request = kwargs['request'] | ||
except KeyError: | ||
request = QgsFeatureRequest() | ||
|
||
try: | ||
compare = kwargs['compare'] | ||
except KeyError: | ||
compare = {} | ||
|
||
# Compare fields | ||
_TestCase.assertEqual(self, layer1.fields().count(), layer2.fields().count()) | ||
for fieldnum in range(layer1.fields().count()): | ||
field1 = layer1.fields().at(fieldnum) | ||
field2 = layer2.fields().at(fieldnum) | ||
_TestCase.assertEqual(self, field1.name(), field2.name()) | ||
# _TestCase.assertEqual(self, field1.type(), field2.type(), 'Field "{}" is not equal: {}({}) != {}({})'.format(field1.name(), field1.typeName(), field1.type(), field2.typeName(), field2.type())) | ||
|
||
# Compare CRS | ||
_TestCase.assertEqual(self, layer1.dataProvider().crs().authid(), layer2.dataProvider().crs().authid()) | ||
|
||
# Compare features | ||
_TestCase.assertEqual(self, layer1.featureCount(), layer2.featureCount()) | ||
|
||
try: | ||
precision = compare['geometry']['precision'] | ||
except KeyError: | ||
precision = 17 | ||
|
||
for feats in zip(layer1.getFeatures(request), layer2.getFeatures(request)): | ||
if feats[0].geometry() is not None: | ||
geom0 = feats[0].geometry().geometry().asWkt(precision) | ||
else: | ||
geom0 = None | ||
if feats[1].geometry() is not None: | ||
geom1 = feats[1].geometry().geometry().asWkt(precision) | ||
else: | ||
geom1 = None | ||
_TestCase.assertEqual( | ||
self, | ||
geom0, | ||
geom1, | ||
'Features {}/{} differ in geometry: \n\n {}\n\n vs \n\n {}'.format( | ||
feats[0].id(), | ||
feats[1].id(), | ||
geom0, | ||
geom1 | ||
) | ||
) | ||
|
||
for attr0, attr1, field1, field2 in zip(feats[0].attributes(), feats[1].attributes(), layer1.fields().toList(), layer2.fields().toList()): | ||
try: | ||
cmp = compare['fields'][field1.name()] | ||
except KeyError: | ||
try: | ||
cmp = compare['fields']['__all__'] | ||
except KeyError: | ||
cmp = {} | ||
|
||
# Skip field | ||
if 'skip' in cmp: | ||
continue | ||
|
||
# Cast field to a given type | ||
if 'cast' in cmp: | ||
if cmp['cast'] == 'int': | ||
attr0 = int(attr0) if attr0 else None | ||
attr1 = int(attr1) if attr0 else None | ||
if cmp['cast'] == 'float': | ||
attr0 = float(attr0) if attr0 else None | ||
attr1 = float(attr1) if attr0 else None | ||
if cmp['cast'] == 'str': | ||
attr0 = str(attr0) | ||
attr1 = str(attr1) | ||
|
||
# Round field (only numeric so it works with __all__) | ||
if 'precision' in cmp and field1.type() in [QVariant.Int, QVariant.Double, QVariant.LongLong]: | ||
attr0 = round(attr0, cmp['precision']) | ||
attr1 = round(attr1, cmp['precision']) | ||
|
||
_TestCase.assertEqual( | ||
self, | ||
attr0, | ||
attr1, | ||
'Features {}/{} differ in attributes\n\n * Field1: {} ({})\n * Field2: {} ({})\n\n * {} != {}'.format(feats[0].id(), | ||
feats[1].id(), | ||
field1.name(), | ||
field1.typeName(), | ||
field2.name(), | ||
field2.typeName(), | ||
repr(attr0), | ||
repr(attr1) | ||
) | ||
) | ||
|
||
# Patch unittest | ||
unittest.TestCase = TestCase | ||
|
||
|
||
def start_app(): | ||
""" | ||
Will start a QgsApplication and call all initialization code like | ||
registering the providers and other infrastructure. It will not load | ||
any plugins. | ||
You can always get the reference to a running app by calling `QgsApplication.instance()`. | ||
The initialization will only happen once, so it is safe to call this method repeatedly. | ||
Returns | ||
------- | ||
QgsApplication | ||
A QgsApplication singleton | ||
""" | ||
global QGISAPP | ||
|
||
try: | ||
QGISAPP | ||
except NameError: | ||
myGuiFlag = True # All test will run qgis in gui mode | ||
|
||
# In python3 we need to convert to a bytes object (or should | ||
# QgsApplication accept a QString instead of const char* ?) | ||
try: | ||
argvb = list(map(os.fsencode, sys.argv)) | ||
except AttributeError: | ||
argvb = sys.argv | ||
|
||
# Note: QGIS_PREFIX_PATH is evaluated in QgsApplication - | ||
# no need to mess with it here. | ||
QGISAPP = QgsApplication(argvb, myGuiFlag) | ||
|
||
QGISAPP.initQgis() | ||
s = QGISAPP.showSettings() | ||
print(s) | ||
|
||
return QGISAPP | ||
|
||
|
||
def stop_app(): | ||
""" | ||
Cleans up and exits QGIS | ||
""" | ||
global QGISAPP | ||
|
||
QGISAPP.exitQgis() | ||
del QGISAPP |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
# -*- coding: utf-8 -*- | ||
|
||
""" | ||
*************************************************************************** | ||
mocked | ||
--------------------- | ||
Date : January 2016 | ||
Copyright : (C) 2016 by Matthias Kuhn | ||
Email : matthias@opengis.ch | ||
*************************************************************************** | ||
* * | ||
* This program is free software; you can redistribute it and/or modify * | ||
* it under the terms of the GNU General Public License as published by * | ||
* the Free Software Foundation; either version 2 of the License, or * | ||
* (at your option) any later version. * | ||
* * | ||
*************************************************************************** | ||
""" | ||
|
||
__author__ = 'Matthias Kuhn' | ||
__date__ = 'January 2016' | ||
__copyright__ = '(C) 2016, Matthias Kuhn' | ||
|
||
# This will get replaced with a git SHA1 when you do a git archive | ||
|
||
__revision__ = ':%H$' | ||
|
||
import os | ||
import sys | ||
import mock | ||
|
||
from qgis.gui import QgisInterface, QgsMapCanvas | ||
from qgis.core import QgsApplication | ||
|
||
from PyQt4.QtGui import QMainWindow | ||
from PyQt4.QtCore import QSize | ||
|
||
from qgis.testing import start_app | ||
|
||
|
||
def get_iface(): | ||
""" | ||
Will return a mock QgisInterface object with some methods implemented in a generic way. | ||
You can further control its behavior | ||
by using the mock infrastructure. Refer to https://docs.python.org/3/library/unittest.mock.html | ||
for more details. | ||
Returns | ||
------- | ||
QgisInterface | ||
A mock QgisInterface | ||
""" | ||
|
||
start_app() | ||
|
||
my_iface = mock.Mock(spec=QgisInterface) | ||
|
||
my_iface.mainWindow.return_value = QMainWindow() | ||
|
||
canvas = QgsMapCanvas(my_iface.mainWindow()) | ||
canvas.resize(QSize(400, 400)) | ||
|
||
my_iface.mapCanvas.return_value = canvas | ||
|
||
return my_iface |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.