Skip to content

Commit

Permalink
Initial test framework for running python tests as part of the CTest …
Browse files Browse the repository at this point in the history
…suite.
  • Loading branch information
timlinux committed Jul 22, 2012
1 parent c517a90 commit b702d3c
Show file tree
Hide file tree
Showing 6 changed files with 355 additions and 0 deletions.
80 changes: 80 additions & 0 deletions cmake/UsePythonTest.cmake
@@ -0,0 +1,80 @@
# Add a python test from a python file
# One cannot simply do:
# SET(ENV{PYTHONPATH} ${LIBRARY_OUTPUT_PATH})
# SET(my_test "from test_mymodule import *\;test_mymodule()")
# ADD_TEST(PYTHON-TEST-MYMODULE python -c ${my_test})
# Since cmake is only transmitting the ADD_TEST line to ctest thus you are loosing
# the env var. The only way to store the env var is to physically write in the cmake script
# whatever PYTHONPATH you want and then add the test as 'cmake -P python_test.cmake'
#
# Usage:
# SET_SOURCE_FILES_PROPERTIES(test.py PROPERTIES PYTHONPATH
# "${LIBRARY_OUTPUT_PATH}:${VTK_DIR}")
# ADD_PYTHON_TEST(PYTHON-TEST test.py)
#
# Copyright (c) 2006-2010 Mathieu Malaterre <mathieu.malaterre@gmail.com>
#
# Redistribution and use is allowed according to the terms of the New
# BSD license.
# For details see the accompanying COPYING-CMAKE-SCRIPTS
#

# Need python interpreter:
FIND_PACKAGE(PythonInterp REQUIRED)
MARK_AS_ADVANCED(PYTHON_EXECUTABLE)

MACRO(ADD_PYTHON_TEST TESTNAME FILENAME)
GET_SOURCE_FILE_PROPERTY(loc ${FILENAME} LOCATION)
GET_SOURCE_FILE_PROPERTY(pyenv ${FILENAME} PYTHONPATH)
IF(CMAKE_CONFIGURATION_TYPES)
# I cannot use CMAKE_CFG_INTDIR since it expand to "$(OutDir)"
IF(pyenv)
SET(pyenv "${pyenv};${LIBRARY_OUTPUT_PATH}/${CMAKE_BUILD_TYPE}")
ELSE(pyenv)
SET(pyenv ${LIBRARY_OUTPUT_PATH}/${CMAKE_BUILD_TYPE})
#SET(pyenv ${LIBRARY_OUTPUT_PATH}/${CMAKE_CFG_INTDIR})
#SET(pyenv ${LIBRARY_OUTPUT_PATH}/${CMAKE_CONFIG_TYPE})
#SET(pyenv ${LIBRARY_OUTPUT_PATH}/\${CMAKE_CONFIG_TYPE})
ENDIF(pyenv)
ELSE(CMAKE_CONFIGURATION_TYPES)
IF(pyenv)
SET(pyenv ${pyenv}:${LIBRARY_OUTPUT_PATH})
ELSE(pyenv)
SET(pyenv ${LIBRARY_OUTPUT_PATH})
ENDIF(pyenv)
ENDIF(CMAKE_CONFIGURATION_TYPES)
STRING(REGEX REPLACE ";" " " wo_semicolumn "${ARGN}")
FILE(WRITE ${CMAKE_CURRENT_BINARY_DIR}/${TESTNAME}.cmake
"
SET(ENV{PYTHONPATH} ${pyenv}:\$ENV{PYTHONPATH})
SET(ENV{LD_LIBRARY_PATH} ${pyenv}:\$ENV{LD_LIBRARY_PATH})
MESSAGE(\"${pyenv}\")
EXECUTE_PROCESS(
COMMAND ${PYTHON_EXECUTABLE} ${loc} ${wo_semicolumn}
#WORKING_DIRECTORY @LIBRARY_OUTPUT_PATH@
RESULT_VARIABLE import_res
OUTPUT_VARIABLE import_output
ERROR_VARIABLE import_output
)
# Pass the output back to ctest
IF(import_output)
MESSAGE("\${import_output}")
ENDIF(import_output)
IF(import_res)
MESSAGE(SEND_ERROR "\${import_res}")
ENDIF(import_res)
"
)
ADD_TEST(${TESTNAME} ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_BINARY_DIR}/${TESTNAME}.cmake)
ENDMACRO(ADD_PYTHON_TEST)

