UPDATE: This issue may be isolated to my MacBook. The seg fault did not occur in testing on Windows.
I have put together a sample unit test for a QGIS python plugin. It is causing a Segmentation Fault 11 when the test is run after the tests complete and the XML output is written.
I based the unit tests on code in this excellent discussion.
I seem to have isolated the issue to three offending lines of code.
app = QgsApplication([], True)
# create the plugin
iface = DummyInterface()
plugin = gupsapp.classFactory(iface)
If these lines are run upon entry of each test, everything is good, no seg faults. However, if I move these lines to the setUp method (in the hopes of creating a custom unittest base class) the seg fault pops up.
I have tried including a tearDown() and a tearDownClass() with various combinations of QgsApplication.exit() and QgsApplication.exitQgis() which either does not help or seg faults between tests.
Unit test code:
from qgis.core import *
from qgis.gui import *
from PyQt4 import QtCore
# from PyQt4 import QtGui
# from PyQt4 QtTest
import unittest
import sys
import logging
import gupsapp
import os
import platform
import xmlrunner
class DummyInterface(object):
def __getattr__(self, *args, **kwargs):
def dummy(*args, **kwargs):
return self
return dummy
def __iter__(self):
return self
def next(self):
raise StopIteration
def layers(self):
return QgsMapLayerRegistry.instance().mapLayers().values()
class PyQgisUnitTest(unittest.TestCase):
@classmethod
def setUpClass(cls):
"""Called before each test is run. This method should be used
to create any test data or other resources needed for the tests.
:return:
"""
log.info("setUpClass")
log.debug("platform.system == %s", platform.system())
if platform.system() == 'Windows':
# configure python to play nicely with QGIS in Windows using default install paths
# the preferred method of setting these value is as system environment
osgeo4w_root = r'C:/OSGeo4W64'
os.environ['PATH'] = '{}/bin{}{}'.format(osgeo4w_root, os.pathsep, os.environ['PATH'])
sys.path.insert(0, '{}/apps/qgis/python'.format(osgeo4w_root))
sys.path.insert(1, '{}/apps/python27/lib/site-packages'.format(osgeo4w_root))
prefix_path = '{}/apps/qgis'.format(osgeo4w_root)
log.debug("prefix_path == %s", prefix_path)
QgsApplication.setPrefixPath(prefix_path, True)
else:
# MAC default: /Applications/QGIS.app/Contents
QgsApplication.setPrefixPath("/Applications/QGIS.app/Contents/MacOS", True)
QgsApplication.initQgis()
if len(QgsProviderRegistry.instance().providerList()) == 0:
raise RuntimeError('No data providers available.')
QtCore.QCoreApplication.setOrganizationName('QGIS')
QtCore.QCoreApplication.setApplicationName('QGIS GUPS')
@classmethod
def tearDownClass(cls):
QgsApplication.exitQgis()
def setUp(self):
"""Segmentation Fault: 11 occurs when these lines are performed as part of this
setUp method.
:return:
"""
self.app = QgsApplication([], True)
# create the plugin
self.iface = DummyInterface()
self.plugin = gupsapp.classFactory(self.iface)
def tearDown(self):
"""Causes Segmentation fault: 11 BEFORE tests complete with QgisApplication.exitQgis().
:return:
"""
# QgsApplication.exitQgis()
QgsApplication.exit()
def test_one(self):
"""Sample test attempts to load a shape file into a layer and verify that the layer is valid.
:return:
"""
"""Segmentation Fault: 11 does NOT occur when these lines are performed in as
part of the test methods.
# initialize QGIS application
app = QgsApplication([], True)
# create the plugin
iface = DummyInterface()
plugin = gupsapp.classFactory(iface)
"""
# load a sample shape file
if platform.system() == "Windows":
# default GUPSGIS path for Windows
home = os.environ['USERPROFILE']
else:
# default GUPSGIS path for Unix/Mac
home = os.environ['HOME']
layer = QgsVectorLayer("{}/GUPSGIS/gupsdata/BBSP/shape/55025/GUPS15_2014_aial_55025.shp".format(home),
"GUPS15_2014_aial_55025", "ogr")
log.debug("layer == %s ", layer)
self.assertTrue(layer.isValid(), 'Failed to load "{}".'.format(layer.source()))
assert layer.isValid()
def test_area(self):
"""Tests the area calculation of CreateChangePolygons.
:return:
"""
"""Segmentation Fault: 11 does NOT occur when these lines are performed in as
part of the test methods.
app = QgsApplication([], True)
# create the plugin
iface = DummyInterface()
plugin = gupsapp.classFactory(iface)
"""
# create a polygon with an area of 1e6 sqKm
gPolygon = QgsGeometry.fromPolygon([[QgsPoint(0, 0), QgsPoint(1e6, 0), QgsPoint(1e6, 1), QgsPoint(0, 1)]])
log.debug("gPolygon.wkbType() == %s", gPolygon.wkbType())
# create a CreateChangePolygons module
self.plugin.delayCreateChangePolygons()
# calculate the area in acres
area = self.plugin.createchangepolygons.updateTable(gPolygon)
# convert to acres
expected_area_in_acres = 1.0 * 247.11
self.assertTrue(area == expected_area_in_acres)
if __name__ == "__main__":
logging.basicConfig(format='%(asctime)s %(message)s', datefmt='%m/%d/%Y %I:%M:%S %p')
log = logging.getLogger(__file__)
log.setLevel(logging.DEBUG)
log.info("main info")
unittest.main(testRunner=xmlrunner.XMLTestRunner(output='test-reports'),
# these make sure that some options that are not applicable
# remain hidden from the help menu.
failfast=False, buffer=False, catchbreak=False)