2016-01-07 17:14:05 +01:00
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
|
|
|
|
"""
|
|
|
|
***************************************************************************
|
2016-02-05 10:29:13 +01:00
|
|
|
AlgorithmsTest.py
|
2016-01-07 17:14:05 +01:00
|
|
|
---------------------
|
|
|
|
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$'
|
|
|
|
|
2016-03-21 04:58:12 +01:00
|
|
|
import qgis # NOQA switch sip api
|
2016-01-07 17:14:05 +01:00
|
|
|
import os
|
|
|
|
import yaml
|
|
|
|
import nose2
|
|
|
|
import gdal
|
|
|
|
import hashlib
|
|
|
|
import tempfile
|
|
|
|
|
|
|
|
from osgeo.gdalconst import GA_ReadOnly
|
|
|
|
|
|
|
|
import processing
|
2016-05-12 15:02:36 +03:00
|
|
|
from processing.modeler.ModelerAlgorithmProvider import ModelerAlgorithmProvider
|
|
|
|
from processing.modeler.ModelerOnlyAlgorithmProvider import ModelerOnlyAlgorithmProvider
|
|
|
|
from processing.algs.qgis.QGISAlgorithmProvider import QGISAlgorithmProvider
|
|
|
|
from processing.algs.grass.GrassAlgorithmProvider import GrassAlgorithmProvider
|
|
|
|
from processing.algs.grass7.Grass7AlgorithmProvider import Grass7AlgorithmProvider
|
|
|
|
from processing.algs.lidar.LidarToolsAlgorithmProvider import LidarToolsAlgorithmProvider
|
|
|
|
from processing.algs.gdal.GdalOgrAlgorithmProvider import GdalOgrAlgorithmProvider
|
|
|
|
from processing.algs.otb.OTBAlgorithmProvider import OTBAlgorithmProvider
|
|
|
|
from processing.algs.r.RAlgorithmProvider import RAlgorithmProvider
|
|
|
|
from processing.algs.saga.SagaAlgorithmProvider import SagaAlgorithmProvider
|
|
|
|
from processing.script.ScriptAlgorithmProvider import ScriptAlgorithmProvider
|
|
|
|
from processing.algs.taudem.TauDEMAlgorithmProvider import TauDEMAlgorithmProvider
|
|
|
|
from processing.preconfigured.PreconfiguredAlgorithmProvider import PreconfiguredAlgorithmProvider
|
|
|
|
|
2016-01-07 17:14:05 +01:00
|
|
|
|
2016-03-21 04:58:12 +01:00
|
|
|
from qgis.core import QgsVectorLayer, QgsRasterLayer, QgsMapLayerRegistry
|
2016-01-07 17:14:05 +01:00
|
|
|
|
2016-03-04 08:12:10 +01:00
|
|
|
from qgis.testing import _UnexpectedSuccess
|
|
|
|
|
2016-03-21 04:58:12 +01:00
|
|
|
from utilities import unitTestDataPath
|
2016-01-07 17:14:05 +01:00
|
|
|
|
|
|
|
|
|
|
|
def processingTestDataPath():
|
|
|
|
return os.path.join(os.path.dirname(__file__), 'testdata')
|
|
|
|
|
|
|
|
|
2016-03-21 04:58:12 +01:00
|
|
|
class AlgorithmsTest:
|
2016-01-07 17:14:05 +01:00
|
|
|
|
|
|
|
def test_algorithms(self):
|
|
|
|
"""
|
|
|
|
This is the main test function. All others will be executed based on the definitions in testdata/algorithm_tests.yaml
|
|
|
|
"""
|
2016-04-01 11:25:00 +03:00
|
|
|
ver = processing.version()
|
2016-04-12 13:38:56 +02:00
|
|
|
print("Processing {}.{}.{}".format(ver / 10000, ver / 100 % 100, ver % 100))
|
2016-02-05 10:29:13 +01:00
|
|
|
with open(os.path.join(processingTestDataPath(), self.test_definition_file()), 'r') as stream:
|
2016-01-07 17:14:05 +01:00
|
|
|
algorithm_tests = yaml.load(stream)
|
|
|
|
|
|
|
|
for algtest in algorithm_tests['tests']:
|
2016-03-04 09:43:42 +01:00
|
|
|
yield self.check_algorithm, algtest['name'], algtest
|
2016-01-07 17:14:05 +01:00
|
|
|
|
|
|
|
def check_algorithm(self, name, defs):
|
|
|
|
"""
|
|
|
|
Will run an algorithm definition and check if it generates the expected result
|
|
|
|
:param name: The identifier name used in the test output heading
|
|
|
|
:param defs: A python dict containing a test algorithm definition
|
|
|
|
"""
|
2016-05-22 14:41:15 +02:00
|
|
|
QgsMapLayerRegistry.instance().removeAllMapLayers()
|
2016-05-23 16:49:16 +02:00
|
|
|
print('DEBUG on {}'.format(name))
|
|
|
|
print('Params = {}'.format(defs))
|
2016-01-07 17:14:05 +01:00
|
|
|
params = self.load_params(defs['params'])
|
|
|
|
alg = processing.Processing.getAlgorithm(defs['algorithm']).getCopy()
|
|
|
|
|
|
|
|
if isinstance(params, list):
|
|
|
|
for param in zip(alg.parameters, params):
|
|
|
|
param[0].setValue(param[1])
|
|
|
|
else:
|
2016-04-14 09:34:10 +02:00
|
|
|
for k, p in params.items():
|
2016-01-07 17:14:05 +01:00
|
|
|
alg.setParameterValue(k, p)
|
|
|
|
|
2016-04-14 09:34:10 +02:00
|
|
|
for r, p in defs['results'].items():
|
2016-01-07 17:14:05 +01:00
|
|
|
alg.setOutputValue(r, self.load_result_param(p))
|
|
|
|
|
2016-03-04 09:43:42 +01:00
|
|
|
expectFailure = False
|
|
|
|
if 'expectedFailure' in defs:
|
2016-05-19 10:15:55 +02:00
|
|
|
exec('\n'.join(defs['expectedFailure'][:-1]), globals(), locals())
|
2016-03-04 09:43:42 +01:00
|
|
|
expectFailure = eval(defs['expectedFailure'][-1])
|
|
|
|
|
|
|
|
if expectFailure:
|
|
|
|
try:
|
2016-05-19 14:15:13 +02:00
|
|
|
alg.execute()
|
|
|
|
self.check_results(alg.getOutputValuesAsDictionary(), defs['results'])
|
2016-03-04 09:43:42 +01:00
|
|
|
except Exception:
|
|
|
|
pass
|
|
|
|
else:
|
|
|
|
raise _UnexpectedSuccess
|
|
|
|
else:
|
2016-05-19 14:15:13 +02:00
|
|
|
alg.execute()
|
|
|
|
self.check_results(alg.getOutputValuesAsDictionary(), defs['results'])
|
2016-01-07 17:14:05 +01:00
|
|
|
|
|
|
|
def load_params(self, params):
|
|
|
|
"""
|
|
|
|
Loads an array of parameters
|
|
|
|
"""
|
2016-02-26 14:08:37 +11:00
|
|
|
if isinstance(params, list):
|
2016-01-07 17:14:05 +01:00
|
|
|
return [self.load_param(p) for p in params]
|
2016-02-26 14:08:37 +11:00
|
|
|
elif isinstance(params, dict):
|
2016-04-14 09:34:10 +02:00
|
|
|
return {key: self.load_param(p) for key, p in params.items()}
|
2016-01-07 17:14:05 +01:00
|
|
|
else:
|
|
|
|
return params
|
|
|
|
|
|
|
|
def load_param(self, param):
|
|
|
|
"""
|
|
|
|
Loads a parameter. If it's not a map, the parameter will be returned as-is. If it is a map, it will process the
|
|
|
|
parameter based on its key `type` and return the appropriate parameter to pass to the algorithm.
|
|
|
|
"""
|
|
|
|
try:
|
|
|
|
if param['type'] == 'vector' or param['type'] == 'raster':
|
|
|
|
return self.load_layer(param)
|
2016-05-06 14:25:12 +02:00
|
|
|
elif param['type'] == 'multi':
|
2016-01-07 17:14:05 +01:00
|
|
|
return [self.load_param(p) for p in param['params']]
|
2016-05-06 14:25:12 +02:00
|
|
|
elif param['type'] == 'file':
|
|
|
|
return self.filepath_from_param(param)
|
2016-01-07 17:14:05 +01:00
|
|
|
except TypeError:
|
|
|
|
# No type specified, use whatever is there
|
|
|
|
return param
|
|
|
|
|
2016-02-05 11:06:05 +01:00
|
|
|
raise KeyError("Unknown type '{}' specified for parameter".format(param['type']))
|
2016-01-07 17:14:05 +01:00
|
|
|
|
|
|
|
def load_result_param(self, param):
|
|
|
|
"""
|
2016-02-21 13:15:21 +01:00
|
|
|
Loads a result parameter. Creates a temporary destination where the result should go to and returns this location
|
2016-01-07 17:14:05 +01:00
|
|
|
so it can be sent to the algorithm as parameter.
|
|
|
|
"""
|
2016-02-23 10:17:18 +01:00
|
|
|
if param['type'] in ['vector', 'file', 'regex']:
|
2016-01-07 17:14:05 +01:00
|
|
|
outdir = tempfile.mkdtemp()
|
|
|
|
self.cleanup_paths.append(outdir)
|
|
|
|
basename = os.path.basename(param['name'])
|
|
|
|
filepath = os.path.join(outdir, basename)
|
|
|
|
return filepath
|
2016-02-05 11:06:05 +01:00
|
|
|
elif param['type'] == 'rasterhash':
|
|
|
|
outdir = tempfile.mkdtemp()
|
|
|
|
self.cleanup_paths.append(outdir)
|
|
|
|
basename = 'raster.tif'
|
|
|
|
filepath = os.path.join(outdir, basename)
|
|
|
|
return filepath
|
2016-01-07 17:14:05 +01:00
|
|
|
|
2016-02-05 11:06:05 +01:00
|
|
|
raise KeyError("Unknown type '{}' specified for parameter".format(param['type']))
|
2016-01-07 17:14:05 +01:00
|
|
|
|
|
|
|
def load_layer(self, param):
|
|
|
|
"""
|
|
|
|
Loads a layer which was specified as parameter.
|
|
|
|
"""
|
2016-02-21 13:15:21 +01:00
|
|
|
filepath = self.filepath_from_param(param)
|
2016-01-07 17:14:05 +01:00
|
|
|
if param['type'] == 'vector':
|
|
|
|
lyr = QgsVectorLayer(filepath, param['name'], 'ogr')
|
|
|
|
elif param['type'] == 'raster':
|
2016-05-06 12:02:59 +02:00
|
|
|
lyr = QgsRasterLayer(filepath, param['name'], 'gdal')
|
2016-01-07 17:14:05 +01:00
|
|
|
|
|
|
|
self.assertTrue(lyr.isValid(), 'Could not load layer "{}"'.format(filepath))
|
|
|
|
QgsMapLayerRegistry.instance().addMapLayer(lyr)
|
|
|
|
return lyr
|
|
|
|
|
2016-02-21 13:15:21 +01:00
|
|
|
def filepath_from_param(self, param):
|
|
|
|
"""
|
|
|
|
Creates a filepath from a param
|
|
|
|
"""
|
|
|
|
prefix = processingTestDataPath()
|
|
|
|
if 'location' in param and param['location'] == 'qgs':
|
|
|
|
prefix = unitTestDataPath()
|
2016-05-23 16:49:16 +02:00
|
|
|
print('DEBUG filepath_from_param: {} -> {}'.format(param['name'], os.path.join(prefix, param['name'])))
|
2016-02-21 13:15:21 +01:00
|
|
|
return os.path.join(prefix, param['name'])
|
|
|
|
|
2016-01-07 17:14:05 +01:00
|
|
|
def check_results(self, results, expected):
|
|
|
|
"""
|
|
|
|
Checks if result produced by an algorithm matches with the expected specification.
|
|
|
|
"""
|
2016-05-23 16:49:16 +02:00
|
|
|
print('DEBUG check_results: results {}\n\texpected {}'.format(results, expected))
|
2016-04-14 09:34:10 +02:00
|
|
|
for id, expected_result in expected.items():
|
2016-01-07 17:14:05 +01:00
|
|
|
if 'vector' == expected_result['type']:
|
|
|
|
expected_lyr = self.load_layer(expected_result)
|
|
|
|
try:
|
|
|
|
results[id]
|
|
|
|
except KeyError as e:
|
2016-03-15 16:43:52 +01:00
|
|
|
raise KeyError('Expected result {} does not exist in {}'.format(unicode(e), results.keys()))
|
2016-01-07 17:14:05 +01:00
|
|
|
|
|
|
|
result_lyr = QgsVectorLayer(results[id], id, 'ogr')
|
|
|
|
|
2016-02-13 15:09:31 +01:00
|
|
|
compare = expected_result.get('compare', {})
|
2016-01-07 17:14:05 +01:00
|
|
|
|
|
|
|
self.assertLayersEqual(expected_lyr, result_lyr, compare=compare)
|
|
|
|
|
|
|
|
elif 'rasterhash' == expected_result['type']:
|
|
|
|
dataset = gdal.Open(results[id], GA_ReadOnly)
|
|
|
|
strhash = hashlib.sha224(dataset.ReadAsArray(0).data).hexdigest()
|
|
|
|
|
|
|
|
self.assertEqual(strhash, expected_result['hash'])
|
2016-02-21 13:15:21 +01:00
|
|
|
elif 'file' == expected_result['type']:
|
|
|
|
expected_filepath = self.filepath_from_param(expected_result)
|
|
|
|
result_filepath = results[id]
|
|
|
|
|
|
|
|
self.assertFilesEqual(expected_filepath, result_filepath)
|
2016-02-23 10:17:18 +01:00
|
|
|
elif 'regex' == expected_result['type']:
|
|
|
|
with open(results[id], 'r') as file:
|
|
|
|
data = file.read()
|
|
|
|
|
|
|
|
for rule in expected_result.get('rules', []):
|
|
|
|
self.assertRegexpMatches(data, rule)
|
2016-01-07 17:14:05 +01:00
|
|
|
|
|
|
|
|
|
|
|
if __name__ == '__main__':
|
|
|
|
nose2.main()
|