Skip to content
Permalink
Browse files

Add qgis.testing module for generic qgis test helpers

  • Loading branch information
m-kuhn committed Feb 4, 2016
1 parent a3d3ffd commit c21889f945fe01bfecbd88caad5998141e33492b
Showing with 871 additions and 620 deletions.
  1. +1 −0 ci/travis/linux/before_install.sh
  2. +1 −1 ci/travis/osx/before_install.sh
  3. +1 −0 python/CMakeLists.txt
  4. +24 −0 python/testing/CMakeLists.txt
  5. +197 −0 python/testing/__init__.py
  6. +67 −0 python/testing/mocked.py
  7. +27 −12 tests/src/python/test_provider_memory.py
  8. +30 −17 tests/src/python/test_provider_mssql.py
  9. +6 −8 tests/src/python/test_provider_postgres.py
  10. +7 −7 tests/src/python/test_provider_shapefile.py
  11. +6 −8 tests/src/python/test_provider_spatialite.py
  12. +7 −7 tests/src/python/test_provider_tabfile.py
  13. +7 −9 tests/src/python/test_provider_virtual.py
  14. +7 −7 tests/src/python/test_qgis_local_server.py
  15. +7 −16 tests/src/python/test_qgsanalysis.py
  16. +2 −2 tests/src/python/test_qgsapplication.py
  17. +2 −1 tests/src/python/test_qgsappstartup.py
  18. +3 −3 tests/src/python/test_qgsatlascomposition.py
  19. +6 −7 tests/src/python/test_qgsattributetablemodel.py
  20. +4 −5 tests/src/python/test_qgsauthsystem.py
  21. +12 −9 tests/src/python/test_qgsblendmodes.py
  22. +5 −6 tests/src/python/test_qgscategorizedsymbolrendererv2.py
  23. +2 −2 tests/src/python/test_qgscolorscheme.py
  24. +2 −2 tests/src/python/test_qgscolorschemeregistry.py
  25. +6 −7 tests/src/python/test_qgscomposereffects.py
  26. +16 −9 tests/src/python/test_qgscomposerhtml.py
  27. +3 −3 tests/src/python/test_qgscomposerlabel.py
  28. +7 −9 tests/src/python/test_qgscomposermap.py
  29. +6 −7 tests/src/python/test_qgscomposermapgrid.py
  30. +6 −7 tests/src/python/test_qgscomposerpicture.py
  31. +6 −7 tests/src/python/test_qgscomposershapes.py
  32. +12 −12 tests/src/python/test_qgscomposition.py
  33. +13 −7 tests/src/python/test_qgsconditionalstyle.py
  34. +4 −8 tests/src/python/test_qgscoordinatetransform.py
  35. +30 −30 tests/src/python/test_qgsdelimitedtextprovider.py
  36. +4 −5 tests/src/python/test_qgsdistancearea.py
  37. +3 −6 tests/src/python/test_qgsdoccoverage.py
  38. +8 −7 tests/src/python/test_qgseditwidgets.py
  39. +2 −2 tests/src/python/test_qgsexpression.py
  40. +4 −8 tests/src/python/test_qgsfeature.py
  41. +6 −5 tests/src/python/test_qgsfeatureiterator.py
  42. +6 −8 tests/src/python/test_qgsfield.py
  43. +6 −7 tests/src/python/test_qgsfontutils.py
  44. +24 −20 tests/src/python/test_qgsgeometry.py
  45. +3 −3 tests/src/python/test_qgsgeometry_avoid_intersections.py
  46. +26 −20 tests/src/python/test_qgsgeometrygeneratorsymbollayerv2.py
  47. +5 −6 tests/src/python/test_qgsgraduatedsymbolrendererv2.py
  48. +5 −6 tests/src/python/test_qgsissue7244.py
  49. +7 −3 tests/src/python/test_qgslayerdefinition.py
  50. +4 −4 tests/src/python/test_qgslogger.py
  51. +1 −1 tests/src/python/test_qgsmaplayerregistry.py
  52. +2 −2 tests/src/python/test_qgsmapunitscale.py
  53. +3 −2 tests/src/python/test_qgsnetworkcontentfetcher.py
  54. +12 −8 tests/src/python/test_qgspallabeling_base.py
  55. +1 −1 tests/src/python/test_qgspallabeling_placement.py
  56. +3 −3 tests/src/python/test_qgspoint.py
  57. +3 −3 tests/src/python/test_qgsproject.py
  58. +6 −7 tests/src/python/test_qgsrangewidgets.py
  59. +10 −12 tests/src/python/test_qgsrasterfilewriter.py
  60. +6 −6 tests/src/python/test_qgsrasterlayer.py
  61. +11 −9 tests/src/python/test_qgsrectangle.py
  62. +5 −6 tests/src/python/test_qgsrelation.py
  63. +8 −10 tests/src/python/test_qgsrelationeditwidget.py
  64. +11 −8 tests/src/python/test_qgsrulebasedrenderer.py
  65. +1 −1 tests/src/python/test_qgsserver.py
  66. +3 −3 tests/src/python/test_qgsserver_accesscontrol.py
  67. +12 −8 tests/src/python/test_qgssinglesymbolrenderer.py
  68. +3 −6 tests/src/python/test_qgssipcoverage.py
  69. +4 −3 tests/src/python/test_qgsspatialindex.py
  70. +27 −23 tests/src/python/test_qgssymbolexpressionvariables.py
  71. +41 −43 tests/src/python/test_qgssymbollayerv2.py
  72. +9 −8 tests/src/python/test_qgssymbolv2.py
  73. +2 −2 tests/src/python/test_qgsvectorcolorramp.py
  74. +9 −7 tests/src/python/test_qgsvectorfilewriter.py
  75. +6 −7 tests/src/python/test_qgsvectorlayer.py
  76. +2 −2 tests/src/python/test_qgsvirtuallayerdefinition.py
  77. +8 −7 tests/src/python/test_qgszonalstatistics.py
  78. +5 −6 tests/src/python/test_syntactic_sugar.py
  79. +2 −71 tests/src/python/utilities.py
