[labeling] Initial python unittest setup and some tests

- Note: some tests will fail until differences in platform dpi are worked out
This commit is contained in:
Larry Shaffer 2013-08-08 19:04:56 -06:00
parent eb24255fcd
commit 9c4c2150bf
13 changed files with 444 additions and 209 deletions

View File

@ -22,7 +22,10 @@ ADD_PYTHON_TEST(PyQgsPoint test_qgspoint.py)
ADD_PYTHON_TEST(PyQgsAtlasComposition test_qgsatlascomposition.py)
ADD_PYTHON_TEST(PyQgsComposerLabel test_qgscomposerlabel.py)
ADD_PYTHON_TEST(PyQgsExpression test_qgsexpression.py)
#ADD_PYTHON_TEST(PyQgsPalLabeling test_qgspallabeling.py)
ADD_PYTHON_TEST(PyQgsPalLabelingBase test_qgspallabeling_base.py)
ADD_PYTHON_TEST(PyQgsPalLabelingCanvas test_qgspallabeling_canvas.py)
#ADD_PYTHON_TEST(PyQgsPalLabelingCanvas test_qgspallabeling_composer.py)
#ADD_PYTHON_TEST(PyQgsPalLabelingCanvas test_qgspallabeling_server.py)
ADD_PYTHON_TEST(PyQgsVectorFileWriter test_qgsvectorfilewriter.py)
ADD_PYTHON_TEST(PyQgsSpatialiteProvider test_qgsspatialiteprovider.py)
ADD_PYTHON_TEST(PyQgsZonalStatistics test_qgszonalstatistics.py)

View File