# Byte compile recursively a directory (DIRNAME)
MACRO(ADD_PYTHON_COMPILEALL_TEST DIRNAME)
# First get the path:
GET_FILENAME_COMPONENT(temp_path "${PYTHON_LIBRARIES}" PATH)
# Find the python script:
GET_FILENAME_COMPONENT(PYTHON_COMPILE_ALL_PY "${temp_path}/../compileall.py" ABSOLUTE)
# add test, use DIRNAME to create uniq name for the test:
ADD_TEST(COMPILE_ALL-${DIRNAME} ${PYTHON_EXECUTABLE} "${PYTHON_COMPILE_ALL_PY}" -q ${DIRNAME})
ENDMACRO(ADD_PYTHON_COMPILEALL_TEST)
1 change: 1 addition & 0 deletions tests/src/CMakeLists.txt
Expand Up @@ -2,4 +2,5 @@ IF (ENABLE_TESTS)
ADD_SUBDIRECTORY(core)
ADD_SUBDIRECTORY(gui)
ADD_SUBDIRECTORY(analysis)
ADD_SUBDIRECTORY(python)
ENDIF (ENABLE_TESTS)
2 changes: 2 additions & 0 deletions tests/src/python/CMakeLists.txt
@@ -0,0 +1,2 @@
INCLUDE(UsePythonTest)
ADD_PYTHON_TEST(QGisApp test_qgisapp.py)
99 changes: 99 additions & 0 deletions tests/src/python/qgis_interface.py
@@ -0,0 +1,99 @@
"""
InaSAFE Disaster risk assessment tool developed by AusAid -
**QGIS plugin implementation.**
Contact : ole.moller.nielsen@gmail.com
.. 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.
.. note:: This source code was copied from the 'postgis viewer' application
with original authors:
Copyright (c) 2010 by Ivan Mincik, ivan.mincik@gista.sk
Copyright (c) 2011 German Carrillo, geotux_tuxman@linuxmail.org
"""

__author__ = 'tim@linfiniti.com'
__version__ = '0.5.0'
__revision__ = '$Format:%H$'
__date__ = '10/01/2011'
__copyright__ = ('Copyright (c) 2010 by Ivan Mincik, ivan.mincik@gista.sk and '
'Copyright (c) 2011 German Carrillo, '
'geotux_tuxman@linuxmail.org')


from PyQt4.QtCore import QObject
from qgis.core import QgsMapLayerRegistry


class QgisInterface(QObject):
"""Class to expose qgis objects and functionalities to plugins.
This class is here for enabling us to run unit tests only,
so most methods are simply stubs.
"""

def __init__(self, canvas):
"""Constructor"""
QObject.__init__(self)
self.canvas = canvas

def zoomFull(self):
"""Zoom to the map full extent"""
pass

def zoomToPrevious(self):
"""Zoom to previous view extent"""
pass

def zoomToNext(self):
"""Zoom to next view extent"""
pass

def zoomToActiveLayer(self):
"""Zoom to extent of active layer"""
pass

def addVectorLayer(self, vectorLayerPath, baseName, providerKey):
"""Add a vector layer"""
pass

def addRasterLayer(self, rasterLayerPath, baseName):
"""Add a raster layer given a raster layer file name"""
pass

def activeLayer(self):
"""Get pointer to the active layer (layer selected in the legend)"""
myLayers = QgsMapLayerRegistry.instance().mapLayers()
for myItem in myLayers:
return myLayers[myItem]

def addToolBarIcon(self, qAction):
"""Add an icon to the plugins toolbar"""
pass

def removeToolBarIcon(self, qAction):
"""Remove an action (icon) from the plugin toolbar"""
pass

def addToolBar(self, name):
"""Add toolbar with specified name"""
pass

def mapCanvas(self):
"""Return a pointer to the map canvas"""
return self.canvas

def mainWindow(self):
"""Return a pointer to the main window
In case of QGIS it returns an instance of QgisApp
"""
pass

def addDockWidget(self, area, dockwidget):
""" Add a dock widget to the main window """
pass
38 changes: 38 additions & 0 deletions tests/src/python/test_qgisapp.py
@@ -0,0 +1,38 @@
import unittest
from utilities import (getQgisTestApp,
setCanvasCrs,
GEOCRS,
GOOGLECRS
)
QGISAPP, CANVAS, IFACE, PARENT = getQgisTestApp()

class TestQGisApp(unittest.TestCase):

def testValidThemeName(self):
"""That can set the app to use a valid theme"""
QGISAPP.setThemeName('gis')
myExpectedResult = 'gis'
myResult = QGISAPP.themeName()
myMessage = ('Expected:\n%s\nGot:\n%s\n' %
(myExpectedResult, myResult))

mySettings = QGISAPP.showSettings()
print mySettings

assert myExpectedResult == myResult, myMessage