@@ -61,3 +61,4 @@ sudo apt-get install --force-yes --no-install-recommends --no-install-suggests \
postgresql-9.1-postgis-2.1/precise # from ubuntugis-unstable, not pgdg

sudo -H pip install autopep8 # TODO when switching to trusty or above: replace python-pip with python-autopep8
sudo -H pip install nose2 pyyaml mock
@@ -18,4 +18,4 @@ mkdir -p /Users/travis/Library/Python/2.7/lib/python/site-packages
echo 'import site; site.addsitedir("/usr/local/lib/python2.7/site-packages")' >> /Users/travis/Library/Python/2.7/lib/python/site-packages/homebrew.pth

# Needed for Processing
pip install psycopg2 numpy
pip install psycopg2 numpy nose2 pyyaml mock
@@ -59,6 +59,7 @@ ADD_SUBDIRECTORY(console)
ADD_SUBDIRECTORY(PyQt)
ADD_SUBDIRECTORY(pyplugin_installer)
ADD_SUBDIRECTORY(ext-libs)
ADD_SUBDIRECTORY(testing)

IF(POLICY CMP0040)
CMAKE_POLICY (POP) # see PUSH above
@@ -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)
@@ -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
@@ -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
@@ -17,23 +17,37 @@
import shutil
import glob

from qgis.core import QGis, QgsField, QgsPoint, QgsMapLayer, QgsVectorLayer, QgsFeatureRequest, QgsFeature, QgsProviderRegistry, \
QgsGeometry, NULL
from PyQt4.QtCore import QSettings
from utilities import (unitTestDataPath,
getQgisTestApp,
unittest,
TestCase,
compareWkt
)
from qgis.core import (
QGis,
QgsField,
QgsPoint,
QgsMapLayer,
QgsVectorLayer,
QgsFeatureRequest,
QgsFeature,
QgsProviderRegistry,
QgsGeometry,
NULL
)

from qgis.testing import (
start_app,
unittest
)

from utilities import (
unitTestDataPath,
compareWkt
)

from providertestbase import ProviderTestCase
from PyQt4.QtCore import QVariant

QGISAPP, CANVAS, IFACE, PARENT = getQgisTestApp()
start_app()
TEST_DATA_DIR = unitTestDataPath()


class TestPyQgsMemoryProvider(TestCase, ProviderTestCase):
class TestPyQgsMemoryProvider(unittest.TestCase, ProviderTestCase):

@classmethod
def setUpClass(cls):
@@ -241,7 +255,8 @@ def testSaveFields(self):
assert f == importedFields.field(f.name())


class TestPyQgsMemoryProviderIndexed(TestCase, ProviderTestCase):
class TestPyQgsMemoryProviderIndexed(unittest.TestCase, ProviderTestCase):

"""Runs the provider test suite against an indexed memory layer"""

@classmethod

0 comments on commit c21889f

Please sign in to comment.
You can’t perform that action at this time.