@ -1,208 +0,0 @@
# -*- coding: utf-8 -*-
"""QGIS Unit tests for QgsPalLabeling
.. note:: 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__ = 'Larry Shaffer'
__date__ = '12/10/2012'
__copyright__ = 'Copyright 2012, The QGIS Project'
# This will get replaced with a git SHA1 when you do a git archive
__revision__ = '$Format:%H$'
import os
import qgis
from PyQt4.QtCore import *
from PyQt4.QtGui import *
from qgis.core import (QgsPalLabeling,
QgsPalLayerSettings,
QgsVectorLayer,
QgsMapLayerRegistry,
QgsMapRenderer,
QgsCoordinateReferenceSystem,
QgsRenderChecker,
QGis)
from utilities import (getQgisTestApp,
TestCase,
unittest,
expectedFailure,
unitTestDataPath)
# Convenience instances in case you may need them
QGISAPP, CANVAS, IFACE, PARENT = getQgisTestApp()
TEST_DATA_DIR = unitTestDataPath()
class TestQgsPalLabeling(TestCase):
@classmethod
def setUpClass(cls):
"""Run before all tests"""
# Store/load the FreeSansQGIS labeling test font
myFontDB = QFontDatabase()
cls._testFontID = myFontDB.addApplicationFont(
os.path.join(TEST_DATA_DIR, 'font', 'FreeSansQGIS.ttf'))
myMessage = ('\nCould not store test font in font database, '
'skipping test suite')
assert cls._testFontID != -1, myMessage
cls._testFont = myFontDB.font('FreeSansQGIS', 'Medium', 12)
myAppFont = QApplication.font()
myMessage = ('\nCould not load test font from font database, '
'skipping test suite')
assert cls._testFont.toString() != myAppFont.toString(), myMessage
# initialize class MapRegistry, Canvas, MapRenderer, Map and PAL
cls._MapRegistry = QgsMapLayerRegistry.instance()
cls._Canvas = CANVAS
# to match render test comparisons background
cls._Canvas.setCanvasColor(QColor(152, 219, 249))
cls._Map = cls._Canvas.map()
cls._Map.resize(QSize(600, 400))
cls._MapRenderer = cls._Canvas.mapRenderer()
cls._MapRenderer.setOutputSize(QSize(600, 400), 72)
cls._Pal = QgsPalLabeling();
cls._MapRenderer.setLabelingEngine(cls._Pal)
cls._PalEngine = cls._MapRenderer.labelingEngine()
myMessage = ('\nCould initialize PAL labeling engine, '
'skipping test suite')
assert cls._PalEngine, myMessage
@classmethod
def tearDownClass(cls):
"""Run after all tests"""
# remove test font
myFontDB = QFontDatabase()
myResult = myFontDB.removeApplicationFont(cls._testFontID)
myMessage = ('\nFailed to remove test font from font database')
assert myResult, myMessage
def setUp(self):
"""Run before each test."""
pass
def tearDown(self):
"""Run after each test."""
pass
def test_AddPALToVectorLayer(self):
"""Check if we can set a label field, verify that PAL is assigned
and that output is rendered correctly"""
# TODO: add UTM PAL-specific shps, with 4326 as on-the-fly cross-check
# setCanvasCrs(26913)
myShpFile = os.path.join(TEST_DATA_DIR, 'lines.shp')
myVectorLayer = QgsVectorLayer(myShpFile, 'Lines', 'ogr')
self._MapRegistry.addMapLayer(myVectorLayer)
myLayers = QStringList()
myLayers.append(myVectorLayer.id())
self._MapRenderer.setLayerSet(myLayers)
self._MapRenderer.setExtent(myVectorLayer.extent())
self._Canvas.zoomToFullExtent()
# check layer labeling is PAL with customProperty access
# should not be activated on layer load
myPalSet = myVectorLayer.customProperty( "labeling" ).toString()
myMessage = '\nExpected: Empty QString\nGot: %s' % (str(myPalSet))
assert str(myPalSet) == '', myMessage
# simulate clicking checkbox, setting label field and clicking apply
self._testFont.setPointSize(20)
myPalLyr = QgsPalLayerSettings()
myPalLyr.enabled = True
myPalLyr.fieldName = 'Name'
myPalLyr.placement = QgsPalLayerSettings.Line
myPalLyr.placementFlags = QgsPalLayerSettings.AboveLine
myPalLyr.xQuadOffset = 0
myPalLyr.yQuadOffset = 0
myPalLyr.xOffset = 0
myPalLyr.yOffset = 0
myPalLyr.angleOffset = 0
myPalLyr.centroidWhole = False
myPalLyr.textFont = self._testFont
myPalLyr.textNamedStyle = QString("Medium")
myPalLyr.textColor = Qt.black
myPalLyr.textTransp = 0
myPalLyr.previewBkgrdColor = Qt.white
myPalLyr.priority = 5
myPalLyr.obstacle = True
myPalLyr.dist = 0
myPalLyr.scaleMin = 0
myPalLyr.scaleMax = 0
myPalLyr.bufferSize = 1
myPalLyr.bufferColor = Qt.white
myPalLyr.bufferTransp = 0
myPalLyr.bufferNoFill = False
myPalLyr.bufferJoinStyle = Qt.RoundJoin
myPalLyr.formatNumbers = False
myPalLyr.decimals = 3
myPalLyr.plusSign = False
myPalLyr.labelPerPart = False
myPalLyr.displayAll = True
myPalLyr.mergeLines = False
myPalLyr.minFeatureSize = 0.0
myPalLyr.vectorScaleFactor = 1.0
myPalLyr.rasterCompressFactor = 1.0
myPalLyr.addDirectionSymbol = False
myPalLyr.upsidedownLabels = QgsPalLayerSettings.Upright
myPalLyr.fontSizeInMapUnits = False
myPalLyr.bufferSizeInMapUnits = False
myPalLyr.labelOffsetInMapUnits = True
myPalLyr.distInMapUnits = False
myPalLyr.wrapChar = ""
myPalLyr.preserveRotation = True
myPalLyr.writeToLayer(myVectorLayer)
# check layer labeling is PAL with customProperty access
myPalSet = myVectorLayer.customProperty( "labeling" ).toString()
myMessage = '\nExpected: pal\nGot: %s' % (str(myPalSet))
assert str(myPalSet) == 'pal', myMessage
# check layer labeling is PAL via engine interface
myMessage = '\nCould not get whether PAL enabled from labelingEngine'
assert self._PalEngine.willUseLayer(myVectorLayer), myMessage
#
myChecker = QgsRenderChecker()
myChecker.setControlName("expected_pal_aboveLineLabeling")
myChecker.setMapRenderer(self._MapRenderer)
myResult = myChecker.runTest("pal_aboveLineLabeling_python");
myMessage = ('\nVector layer \'above line\' label engine '
'rendering test failed')
assert myResult, myMessage
# compare against a straight rendering/save as from QgsMapCanvasMap
# unnecessary? works a bit different than QgsRenderChecker, though
# myImage = os.path.join(unicode(QDir.tempPath()),
# 'render_pal_aboveLineLabeling.png')
# self._Map.render()
# self._Canvas.saveAsImage(myImage)
# myChecker.setRenderedImage(myImage)
# myResult = myChecker.compareImages("pal_aboveLineLabeling_python")
# myMessage = ('\nVector layer \'above line\' label engine '
# 'comparison to QgsMapCanvasMap.render() test failed')
# assert myResult, myMessage
self._MapRegistry.removeMapLayer(myVectorLayer.id())
# @expectedFailure
# def testIssue####(self):
# """Test we can .
# """
# pass
if __name__ == '__main__':
unittest.main()

