2013-02-23 21:43:17 +01:00
# -*- coding: utf-8 -*-
"""
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
TestTools . py
- - - - - - - - - - - - - - - - - - - - -
Date : February 2013
Copyright : ( C ) 2013 by Victor Olaya
Email : volayaf at gmail dot com
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* *
* 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 . *
* *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
"""
2013-10-01 20:52:22 +03:00
2013-02-23 21:43:17 +01:00
__author__ = ' Victor Olaya '
__date__ = ' February 2013 '
__copyright__ = ' (C) 2013, Victor Olaya '
2013-10-01 20:52:22 +03:00
2013-03-10 21:10:01 +01:00
import os
2017-08-30 15:25:11 +10:00
import posixpath
2016-06-12 14:23:09 +02:00
import re
2016-02-03 16:42:18 +01:00
import yaml
import hashlib
2017-08-08 23:23:29 +10:00
import ast
2016-02-03 16:42:18 +01:00
2013-10-01 20:52:22 +03:00
from osgeo import gdal
from osgeo . gdalconst import GA_ReadOnly
2016-05-28 13:44:14 +02:00
from numpy import nan_to_num
2017-05-16 16:36:00 +10:00
from qgis . core import ( QgsApplication ,
2017-08-08 23:23:29 +10:00
QgsProcessing ,
QgsProcessingParameterDefinition ,
QgsProcessingParameterBoolean ,
QgsProcessingParameterNumber ,
2018-04-19 17:24:13 +10:00
QgsProcessingParameterDistance ,
2017-08-08 23:23:29 +10:00
QgsProcessingParameterFile ,
2017-09-13 17:57:41 +03:00
QgsProcessingParameterBand ,
2017-08-08 23:23:29 +10:00
QgsProcessingParameterString ,
QgsProcessingParameterVectorLayer ,
QgsProcessingParameterFeatureSource ,
QgsProcessingParameterRasterLayer ,
QgsProcessingParameterMultipleLayers ,
QgsProcessingParameterRasterDestination ,
QgsProcessingParameterFeatureSink ,
QgsProcessingParameterVectorDestination ,
2017-08-19 23:19:38 +10:00
QgsProcessingParameterFileDestination ,
QgsProcessingParameterEnum )
2016-04-22 10:38:48 +02:00
from qgis . PyQt . QtCore import QCoreApplication , QMetaObject
2016-10-06 15:57:13 +03:00
from qgis . PyQt . QtWidgets import QDialog , QVBoxLayout , QTextEdit , QMessageBox
2013-10-01 20:52:22 +03:00
2016-02-03 16:42:18 +01:00
def extractSchemaPath ( filepath ) :
"""
2019-10-25 21:51:21 +02:00
Tries to find where the file is relative to the QGIS source code directory .
2016-02-03 16:42:18 +01:00
If it is already placed in the processing or QGIS testdata directory it will
return an appropriate schema and relative filepath
Args :
filepath : The path of the file to examine
Returns :
A tuple ( schema , relative_file_path ) where the schema is ' qgs ' or ' proc '
if we can assume that the file is in this testdata directory .
"""
parts = [ ]
schema = None
localpath = ' '
path = filepath
part = True
2016-06-12 17:36:42 +02:00
while part and filepath :
2016-02-03 16:42:18 +01:00
( path , part ) = os . path . split ( path )
if part == ' testdata ' and not localpath :
localparts = parts
localparts . reverse ( )
2017-08-30 15:25:11 +10:00
# we always want posix style paths here
localpath = posixpath . join ( * localparts )
2016-02-03 16:42:18 +01:00
parts . append ( part )
parts . reverse ( )
try :
testsindex = parts . index ( ' tests ' )
except ValueError :
return ' ' , filepath
if parts [ testsindex - 1 ] == ' processing ' :
schema = ' proc '
return schema , localpath
2013-10-01 20:52:22 +03:00
2013-02-17 23:14:12 +01:00
2016-06-12 14:23:09 +02:00
def parseParameters ( command ) :
"""
Parse alg string to grab parameters value .
Can handle quotes and comma .
"""
pos = 0
exp = re . compile ( r """ ([ ' " ]?)(.*?) \ 1(,|$) """ )
while True :
m = exp . search ( command , pos )
result = m . group ( 2 )
separator = m . group ( 3 )
2016-06-12 17:36:42 +02:00
# Handle special values:
if result == ' None ' :
result = None
2016-09-21 18:24:26 +02:00
elif result . lower ( ) == str ( True ) . lower ( ) :
2016-06-12 17:36:42 +02:00
result = True
2016-09-21 18:24:26 +02:00
elif result . lower ( ) == str ( False ) . lower ( ) :
2016-06-12 17:36:42 +02:00
result = False
2016-06-12 14:23:09 +02:00
yield result
if not separator :
break
pos = m . end ( 0 )
2017-08-08 23:23:29 +10:00
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 ] )
2019-03-21 15:22:57 +10:00
alg_id = m . group ( 1 )
params = m . group ( 2 )
# replace QgsCoordinateReferenceSystem('EPSG:4325') with just string value
exp = re . compile ( r """ QgsCoordinateReferenceSystem \ (([ ' " ].*?[ ' " ]) \ ) """ )
params = exp . sub ( ' \\ 1 ' , params )
return alg_id , ast . literal_eval ( params )
2017-08-08 23:23:29 +10:00
2013-03-23 21:45:52 +01:00
def createTest ( text ) :
2016-02-03 16:42:18 +01:00
definition = { }
2017-08-08 23:23:29 +10:00
alg_id , parameters = splitAlgIdAndParameters ( text )
alg = QgsApplication . processingRegistry ( ) . createAlgorithmById ( alg_id )
2016-02-03 16:42:18 +01:00
2017-08-08 23:23:29 +10:00
definition [ ' name ' ] = ' Test ( {} ) ' . format ( alg_id )
definition [ ' algorithm ' ] = alg_id
2016-02-03 16:42:18 +01:00
2016-02-05 10:58:21 +01:00
params = { }
2016-02-03 16:42:18 +01:00
results = { }
2013-02-17 23:14:12 +01:00
i = 0
2017-05-15 12:29:44 +10:00
for param in alg . parameterDefinitions ( ) :
2017-05-16 16:36:00 +10:00
if param . flags ( ) & QgsProcessingParameterDefinition . FlagHidden or param . isDestination ( ) :
2016-02-03 16:42:18 +01:00
continue
2017-08-20 01:37:13 +10:00
if not param . name ( ) in parameters :
continue
2013-10-01 20:52:22 +03:00
i + = 1
2017-08-08 23:23:29 +10:00
token = parameters [ param . name ( ) ]
2016-06-12 17:36:42 +02:00
# Handle empty parameters that are optionals
2017-05-16 16:36:00 +10:00
if param . flags ( ) & QgsProcessingParameterDefinition . FlagOptional and token is None :
2016-06-12 17:36:42 +02:00
continue
2016-02-03 16:42:18 +01:00
2017-08-08 23:23:29 +10:00
if isinstance ( param , ( QgsProcessingParameterVectorLayer , QgsProcessingParameterFeatureSource ) ) :
2016-06-12 14:23:09 +02:00
schema , filepath = extractSchemaPath ( token )
2016-02-03 16:42:18 +01:00
p = {
' type ' : ' vector ' ,
' 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.] '
2017-05-16 16:36:00 +10:00
params [ param . name ( ) ] = p
2017-08-08 23:23:29 +10:00
elif isinstance ( param , QgsProcessingParameterRasterLayer ) :
2016-06-12 14:23:09 +02:00
schema , filepath = extractSchemaPath ( token )
2016-02-03 16:42:18 +01:00
p = {
' type ' : ' raster ' ,
' 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.] '
2017-05-16 16:36:00 +10:00
params [ param . name ( ) ] = p
2017-08-08 23:23:29 +10:00
elif isinstance ( param , QgsProcessingParameterMultipleLayers ) :
2017-08-20 01:37:13 +10:00
multiparams = token
2016-02-03 16:42:18 +01:00
newparam = [ ]
2016-05-06 15:05:49 +02:00
# Handle datatype detection
2017-08-08 23:23:29 +10:00
dataType = param . layerType ( )
2017-08-19 02:46:22 +10:00
if dataType in [ QgsProcessing . TypeVectorAnyGeometry , QgsProcessing . TypeVectorPoint , QgsProcessing . TypeVectorLine , QgsProcessing . TypeVectorPolygon , QgsProcessing . TypeVector ] :
2016-05-06 15:05:49 +02:00
dataType = ' vector '
else :
dataType = ' raster '
2017-08-08 23:23:29 +10:00
schema = None
2016-02-03 16:42:18 +01:00
for mp in multiparams :
schema , filepath = extractSchemaPath ( mp )
newparam . append ( {
2016-05-06 15:05:49 +02:00
' type ' : dataType ,
2016-02-03 16:42:18 +01:00
' name ' : filepath
} )
p = {
' type ' : ' multi ' ,
' params ' : newparam
}
if not schema :
p [ ' location ' ] = ' [The source data is not in the testdata directory. Please use data in the processing/tests/testdata folder.] '
2017-05-16 16:36:00 +10:00
params [ param . name ( ) ] = p
2017-08-08 23:23:29 +10:00
elif isinstance ( param , QgsProcessingParameterFile ) :
2016-06-12 14:23:09 +02:00
schema , filepath = extractSchemaPath ( token )
2016-05-06 14:44:04 +02:00
p = {
' type ' : ' file ' ,
' 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.] '
2017-05-16 16:36:00 +10:00
params [ param . name ( ) ] = p
2017-08-08 23:23:29 +10:00
elif isinstance ( param , QgsProcessingParameterString ) :
2017-05-16 16:36:00 +10:00
params [ param . name ( ) ] = token
2017-08-08 23:23:29 +10:00
elif isinstance ( param , QgsProcessingParameterBoolean ) :
2017-05-16 16:36:00 +10:00
params [ param . name ( ) ] = token
2018-04-20 09:34:43 +10:00
elif isinstance ( param , ( QgsProcessingParameterNumber , QgsProcessingParameterDistance ) ) :
2017-08-08 23:23:29 +10:00
if param . dataType ( ) == QgsProcessingParameterNumber . Integer :
2017-05-16 16:36:00 +10:00
params [ param . name ( ) ] = int ( token )
2016-06-12 17:36:42 +02:00
else :
2017-05-16 16:36:00 +10:00
params [ param . name ( ) ] = float ( token )
2017-08-19 23:19:38 +10:00
elif isinstance ( param , QgsProcessingParameterEnum ) :
2017-09-10 10:45:34 +10:00
if isinstance ( token , list ) :
params [ param . name ( ) ] = [ int ( t ) for t in token ]
else :
params [ param . name ( ) ] = int ( token )
2017-09-13 17:57:41 +03:00
elif isinstance ( param , QgsProcessingParameterBand ) :
params [ param . name ( ) ] = int ( token )
2017-09-05 11:25:58 +10:00
elif token :
2016-06-12 17:36:42 +02:00
if token [ 0 ] == ' " ' :
token = token [ 1 : ]
if token [ - 1 ] == ' " ' :
token = token [ : - 1 ]
2017-08-08 23:23:29 +10:00
params [ param . name ( ) ] = token
2016-02-03 16:42:18 +01:00
definition [ ' params ' ] = params
2017-07-15 22:08:39 +10:00
for i , out in enumerate ( [ out for out in alg . destinationParameterDefinitions ( ) if not out . flags ( ) & QgsProcessingParameterDefinition . FlagHidden ] ) :
2018-01-23 10:03:38 +10:00
if not out . name ( ) in parameters :
continue
2017-08-08 23:23:29 +10:00
token = parameters [ out . name ( ) ]
2016-02-03 16:42:18 +01:00
2017-08-08 23:23:29 +10:00
if isinstance ( out , QgsProcessingParameterRasterDestination ) :
2016-10-06 15:57:13 +03:00
if token is None :
QMessageBox . warning ( None ,
tr ( ' Error ' ) ,
tr ( ' Seems some outputs are temporary '
' files. To create test you need to '
' redirect all algorithm outputs to '
' files ' ) )
return
2016-06-12 14:23:09 +02:00
dataset = gdal . Open ( token , GA_ReadOnly )
2018-06-01 16:19:38 +10:00
if dataset is None :
QMessageBox . warning ( None ,
tr ( ' Error ' ) ,
tr ( ' Seems some outputs are temporary '
' files. To create test you need to '
' redirect all algorithm outputs to '
' files ' ) )
return
2016-05-28 13:44:14 +02:00
dataArray = nan_to_num ( dataset . ReadAsArray ( 0 ) )
strhash = hashlib . sha224 ( dataArray . data ) . hexdigest ( )
2016-02-03 16:42:18 +01:00
2017-08-08 23:23:29 +10:00
results [ out . name ( ) ] = {
2016-02-03 16:42:18 +01:00
' type ' : ' rasterhash ' ,
' hash ' : strhash
}
2017-08-08 23:23:29 +10:00
elif isinstance ( out , ( QgsProcessingParameterVectorDestination , QgsProcessingParameterFeatureSink ) ) :
2016-06-12 14:23:09 +02:00
schema , filepath = extractSchemaPath ( token )
2017-08-08 23:23:29 +10:00
results [ out . name ( ) ] = {
2016-02-03 16:42:18 +01:00
' type ' : ' vector ' ,
' name ' : filepath
}
if not schema :
2017-08-08 23:23:29 +10:00
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 ) :
2016-06-12 14:23:09 +02:00
schema , filepath = extractSchemaPath ( token )
2017-08-08 23:23:29 +10:00
results [ out . name ( ) ] = {
2016-02-21 13:15:44 +01:00
' type ' : ' file ' ,
' name ' : filepath
}
if not schema :
2017-08-08 23:23:29 +10:00
results [ out . name ( ) ] [ ' location ' ] = ' [The expected result file is not in the testdata directory. Please redirect the output to processing/tests/testdata/expected.] '
2016-02-03 16:42:18 +01:00
definition [ ' results ' ] = results
dlg = ShowTestDialog ( yaml . dump ( [ definition ] , default_flow_style = False ) )
2013-02-17 23:14:12 +01:00
dlg . exec_ ( )
2013-02-28 22:08:32 +01:00
2015-08-22 14:29:41 +02:00
2014-10-03 21:56:24 +03:00
def tr ( string ) :
return QCoreApplication . translate ( ' TestTools ' , string )
2013-02-17 23:14:12 +01:00
fix python pep8 warnings and fix some revealed errors
pep8 --ignore=E111,E128,E201,E202,E203,E211,E221,E222,E225,E226,E227,E231,E241,E261,E265,E272,E302,E303,E501,E701 \
--exclude="ui_*.py,debian/*,python/ext-libs/*" \
.
2015-02-01 14:15:42 +01:00
class ShowTestDialog ( QDialog ) :
2013-10-01 20:52:22 +03:00
2013-02-28 22:08:32 +01:00
def __init__ ( self , s ) :
fix python pep8 warnings and fix some revealed errors
pep8 --ignore=E111,E128,E201,E202,E203,E211,E221,E222,E225,E226,E227,E231,E241,E261,E265,E272,E302,E303,E501,E701 \
--exclude="ui_*.py,debian/*,python/ext-libs/*" \
.
2015-02-01 14:15:42 +01:00
QDialog . __init__ ( self )
2013-02-17 23:14:12 +01:00
self . setModal ( True )
2013-10-01 20:52:22 +03:00
self . resize ( 600 , 400 )
2018-02-21 18:44:04 +10:00
self . setWindowTitle ( self . tr ( ' Unit Test ' ) )
2013-02-17 23:14:12 +01:00
layout = QVBoxLayout ( )
fix python pep8 warnings and fix some revealed errors
pep8 --ignore=E111,E128,E201,E202,E203,E211,E221,E222,E225,E226,E227,E231,E241,E261,E265,E272,E302,E303,E501,E701 \
--exclude="ui_*.py,debian/*,python/ext-libs/*" \
.
2015-02-01 14:15:42 +01:00
self . text = QTextEdit ( )
2016-02-03 16:42:18 +01:00
self . text . setFontFamily ( " monospace " )
2013-02-28 22:08:32 +01:00
self . text . setEnabled ( True )
2016-06-12 14:23:09 +02:00
# Add two spaces in front of each text for faster copy/paste
self . text . setText ( ' {} ' . format ( s . replace ( ' \n ' , ' \n ' ) ) )
2013-02-28 22:08:32 +01:00
layout . addWidget ( self . text )
2013-02-17 23:14:12 +01:00
self . setLayout ( layout )
fix python pep8 warnings and fix some revealed errors
pep8 --ignore=E111,E128,E201,E202,E203,E211,E221,E222,E225,E226,E227,E231,E241,E261,E265,E272,E302,E303,E501,E701 \
--exclude="ui_*.py,debian/*,python/ext-libs/*" \
.
2015-02-01 14:15:42 +01:00
QMetaObject . connectSlotsByName ( self )