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 test 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 as in as the 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)
أكثر...
I based the unit test 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 as in as the 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)
أكثر...