def testInvalidThemeName(self):
"""That setting the app to use an invalid theme will fallback to 'default'"""
QGISAPP.setThemeName('fooobar')
myExpectedResult = 'default'
myResult = QGISAPP.themeName()
myMessage = ('Expected:\n%s\nGot:\n%s\n' %
(myExpectedResult, myResult))
assert myExpectedResult == myResult, myMessage




if __name__ == '__main__':
unittest.main()

135 changes: 135 additions & 0 deletions tests/src/python/utilities.py
@@ -0,0 +1,135 @@
import os
import sys
from PyQt4 import QtGui, QtCore
from qgis.core import (QgsApplication,
QgsVectorLayer,
QgsRasterLayer,
QgsRectangle,
QgsCoordinateReferenceSystem)
from qgis.gui import QgsMapCanvas
from qgis_interface import QgisInterface
import hashlib

QGISAPP = None # Static vainasafele used to hold hand to running QGis app
CANVAS = None
PARENT = None
IFACE = None
GEOCRS = 4326 # constant for EPSG:GEOCRS Geographic CRS id
GOOGLECRS = 900913 # constant for EPSG:GOOGLECRS Google Mercator id


def assertHashesForFile(theHashes, theFilename):
"""Assert that a files has matches one of a list of expected hashes"""
myHash = hashForFile(theFilename)
myMessage = ('Unexpected hash'
'\nGot: %s'
'\nExpected: %s'
'\nPlease check graphics %s visually '
'and add to list of expected hashes '
'if it is OK on this platform.'
% (myHash, theHashes, theFilename))
assert myHash in theHashes, myMessage


def assertHashForFile(theHash, theFilename):
"""Assert that a files has matches its expected hash"""
myHash = hashForFile(theFilename)
myMessage = ('Unexpected hash'
'\nGot: %s'
'\nExpected: %s' % (myHash, theHash))
assert myHash == theHash, myMessage


def hashForFile(theFilename):
"""Return an md5 checksum for a file"""
myPath = theFilename
myData = file(myPath).read()
myHash = hashlib.md5()
myHash.update(myData)
myHash = myHash.hexdigest()
return myHash


def getQgisTestApp():
""" Start one QGis application to test agaist
Input
NIL
Output
handle to qgis app
If QGis is already running the handle to that app will be returned
"""

global QGISAPP # pylint: disable=W0603

if QGISAPP is None:
myGuiFlag = True # All test will run qgis in gui mode
QGISAPP = QgsApplication(sys.argv, myGuiFlag)
if 'QGISPATH' in os.environ:
myPath = os.environ['QGISPATH']
myUseDefaultPathFlag = True
QGISAPP.setPrefixPath(myPath, myUseDefaultPathFlag)

QGISAPP.initQgis()
s = QGISAPP.showSettings()
print s

global PARENT # pylint: disable=W0603
if PARENT is None:
PARENT = QtGui.QWidget()

global CANVAS # pylint: disable=W0603
if CANVAS is None:
CANVAS = QgsMapCanvas(PARENT)
CANVAS.resize(QtCore.QSize(400, 400))

global IFACE # pylint: disable=W0603
if IFACE is None:
# QgisInterface is a stub implementation of the QGIS plugin interface
IFACE = QgisInterface(CANVAS)

return QGISAPP, CANVAS, IFACE, PARENT


def unitTestDataPath(theSubdir=None):
"""Return the absolute path to the InaSAFE unit test data dir.
.. note:: This is not the same thing as the SVN inasafe_data dir. Rather
this is a new dataset where the test datasets are all tiny for fast
testing and the datasets live in the same repo as the code.
Args:
* theSubdir: (Optional) Additional subdir to add to the path - typically
'hazard' or 'exposure'.
"""
myPath = __file__
if theSubdir is not None:
myPath = os.path.abspath(os.path.join(myPath,
'unit_test_data',
theSubdir))
else:
myPath = os.path.abspath(os.path.join(myPath, 'unit_test_data'))
return myPath

def setCanvasCrs(theEpsgId, theOtfpFlag=False):
"""Helper to set the crs for the CANVAS before a test is run.
Args:
* theEpsgId - Valid EPSG identifier (int)
* theOtfpFlag - whether on the fly projections should be enabled
on the CANVAS. Default to False.
"""
# Enable on-the-fly reprojection
CANVAS.mapRenderer().setProjectionsEnabled(theOtfpFlag)

# Create CRS Instance
myCrs = QgsCoordinateReferenceSystem()
myCrs.createFromId(theEpsgId, QgsCoordinateReferenceSystem.EpsgCrsId)

# Reproject all layers to WGS84 geographic CRS
CANVAS.mapRenderer().setDestinationCrs(myCrs)

0 comments on commit b702d3c

Please sign in to comment.