View File

@ -0,0 +1,291 @@
# -*- coding: utf-8 -*-
"""QGIS Unit tests for QgsPalLabeling: base suite setup
.. note:: from build dir: ctest -R PyQgsPalLabelingBase -V
Set env variable PAL_SUITE to run specific tests (define in __main__)
Set env variable PAL_VERBOSE to output individual test summary
Set env variable PAL_CONTROL_IMAGE to trigger building of new control images
.. note:: 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__ = 'Larry Shaffer'
__date__ = '07/09/2013'
__copyright__ = 'Copyright 2013, The QGIS Project'
# This will get replaced with a git SHA1 when you do a git archive
__revision__ = '$Format:%H$'
import os
import sys
import glob
import StringIO
from PyQt4.QtCore import *
from PyQt4.QtGui import *
from qgis.core import (
QGis,
QgsCoordinateReferenceSystem,
QgsDataSourceURI,
QgsLogger,
QgsMapLayerRegistry,
QgsMapRenderer,
QgsPalLabeling,
QgsPalLayerSettings,
QgsProviderRegistry,
QgsVectorLayer,
QgsRenderChecker
)
from utilities import (
getQgisTestApp,
TestCase,
unittest,
expectedFailure,
unitTestDataPath
)
QGISAPP, CANVAS, IFACE, PARENT = getQgisTestApp()
class TestQgsPalLabeling(TestCase):
_TestDataDir = unitTestDataPath()
_PalDataDir = os.path.join(_TestDataDir, 'labeling')
_PalFeaturesDb = os.path.join(_PalDataDir, 'pal_features_v3.sqlite')
_TestFontID = -1
_MapRegistry = None
_MapRenderer = None
_Canvas = None
@classmethod
def setUpClass(cls):
"""Run before all tests"""
# qgis instances
cls._QgisApp, cls._Canvas, cls._Iface, cls._Parent = \
QGISAPP, CANVAS, IFACE, PARENT
# verify that spatialite provider is available
msg = ('\nSpatialite provider not found, '
'SKIPPING TEST SUITE')
res = 'spatialite' in QgsProviderRegistry.instance().providerList()
assert res, msg
# load the FreeSansQGIS labeling test font
fontdb = QFontDatabase()
cls._TestFontID = fontdb.addApplicationFont(
os.path.join(cls._TestDataDir, 'font', 'FreeSansQGIS.ttf'))
msg = ('\nCould not store test font in font database, '
'SKIPPING TEST SUITE')
assert cls._TestFontID != -1, msg
cls._TestFont = fontdb.font('FreeSansQGIS', 'Medium', 48)
appfont = QApplication.font()
msg = ('\nCould not load test font from font database, '
'SKIPPING TEST SUITE')
assert cls._TestFont.toString() != appfont.toString(), msg
cls._TestFunction = ''
cls._TestGroup = ''
cls._TestGroupPrefix = ''
cls._TestGroupAbbr = ''
# initialize class MapRegistry, Canvas, MapRenderer, Map and PAL
cls._MapRegistry = QgsMapLayerRegistry.instance()
# set color to match render test comparisons background
cls._Canvas.setCanvasColor(QColor(152, 219, 249))
cls._Map = cls._Canvas.map()
cls._Map.resize(QSize(600, 400))
cls._MapRenderer = cls._Canvas.mapRenderer()
crs = QgsCoordinateReferenceSystem()
# default for labeling test data sources: WGS 84 / UTM zone 13N
crs.createFromSrid(32613)
cls._MapRenderer.setDestinationCrs(crs)
# TODO: match and store platform's native logical output dpi
cls._MapRenderer.setOutputSize(QSize(600, 400), 72)
cls._Pal = QgsPalLabeling()
cls._MapRenderer.setLabelingEngine(cls._Pal)
cls._PalEngine = cls._MapRenderer.labelingEngine()
msg = ('\nCould not initialize PAL labeling engine, '
'SKIPPING TEST SUITE')
assert cls._PalEngine, msg
@classmethod
def tearDownClass(cls):
"""Run after all tests"""
cls.removeAllLayers()
@classmethod
def removeAllLayers(cls):
cls._MapRegistry.removeAllMapLayers()
@classmethod
def loadFeatureLayer(cls, table):
uri = QgsDataSourceURI()
uri.setDatabase(cls._PalFeaturesDb)
uri.setDataSource('', table, 'geometry')
vlayer = QgsVectorLayer(uri.uri(), table, 'spatialite')
# .qml should contain only style for symbology
vlayer.loadNamedStyle(os.path.join(cls._PalDataDir,
'{0}.qml'.format(table)))
cls._MapRegistry.addMapLayer(vlayer)
cls._MapRenderer.setLayerSet([vlayer.id()])
# zoom to area of interest, which matches output aspect ratio
uri.setDataSource('', 'aoi', 'geometry')
aoilayer = QgsVectorLayer(uri.uri(), table, 'spatialite')
cls._MapRenderer.setExtent(aoilayer.extent())
cls._Canvas.zoomToFullExtent()
return vlayer
def configTest(self, prefix, abbr):
"""Call in setUp() function of test subclass"""
self._TestGroupPrefix = prefix
self._TestGroupAbbr = abbr
# insert test's Class.function marker into debug output stream
# this helps visually track down the start of a test's debug output
testid = self.id().split('.')
self._TestGroup = testid[1]
self._TestFunction = testid[2]
testheader = '\n#####_____ {0}.{1} _____#####\n'.\
format(self._TestGroup, self._TestFunction)
QgsLogger.debug(testheader)
# define the shorthand name of the test (to minimize file name length)
self._Test = '{0}_{1}'.format(self._TestGroupAbbr,
self._TestFunction.replace('test_', ''))
def defaultSettings(self):
lyr = QgsPalLayerSettings()
lyr.enabled = True
lyr.fieldName = 'text' # default in data sources
lyr.textFont = self._TestFont
lyr.textNamedStyle = 'Medium'
return lyr
@staticmethod
def settingsDict(lyr):
"""Return a dict of layer-level labeling settings
.. note:: QgsPalLayerSettings is not a QObject, so we can not collect
current object properties, and the public properties of the C++ obj
can't be listed with __dict__ or vars(). So, we sniff them out relative
to their naming convention (camelCase), as reported by dir().
"""
res = {}
for attr in dir(lyr):
if attr[0].islower() and not attr.startswith("__"):
value = getattr(lyr, attr)
if not callable(value):
res[attr] = value
return res
def saveContolImage(self):
if 'PAL_CONTROL_IMAGE' not in os.environ:
return
testgrpdir = 'expected_' + self._TestGroupPrefix
testdir = os.path.join(self._TestDataDir, 'control_images',
testgrpdir, self._Test)
if not os.path.exists(testdir):
os.makedirs(testdir)
imgbasepath = os.path.join(testdir, self._Test)
imgpath = imgbasepath + '.png'
for f in glob.glob(imgbasepath + '.*'):
if os.path.exists(f):
os.remove(f)
self._Map.render()
self._Canvas.saveAsImage(imgpath)
def renderCheck(self):
chk = QgsRenderChecker()
chk.setControlPathPrefix('expected_' + self._TestGroupPrefix)
chk.setControlName(self._Test)
chk.setMapRenderer(self._MapRenderer)
res = chk.runTest(self._Test)
msg = '\nRender check failed for "{0}"'.format(self._Test)
return res, msg
class TestQgsPalLabelingBase(TestQgsPalLabeling):
@classmethod
def setUpClass(cls):
TestQgsPalLabeling.setUpClass()
cls.layer = TestQgsPalLabeling.loadFeatureLayer('point')
def setUp(self):
"""Run before each test."""
self.configTest('pal_base', 'base')
def tearDown(self):
"""Run after each test."""
pass
def test_default_pal_disabled(self):
# Verify PAL labeling is disabled for layer by default
palset = self.layer.customProperty('labeling', '').toString()
msg = '\nExpected: Empty string\nGot: {0}'.format(palset)
self.assertEqual(palset, '', msg)
def test_settings_enable_pal(self):
# Verify default PAL settings enable PAL labeling for layer
lyr = QgsPalLayerSettings()
lyr.writeToLayer(self.layer)
palset = self.layer.customProperty('labeling', '').toString()
msg = '\nExpected: Empty string\nGot: {0}'.format(palset)
self.assertEqual(palset, 'pal', msg)
def test_layer_pal_activated(self):
# Verify, via engine, that PAL labeling can be activated for layer
lyr = self.defaultSettings()
lyr.writeToLayer(self.layer)
msg = '\nLayer labeling not activated, as reported by labelingEngine'
self.assertTrue(self._PalEngine.willUseLayer(self.layer), msg)
def test_write_read_settings(self):
# Verify written PAL settings are same when read from layer
# load and write default test settings
lyr1 = self.defaultSettings()
lyr1dict = self.settingsDict(lyr1)
# print lyr1dict
lyr1.writeToLayer(self.layer)
# read settings
lyr2 = QgsPalLayerSettings()
lyr2.readFromLayer(self.layer)
lyr2dict = self.settingsDict(lyr1)
# print lyr2dict
msg = '\nLayer settings read not same as settings written'
self.assertDictEqual(lyr1dict, lyr2dict, msg)
def runSuite(module, tests):
"""This allows for a list of test names to be selectively run.
Also, ensures unittest verbose output comes at end, after debug output"""
loader = unittest.defaultTestLoader
if 'PAL_SUITE' in os.environ and tests:
suite = loader.loadTestsFromNames(tests, module)
else:
suite = loader.loadTestsFromModule(module)
verb = 2 if 'PAL_VERBOSE' in os.environ else 0
out = StringIO.StringIO()
res = unittest.TextTestRunner(stream=out, verbosity=verb).run(suite)
if verb:
print '\nIndividual test summary:'
print '\n' + out.getvalue()
out.close()
return res
if __name__ == '__main__':
# NOTE: unless PAL_SUITE env var is set
# all test class methods will be run
b = 'TestQgsPalLabelingBase.'
tests = [b + 'test_write_read_settings']
res = runSuite(sys.modules[__name__], tests)
sys.exit(not res.wasSuccessful())

View File

@ -0,0 +1,107 @@
# -*- coding: utf-8 -*-
"""QGIS unit tests for QgsPalLabeling: label rendering to screen canvas
.. note:: from build dir: ctest -R PyQgsPalLabelingCanvas -V
Set env variable PAL_SUITE to run specific tests (define in __main__)
Set env variable PAL_VERBOSE to output individual test summary
Set env variable PAL_CONTROL_IMAGE to trigger building of new control images
.. note:: 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__ = 'Larry Shaffer'
__date__ = '07/09/2013'
__copyright__ = 'Copyright 2013, The QGIS Project'
# This will get replaced with a git SHA1 when you do a git archive
__revision__ = '$Format:%H$'
import sys
import os
from PyQt4.QtCore import *
from PyQt4.QtGui import *
from qgis.core import *
from utilities import (
unittest,
expectedFailure,
)
from test_qgspallabeling_base import TestQgsPalLabeling, runSuite
class TestQgsPalLabelingPoint(TestQgsPalLabeling):
"""Most layer-level labeling properties shared across feature types are
tested in this class"""
@classmethod
def setUpClass(cls):
TestQgsPalLabeling.setUpClass()
cls.layer = TestQgsPalLabeling.loadFeatureLayer('point')
def setUp(self):
"""Run before each test."""
self.configTest('pal_canvas', 'sp')
self.lyr = self.defaultSettings()
def tearDown(self):
"""Run after each test."""
pass
def test_default_label(self):
# Verify basic default label placement and text size in points
self.lyr.writeToLayer(self.layer)
self.saveContolImage()
self.assertTrue(self.renderCheck())
def test_text_size_map_unit(self):
# Verify label text size in map units
self.lyr.fontSizeInMapUnits = True
tmpFont = QFont(self._TestFont)
tmpFont.setPointSizeF(0.25)
self.lyr.textFont = tmpFont
self.lyr.writeToLayer(self.layer)
self.saveContolImage()
self.assertTrue(self.renderCheck())
def test_text_color(self):
# Verify label color change
self.lyr.textColor = Qt.blue
self.lyr.writeToLayer(self.layer)
self.saveContolImage()
self.assertTrue(self.renderCheck())
# class TestQgsPalLabelingLine(TestQgsPalLabeling):
# """Layer-level property tests for line features"""
#
# @classmethod
# def setUpClass(cls):
# TestQgsPalLabeling.setUpClass()
# cls.layer = TestQgsPalLabeling.loadFeatureLayer('line')
#
# def setUp(self):
# """Run before each test."""
# self.configTest('pal_canvas', 'sl')
# self.lyr = self.defaultSettings()
#
# def tearDown(self):
# """Run after each test."""
# pass
if __name__ == '__main__':
# NOTE: unless PAL_SUITE env var is set
# all test class methods will be run
sp = 'TestQgsPalLabelingPoint.'
sl = 'TestQgsPalLabelingLine.'
sc = 'TestQgsPalLabelingCurved.'
sg = 'TestQgsPalLabelingPolygon.'
mf = 'TestQgsPalLabelingMultiFeature.'
tests = [sp + 'test_text_size_map_unit']
res = runSuite(sys.modules[__name__], tests)
sys.exit(not res.wasSuccessful())

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

