diff --git a/python/plugins/processing/gui/TestTools.py b/python/plugins/processing/gui/TestTools.py index 35fb4eed6ca..420ae5fc264 100644 --- a/python/plugins/processing/gui/TestTools.py +++ b/python/plugins/processing/gui/TestTools.py @@ -30,6 +30,7 @@ import os import re import yaml import hashlib +import ast from osgeo import gdal from osgeo.gdalconst import GA_ReadOnly @@ -37,31 +38,23 @@ from osgeo.gdalconst import GA_ReadOnly from numpy import nan_to_num from qgis.core import (QgsApplication, - QgsProcessingParameterDefinition) + QgsProcessing, + QgsProcessingParameterDefinition, + QgsProcessingParameterBoolean, + QgsProcessingParameterNumber, + QgsProcessingParameterFile, + QgsProcessingParameterString, + QgsProcessingParameterVectorLayer, + QgsProcessingParameterFeatureSource, + QgsProcessingParameterRasterLayer, + QgsProcessingParameterMultipleLayers, + QgsProcessingParameterRasterDestination, + QgsProcessingParameterFeatureSink, + QgsProcessingParameterVectorDestination, + QgsProcessingParameterFileDestination) from qgis.PyQt.QtCore import QCoreApplication, QMetaObject from qgis.PyQt.QtWidgets import QDialog, QVBoxLayout, QTextEdit, QMessageBox -from processing.core.Processing import Processing -from processing.core.outputs import ( - OutputNumber, - OutputString, - OutputRaster, - OutputVector, - OutputHTML, - OutputFile -) - -from processing.core.parameters import ( - ParameterRaster, - ParameterVector, - ParameterMultipleInput, - ParameterFile, - ParameterString, - ParameterNumber, - ParameterBoolean, - ParameterTable -) - def extractSchemaPath(filepath): """ @@ -132,15 +125,24 @@ def parseParameters(command): pos = m.end(0) +def splitAlgIdAndParameters(command): + """ + Extracts the algorithm ID and input parameter list from a processing runalg command + """ + exp = re.compile(r"""['"](.*?)['"]\s*,\s*(.*)""") + m = exp.search(command[len('processing.run('):-1]) + return m.group(1), ast.literal_eval(m.group(2)) + + def createTest(text): definition = {} - tokens = list(parseParameters(text[len('processing.run('):-1])) - cmdname = tokens[0] - alg = QgsApplication.processingRegistry().createAlgorithmById(cmdname) + alg_id, parameters = splitAlgIdAndParameters(text) - definition['name'] = 'Test ({})'.format(cmdname) - definition['algorithm'] = cmdname + alg = QgsApplication.processingRegistry().createAlgorithmById(alg_id) + + definition['name'] = 'Test ({})'.format(alg_id) + definition['algorithm'] = alg_id params = {} results = {} @@ -151,12 +153,12 @@ def createTest(text): continue i += 1 - token = tokens[i] + token = parameters[param.name()] # Handle empty parameters that are optionals if param.flags() & QgsProcessingParameterDefinition.FlagOptional and token is None: continue - if isinstance(param, ParameterVector): + if isinstance(param, (QgsProcessingParameterVectorLayer, QgsProcessingParameterFeatureSource)): schema, filepath = extractSchemaPath(token) p = { 'type': 'vector', @@ -166,7 +168,7 @@ def createTest(text): p['location'] = '[The source data is not in the testdata directory. Please use data in the processing/tests/testdata folder.]' params[param.name()] = p - elif isinstance(param, ParameterRaster): + elif isinstance(param, QgsProcessingParameterRasterLayer): schema, filepath = extractSchemaPath(token) p = { 'type': 'raster', @@ -176,27 +178,18 @@ def createTest(text): p['location'] = '[The source data is not in the testdata directory. Please use data in the processing/tests/testdata folder.]' params[param.name()] = p - elif isinstance(param, ParameterTable): - schema, filepath = extractSchemaPath(token) - p = { - 'type': 'table', - 'name': filepath - } - if not schema: - p['location'] = '[The source data is not in the testdata directory. Please use data in the processing/tests/testdata folder.]' - - params[param.name()] = p - elif isinstance(param, ParameterMultipleInput): + elif isinstance(param, QgsProcessingParameterMultipleLayers): multiparams = token.split(';') newparam = [] # Handle datatype detection - dataType = param.dataType() - if dataType in ['points', 'lines', 'polygons', 'any vectors']: + dataType = param.layerType() + if dataType in [QgsProcessing.TypeVectorAny, QgsProcessing.TypeVectorPoint, QgsProcessing.TypeVectorLine, QgsProcessing.TypeVectorPolygon, QgsProcessing.TypeTable]: dataType = 'vector' else: dataType = 'raster' + schema = None for mp in multiparams: schema, filepath = extractSchemaPath(mp) newparam.append({ @@ -211,7 +204,7 @@ def createTest(text): p['location'] = '[The source data is not in the testdata directory. Please use data in the processing/tests/testdata folder.]' params[param.name()] = p - elif isinstance(param, ParameterFile): + elif isinstance(param, QgsProcessingParameterFile): schema, filepath = extractSchemaPath(token) p = { 'type': 'file', @@ -221,12 +214,12 @@ def createTest(text): p['location'] = '[The source data is not in the testdata directory. Please use data in the processing/tests/testdata folder.]' params[param.name()] = p - elif isinstance(param, ParameterString): + elif isinstance(param, QgsProcessingParameterString): params[param.name()] = token - elif isinstance(param, ParameterBoolean): + elif isinstance(param, QgsProcessingParameterBoolean): params[param.name()] = token - elif isinstance(param, ParameterNumber): - if param.isInteger: + elif isinstance(param, QgsProcessingParameterNumber): + if param.dataType() == QgsProcessingParameterNumber.Integer: params[param.name()] = int(token) else: params[param.name()] = float(token) @@ -235,16 +228,14 @@ def createTest(text): token = token[1:] if token[-1] == '"': token = token[:-1] - params[param.name] = token + params[param.name()] = token definition['params'] = params for i, out in enumerate([out for out in alg.destinationParameterDefinitions() if not out.flags() & QgsProcessingParameterDefinition.FlagHidden]): - token = tokens[i - len(alg.destinationParameterDefinitions())] + token = parameters[out.name()] - if isinstance(out, (OutputNumber, OutputString)): - results[out.name] = str(out) - elif isinstance(out, OutputRaster): + if isinstance(out, QgsProcessingParameterRasterDestination): if token is None: QMessageBox.warning(None, tr('Error'), @@ -258,26 +249,26 @@ def createTest(text): dataArray = nan_to_num(dataset.ReadAsArray(0)) strhash = hashlib.sha224(dataArray.data).hexdigest() - results[out.name] = { + results[out.name()] = { 'type': 'rasterhash', 'hash': strhash } - elif isinstance(out, OutputVector): + elif isinstance(out, (QgsProcessingParameterVectorDestination, QgsProcessingParameterFeatureSink)): schema, filepath = extractSchemaPath(token) - results[out.name] = { + results[out.name()] = { 'type': 'vector', 'name': filepath } if not schema: - results[out.name]['location'] = '[The expected result data is not in the testdata directory. Please write it to processing/tests/testdata/expected. Prefer gml files.]' - elif isinstance(out, OutputHTML) or isinstance(out, OutputFile): + results[out.name()]['location'] = '[The expected result data is not in the testdata directory. Please write it to processing/tests/testdata/expected. Prefer gml files.]' + elif isinstance(out, QgsProcessingParameterFileDestination): schema, filepath = extractSchemaPath(token) - results[out.name] = { + results[out.name()] = { 'type': 'file', 'name': filepath } if not schema: - results[out.name]['location'] = '[The expected result file is not in the testdata directory. Please redirect the output to processing/tests/testdata/expected.]' + results[out.name()]['location'] = '[The expected result file is not in the testdata directory. Please redirect the output to processing/tests/testdata/expected.]' definition['results'] = results dlg = ShowTestDialog(yaml.dump([definition], default_flow_style=False))