View File

@ -0,0 +1,6 @@
0.005
0
0
-0.005
609508.50249999994412065
4825130.99749999959021807

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

View File

@ -0,0 +1,6 @@
0.005
0
0
-0.005
609508.50249999994412065
4825130.99749999959021807

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

View File

@ -0,0 +1,6 @@
0.005
0
0
-0.005
609508.50249999994412065
4825130.99749999959021807

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

24
tests/testdata/labeling/point.qml vendored Normal file
View File

@ -0,0 +1,24 @@
<!DOCTYPE qgis PUBLIC 'http://mrcc.com/qgis.dtd' 'SYSTEM'>
<qgis version="1.9.0-Master" minimumScale="-4.65661e-10" maximumScale="1e+08" minLabelScale="0" maxLabelScale="1e+08" hasScaleBasedVisibilityFlag="0" scaleBasedLabelVisibilityFlag="0">
<renderer-v2 symbollevels="0" type="singleSymbol">
<symbols>
<symbol alpha="1" type="marker" name="0">
<layer pass="0" class="SimpleMarker" locked="0">
<prop k="angle" v="0"/>
<prop k="color" v="61,128,45,255"/>
<prop k="color_border" v="0,0,0,255"/>
<prop k="name" v="circle"/>
<prop k="offset" v="0,0"/>
<prop k="offset_unit" v="MM"/>
<prop k="outline_width" v="0"/>
<prop k="outline_width_unit" v="MM"/>
<prop k="scale_method" v="area"/>
<prop k="size" v="2"/>
<prop k="size_unit" v="MM"/>
</layer>
</symbol>
</symbols>
<rotation field=""/>
<sizescale field="" scalemethod="area"/>
</renderer-v2>
</qgis>